Rate limits & errors
Rate-limit headers, error responses, and request limits.
Rate limits
Rate limits apply to generation endpoints. Check your current values in GET /v1/info under limits, or contact hello@kalpalabs.ai for a higher limit.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute |
X-RateLimit-Remaining | Requests currently available |
X-RateLimit-Reset | Seconds until the limit resets |
When you reach the limit, the API returns 429 with Retry-After:
{ "error": { "type": "rate_limit_exceeded", "message": "Rate limit exceeded (600 requests/min). Retry in 2s.", "request_id": "…" } }Wait for Retry-After before retrying.
Usage allowance
Every API key belongs to an organization. The first organization you create has a one-time allowance of generated audio (1,000 minutes) that expires three months after it was created; organizations you create after that start without one. Once the allowance is used up or has expired, generation requests (/v1/tts/{voice_id}, /v1/converse, and the streaming variants) return 402, and a WebSocket initializeConnection closes with code 4402:
{ "error": { "type": "allowance_exhausted", "message": "Your organization has used its free allowance (1,000 of 1,000 minutes of generated audio). Contact hello@kalpalabs.ai to keep generating.", "request_id": "…" } }Do not retry a 402: nothing about it clears with time. GET /v1/models, /v1/voices, /v1/info and /v1/usage keep answering with the same key, and the organization block of GET /v1/usage shows the usage and allowance the refusal was based on. Write to hello@kalpalabs.ai to move to a contract.
Usage is ingested asynchronously, so the gate sees a just-completed generation a few seconds later, and a WebSocket session is checked when it starts, not before every response.
If an organization is suspended, every one of its keys gets 403 with type account_suspended on every endpoint (WebSocket close code 4403).
Overloaded
When every model server is busy, a generation request is refused before generation starts, with type overloaded. REST requests (/v1/tts/{voice_id}, /, /v1/converse, /v1/converse/stream) return 503 with Retry-After:
{ "error": { "type": "overloaded", "message": "The model is temporarily overloaded. Retry shortly.", "request_id": "…" } }On a WebSocket, the affected response gets a non-fatal error frame with error.type overloaded, then settles like any failed response: responseCreated if it was not sent yet, then responseDone with status cancelled. The connection stays open, so send the text and flush again.
With generation_config, an utterance refused before the server has received its flush gets the error with fatal: true instead, and the connection closes (1011) without a responseDone: reconnect, then resend the whole turn.
overloaded is not caused by your request or your rate limit, and no audio was generated, so it is always safe to retry. Wait for Retry-After (about a second; WebSocket frames carry no header), then back off exponentially between further attempts. A failure after generation has started is an inference_error.
The error envelope
REST errors use one response shape:
{ "error": { "type": "…", "message": "…", "request_id": "…" } }Validation errors may also include error.details. SSE and WebSocket streams send errors as protocol events after streaming begins; the raw audio stream of POST / has no events, so a failure after its audio starts terminates the connection before the response completes.
| Status | type | Meaning |
|---|---|---|
400 | invalid_request | Invalid request, audio, model, or conversation. |
400 | context_length_exceeded | Generation filled the model's context window before a stop token. Try shorter sentences. |
401 | authentication_error | Missing or invalid API key. |
402 | allowance_exhausted | Your organization's allowance of generated audio is used up or has expired. Do not retry. |
403 | account_suspended | Your organization is suspended. |
404 | not_found | Path or voice not found. |
405 | method_not_allowed | HTTP method not supported. |
422 | invalid_request | Request body does not match the schema. |
429 | rate_limit_exceeded | Rate limit reached. |
500 | internal_error | Unexpected server error. |
502 | inference_error | Model backend error or timeout. |
503 | overloaded | The model is temporarily at capacity. Retry after Retry-After. |
503 | service_unavailable | API-key verification is temporarily unavailable. |
Request caps
Current request limits are also available from GET /v1/info:
| Limit | Current value |
|---|---|
| Text per request or turn | 8,000 characters |
| Turns per REST/SSE conversation | 64 |
| Audio per turn | 25 MiB decoded WAV |
Debugging a failed call
Use error.type in application logic and message for display. For persistent failures, share the request_id with support; it also appears in the X-Request-ID response header.