MelioraAPI EN
Ранний доступ. Ключи выдаются по запросу. Как получить ключ

Лимиты считаются в весовых единицах: тяжелые запросы расходуют больше. Превышение дает 429, перегрузка или временная недоступность 503. Оба ответа говорят, когда повторить.

#Вес запроса

МаршрутВес
GET /v1/me1
GET /v1/workspaces1
GET /v1/campaigns2
GET /v1/campaigns/{id}1
GET /v1/leads2
GET /v1/leads/{id}1
GET /v1/leads/{id}/messages3
GET /v1/events2, с include_text=true 3

#Лимиты по тарифам

Лимиты действуют одновременно на ключ и на всю организацию, по всем ее ключам вместе.

ОкноProPremiumEnterprise
Ключ, в секунду51010
Ключ, в минуту300600600
Организация, в секунду102525
Организация, в минуту60015001500
Одновременных запросов на ключ555
Одновременных запросов на организацию101010

Пример: на тарифе Pro один ключ может сделать 2 запроса списка лидов в секунду (вес 2, лимит 5) и до 150 таких запросов в минуту. Действующие лимиты ключа возвращает GET /v1/me в поле limits.

Кроме того, API ограничивает запросы с одного IP-адреса: в среднем 20 в секунду, кратковременно до 40. Если с одного адреса за 5 минут пришло 20 ответов 401, адрес блокируется на 15 минут: все его запросы получают 429 с Retry-After. Проверьте ключ, прежде чем повторять запросы с ошибкой 401.

Иногда, например во время работ на нашей стороне, лимиты временно снижаются. Тогда в limits.mode приходит emergency, а в полях лимитов меньшие значения. Код, который читает заголовки ответа, подстроится сам.

#Заголовки

Успешные ответы и ответы 429 по лимитам ключа и организации содержат состояние лимитов. Ответ 429 по лимиту IP-адреса приходит без этих заголовков, только с Retry-After.

HTTP
RateLimit-Policy: "key_second";q=5;w=1, "key_minute";q=300;w=60, "org_second";q=10;w=1, "org_minute";q=600;w=60
RateLimit: "key_second";r=3;t=1, "key_minute";r=291;t=60, "org_second";r=8;t=1, "org_minute";r=583;t=60
RateLimit-Limit: 5
RateLimit-Remaining: 3
RateLimit-Reset: 1
ЗаголовокЗначение
RateLimit-PolicyОкна: имя, лимит q в весовых единицах и длина окна w в секундах.
RateLimitПо каждому окну остаток r и секунды до сброса t.
RateLimit-LimitЛимит окна, которое ближе всего к исчерпанию.
RateLimit-RemainingОстаток в этом окне.
RateLimit-ResetСекунд до сброса этого окна.

#Ответ 429

Если вес запроса больше остатка или превышено число одновременных запросов, приходит 429 rate_limit_exceeded с заголовком Retry-After в секундах. Запрос не выполнен. Подождите Retry-After секунд и добавьте случайный разброс, чтобы несколько процессов не повторили запрос одновременно.

#Ответ 503

503 означает, что запрос сейчас не выполнен. Код уточняет причину:

КодПричинаRetry-After
server_busyСервис перегружен1 секунда
service_unavailableДанные временно не прочитаны5 секунд
integration_initializingНовый ключ еще готовится5 секунд

Повторяйте с растущей паузой: начните с Retry-After, затем удваивайте до 60 секунд. Если 503 приходит дольше нескольких минут, напишите в поддержку и приложите Request-Id.

#Повтор безопасен

Все маршруты v1 только читают данные, поэтому любой запрос можно повторить без побочных эффектов. Ошибка не продвигает курсор: повтор с тем же курсором вернет ту же страницу.

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function meliora(path, params = {}, maxAttempts = 6) {
  const url = new URL(`https://api.melioralab.io/v1${path}`);
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
  for (let attempt = 1; ; attempt++) {
    let res;
    try {
      res = await fetch(url, {
        headers: { Authorization: `Bearer ${process.env.MELIORA_API_KEY}` },
        signal: AbortSignal.timeout(15_000),
      });
    } catch (networkError) {
      if (attempt >= maxAttempts) throw networkError;
      await sleep(Math.min(60, 2 ** attempt) * 1000);
      continue;
    }
    if (res.ok) return res.json();
    const problem = await res.json().catch(() => ({}));
    if ((res.status === 429 || res.status === 503) && attempt < maxAttempts) {
      const base = Number(res.headers.get('Retry-After')) || 5;
      const wait = Math.min(60, base * (res.status === 503 ? 2 ** (attempt - 1) : 1));
      await sleep((wait + Math.random() * Math.max(1, wait * 0.25)) * 1000);
      continue;
    }
    const error = new Error(`Meliora API ${res.status} ${problem.code ?? ''}`);
    Object.assign(error, { status: res.status, code: problem.code, requestId: res.headers.get('Request-Id') });
    throw error;
  }
}

#Как не упираться в лимиты

  • Опрашивайте ленту событий раз в минуту вместо регулярного обхода всех лидов.
  • Используйте limit=100 для списков и до 200 для ленты: меньше запросов на тот же объем.
  • Фильтруйте на нашей стороне: campaign_id, status, username дешевле, чем выгрузка всего списка.
  • Не запускайте больше 5 параллельных запросов на ключ, лишние получат 429.
  • Запрашивайте тексты только там, где они нужны: события с include_text=true и переписка весят 3.