Errors and limits
Request errors return a non-2xx HTTP status and an error envelope:
{
"code": 402,
"error": {
"message": "Insufficient balance. Please top up your account",
"type": "PaymentRequired",
"code": "insufficient_balance"
}
}
Branch on the HTTP status and error.code, not on the English message.
| HTTP | Common code | Action |
|---|---|---|
| 400 | invalid_input | Correct the model ID, fields, values, or conditional requirements. |
| 401 | api_key_missing, api_key_invalid | Send a valid API key. |
| 402 | insufficient_balance | Top up before retrying. No task was created. |
| 404 | task_not_found, generation_not_found | Check the ID and owning account. |
| 429 | unknown_error | Back off and retry with jitter. |
| 500, 503 | unknown_error | Retry a limited number of times with exponential backoff. |
Failed tasks
A media generation can be accepted and fail later. The status endpoint still
returns HTTP 200, but data.status is failed and data.errorMessage
contains a sanitized explanation. The charge is refunded automatically.
Do not submit a second paid generation merely because polling is slow. Retry only after the original task is terminal.
Rate limits
Every endpoint under /api/v1 shares one limit: 120 requests per minute.
The window is a rolling 60 seconds and the counter is keyed on the calling IP
address, not on the API key — several keys behind one address share the budget,
and the count is pooled across our API replicas, so it is the same 120 wherever
the request lands.
The limit is on request volume only. It is not a quota on generations, on concurrent tasks, or on spend; how much you can generate is bounded by your prepaid balance, and a queued generation costs nothing further while it runs.
Reading the headers
Every response carries the state of your window, so a client can pace itself
without ever provoking a 429:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the window (120). |
X-RateLimit-Remaining | Advisory count of requests left before the limit is reached. |
X-RateLimit-Reset | Seconds until the window resets and the count clears. |
Retry-After | Sent only on a 429: seconds to wait before retrying. |
curl -sS -D - -o /dev/null https://api.apihubs.ru/api/v1/catalog
HTTP/2 200
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
x-ratelimit-reset: 60
A worker can use X-RateLimit-Remaining to reduce the chance of a rate-limit
error. The value is advisory: concurrent requests from the same IP can consume
the final slot before the worker sends its next request.
When the limit is exceeded
The 121st request in the same rolling 60-second window returns HTTP 429 with
the standard error envelope and a Retry-After header:
{
"code": 429,
"error": {
"message": "ThrottlerException: Too Many Requests",
"type": "Error",
"code": "unknown_error"
}
}
Branch on the HTTP status. Nothing was created and nothing was charged, so the
request is safe to repeat once Retry-After seconds have passed.
Staying under the limit
Polling is what exhausts the budget in practice — a fleet of workers asking for task status every second reaches 120 requests a minute with two tasks in flight. In order of effectiveness:
- Use webhooks. Pass
webhookongeneration/createand the finished task is delivered to you. This removes status polling entirely and is the single biggest reduction in request volume. - Back off while polling. Wait 5–10 seconds before the first status check, then widen the interval up to 30 seconds. A video generation takes minutes; polling it every second buys nothing.
- Poll one task per request. Batch your own bookkeeping instead of re-checking every task in your queue on every tick.
- Stop at a terminal status.
succeededandfailednever change again.
If your workload genuinely needs a higher ceiling, ask — the limit exists to keep one client from crowding out the rest, not to cap legitimate throughput.
Retries and backoff
Retry with exponential backoff and random jitter, capped at a small number of attempts. Jitter matters as much as the backoff: without it, a batch of clients that hit the limit together retries together and hits it again.
- Retry
429, connection failures,500, and503with capped backoff. - Do not retry
400,401,402, or404unchanged — the outcome will be the same until the request itself changes. - Reconcile a timed-out create call before submitting another generation. A create that timed out on your side may still have been accepted and charged; check your task list rather than paying for a second one.