Docs › Errors
Errors
Every status and error code the API returns, what caused it and what to do next.
Error format
Errors come back as JSON with an HTTP status and a machine-readable code. Branch on code, not on the message text.
{
"error": {
"code": "insufficient_credits",
"message": "…"
}
}Status codes
| Status | Code | What happened | What to do |
|---|---|---|---|
| 400 | bad_json | The body isn’t valid JSON. | Fix the JSON and send Content-Type: application/json. |
| 400 | missing_model | No model in the chat request. | Add a model ID. |
| 400 | missing_messages | messages is missing or not an array. | Send an array of messages. |
| 400 | bad_pack | Unknown pack_id on checkout. | Use mini, maker or studio. |
| 400 | amount_mismatch | amount_usd doesn’t match the pack price. | Leave amount_usd out, or send the exact price. |
| 400 | bad_tier | Founding tier isn’t paid or verified. | Fix the tier. |
| 401 | bad_key | Key missing, malformed, unknown or revoked. | Check the Authorization: Bearer ck_… header. Don’t retry. |
| 402 | insufficient_credits | Your balance is $0, or it can’t cover the hold for this call. | Add credit on Pricing, lower max_tokens, or pick a cheaper model. See holds. |
| 403 | anonymous_disabled | New-key sign-up is switched off. | Try again later. |
| 404 | not_found | No such endpoint. | Check the path and method. |
| 404 | model_not_found | The model ID isn’t in the catalog (chat request or GET /v1/models/{id}). | Use an ID from GET /v1/models. |
| 404 | event_not_found | GET /v1/logs/{id} found nothing among your recent events. | Check the event ID. |
| 409 | already_founding | The account already holds that Founding tier. | Nothing to do. |
| 429 | key_cap_exhausted | This key hit its daily spending cap. | Wait for 00:00 UTC or raise the cap. |
| 429 | rate_limit_exceeded_key | More than 60 requests in a minute on this key. | Wait Retry-After seconds (60). |
| 429 | rate_limit_exceeded_ip | More than 120 requests in a minute from this IP. | Wait Retry-After seconds (60). |
| 429 | anonymous_ip_limit, anonymous_daily_cap | Too many new keys today. | Wait (Retry-After: 3600) or reuse your key. |
| 500 | internal_error | Unexpected server error. | Retry once after a short wait. |
| 502 | rail_error | The model provider failed. Nothing was charged; the hold was returned. | Retry with backoff, or try another model. |
| 502 | processor_error | Stripe couldn’t create the checkout. | Retry shortly. |
| 503 | no_supply | The model is listed but its provider isn’t available right now. | Try again later or pick another model from GET /v1/models. |
| 503 | no_processor | Payments are temporarily unavailable. | Try again later. |
Out of credit: 402
Running out of credit always returns 402 insufficient_credits. The call was refused before it reached the model, so nothing was charged:
- Balance $0: the message reads “Out of credit. Add credit at https://tokenctr.com/pricing.html”.
- Some credit, but not enough for the hold on a request with a large
max_tokens: lowermax_tokensor add credit. If you leavemax_tokensout, the reply is capped to what your balance can pay for instead of being refused.
The response includes x-cosmos-credit-balance so you can show the user their balance. 429 is only used for rate limits and key spending caps.
Rate limits and retries
Chat completions allow 60 requests per minute per key and 120 per minute per IP address. Over the limit you get 429 with Retry-After: 60.
- Retry:
rate_limit_exceeded_*(afterRetry-After),502 rail_errorand500(with exponential backoff). - Don’t retry:
400,401,402,404,key_cap_exhausted. They’ll fail the same way until something changes. - The OpenAI SDKs retry 429 and 5xx twice by default. Set
max_retries=0if you’d rather handle out-of-credit errors yourself.
import os
from openai import OpenAI, APIStatusError
client = OpenAI(base_url="https://api.tokenctr.com/v1", api_key=os.environ["TOKENCENTER_API_KEY"], max_retries=0)
try:
resp = client.chat.completions.create(model="mistralai/ministral-3b-2512",
messages=[{"role": "user", "content": "Hi"}], max_tokens=50)
except APIStatusError as err:
code = (err.body or {}).get("error", {}).get("code") if isinstance(err.body, dict) else None
if err.status_code == 402 or code == "insufficient_credits":
print("Out of credit. Top up at https://tokenctr.com/pricing.html")
else:
raiseStreaming requests
With "stream": true, a successful call returns 200 with text/event-stream in the OpenAI format: chat.completion.chunk events (role, then the text, then a final chunk with finish_reason and usage) and data: [DONE]. The reply is generated in full and then sent, so the chunks arrive together rather than token by token. Errors on a streaming request are returned as normal JSON with their real status (for example 402), not as an event, so the OpenAI SDKs raise them as usual.