Skip to content

Error: Response payload is not completed

This is your HTTP client complaining about the shape of the bytes it received, not an error OpenAI sent. The documented API statuses are 400, 401, 403, 429, 500 and 503, each with a type and a code in the body; none of them produce this wording. What it means is narrower and more useful than “the connection broke”: the response body ended while the client was still owed bytes — either a declared content length was not delivered in full, or a chunked body stopped without its terminating chunk. So this is not a quota problem, not an auth problem and not a model refusal, because all three of those are complete, well-formed responses that merely say no.

Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.

A broken promise, not a quiet ending

The distinction that makes this error worth its own page is easy to miss: a response that simply contained less output than you hoped for is not an error at all. If the model had produced a short answer, you would have received a valid, complete, small response. If generation had been stopped by policy or by a limit, you would have received a documented error with a code.

Getting this message proves something different. The party that wrote the framing said “more is coming”, and the byte stream ended anyway. The sender and the terminator were not in agreement — which is only possible if something ended the transfer other than the component that started it, or if the sender died mid-write. An orderly response, of any length, for any reason, cannot produce this error. That single observation removes the entire category of “maybe the model just stopped” from your investigation.

It also tells you where to look. Something in the path between the origin and your process is far more likely to end a body mid-flight than the origin is, and the usual suspects — a reverse proxy, an API gateway, a load balancer, an egress appliance, a compression layer — all sit exactly there. The next section turns that into a measurement rather than a hunch.

Which number stays constant

Here is the test that actually discriminates, and it costs you two log lines. On every failure, record how many bytes you received and how many seconds elapsed before the error. Collect several failures, then look at which of the two is stable.

Elapsed time is constant; byte count varies. A timer fired. Some hop has a read or idle timeout shorter than your generation needs, and it closed the connection without regard for how much had been sent. The fix is to raise that timeout at every hop on the path — raising it in one place while two others still hold the old value is the usual reason the change appears to do nothing.

Byte count is constant; elapsed time varies. A boundary, not a timer. Something is buffering the response and flushing in fixed units, or a size limit is truncating the body. Timeouts are irrelevant here and raising them will not move the failure by a single byte. Look for response buffering and compression on the streaming route and turn them off for it.

Neither is constant. Genuine variance — upstream instability or a lossy network. This is the one case where retrying is the whole strategy rather than a diagnostic step.

That one table is why this page exists. Every other approach to this error starts by guessing between “timeout” and “proxy”, and those two guesses have contradictory fixes.

Is retrying useful?

Yes — but make the retry deliberate, not automatic.

Most occurrences are transient and a single retry clears them. Two things stop it from being a reflex you can safely wire into a wrapper.

First, your SDK will not do it for you. On a streaming call, the bytes were already flowing, and the openai-python README states plainly that stream consumption is not automatically retried, because replaying a request could duplicate output already delivered to your application. Your max_retries setting — 2 by default — is not consulted at this point. Any retry here is one you wrote.

Second, that same reasoning applies to your own code. The docs advise not to automatically replay a request after consuming output. A retry regenerates from the beginning, so if your application already acted on the partial body it received — wrote a file, invoked a tool, sent a message — those side effects happen twice. Decide explicitly whether the work done so far is discardable before you re-issue.

The termination rule is the usual one: retry once, and if the second attempt dies with the same byte count or the same elapsed time, you have just proven the failure is deterministic and every further attempt is spending input tokens to reproduce a known result. Failed requests still count against your per-minute limit, so a hot loop is not free. Honor Retry-After when one is present; otherwise use exponential backoff with jitter and cap both the attempt count and the total retry time. No base delay is published, so do not copy one.

Telling the causes apart

Does it only affect long generations? Short requests finishing well inside any plausible timeout will succeed regardless of which hop is misconfigured, so “small requests work” is not evidence that the path is healthy. It only narrows the failure to something proportional to duration or size.

Does the output arrive steadily, or all at once near the end? Incremental arrival followed by a cut is a genuine mid-flight termination. A long silence followed by a burst and then the error means something held the whole body before passing it on, and that intermediary is your suspect.

Does it reproduce outside your network? Run the same request from a different network or a cloud shell. Corporate egress appliances and inspecting proxies are in exactly the position to truncate a body, and this test rules them in or out in one attempt.

Did it begin after an infrastructure change? A gateway upgrade, a new container image, a changed ingress configuration. Suspect the change before the vendor, and roll it back far enough to prove which side owns the failure.

Does the same request fail without streaming? If a non-streaming call returns a full body while the streamed one truncates, the problem is specific to how the streaming route is handled — usually buffering — rather than to the request itself.

Fix by scenario

  • Elapsed time constant — raise the read and idle timeouts at every hop, then verify each one from the running configuration rather than from the file you edited.
  • Byte count constant — disable response buffering and compression on the streaming route. Raising a timeout here is the cheap wrong fix: it converts a fast failure into a slow one and does not change the boundary.
  • Neither constant — retry with backoff around the whole streaming call, and keep the retry deliberate for the reasons above.
  • Tempted to abandon streaming — know what you are trading. A non-streaming call has to finish inside the SDK’s default 10-minute timeout, and the docs recommend streaming or the Batch API precisely for work that runs longer. You may swap a truncated body for a hard timeout on exactly the requests that were failing.
  • Reporting it — capture the x-request-id response header. For failed requests you have to catch the SDK’s error type and read .request_id in Python or .requestID in Node; the value looks like req_123. Attach the byte count and elapsed time you measured, because those are the two numbers whoever runs the intermediary will ask for.

How to confirm it’s fixed

Re-run the generation that was failing — the long one, not a convenient short one — and require it to complete twice in a row on the same network path. One success is indistinguishable from a transient condition clearing on its own.

Then make it falsifiable using the measurement you already have. A run counts as proof only if it exceeds the constant you identified: more bytes than the byte count that used to truncate, or more elapsed seconds than the interval that used to fire. A completed run that stayed under the old boundary tells you nothing, and that is the specific way teams close this ticket twice.

Sibling failures in this cluster are told apart by what the client was holding when it gave up. If your code caught something and printed a generic sentence with no code attached, you are looking at An error occurred during streaming, and the first task there is recovering the typed event underneath rather than diagnosing the string. If the client reports the stream closing before the completion event and announces its own retries, see stream disconnected before completion, where the client did receive a terminated stream, just not a finished one. And when the error body itself will not parse, read failed to parse ErrorResponse — an unparseable error payload is a strong hint that something other than the API produced it, which is the same suspicion this page ends on. If you cannot tell which of them you have, the AI coding error triage tool sorts them by the signals used above: whether framing or content failed, and whether the byte count or the elapsed time repeats.