Limits and errors
Error shapes, status codes, retry and timeout guidance, and the maximum output for each model.
Requests fail for a handful of reasons, and most of them tell you exactly what to do next. This page covers the error format, what each status code means, how to retry safely, and the output limits per model.
Error shape
Every error is JSON. On the OpenAI-style routes (/chat/completions, /responses, /completions, /models, /key) the body looks like this:
{
"error": {
"code": 401,
"message": "Invalid API key."
}
}On /messages errors use the Anthropic shape, so the Anthropic SDKs and Claude Code parse them natively:
{
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key."
}
}Read the HTTP status first. The message is for humans and may change.
Status codes
| Status | Meaning | What to do |
|---|---|---|
400 | Malformed JSON, an unknown model id, or an unsupported parameter such as reasoning.max_tokens | Fix the request. Check ids against GET /api/v1/models. Do not retry. |
401 | Key missing, unknown, disabled, or expired | Check the header and XFIELD_API_KEY. Create a new key if needed. Do not retry. |
403 | Account disabled, or the content policy has not been confirmed | Sign in and confirm the policy, or contact us. Do not retry. |
415 | Body is not JSON | Send Content-Type: application/json. Do not retry. |
429 | Rate limited | Retry with backoff. |
500, 502, 504 | Upstream trouble | Retry with backoff. |
503 | Temporarily unavailable, usually with Retry-After | Wait for Retry-After, then retry. |
Send the key as Authorization: Bearer for OpenAI SDKs or x-api-key for Anthropic SDKs. A 401 on a key you just created is almost always the wrong header or a stray space.
Retrying
Retry only 429 and 5xx. Use exponential backoff with jitter so many clients do not retry in lockstep, and honor Retry-After when it is present.
import os, random, time
import httpx
URL = "https://xfield.cloud.brutal.ai/api/v1/chat/completions"
HEADERS = {"Authorization": f"Bearer {os.environ['XFIELD_API_KEY']}"}
def post_with_retry(body, attempts=6):
for attempt in range(attempts):
r = httpx.post(URL, json=body, headers=HEADERS, timeout=600)
if r.status_code != 429 and r.status_code < 500:
r.raise_for_status()
return r.json()
retry_after = r.headers.get("retry-after")
delay = float(retry_after) if retry_after else min(60, 2 ** attempt)
time.sleep(delay + random.uniform(0, 1))
r.raise_for_status()The official OpenAI and Anthropic SDKs already retry 429 and 5xx with backoff. Raise their max_retries setting rather than wrapping them in your own loop.
Timeouts for long reasoning
At high effort, a model can think for minutes before it writes the first visible token. Default client timeouts are often too short.
- Stream. With
stream: true, reasoning arrives asdelta.reasoningchunks, so the connection never sits idle and you can see progress. - Raise the timeout. For non-streamed requests at
high,xhigh, ormaxeffort, set the client timeout to ten minutes or more. - Lower effort when speed matters.
reasoning_effort: "low"or"none"returns much faster for simple tasks. - Check proxies. Load balancers and serverless functions in front of your client often cut connections at 30 to 60 seconds. Streaming avoids most of these.
Max output per model
| Model | Context | Max output |
|---|---|---|
deepseek-v4.1-flash-1017-uncensored | 1M | 64K |
kimi-k3.1-pro-uncensored | 1M | 922K |
glm-5.3-uncensored | 1M | 128K |
glm-5.3-flash-uncensored | 1M | 32K |
qwen-3.8-27b-uncensored | 128K | 32K |
If a long answer is cut off, ask for the output in parts or lower the reasoning effort. The live list, with features per model, is on /models.