API reference
Errors
When a request fails, the API answers with an HTTP status and a JSON body that says what went wrong. This page lists every error, what it means and what to do about it.
The error body
Errors use OpenAI's format: one error object with a message for people and a code for your program. This is a 402 for a request too large for the free tier:
{
"error": {
"message": "This request is too large for the free tier: at most 80,000 prompt tokens (DeepSeek's count plus 10%, and 1,024 per image) and 320,000 bytes of JSON not counting image data. Send less input, or top up.",
"type": "insufficient_quota",
"code": "FREE_TIER_DAILY_LIMIT",
"param": null,
"reason": "request_too_large",
"balance": 0
}
}MODEL_NOT_FOUND or FREE_TIER_THINKING. Test for this in code.model or max_tokens, else null.402 errors: allowance, request_too_large or paused.402: your API balance, in LYD.402 when you have a balance that is too small for this request: the smallest balance that would run it as a paid request.TOO_MANY_IMAGES and on 429 rate_limit_error: the limit you reached.403 ACCOUNT_SUSPENDED: until (a time, or null), reason (or null) and whatsapp, the number to contact.UPSTREAM_ERROR: the HTTP status DeepSeek answered with.Status and type
Each HTTP status has one type. The OpenAI libraries raise a different exception for each status:
| Status | type | Python exception | Node.js error |
|---|---|---|---|
400 | invalid_request_error | BadRequestError | BadRequestError |
401 | authentication_error | AuthenticationError | AuthenticationError |
402 | insufficient_quota | APIStatusError | APIError |
403 | permission_error | PermissionDeniedError | PermissionDeniedError |
404 | invalid_request_error | NotFoundError | NotFoundError |
405 | invalid_request_error | APIStatusError | APIError |
413 | none: an HTML page | APIStatusError | APIError |
422 | invalid_request_error | UnprocessableEntityError | UnprocessableEntityError |
429 | rate_limit_error | RateLimitError | RateLimitError |
500, 502, 503 | server_error | InternalServerError | InternalServerError |
In Python every one of them is an openai.APIStatusError; in Node.js, an OpenAI.APIError.
Errors from DeepSeek
The gateway checks a few fields itself. DeepSeek checks the rest, and when it refuses a request, its error comes back as DeepSeek sent it: the same format, with code set by DeepSeek (usually invalid_request_error) and DeepSeek's own request_id at the end of the message. This is DeepSeek's answer to "temperature": 5:
{
"error": {
"message": "Invalid temperature value, the valid range of temperature is [0, 2] (request_id: 6cc042c7-9847-4c53-aed8-266da6985f06)",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request_error"
}
}DeepSeek answers 400 for a value it refuses, and 422 for a body it can't read at all, such as an unknown role. A streamed request that DeepSeek refuses gets the same JSON error, not a stream.
Errors that aren't JSON
A few errors come from the servers in front of the API, before your request reaches it. Their body is not JSON, so check the Content-Type before you parse it:
413: the body is over 2 MB. nginx answers with an HTML page (below).403with the texterror code: 1010: Cloudflare refused the HTTP client. This happens with Python's urllib, for example. The OpenAI libraries, curl, requests, httpx and Node.js's fetch work. See Troubleshooting and FAQ.- A
5xxstatus with an HTML page, such as502,504or one of Cloudflare's52x: the servers in front of the API got no answer from it, or not in time. A504after 120 seconds is nginx's time limit: stream long replies, see Timeouts. Otherwise retry.
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx</center>
</body>
</html>Handling errors in code
This request names a model the API doesn't serve, so it fails with 400. It costs nothing and needs no balance:
curl -sS -w '\nHTTP status: %{http_code}\n' https://acacus.ly/v1/chat/completions \
-H "Authorization: Bearer $ACACUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Say OK."}],
"thinking": {"type": "disabled"}
}'The curl example prints:
{"error":{"message":"Unknown model: deepseek-chat. Model ids are exact. GET /v1/models, sent with this key, lists the models it can use: deepseek-v4-flash, deepseek-v4-pro and any others your account has.","type":"invalid_request_error","code":"MODEL_NOT_FOUND","param":"model"}}
HTTP status: 400The Python and Node.js examples print this, with a new request id each time:
400 MODEL_NOT_FOUND invalid_request_error model
Unknown model: deepseek-chat. Model ids are exact. GET /v1/models, sent with this key, lists the models it can use: deepseek-v4-flash, deepseek-v4-pro and any others your account has.
x-request-id: a826fc60-8911-4518-be8d-2e960b80eb23In Python, e.body holds the error object, so the details are e.body["balance"] and so on. In Node.js they are in err.error.
All errors
The code of each error, the start of its message, when it happens and what to do. Errors that come from the gateway's own checks cost nothing: see Billing and the free tier.
400 Bad request
| Code | Message | When | What to do |
|---|---|---|---|
INVALID_JSON | The request body is not valid JSON. | The body can't be read as JSON. | Send a JSON object. |
INVALID_JSON | The request body must be a JSON object. | The body is JSON but not an object, such as null or a list. | Send a JSON object. |
INVALID_PARAMETER | max_tokens must be a whole number of at least 1 (got 0). | model is not a string; max_tokens or max_completion_tokens is not a whole number of at least 1; stream is not true or false; or the request sends functions or function_call. param names the field. | Fix that field. Use tools and tool_choice instead of functions. |
MODEL_NOT_FOUND | Unknown model: deepseek-chat. Model ids are exact. GET /v1/models, sent with this key, lists the models it can use: deepseek-v4-flash, deepseek-v4-pro and any others your account has. | The model id isn't one your key can use. | Use an id from Models, or from GET /v1/models sent with your key. Ids are exact. |
MODEL_NO_VISION | deepseek-v4-pro can't read images. Send the message to a model that reads images (deepseek-v4-flash), or remove the images. | An image sent to deepseek-v4-pro. | Send images to deepseek-v4-flash, or remove them. |
IMAGE_NOT_IN_USER_MESSAGE | Images are accepted in user messages only. | An image in a system, assistant or tool message. | Move the image to a user message. |
TOO_MANY_IMAGES | Too many images in one request (at most 32). | More than 32 images. limit is 32 images. | Send fewer images per request. |
DeepSeek's, usually invalid_request_error | Invalid n value (currently only n = 1 is supported) (request_id: ...) | DeepSeek refuses a value: n other than 1, json_schema, 17 or more stop strings, a temperature or top_logprobs out of range, an empty messages list, or an image it can't download or read. | Fix what the message names. See Chat completions. |
401 Unauthorized
| Code | Message | When | What to do |
|---|---|---|---|
MISSING_API_KEY | Missing or malformed Authorization header. Send: Authorization: Bearer <your API key> | No Authorization header, or not in the form Bearer <key>. | Send Authorization: Bearer sk-shfr-.... |
INVALID_API_KEY | Invalid API key: it is unknown or was revoked. Keys are managed at https://acacus.ly/platform/api-keys | The key doesn't exist, was revoked, or doesn't start with sk-. | Check that you send the whole key, without quotes or spaces. If it was revoked or lost, create a new one on API keys. |
402 Payment required
Your balance, or the free allowance, doesn't cover the request. Every 402 has balance; some have reason and requiredLyd. How the checks work is in Billing and the free tier.
| Code | Message | When | What to do |
|---|---|---|---|
FREE_TIER_DAILY_LIMIT | You have used today's free allowance. Top up, or try again after 00:00 UTC. | You used today's free allowance. | Top up, or wait until 00:00 UTC. |
FREE_TIER_DAILY_LIMIT, reason allowance | This request needs more than what is left of today's free allowance. The check counts the reply at its full max_tokens (N here), so a lower max_tokens or less input may fit. ... | What is left of today's allowance is too small for this request. | Lower max_tokens, send less input, or top up. |
FREE_TIER_DAILY_LIMIT, reason request_too_large | This request is too large for the free tier: at most 80,000 prompt tokens ... and 320,000 bytes of JSON not counting image data. ... | The request is over the free size limits. | Send less input, or top up. |
FREE_TIER_EXHAUSTED | The free tier is used up for today. Top up, or try again after 00:00 UTC. | The free tier as a whole is used up for today. | Top up, or wait until 00:00 UTC. |
FREE_TIER_EXHAUSTED, reason paused | The free tier is paused for now. Top up to continue, or try again later. | Free requests are paused for now. | Top up, or try again later. |
FREE_TIER_EXHAUSTED | Insufficient balance. Top up to continue. | The free tier is switched off, and your balance doesn't cover the request. | Top up. |
FREE_TIER_MODEL | The free tier only includes deepseek-v4-flash. Top up to use ... | A free request for a model other than deepseek-v4-flash. | Use deepseek-v4-flash, or top up. |
FREE_TIER_THINKING | Thinking isn't included in the free tier. Top up to use it, or send the request without thinking: ... | A free request that asks for thinking: a thinking field that isn't disabled, or a reasoning_effort other than none. | Send "thinking": {"type": "disabled"}, or top up. See Reasoning. |
When your balance is too small for a paid request, the API tries it as a free request. That is why a 402 can carry a free-tier code while you have money in your API balance: then requiredLyd says how much balance the request needs.
403 Forbidden
| Code | Message | When | What to do |
|---|---|---|---|
ACCOUNT_SUSPENDED | This Acacus account is suspended. Contact us on WhatsApp at +218 94 380 1609. | The account is suspended. suspension says until when, and the reason when one is given. | Contact us on WhatsApp. |
| None: plain text from Cloudflare | error code: 1010 | Cloudflare refused the HTTP client. | Use the OpenAI libraries, curl, requests, httpx or Node.js's fetch. See Troubleshooting and FAQ. |
404 Not found and 405 Method not allowed
| Code | Message | When | What to do |
|---|---|---|---|
NOT_FOUND | Unknown endpoint: POST /v1/embeddings. This API serves POST /v1/chat/completions, GET /v1/models, GET /v1/models/{id} and GET /v1/usage. | A path this API doesn't have. | Check the path. The endpoints are listed in the Overview. |
MODEL_NOT_FOUND | Unknown model: deepseek-chat. Without an API key this API lists deepseek-v4-flash, deepseek-v4-pro; send Authorization: Bearer <your key> to see the other models your key may use. | GET /v1/models/{id} for an id this API doesn't serve. | Use an id from GET /v1/models. |
METHOD_NOT_ALLOWED (405) | GET is not allowed on /v1/chat/completions. Use POST. | The wrong HTTP method on a known path. The Allow header names the right one. | Use the method in Allow. |
413 Request too large
| Code | Message | When | What to do |
|---|---|---|---|
| None: an HTML page from nginx | 413 Request Entity Too Large | The request body is over 2 MB. | Send less. Images count toward the size as base64: see Images. |
422 Unprocessable
| Code | Message | When | What to do |
|---|---|---|---|
DeepSeek's, usually invalid_request_error | Failed to deserialize the JSON body into the target type: ... | DeepSeek can't read the body: for example no messages field, or an unknown role. | Fix the structure the message names. |
429 Too many requests
| Code | Message | When | What to do |
|---|---|---|---|
rate_limit_error | Rate limit exceeded: at most 30 requests a minute per account. Try again in 55 seconds. | More than 30 requests in the current minute. limit and the Retry-After header are set. | Wait Retry-After seconds, then retry. |
concurrency_limit | Too many requests in flight for this account: wait for a previous reply to finish. | 3 of your requests are already running. | Retry when one of them has finished. |
| DeepSeek's | DeepSeek's text | DeepSeek's own limit. | Retry with a growing wait. |
The limits are explained in Rate limits and other limits.
500, 502 and 503: server errors
| Code | Message | When | What to do |
|---|---|---|---|
INTERNAL_ERROR (500) | Internal server error. Please retry; if it keeps failing, contact support with the x-request-id. | A fault in the API. | Retry. If it keeps failing, contact support with the x-request-id. |
UPSTREAM_ERROR (502, or DeepSeek's status) | The model provider sent an empty answer. Please retry. | DeepSeek's answer was empty or not JSON. upstreamStatus has DeepSeek's status. | Retry. |
UPSTREAM_INTERRUPTED (502) | The upstream response was interrupted. Please retry. | DeepSeek's reply broke off before it was complete. If it reported its usage, that is billed; if not, the prompt plus an estimate of the reply for the time the request ran (20 tokens per second, up to max_tokens). Nothing is billed when no part of the reply had arrived. | Retry. The retry is billed again. |
UPSTREAM_DOWN (503) | The model provider is unavailable right now. Try again in a minute. | DeepSeek can't be used right now. | Retry after a minute. |
DeepSeek's (500, 503) | DeepSeek's text | A DeepSeek server error, passed on. | Retry with a growing wait. |
Retrying
429rate_limit_error: wait the number of seconds inRetry-After, then retry.429concurrency_limit: retry when one of your running requests has finished.500,502,503, and DeepSeek's own429: retry with a growing wait. A retriedUPSTREAM_INTERRUPTEDis billed again, because the first try was billed for its prompt.- Every other
4xx: fix the request first. Sent again unchanged, it gets the same error.
The OpenAI libraries retry 408, 409, 429 and 5xx errors on their own, 2 times by default, and wait the Retry-After time when there is one (checked in openai-python 3.19.2 and openai-node 7.23.0). Each retry counts toward your requests per minute. Change it with max_retries in Python or maxRetries in Node.js.
Request ids and support
Every reply from the API has an x-request-id header. Send your own x-request-id with a request and the reply carries the same value, so you can match the two in your logs. The libraries read it for you: e.request_id in Python and err.requestID in Node.js.
Replies from the servers in front of the API have no x-request-id: a 413, a 403 with error code: 1010, or a 5xx HTML page (see Errors that aren't JSON). For those, note the cf-ray header, which Cloudflare puts on every reply, and the time.
If an error doesn't make sense, write to [email protected] or on WhatsApp with the x-request-id (or the cf-ray header when there is no x-request-id), the time, and the status and body you got. Leave your API key out.