Confirm this is a feature request for the Python library and not the underlying OpenAI API.
Describe the feature or improvement you're requesting
When the Responses API runs in background mode (background=True) and a run terminates in status="failed" (or status="incomplete"), the SDK does not surface a stable, machine-readable identifier that callers can map to an existing exception class.
The failure payload looks like:
{
"status": "failed",
"error": { "code": "server_error", "message": "..." },
"incomplete_details": null
}
The HTTP poll itself returns 200 OK, so the SDK's status-code dispatcher is never invoked and no typed exception is raised. The only signal of what kind of failure occurred is the free-form error.code string, which:
- is not documented as a stable enum,
- does not correspond 1:1 to any SDK exception class (
server_error ≠ InternalServerError, rate_limit_exceeded ≠ RateLimitError, etc.),
- is not guaranteed to be present or non-null for every failure mode (e.g.
incomplete runs surface their reason in incomplete_details.reason instead, with yet another vocabulary),
- and may carry values not enumerated anywhere the SDK or callers can rely on.
What exists today (synchronous mode)
For synchronous calls there is a clean, code-driven mapping in openai/_client.py keyed on HTTP status code:
def _make_status_error(self, err_msg, *, body, response):
data = body.get("error", body) if is_mapping(body) else body
if response.status_code == 400: return BadRequestError(...)
if response.status_code == 401: return AuthenticationError(...)
if response.status_code == 403: return PermissionDeniedError(...)
if response.status_code == 404: return NotFoundError(...)
if response.status_code == 409: return ConflictError(...)
if response.status_code == 422: return UnprocessableEntityError(...)
if response.status_code == 429: return RateLimitError(...)
if response.status_code >= 500: return InternalServerError(...)
return APIStatusError(...)
So the synchronous path has a fully deterministic, documented contract: HTTP code → exception class. The background path has no equivalent — neither in the response body (no stable code) nor in the SDK (no dispatcher).
Why this matters
In synchronous mode the SDK gives us a clean contract. In background mode the same root cause surfaces only as an opaque string with no contract. Any retry/backoff logic built around isinstance(e, InternalServerError) or isinstance(e, RateLimitError) simply cannot work for background runs, even though those are exactly the conditions where retry would be most useful.
Suggested feature
Either:
-
Add a stable, documented field to background-run failure payloads (e.g. error.type or error.class) whose values directly correspond to existing SDK exception classes — e.g. internal_server_error, rate_limit_error, bad_request_error, content_filter_error, etc. The SDK can then mechanically map this field to the right exception class, mirroring what _make_status_error does today for HTTP status codes.
-
Or: publish a documented, versioned mapping from each possible error.code / incomplete_details.reason value to the SDK exception class (or "no class") and to a retryable flag. Treat it as part of the public API contract.
Either approach gives background runs the same deterministic contract synchronous calls already enjoy.
Environment
openai Python SDK version: <fill in>
- API:
POST /v1/responses with background=true, polled via GET /v1/responses/{id}
Additional context
No response
Confirm this is a feature request for the Python library and not the underlying OpenAI API.
Describe the feature or improvement you're requesting
When the Responses API runs in background mode (
background=True) and a run terminates instatus="failed"(orstatus="incomplete"), the SDK does not surface a stable, machine-readable identifier that callers can map to an existing exception class.The failure payload looks like:
{ "status": "failed", "error": { "code": "server_error", "message": "..." }, "incomplete_details": null }The HTTP poll itself returns
200 OK, so the SDK's status-code dispatcher is never invoked and no typed exception is raised. The only signal of what kind of failure occurred is the free-formerror.codestring, which:server_error≠InternalServerError,rate_limit_exceeded≠RateLimitError, etc.),incompleteruns surface their reason inincomplete_details.reasoninstead, with yet another vocabulary),What exists today (synchronous mode)
For synchronous calls there is a clean, code-driven mapping in
openai/_client.pykeyed on HTTP status code:So the synchronous path has a fully deterministic, documented contract: HTTP code → exception class. The background path has no equivalent — neither in the response body (no stable code) nor in the SDK (no dispatcher).
Why this matters
In synchronous mode the SDK gives us a clean contract. In background mode the same root cause surfaces only as an opaque string with no contract. Any retry/backoff logic built around
isinstance(e, InternalServerError)orisinstance(e, RateLimitError)simply cannot work for background runs, even though those are exactly the conditions where retry would be most useful.Suggested feature
Either:
Add a stable, documented field to background-run failure payloads (e.g.
error.typeorerror.class) whose values directly correspond to existing SDK exception classes — e.g.internal_server_error,rate_limit_error,bad_request_error,content_filter_error, etc. The SDK can then mechanically map this field to the right exception class, mirroring what_make_status_errordoes today for HTTP status codes.Or: publish a documented, versioned mapping from each possible
error.code/incomplete_details.reasonvalue to the SDK exception class (or "no class") and to a retryable flag. Treat it as part of the public API contract.Either approach gives background runs the same deterministic contract synchronous calls already enjoy.
Environment
openaiPython SDK version:<fill in>POST /v1/responseswithbackground=true, polled viaGET /v1/responses/{id}Additional context
No response