Error codes and handling patterns specific to the Responses API.
An error on POST /v1/responses comes back at its HTTP status as a failed response — the object the OpenAI library's responses.create reads — not the { "error": { "code": 402 } } object the other routes answer with.
{
"id": "resp_a2d1a1edc64042ea908548f79ff9cfd3",
"status": "failed",
"error": {
"code": "server_error",
"message": "The model field is required."
},
"error_type": "invalid_request",
"metadata": {
"errors": {
"model": ["The model field is required."]
}
}
}| Field | Type | Always present | Description |
|---|---|---|---|
id | string | Yes | resp_ followed by the request's X-Request-Id without its dashes |
status | string | Yes | Always failed |
error.code | string | Yes | rate_limit_exceeded on a 429, server_error on every other status |
error.message | string | Yes | A human-readable explanation |
error_type | string | Yes | The cause in one word, the same word the other routes use — see Error types |
metadata | object | No | The detail, when there is one: the fields that failed on a 400 (errors), the limit that stopped a 402 (limit_source), what a filter found on a 403 (types) |
Read the cause from the HTTP status and error_type; error.code only separates a rate limit from everything else.
| HTTP | error_type | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed, a required field is missing, or the input is longer than the model takes. metadata.errors lists the fields |
| 401 | authentication | The key is missing, wrong, disabled or expired |
| 402 | payment_required | Your wallet is at zero or below, the key's cap is spent, or a guardrail's budget is spent. metadata.limit_source says which: wallet, key or guardrail |
| 403 | permission_denied | A guardrail blocked the request, a filter flagged it, or the model refused as an error |
| 404 | not_found | The model is not on sale, or the container is not yours |
| 408 · 504 | timeout | The provider did not answer in time |
| 429 | rate_limit_exceeded | The provider is limiting its rate. Wait the Retry-After seconds |
| 500 | server | An unexpected error; the message is generic |
| 502 | provider_unavailable | The model is down, or answered with nothing usable |
| 503 | provider_overloaded | No provider meets the routing the request asked for |
A provider that names a more precise type keeps its own word in error_type. There is no cap on the number of requests: a 429 is always the provider's. The full list is on Errors and Debugging.
import os
import time
from openai import APIStatusError, OpenAI
client = OpenAI(
base_url="https://opendunes.com/v1",
api_key=os.environ["OPENDUNES_API_KEY"],
)
for attempt in range(3):
try:
response = client.responses.create(
model="openai/gpt-5.1",
input="Hello",
)
break
except APIStatusError as e:
if e.status_code == 429:
time.sleep(int(e.response.headers.get("Retry-After", "5")))
elif e.status_code == 402:
raise RuntimeError("Balance too low — deposit DA at opendunes.com/dashboard/billing") from e
else:
raiseA 402 is never a wait-and-retry case.
An error before the stream opens is the ordinary error response above. Once the stream is under way its 200 is already sent, and a failure arrives as a response.failed event whose response carries status: "failed" and the error. Check for it before treating the stream as complete. See Streaming.