Reference
Updated
Errors
The error envelope, every status and code the helping.io API returns, what each one asks you to do, and the timeouts and retry rules to design for.
Every error on /v1 is the standard Responses API envelope, including the
ones raised before your request reaches the endpoint, such as a bad key.
{ "error": { "message": "missing or malformed Authorization header", "type": "invalid_request_error", "param": null, "code": "invalid_api_key" }}messageis human-readable and safe to log.typefollows from the HTTP status, so SDK retry logic can branch on it. The one exception isinsufficient_quota: a429that carries typeinsufficient_quota, as OpenAI’s own quota refusal does, so a client that handles that refusal from OpenAI handles this one.codeis the specific condition, always lowercase, and is the field to switch on in your own code.paramnames the request field a refusal is about, when it is about one:model,input,metadata,tool_choice,max_output_tokensand the rest are reported as you sent them, so your error handler can branch on the field instead of reading the message. It isnullfor everything else, including a body that will not parse, a bad key, and every rate limit. Read it when it is there; do not require it.
Status to type
Section titled “Status to type”| HTTP status | type |
|---|---|
400 | invalid_request_error |
401 | invalid_request_error |
402 | invalid_request_error |
403 | permission_error |
404 | not_found_error |
409 | invalid_request_error |
429 | insufficient_quota |
429 | rate_limit_error |
500 | api_error |
502 | api_error |
503 | overloaded_error |
504 | api_error |
Any other 4xx carries invalid_request_error.
Authentication, authorization, and billing
Section titled “Authentication, authorization, and billing”| Status | code | Retry-After | Meaning |
|---|---|---|---|
401 | invalid_api_key | No | The Authorization header is missing, malformed, or names a key that is not usable; check the key. |
403 | access_denied | No | The key is valid but is not entitled to what the request asked for; ask your administrator. |
429 | insufficient_quota | No | The organization has no active plan, or no credit balance to pay for the request; the message says which. |
402 | payment_method_required | No | The organization has no payment method on file, so no request can be served; add one in the billing settings. |
503 | auth_unavailable | Yes | Your credentials could not be checked this instant, which is not a verdict on the key; retry after the Retry-After header. |
insufficient_quota means your organization cannot pay for the request: it
has no active plan, or it has no credit balance. The message says which. It is
not a rate limit, and retrying does not change the answer.
Request validation
Section titled “Request validation”Nothing is created and nothing is charged when one of these comes back.
| Status | code | param | Meaning |
|---|---|---|---|
400 | invalid_input | The offending field | The request cannot be honored as written; the message names the field and the rule it broke. |
400 | context_length_exceeded | Always null | The conversation plus this turn's input is larger than the model's context window; start a new conversation, send less, or set truncation to auto to let the oldest turns be dropped. |
400 | model_policy_violation | The offending field | Your organization's policy does not allow the model this request selected; the message names the model and the rule that refused it, and param is `model`. |
404 | resource_not_found | Always null | The path or the previous response named by this request is not available to you. |
Conflicts
Section titled “Conflicts”| Status | code | Meaning |
|---|---|---|
409 | idempotency_conflict | This Idempotency-Key was already used within the last 24 hours, and what it made cannot be returned for this request: the body differs, or this API key cannot read it. Nothing was created. |
409 | idempotency_in_progress | The first request with this Idempotency-Key is still being processed. Nothing was created; send the request again shortly. |
See Idempotency.
Capacity and limits
Section titled “Capacity and limits”Entries marked Retry-After carry that header; honor it. See
Rate-limit headers for the headers
that let you avoid most of them.
| Status | code | Retry-After | Meaning |
|---|---|---|---|
429 | rate_limit_exceeded | Yes | This request exceeded a rate limit that applies to you; retry after the Retry-After header. |
429 | stream_limit_exceeded | Yes | You are already holding the maximum number of open streams; nothing was started, so retry once one ends. |
429 | scope_budget_exceeded | No | A spend budget set for your organization, a member, or an API key is exhausted; the message states which. |
429 | insufficient_credits | No | The organization's credit balance is exhausted; top up in the billing settings. |
503 | gateway_overloaded | Yes | The API could not accept this request and nothing was created; retry after the Retry-After header, and retrying with the same Idempotency-Key still deduplicates. |
503 | database_unavailable | Yes | The request could not be served this instant and nothing was created; retry after the Retry-After header. |
Model provider failures
Section titled “Model provider failures”| Status | code | Meaning |
|---|---|---|
429 | provider_rate_limited | The model provider rate limited this request; retry, honoring Retry-After when one is present. |
502 | provider_overloaded | The model provider reported that it is overloaded; retry shortly. |
502 | provider_unavailable | The model provider did not complete the request; retry shortly. |
A provider that rate limits your request is refused with 429, carrying the
envelope the table above describes:
{ "error": { "code": "provider_rate_limited", "message": "The upstream model provider rate limited this request; retry later.", "param": null, "type": "rate_limit_error" }}Non-transient provider failures do not use the error envelope. They arrive as
a 200 whose response object has status: "failed" and an error with a
code and a message. See Failed responses.
Server faults
Section titled “Server faults”| Status | code | Meaning |
|---|---|---|
504 | request_timeout | This request's wait window ended before a result was available and no response could be read back for it; quote the x-request-id response header when reporting it. |
500 | internal_server_error | The request could not be completed; quote the x-request-id response header when reporting it. |
500 | database_error | The request could not be completed; quote the x-request-id response header when reporting it. |
500 | item_unreadable | The item is stored but could not be read, so it cannot be returned. A list of items names it in helpingio:unreadable and returns the others. |
item_unreadable answers a request for one item that is stored but could not be
read. A list of items does not fail because of it: the list names the item in
helpingio:unreadable and returns the others.
Failed responses
Section titled “Failed responses”A response that fails after it has started is not an HTTP error. It is a
response object with status: "failed" and a populated error:
{ "id": "resp_…", "object": "response", "status": "failed", "output": [], "error": { "code": "provider_client_error", "message": "The upstream model provider rejected the request." }, "usage": { "input_tokens": 100, "input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 }, "output_tokens": 0, "output_tokens_details": { "reasoning_tokens": 0 }, "total_tokens": 100 }}The same shape arrives inside a streaming response.failed event. usage,
when present, shows what the failed response consumed; read it with a
presence check.
error.code on this channel is a closed set, and a failure this surface does
not name separately arrives as run_failed. Switch on the code; the message
is written for a person to read and may be reworded.
code | Meaning |
|---|---|
budget_exceeded | The spending limit for a single response was reached. The message states what was spent against the limit, in credits. |
canceled | The request was canceled before it finished. |
end_user_blocked | The model provider refuses requests for the end user this request was sent for, named by its safety_identifier or user. Sending it again will not succeed for that end user. |
insufficient_credits | The organization ran out of credits while the request was running. Add credits and send it again. The message states the balance, in credits. |
org_daily_budget_exceeded | The organization's daily spending limit was reached. The message states what was spent against the limit, in credits. |
provider_client_error | The upstream model provider rejected the request. The request reached the provider and came back refused. |
provider_error | The upstream model provider returned an unexpected error. |
provider_overloaded | The upstream model provider is overloaded. Retry later. |
provider_rate_limited | The upstream model provider rate limited the request. Retry later. |
provider_unavailable | The upstream model provider failed to complete the request. Retry later. |
run_failed | The request did not complete. Send it again, and contact support with the response id if it keeps failing. |
scope_budget_exceeded | A spending limit set for this period was reached. The message states what was spent against the limit, in credits. |
Five of these codes are in the HTTP tables above as well, because where a failure reaches you depends on how you asked:
- A non-streaming
POST /v1/responsesanswers an upstream provider failure with an HTTP error, soprovider_rate_limited,provider_overloadedandprovider_unavailablearrive there with a status. - A streaming request answers
200before it can know, so the same failure arrives in the response object instead. So does aGET /v1/responses/{response_id}, which answers what happened rather than refusing. scope_budget_exceededis an HTTP refusal when the limit is already spent as the request arrives, and a failed response when it runs out while the request is running.- Out of credits as the request arrives is the HTTP refusal
insufficient_quota.insufficient_creditsis the later, rarer case: an HTTP refusal when the request was accepted but the balance could not pay to start it, and a failed response when credits run out while the request is running. Handle both codes the same way.
A spending failure in a failed response states your own figures in credits, to one decimal place: what was spent against the limit, or the balance. Switch on the code; the figures are for the person reading the message.
Errors during a stream
Section titled “Errors during a stream”Once a stream is open the HTTP status has already been sent, so every later failure is in-band:
response.failedcarries the failed response object above.response.incompletemeans the response stopped early, for example atmax_output_tokens. Readincomplete_details.reason.- An
errorevent carries a failure that belongs to neither: the stream itself could not deliver a result.
The codes an error event can name:
code | Meaning |
|---|---|
response_unresolved | The stream ended before a result was ready. The response may still finish: retrieve it by the id response.created carried. Retrying without the same Idempotency-Key starts a second response. |
Timeouts and retries
Section titled “Timeouts and retries”The rate limits, and the headers that report them, are on Rate limits.
Timeouts
Section titled “Timeouts”| Bound | Value | What happens |
|---|---|---|
| Non-streaming wait | 90 seconds | The in-progress response object is returned with 200. Poll by id |
| Stream ceiling | 300 seconds | The stream closes with its terminal event. Retrieve by id |
| Stream keep-alive | every 15 seconds | An SSE comment frame keeps intermediaries from timing out a quiet stream |
Idempotency
Section titled “Idempotency”POST /v1/responses honors the Idempotency-Key header, and so do the
requests that create a conversation, update one and add items to one. The
official SDKs do not send it on their own, so pass it yourself in the request
options and reuse it when you retry:
- A retried request with the same key and the same body returns the first
request’s response, so the model produces output and bills once. That
includes a request that sends
function_call_outputitems. - The same key with a different body is a
409with the codeidempotency_conflict, and nothing is created. - A retry that arrives while the first request is still being processed is a
409with the codeidempotency_in_progress. Nothing is created; send it again shortly. - A request that is refused, for example with a
400, does not use up its key, so you can correct it and send it again with the same key. - A key is remembered for 24 hours. After that, the same key starts a new request.
- Keys belong to your organization, and each endpoint keeps its own keys. A
retry from a different API key than the first request’s is a
409with the codeidempotency_conflictwhen that key cannot read what the first request made. On a conversation’s own endpoints, the same key and body sent to a different conversation is a409with the codeidempotency_conflict. - Keys are printable ASCII, spaces included, at most
255 bytes. A longer key, or one with any other
character, is a
400. Empty keys are ignored. - Without a key, a retry after a delivery failure creates a second response and a second charge.
Backing off well
Section titled “Backing off well”- Honor
Retry-Afteron every429and503. Use exponential backoff with jitter when the header is absent. - Watch the remaining-requests header above and slow down before it reaches zero.
- Send an
Idempotency-Keyon every create so retries are safe. - Prefer streaming for long work. A stream is bounded at 300 seconds, a non-streaming wait at 90.