Limits and errors
Rate and concurrency limits, sizes and timeouts, every error the AI Gateway returns, and when to retry.
Rate limits
These limits are checked before every request, in this order. A refused request is not charged and takes no hold.
| Limit | Scope | When reached |
|---|---|---|
| Key rate limit | One key, requests per minute. Only for keys that have their own limit. | 429 rate_limited, "Too many requests for this key (limit N per minute)" |
| Account rate limit | All keys of your account together, requests per minute. 600 by default. | 429 rate_limited, "Too many requests for this account (limit N per minute)" |
| Monthly spend limit | One key, Rupiah per calendar month (UTC). Only for keys created with one. | 403 key_spend_limit_reached |
| Concurrency | All keys of your account together, requests in progress at the same time. 20 by default. | 429 too_many_concurrent_requests |
Good to know:
- Fixed windows. Per-minute limits count requests in each calendar minute, so the count resets at the start of every minute.
- Every attempt counts. A request counts toward the per-minute limits even when it is then refused.
- The message has the number. The
429message always states the limit that applies to you, so read it from there rather than assuming the defaults. - Streams hold a slot. A streaming request holds its concurrency slot until the stream ends, so close streams you no longer need.
Need higher limits? Contact Helipod support.
Sizes and timeouts
| What | Limit |
|---|---|
| Request body | 8 MB |
| Output cap when you send none | 16,384 tokens, or the model's output limit when lower |
| Output cap above the model's limit | Lowered to the model's limit |
| Context, input and output per model | See Models & pricing in the dashboard |
| Total time for one request | 10 minutes |
| Stream keep-alive | After about 10 seconds without output, then every 15 seconds |
Use streaming for long answers and reasoning models. A streamed request keeps the connection busy with output or keep-alive comments, so proxies and HTTP clients do not cut it. A long non-streaming request sends nothing until the whole answer is ready, and some clients and proxies give up on an idle connection long before 10 minutes. Set your client timeout to at least 10 minutes.
Error body
Errors use the error shape of the format you called, so the official SDKs raise their normal error types.
On /v1/chat/completions and /v1/models (OpenAI style):
{
"error": {
"message": "Insufficient balance. Top up your AI balance to continue.",
"type": "billing_error",
"code": "insufficient_balance"
}
}On /v1/messages and /v1/messages/count_tokens (Anthropic style):
{
"type": "error",
"error": {
"type": "billing_error",
"message": "Insufficient balance. Top up your AI balance to continue."
}
}The Anthropic shape has no code. Branch on the HTTP status and error.type, and on error.code in the OpenAI shape. The type follows the status:
| Status | type |
|---|---|
| 400 | invalid_request_error |
| 401 | authentication_error |
| 402 | billing_error |
| 403 | permission_error |
| 404, 410 | not_found_error |
| 413 | request_too_large |
| 429 | rate_limit_error |
| any other | api_error |
Every response from /v1/chat/completions and /v1/messages, errors included (except 401), carries an x-request-id header. Include it when you contact support.
Errors in a stream
When a streaming request is waiting for a slow model, the gateway has already answered 200 and is sending keep-alive comments. If the model then fails, the status can no longer change, so the error arrives as the last event of the stream:
OpenAI style:
data: {"error":{"message":"The AI provider is unreachable. Please retry.","type":"api_error","code":"upstream_unreachable"}}
data: [DONE]Anthropic style:
event: error
data: {"type":"error","error":{"type":"api_error","message":"The AI provider is unreachable. Please retry."}}Failures in the first seconds, before the gateway answers, come back as a normal error response with the real status.
If a stream breaks in the middle of the answer, the connection closes without the usual end (data: [DONE], or message_stop). Treat a stream that ends without it as failed.
Error codes
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | The body is not valid JSON, not a JSON object, has no model, or the model rejected the parameters (for example the message format or max_tokens). | Fix the request. Do not retry as is. |
| 400 | unsupported_format | The model is not available in the format you called. The message names the right endpoint. | Call the other endpoint. |
| 401 | missing_api_key | No key in Authorization or x-api-key. | Send the key. |
| 401 | invalid_api_key | Unknown or revoked key. | Use a valid key. Do not retry. |
| 401 | expired_api_key | The key is past its expiry. | Create a new key. |
| 402 | insufficient_balance | Your available AI balance cannot cover the request's hold. | Top up, turn on auto top-up, or lower the output cap. Do not retry in a loop. |
| 403 | key_spend_limit_reached | The key reached its monthly spend limit. | Wait for next month or use another key. |
| 404 | model_not_found | Unknown model, switched off, or not in this key's allowlist. | Pick an id from GET /v1/models. |
| 404 | not_found | Unknown endpoint, usually a wrong base URL. | Use https://ai.helipod.io/v1 for OpenAI clients and https://ai.helipod.io for Anthropic clients. |
| 404, 413, 422 | invalid_request | The model refused the request with this status (for example the prompt is too long for it). | Fix the request. |
| 410 | model_retired | The model was retired. The message names the replacement. | Switch model. |
| 413 | request_too_large | The body is over 8 MB. | Send less, or send images by URL. |
| 429 | rate_limited | A per-minute limit (key or account), or the model is busy. | Retry with backoff. |
| 429 | too_many_concurrent_requests | Too many requests in progress for your account. | Wait for some to finish, then retry. |
| 500 | internal_error | Unexpected error in the gateway. | Retry with backoff. |
| 502 | upstream_error | The model returned an error. | Retry with backoff. |
| 502 | upstream_unreachable | The model could not be reached or did not answer in time. | Retry with backoff. |
| 502 | upstream_auth_error | The model is temporarily unavailable. | Retry after a short wait. |
| 503 | upstream_unavailable | The AI service is temporarily unavailable. | Retry later, or try another model. |
| 503 | model_unavailable | This model is temporarily unavailable. | Try another model. |
| 503 | server_busy | The gateway is busy. Sent with retry-after: 1. | Retry after the given seconds. |
| 503 | pricing_unavailable | Prices could not be loaded. | Retry shortly. |
| 503 | service_unavailable | The model cannot be served right now. | Retry later, or try another model. |
Errors returned as an HTTP status (any 4xx or 5xx) are never charged. If a stream breaks after the model has started writing, what was generated up to then is charged, like a cancelled stream.
Retries
- Retry:
429,500,502,503. Use exponential backoff with jitter (for example 1 s, 2 s, 4 s, up to about 30 s) and a limit on attempts. Honourretry-afterwhen it is present. The official OpenAI and Anthropic SDKs already retry these by default. - Do not retry unchanged:
400,401,403,404,410,413. Fix the request, the key or the model first. 402: stop and top up. Retrying only repeats the refusal.- Fall back: for
model_unavailable,upstream_unavailableand repeated5xxon one model, a second model fromGET /v1/modelskeeps your app answering.
A retried request is a new request and is billed on its own if it succeeds. A failed attempt costs nothing.