Сколько запросов в минуту допускает LinkHarbor API, каким может быть размер запроса, как читать заголовки и как правильно обработать 429.
Чтобы API оставался быстрым для всех, LinkHarbor ограничивает число запросов в минуту. Лимиты не зависят от вашего тарифа — месячной квоты на вызовы API нет. В этой статье перечислены все лимиты и показано, как ваш код может аккуратно их обрабатывать.
Как считаются запросы
- Каждый запрос попадает ровно в один бакет, например user для аутентифицированных запросов или token для обмена токена
- Счёт идёт по полным минутам (UTC); с каждой новой минутой счётчик начинается с нуля
- В зависимости от бакета API считает по рабочему пространству, по человеку, по подключению или по IP-адресу
- Запросы, которые отклоняются уже на проверке токена, области доступа или схемы (401, 403 insufficient_scope, 400, 422 validation_failed из проверки схемы), не учитываются; более поздние ошибки, например 403 feature_not_available, учитываются
Все лимиты
| Бакет (X-RateLimit-Bucket) | Лимит | Считается по | Применяется к |
|---|---|---|---|
| user | 600 в минуту | Рабочему пространству для токенов от API-ключей (все ключи и AI-инструменты рабочего пространства вместе); человеку при входе в панель управления | Всем аутентифицированным запросам к /v1, у которых нет собственного бакета |
| token | 30 в минуту | IP-адресу | POST /v1/token |
| public | 120 в минуту | IP-адресу | Запросам к API без аутентификации, например GET /v1/plans и GET /v1/pages/available |
| callback | 120 в минуту | IP-адресу | POST /v1/callbacks/sale |
| internal | 120 в минуту | IP-адресу | Ingest-URL интеграции «Custom (API)» (POST /v1/ingest/{id}) |
| mcp | 120 в минуту | AI-подключению (токену доступа) | MCP-серверу по адресу /mcp |
| oauth | 300 в минуту | IP-адресу | Входу через OAuth и discovery для AI-ассистентов |
| auth | 30 в минуту | IP-адресу | Входу, регистрации, сбросу пароля (панель управления и приложение) |
| refresh | 120 в минуту | IP-адресу | Продлению сессии в панели управления |
| discover | 60 в минуту | Человеку, без входа — IP-адресу | Разделу «Обзор» в приложении |
| subs_read | 120 в минуту | Человеку, без входа — IP-адресу | Чтению ваших подписок (приложение) |
| subs_write | 20 в минуту | Человеку, без входа — IP-адресу | Изменению ваших подписок (приложение) |
Один бюджет на рабочее пространство
Все токены от API-ключей одного рабочего пространства делят 600 запросов в минуту — сколько бы ключей вы ни создали. Сюда же засчитываются инструменты подключённых AI-ассистентов, потому что они вызывают API внутри. Поэтому больше ключей не означает больший лимит. Если большая нагрузка распределена по нескольким программам, рассчитывайте на общий бюджет.
Заголовки
- X-RateLimit-Limit
- Лимит бакета в минуту, например 600.
- X-RateLimit-Remaining
- Сколько запросов осталось в текущей минуте.
- X-RateLimit-Bucket
- Бакет, в который попал запрос, например user.
- Retry-After
- Только при 429 rate_limited: число секунд до начала следующей минуты. При api_key_locked время ожидания указано в details.retry_after_sec.
Когда лимит достигнут
Когда лимит достигнут, API отвечает HTTP 429 с таким телом. Код rate_limited неизменен, а текст в message приходит на языке аккаунта (см. Формат ошибок и коды ошибок), здесь на английском:
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Bucket: user
{ "error": "rate_limited", "message": "Too many requests. Please try again in a moment.", "retry_after_sec": 37 } - Прочитайте Retry-After или retry_after_sec
- Подождите как минимум столько секунд плюс немного случайного времени, чтобы не все процессы стартовали одновременно
- Повторите запрос, но не больше нескольких раз
- Если 429 приходит снова и снова, растяните работу во времени
const BASE = 'https://dock.at-harbor.com';
let cached = null; // { token, expiresAt }
async function getToken() {
if (cached && Date.now() < cached.expiresAt - 60_000) return cached.token;
const res = await fetch(BASE + '/v1/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key_id: process.env.LH_KEY_ID,
key_secret: process.env.LH_KEY_SECRET,
}),
});
if (!res.ok) throw new Error('Token exchange failed: HTTP ' + res.status);
const data = await res.json();
cached = { token: data.token, expiresAt: Date.now() + data.expires_in * 1000 };
return cached.token;
}
async function lh(method, path, body, attempt = 0) {
const headers = { Authorization: 'Bearer ' + (await getToken()) };
if (body !== undefined) headers['Content-Type'] = 'application/json';
const res = await fetch(BASE + path, {
method,
headers,
body: body === undefined ? undefined : JSON.stringify(body),
});
if (res.status === 429 && attempt < 3) {
const wait = Number(res.headers.get('Retry-After') ?? 5);
await new Promise((r) => setTimeout(r, (wait + Math.random() * 2) * 1000));
return lh(method, path, body, attempt + 1);
}
if (res.status === 401 && attempt === 0) {
cached = null; // токен истёк — ещё одна попытка с новым токеном
return lh(method, path, body, attempt + 1);
}
const data = await res.json().catch(() => null);
if (!res.ok) throw new Error((data && data.error) + ' (HTTP ' + res.status + ')');
return data;
}
// Пример: const { items } = await lh('GET', '/v1/pages'); Советы для больших объёмов данных
- Кешируйте токен и обновляйте его только незадолго до истечения 15 минут
- Следите за X-RateLimit-Remaining и замедляйтесь, когда значение становится маленьким
- Отправляйте запросы друг за другом, а не через десятки параллельных соединений
- Загружайте списки один раз и фильтруйте их локально, вместо того чтобы запрашивать каждую страницу отдельно
- Растягивайте большие синхронизации на несколько минут
- Повторяйте запросы при ошибках 5xx с растущими паузами, но не при ошибках 4xx
Другие лимиты
| Что | Лимит | Ответ |
|---|---|---|
| Неудачные входы | Блокировка после 5, 10 и 20 неудачных попыток на адрес электронной почты и IP: 60 секунд, 5 минут, 30 минут | 429 account_locked |
| Отправка форм | 10 в час с одного IP-адреса, по всем формам вместе | 429 form_rate_limited |
Неудачные попытки обмена токена временно блокируют ключ и IP, см. API-ключи и токены. Push-уведомления вы отправляете только в панели управления; их лимиты описаны в статье Подписчики и push-уведомления.
Лимиты контента вашего тарифа — страницы, ссылки, блоки, короткие ссылки и так далее — это не лимиты запросов, а фиксированные ограничения. Они перечислены в статье Сравнение тарифов.
Размер запроса
Каждое тело измеряется до того, как API его прочитает. Допустимый размер зависит от эндпоинта:
| Эндпоинты | Максимальное тело |
|---|---|
| Вход, регистрация и другие вызовы без аутентификации под /v1/auth, POST /v1/token, POST /v1/oauth/exchange, OAuth-эндпоинты для AI-ассистентов под /oauth и согласие для них, POST /v1/ingest/{id}, оформление платежа в блоке «Поддержать», вызовы подписок в приложении (подписки, коллекции, push-устройства), переключение рабочих пространств, POST /v1/auth/logout-all, удаление аккаунта, настройка двухфакторной аутентификации, принятие приглашения, активация реферальной программы и шаг возврата при подключении интеграции | 64 КБ |
| Все остальные JSON-эндпоинты, включая отправку формы на странице | 1 МБ |
| POST /v1/webhooks/{id}/verify-signature и MCP-сервер по адресу /mcp | 2 МБ |
| Загрузка файлов (POST /v1/assets и POST /v1/pages/{id}/uploads, multipart/form-data) | 11 МБ на весь запрос, сам файл не больше 10 МБ |
Если тело больше, API отвечает 413 request_too_large; лимит указан в details.max_bytes и details.max_kb. Слишком большой файл при загрузке приводит к 413 payload_too_large. Эндпоинты без тела, например вызовы DELETE, не принимают тело или принимают только {}.
HTTP/1.1 413 Payload Too Large
{ "error": "request_too_large", "message": "…", "details": { "max_bytes": 65536, "max_kb": 64 }, "request_id": "…" } Ограничения есть и внутри тела: у каждого текста есть максимальная длина, у каждого списка — максимальное число элементов, а неизвестные поля отклоняются. Значения для каждого эндпоинта перечислены в справочнике API. Нарушение приводит к 422 validation_failed, а details называет поле, правило и лимит. Путь и строка запроса вместе могут занимать не больше 16 КБ.
Вам также может пригодиться
Ничего не нашли?
Напишите, что вам нужно, и наша служба поддержки поможет вам лично.