Errors and rate limits
Every error code the API returns, the rate-limit headers, and how to retry.
Error format
Errors use the format of the endpoint you called, so your SDK raises its usual exception types.
OpenAI endpoints (/v1/chat/completions, /v1/responses, and /v1/models without an anthropic-version header):
{
"error": {
"message": "Your organization has no remaining credit. Top up to continue.",
"type": "insufficient_quota",
"code": "insufficient_credits",
"param": null
}
}Anthropic endpoints (/v1/messages, /v1/messages/count_tokens, and /v1/models with an anthropic-version header):
{
"type": "error",
"error": {
"type": "billing_error",
"message": "Your organization has no remaining credit. Top up to continue."
}
}Every error response also carries the header x-interodin-error-code with the code from the table below. Branch on the HTTP status and on error.code (OpenAI format) or on the x-interodin-error-code header (any format). In the Anthropic format several codes share one error.type (for example, all three 403 codes are permission_error), so the header is the reliable way to tell them apart. Don't match on message, because the wording can change.
Error codes
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | The body is not valid JSON, misses a required field, or a field has an invalid value, for example max_tokens of 0. | Fix the request. |
| 400 | unsupported_parameter | A parameter is not supported, for example previous_response_id. | Remove it. For Responses, send the full conversation in input. |
| 400 | image_input_not_supported | The model is text-only and you attached an image to the newest message. Images in tool results or earlier turns are replaced by a text note instead of failing. | Remove the image, or use a model with vision. |
| 400 | unsupported_content | The request contains file or document input (file, input_file or document parts, or any part with a file, file_id, file_data or file_url key, even without a type) in the newest message. No model accepts it. Files in tool results or earlier turns are replaced by a text note. | Send the text content instead. |
| 400 | content_refused | The model declined to process this request. Nothing is charged for the refused request. | Rephrase the request. |
| 400 | context_length_exceeded | Input plus max_tokens exceeds the context window. | Shorten the input or lower max_tokens. |
| 401 | invalid_api_key | The key is missing, malformed or revoked. | Check the header and the key. |
| 402 | insufficient_credits | The organization's balance is used up. | Top up in the console, or turn on auto-recharge. |
| 403 | model_not_allowed | The key is not allowed to use this model. | Use an allowed model, or edit the key. |
| 403 | key_spend_limit_reached | The key reached its monthly spend limit. | Raise the limit or wait for next month. |
| 403 | organization_suspended | The organization is suspended. | Contact support. |
| 404 | model_not_found | The model id does not exist. | Use an id from GET /v1/models, such as odin-v1. |
| 404 | not_found | The path does not exist, for example GET /v1/responses/{id} (responses are not stored). | Check the URL. |
| 413 | request_too_large | The request body is larger than 10 MB. | Send less data per request. |
| 429 | rate_limit_exceeded | Too many requests or tokens per minute. | Wait for Retry-After seconds, then retry. |
| 429 | concurrency_limit | Your organization has too many requests in flight at once. | Wait for Retry-After (1 second), or send fewer requests in parallel. |
| 500 | internal_error | Something failed on our side. | Retry with backoff. If it persists, contact support and include the x-request-id. |
| 502 | upstream_unavailable | The model is temporarily unavailable, or it stopped sending data for 300 seconds. | Retry with backoff. |
| 504 | timeout | The model took too long to respond. | Retry. For long outputs, stream the response. |
Every response, errors included, carries an x-request-id header. Include the id when you contact support. Because we never store request content, the id is how we find your request.
If your client disconnects before the reply is complete, the request appears in your request logs with status 499 (client_closed_request). You never receive this status yourself, because the connection is already closed. If you abort a request that is not streaming, you are billed for the input in full plus an estimate of the output produced so far.
Errors during a stream
Once a stream has started, the status code is already 200. If generation fails after that point, the stream ends with an error event in the same format as the endpoint:
- Chat Completions: a final
data: {"error": {...}}line. - Responses: an
event: errorwith a{"type": "error", "code": "...", "message": "...", "param": null}payload. - Messages: an
event: errorwith a{"type": "error", ...}payload.
A failure during a stream is reported as upstream_unavailable. Input tokens are billed in full. Output is billed for what was delivered before the failure, estimated from the delivered text when the model never reported usage.
Rate limits
Limits apply per API key and per organization, per minute, to both requests and tokens (input plus output). Responses from the generation endpoints (/v1/chat/completions, /v1/responses, /v1/messages, /v1/messages/count_tokens) report the current window. They report whichever of your key's or your organization's windows is closer to its limit. GET /v1/models doesn't send these headers, and neither do errors that happen before the rate-limit check, such as 401 or a 400 for an invalid request.
| Header | Meaning |
|---|---|
x-ratelimit-limit-requests | Requests allowed per minute. |
x-ratelimit-remaining-requests | Requests left in the current window. |
x-ratelimit-reset-requests | Time until the current one-minute request window ends, in whole seconds with an s suffix, for example 12s. |
x-ratelimit-limit-tokens | Tokens allowed per minute. |
x-ratelimit-remaining-tokens | Tokens left in the current window. |
x-ratelimit-reset-tokens | Time until the current one-minute token window ends, for example 12s. |
When you exceed a limit you get 429 rate_limit_exceeded with a Retry-After header, in seconds. Your organization can also have only a limited number of requests in flight at once; past that you get 429 concurrency_limit with Retry-After: 1. Token counting with /v1/messages/count_tokens is free and works at a zero balance, but it still counts toward the rate limits and the in-flight cap. If you need higher limits, contact support.
Retrying
Retry only these responses:
429500502504- network errors
Use exponential backoff with jitter, and honor Retry-After when it is present. Never retry other 4xx errors unchanged, because they will fail again.
The OpenAI and Anthropic SDKs already retry these statuses. You can raise the retry count and the timeout:
import OpenAI from 'openai'
const client = new OpenAI({
baseURL: 'https://api.interodin.com/v1',
apiKey: process.env.INTERODIN_API_KEY,
maxRetries: 4,
timeout: 120_000,
})
const models = await client.models.list()
console.log(models.data.map((m) => m.id))Create response POST
Generates a model response. Responses are never stored: `store` is always `false`, `previous_response_id` is rejected, and there is no endpoint to retrieve or delete a response. Send the whole conversation in `input` each time.
Changelog
Changes to the API, models and documentation.