An error occurred during streaming
This sentence did not come from OpenAI. It carries none of the fields a real API
error carries — no type, no code, no param, no status — because it is what
a client prints after catching an exception and throwing the details away. A
malformed request, by contrast, comes back as a documented 400
invalid_request_error with error.param naming the offending field. You have
none of that, so there is nothing here to look up, and the first job of this
page is not to explain the string but to tell you which of two very different
failures produced it.
Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.
The question that splits this string in two
Before you change anything, answer one question: did any model output reach you before the error appeared?
That question matters because OpenAI’s streaming semantics are split exactly along it. The docs are explicit that for streaming requests, HTTP error responses apply before the stream starts, and that an error arriving after streaming begins comes through as a stream event instead. There is no third option, and the two halves have almost nothing in common.
No output at all. What failed was an ordinary HTTP request that happened to
ask for a stream. It came back with a real status — a 429 for a rate limit, a
503 server_is_overloaded for model overload, a 500 for a server-side fault,
a 401, a 403 — with headers attached and, on the throttling statuses, possibly a
Retry-After value telling you how long to wait. All of that existed and your
wrapper discarded it. Crucially, this half is eligible for the SDK’s automatic
retry: the official Python and TypeScript clients retry connection errors, 408,
409, 429 and 5xx responses with a short exponential backoff, twice by default.
Some output, then the error. Bytes were already flowing, which means the
request had already succeeded at the HTTP level. The failure arrived as a typed
stream event — an error event or a response.failed event — carrying its own
code and message. And here the openai-python README is blunt: stream
consumption is not automatically retried, because replaying a request could
duplicate output already delivered to your application. Your max_retries
setting is not broken in this half; it is simply not in the code path.
So the same eight words mean “the platform refused you and told you why, and your client already tried three times” in one case, and “the platform accepted you, started answering, then failed once, and nothing retried” in the other. Deciding what to do next without knowing which one you have is guessing.
Is retrying useful?
Yes — retry once, then stop and instrument.
Both halves are frequently transient, so a single retry clears most occurrences and is the cheapest thing you can do. What matters is the rule for the second failure: if an identical request dies the same way twice, you are looking at something deterministic, and further retries buy nothing except input tokens. Failed requests still count against your per-minute limit, so a hot retry loop against a throttled account actively makes the situation worse.
If a Retry-After header is present, honor it. If it is not, use exponential
backoff with jitter. Do not copy a “wait N seconds” figure from anywhere,
including this page: the documented guidance is backoff, no base delay is
published, and how each SDK handles long Retry-After values varies by version
and configuration.
One warning specific to the second half. The docs say not to automatically replay a request after consuming output. If your application already acted on the partial response — wrote a file, called a tool, posted a message — a retry regenerates from the beginning and your side effects happen twice. Make the retry deliberate, not a reflex in a wrapper.
Recovering the error that was actually thrown
This is the part that turns the page from advice into a fix, because the next occurrence should print something specific.
Handle the failure events instead of letting a blanket except collapse them.
The Responses API streams semantic events, and the two that matter here are
error and response.failed. The error event has the shape
{"type": "error", "code": ..., "message": ..., "param": ..., "sequence_number": ...};
sequence_number is worth logging because it tells you how far into the stream
the failure landed. The response.failed event carries the whole response
object with an error field on it, whose code names the cause.
For the first half — a status error before the stream — catch the SDK’s typed
exceptions rather than bare Exception. Non-2xx responses raise a subclass of
APIStatusError carrying status_code and response. Read the x-request-id
response header while you are there: Python exposes _request_id on successful
response objects and .request_id on the caught error, Node exposes
_request_id and err.requestID. Without one of those ids, a support ticket
is a description of a feeling.
Reading the code once you have it
Documented ResponseError.code values include server_error,
rate_limit_exceeded, invalid_prompt, vector_store_timeout,
invalid_image, invalid_image_format and invalid_base64_image — and the
published enum is truncated, so treat any list, including this one, as partial.
The useful split is not alphabetical, it is by who can fix it:
server_error— the platform, mid-generation. Retry with backoff.rate_limit_exceeded— note the collision before you act. This code exists both as a mid-streamResponseError.codeand as a legacy HTTP-level code on video requests. A mid-stream one arrived after a successful HTTP response, so there are no rate-limit headers attached to it; do not go looking for them and conclude your account is fine.invalid_prompt,invalid_image,invalid_image_format,invalid_base64_image— your input. Deterministic. Retrying an identical request reproduces it exactly, every time.vector_store_timeout— a dependency the generation reached for, not the model itself.- A code not in any list you can find — the enum is incomplete on purpose. Log it verbatim and check the current reference rather than assuming it maps to something familiar.
Fix by scenario
- No output, and the recovered status is 429 or 503 — pace the traffic and
honor
Retry-After. Check whether the 429 carries a billing-shapederror.code; the docs are explicit that retrying billing, spend or quota errors will not restore access, and that you must update the relevant credits or limits first. - No output, and your wrapper is what hid the status — remove the blanket handler. The SDK already retried twice before the exception escaped, so by the time you see the string, three attempts have failed and the condition is more stable than the message implies.
- Output arrived, then the error, at a wandering point — transient upstream variance. Retry with backoff around the whole streaming call, since nothing below you will do it.
- Output arrived, and it always stops at the same place — the failure is deterministic. Stop retrying and find the event code; a deterministic mid-stream failure is usually an input or a dependency, not capacity.
- You cannot tell which half you are in — that is itself the finding. Fix the logging first; every other action is a coin flip until then.
How to confirm it’s fixed
The confirmation for this error is unusual, because the first thing you fixed was your visibility. The test is that the next failure prints a different message. If the same eight words come back, the instrumentation change did not take effect and you have learned nothing, regardless of whether the underlying problem improved.
For the underlying failure itself, re-run the request that failed, at the same size, on the same network path, and require it to complete twice in a row. One success is indistinguishable from a transient condition clearing on its own while you were editing configuration.
Related errors
Sibling failures in this cluster look identical in a terminal and are not the same thing. When the transport complains that the body ended early rather than handing you an event, you are holding Response payload is not completed, which is a framing violation with a different diagnostic test — it keys off whether the byte count or the elapsed time is constant across failures. When the client reports the stream closing before the completion event and says it is retrying, see stream disconnected before completion; and when the failure is that the error payload itself could not be parsed, the relevant page is failed to parse ErrorResponse, because an unparseable error body points at something that is not the API answering. If you are not sure which of them you are looking at, the AI coding error triage tool sorts them by the signals used above: whether output arrived, whether a status code survived, and whether the failure point moves between attempts.