Skip to main content

Errors

Every error the API returns, and what to do about each. Debugging a live failure? Skip to the error catalog; the body shapes below explain why the JSON you got may not match what your SDK expects.

Error shapes

The shape depends on where the error happens, not just what it is. There are four:

1. The standard error object: most errors on the OpenAI-compatible endpoints (/v1/chat/completions, /v1/responses, /v1/embeddings, /v1/models):

{
"error": {
"message": "The model 'not-a-model' does not exist or you do not have access to it.",
"type": "invalid_request_error",
"param": "model",
"code": "model_not_found"
}
}

2. The Anthropic envelope: everything on /v1/messages and /v1/messages/count_tokens:

{"type": "error", "error": {"type": "not_found_error", "message": "..."}}

3. Plain {"detail": ...}: request-body validation failures (422 with a list of field errors, plus some 400s such as an invalid image data URL) and internal server errors (500) on most OpenAI-compatible endpoints. An OpenAI SDK will not find an error.message in these; read detail. (Exception: /v1/embeddings internal errors use {"error": {"type": "internal_error", ...}}, and its upstream provider errors are relayed with their original OpenAI-shaped bodies.)

4. The 401: authentication fails before the request reaches the API, and the body is typically an HTML error page (some paths return a small JSON detail). Treat any 401 as "bad credentials" and don't try to parse its body. See Authentication.

Dispatch on the HTTP status code, and treat the body as best-effort. The code field, where present, is stable and safe to switch on.

Error catalog

StatuscodeWhat happenedWhat to do
401noneMissing, malformed, or revoked API key.Fix the key. Don't retry.
402wallet_emptyThe request needs wallet credit and your organization's balance is empty. Also what you get on the included-token model once the tokens run out, if your organization has ever added a payment method (see the 429 below for the never-topped-up case).Add credit in the console; the X-MindsHub-Recovery-Url header carries the console path. Don't retry until funded. See Billing.
400max_tokens_exceededmax_tokens above the hard cap of 131,072.Lower max_tokens.
400model_not_configuredThe model is in the catalog but isn't currently routable. Rare.Use another model, and report it to support@mindsdb.com.
404model_not_foundUnknown model name: a typo, a raw provider ID, or an alias not in the catalog.Use an alias from GET /v1/models.
422none (detail list)Request body failed validation: missing required field, wrong type, unknown message role.Fix the request.
429rate_limitedToo fast: requests per minute, tokens per minute, or concurrent requests.Wait and retry, honoring Retry-After (seconds, always ≥ 1). See Rate limits.
429included_allowance_exhaustedYour included tokens are used up, and your organization has no payment method on file, so there's no wallet to fall back on.Wait for the reset (X-MindsHub-Reset-At header, ISO-8601) or add a payment method and credit. No Retry-After is sent on this one.
4xx (relayed)variesThe upstream provider rejected something the platform forwarded, for example a temperature the model doesn't accept. Body has "type": "api_error" and the provider's own message and status.Fix the request for that model, or switch models.
5xx (relayed)variesThe upstream provider failed. Body has "type": "api_error". The platform retries transient provider errors (and fails over where a fallback route exists) before you see this.Retry with backoff.
502noneCouldn't reach the upstream provider.Retry with backoff.
503policy_unavailableMindsHub couldn't verify your account's access, so the request was refused instead of run. Transient.Retry in a few seconds.
500none (detail)Unhandled internal error.Retry once; if it persists, report it to support@mindsdb.com.

The two 429s mean opposite things. rate_limited is "slow down": retry after seconds. included_allowance_exhausted is "out of included tokens": retrying won't help until the reset date or a top-up. Tell them apart by the code, or by Retry-After, which only rate_limited sends.

The X-MindsHub-* headers

Denials from access checks carry machine-readable headers that survive even if an intermediary rewrites the response body:

HeaderSent onValue
X-MindsHub-Reasonmodel_not_found, wallet_empty, included_allowance_exhausted, rate_limited, policy_unavailableThe denial reason. Usually matches the body code, except model_not_found, where the header reads unknown_model.
Retry-Afterrate_limited onlySeconds to wait, always ≥ 1.
X-MindsHub-Reset-Atincluded_allowance_exhaustedISO-8601 instant when your included tokens refill.
X-MindsHub-Recovery-Urlwallet_emptyConsole path for adding credit.

Two more X-MindsHub-* headers travel on successful responses rather than errors, reporting how a request was adapted to the target model:

HeaderSent onValue
X-MindsHub-Dropped-ParamsAny request carrying a parameter the model can't takeComma-separated parameter names, e.g. temperature,top_p
X-MindsHub-Clamped-ParamsAny request carrying a parameter outside the model's rangename=requested>applied, e.g. max_tokens=200000>128000

Neither appears when nothing was adapted, so their absence means the request went upstream as you wrote it. This is why a parameter a model dislikes usually produces a 200 rather than a 400 — see Chat completions. One exception worth knowing: a rewritten tool_choice is not reported in either header (why).

Two gaps to code around: request-validation errors (max_tokens_exceeded, model_not_configured) carry none of these headers, and /v1/messages currently sends none of them on any error (a fix is rolling out); on that endpoint, back off on any 429 without waiting for a hint, using the body's error type. See Anthropic compatibility → Errors for that endpoint's envelope quirks.

Streaming failures

A request with "stream": true can fail three ways:

  1. Before any output: you get a normal JSON error response (any of the above) instead of an SSE stream. Check the response's Content-Type before parsing it as SSE.
  2. Mid-stream, visibly: the stream ends abnormally. There is no error event. On the OpenAI-compatible endpoints, a stream that ends without ever delivering a finish_reason chunk was a failed generation.
  3. Mid-stream, invisibly: on /v1/messages, a failed generation can arrive looking like a normal completion: the stream closes with an ordinary message_delta (stop_reason: "end_turn") and message_stop, just with truncated or empty content. There is currently no wire-level signal for this case. If your application must detect it, sanity-check the output (for example, empty content on a prompt that should produce text).

One model-family caveat for rule 2: streams on Gemini-served models (gemini, gemini-flash) always end with a finish_reason, even on failure, so on those, like on /v1/messages, absence of finish_reason can't be your only health check.

Partial output that arrived before a failure is real output and is billed.

A retry recipe

Using plain HTTP (the requests library) so the status and headers are visible; SDK users get the same decisions from their SDK's status-code exceptions:

import random
import time
import requests

class OutOfTokens(Exception): pass
class RequestFailed(Exception): pass

RETRYABLE = {429, 500, 502, 503, 504}

def exhausted_allowance(response):
# Prefer the header; fall back to the body's error code
# (the header isn't sent on every endpoint or error).
if response.headers.get("X-MindsHub-Reason") == "included_allowance_exhausted":
return True
try:
return response.json().get("error", {}).get("code") == "included_allowance_exhausted"
except ValueError:
return False

def post_with_retries(url, headers, body, max_attempts=5, timeout=120):
for attempt in range(max_attempts):
try:
response = requests.post(url, headers=headers, json=body, timeout=timeout)
except requests.RequestException:
# Connection failure or timeout: the request may still have run
# server-side. See the note on duplicates below.
time.sleep(min(2 ** attempt, 30) + random.random())
continue

if response.ok:
return response
if exhausted_allowance(response):
raise OutOfTokens(response.headers.get("X-MindsHub-Reset-At"))
if response.status_code not in RETRYABLE:
raise RequestFailed(response.status_code, response.text)

retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else min(2 ** attempt, 30)
time.sleep(max(delay, 1.0) + random.random())

raise RequestFailed("gave up", max_attempts)

The decisions that matter: retry 429 rate_limited and 5xx with backoff and jitter; honor Retry-After when present; stop immediately when included tokens are exhausted; and never blind-retry errors that won't change: 401, 402, 404, 422, or a 400 you caused, like an over-cap max_tokens.

This recipe is written for the OpenAI-compatible endpoints. On /v1/messages there is currently no reliable exhausted-allowance discriminator (no headers, and the Anthropic envelope carries no code), so an out-of-tokens 429 will fall into the generic retry loop; keep max_attempts low there, or inspect the error message text as a heuristic.

Retrying after a timeout can generate twice. A request that timed out client-side may still complete server-side and meter, and there is no idempotency key to deduplicate it. For large generations, prefer a generous timeout over aggressive retry, and treat a timeout as "outcome unknown", not "failed".