Errors
On the OpenAI-compatible surface (https://api.recovea.ai/v1), Recovea returns errors in the exact OpenAI shape, so your SDK raises its normal exceptions and your existing error handling keeps working. Every response, error or not, also carries a x-recovea-trace-id header (alongside x-request-id) you can quote to support.
On the Anthropic-compatible surface (
https://api.recovea.ai/anthropic), errors use Anthropic's own error envelope instead —{"type":"error","error":{"type","message"},"request_id":"…"}. The two wire formats never mix. This page documents the/v1(OpenAI) surface.
The error envelope
Every error (any 4xx or 5xx) returns the same nested envelope. All four keys are always present; param and code are JSON null when they don't apply. Recovea never returns a bare string, a flat {message}, or a non-OpenAI shape: SDK exception parsing depends on this structure.
{
"error": {
"message": "…",
"type": "…",
"param": null,
"code": null
}
}
| Key | Meaning |
|---|---|
message | Human-readable description. Safe to log, not to branch on. |
type | Category: invalid_request_error, rate_limit_error, permission_error, or server_error. (Like OpenAI, the auth 401 uses invalid_request_error, not a separate authentication_error.) |
param | The offending field path (e.g. "messages[1].content") or null. |
code | Stable machine string (e.g. invalid_api_key, insufficient_quota, model_not_found) or null. Branch on this. |
Status codes
The HTTP status drives SDK retry behavior. Errors Recovea mints itself use the codes below; provider errors are mirrored through verbatim (see Retries), so you may also see other OpenAI status/code pairs (e.g. a 400 with param set, or a 404) exactly as your provider sent them.
| Status | Meaning | type / code |
|---|---|---|
401 | Missing key (code null) or wrong/revoked key | invalid_request_error / invalid_api_key |
403 | rcv_test_ key with no sandbox upstream configured | permission_error / test_mode_unavailable |
403 | No provider key connected for this workspace | permission_error / no_provider_key |
404 | Unknown model on /v1/models/{id} | invalid_request_error / model_not_found |
404 | Bare non-OpenAI model id on /v1 inference — the message names the vendor/model id to use | invalid_request_error / model_not_found |
429 | Throttle: too many requests | rate_limit_error / rate_limit_exceeded |
429 | Quota exhausted: monthly spend cap reached | rate_limit_error / insufficient_quota |
500 | Stored provider key unusable | server_error / provider_key_unusable |
502 | Upstream provider unreachable | server_error / service_unavailable |
503 | Auth or provider-key lookup temporarily down | server_error / service_unavailable |
Two flavors of 429
Throttling and quota exhaustion both return 429, distinguished only by code, not by status:
rate_limit_exceeded: you are sending faster than your request/token rate limit. Back off and retry; theRetry-Afterheader tells you how long.insufficient_quota: your monthly provider spend cap has been reached. Retrying will not help; raise or top up your budget.
Branch on error.code, never on the status alone.
Retries
OpenAI SDKs auto-retry 408, 409, 429, and ≥500 with exponential backoff; they do not retry 400, 401, 403, or 404. On a 429, Recovea sends a Retry-After (and retry-after-ms) header so the SDK waits the real reset window instead of guessing.
Two guarantees on the proxy:
- We never wrap a provider error in a Recovea 500. When the upstream provider returns an error, we mirror its status and envelope verbatim.
- We never swallow a 429 into a 200 or a 500. Doing so would defeat the SDK's
Retry-After-aware backoff and either hammer or stall your client.
Because of fail-open, a failure in Recovea's own optimization layer does not surface as an error: the request falls through to your provider on your own key. The errors here are the provider's or genuine request problems, passed through, not invented.
Example: a 401
An incorrect or revoked key returns 401 with a friendly, OpenAI-shaped body that points new callers to the docs:
{
"error": {
"message": "Incorrect Recovea API key provided. View and manage your keys at https://dashboard.recovea.ai. New to Recovea? It's an OpenAI-compatible API to see, govern, and prove your AI spend across every provider — get started at https://recovea.ai/docs.",
"type": "invalid_request_error",
"param": null,
"code": "invalid_api_key"
}
}
When no key is sent at all, the status is still 401 and type is invalid_request_error, but code is null (a missing key, not a wrong one) — branch on code accordingly.
Catching errors in Python
The OpenAI SDK maps each status to a typed exception, so you catch Recovea errors exactly as you would catch OpenAI's:
import openai
from openai import OpenAI
client = OpenAI(
base_url="https://api.recovea.ai/v1",
api_key="rcv_live_…",
)
try:
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello from Recovea"}],
)
except openai.AuthenticationError as e: # 401
print("bad key:", e.code) # "invalid_api_key"
except openai.RateLimitError as e: # 429 (both flavors)
if e.code == "insufficient_quota":
print("out of credit: top up")
else:
print("throttled: the SDK will back off and retry")
except openai.NotFoundError as e: # 404
print("unknown model:", e.code) # "model_not_found"
except openai.APIStatusError as e: # any other 4xx/5xx
print(e.status_code, e.code, e.message)
print("trace id:", e.response.headers.get("x-recovea-trace-id"))
Every exception exposes e.status_code, the parsed e.type / e.code / e.param, and the response headers, including x-request-id and x-recovea-trace-id (both set by Recovea on every response). Quote the trace id when you contact support.
Related
- Authentication: keys and the 401 path
- Streaming: how errors surface mid-stream
- Chat Completions: the request/response reference