API Error: 500 {"type":"error","error":{"type":"api_error","message":"Internal server error"},"request_id":"req_011CZrhgdwV7TvN3achye98d"}
This one is the real thing: a documented error, in the documented envelope, from
the service itself. Status 500 maps to error.type: "api_error", which the docs
describe as an internal error and tell you to retry with exponential backoff.
Your request was authenticated, accepted, routed and then failed inside the
service — so it is not auth, not quota, not a malformed body (that would be a
400), and not a routing failure. The only two questions that matter are whether
retrying is safe, and what the request_id buys you.
Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.
Is retrying useful?
Yes — this is the error class the vendor’s own documentation tells you to retry, and the SDKs already do it twice by default.
The official clients retry transient failures including 5xx with exponential
backoff, twice by default, honoring retry-after when it is present. So by the
time a 500 reaches your terminal, it has usually already failed three times. Do
not read a single visible 500 as a single failure; read it as “the built-in
policy was exhausted.”
That reframes what your manual retry is. Hitting Enter again is not attempt two, it is attempt four, and the interesting information is whether it dies the same way. Retry once more, deliberately, and then treat a second identical failure as a deterministic bug rather than an outage. That branch point is the rest of this page.
Retrying is safe, with one exception
A Messages API call is a single request and response. A failed one does not leave a half-created object behind on the server the way a resource-creating call would, so re-sending the identical payload cannot produce two of anything. That is the sense in which a 500 here is safe to retry.
The exception is anything that creates a durable object — most commonly a batch submission. A 500 on a create call is genuinely ambiguous: the object may have been created moments before the failure. List before you re-create. Blindly re-submitting a batch after a 500 is the one way this error costs you money rather than time.
There is a second, subtler version of the same trap inside agent loops. If your retry unit is “the whole turn” rather than “the HTTP request,” and the turn included tool calls that already executed, retrying re-runs those side effects. Writing a file twice is harmless; sending an email twice is not. Retry the request, not the step.
Random 500 or deterministic 500?
The message is identical either way and the request id changes every attempt, so neither tells you which you have. These do:
- Send a trivially small request on the same key and model. If it succeeds while your real request keeps failing, the fault is specific to your payload — the service is up and your request is tripping something in it. If the tiny request also 500s, the problem is general and there is nothing on your side to fix.
- Does it fail at the same place in the same conversation every time? A transient internal error hits randomly and clears; a deterministic one is reproducible on demand. Being able to reproduce it at will is good news, because it means you can bisect it.
- Did it start immediately after a change on your side? A new tool definition, a new content block type, a jump in conversation size. Suspect the change before the vendor, even though the status code is the vendor’s to own.
- Is anyone else seeing it? Simultaneous reports from unrelated accounts make it general. Check that before investing in a bisect.
- Is the request unusually large? The Messages API accepts requests up to 32 MB; exceeding that is documented to return 413, not 500, so size alone is not an explanation — but a request near the top of that range is worth testing smaller as a cheap way to rule it out.
Fix by scenario
- Transient and general — back off and retry. Do not hard-code a delay; the
published guidance is exponential backoff and no base delay is documented to
copy. Raise
max_retriesif a slower, longer retry window suits your workload better than failing fast. - Deterministic on one payload — bisect it. Halve the conversation, then remove the tool definitions, then remove non-text content blocks, until the request succeeds. Whatever flips it is your trigger and your workaround, and it is also the single most useful thing you can hand to support.
- Deterministic and you need it working today — change one input at a time: a different model, the same content without tools, or the same request split in two. A workaround you understand beats a retry loop you are hoping grows out of it.
- Happening on a create-style call — list existing objects before retrying, every time, without exception.
- Happening under heavy parallelism — confirm you are actually looking at a 500 and not a throttle; the two get conflated in logs that record only the message. If some requests are being refused rather than failing, server-side throttling is a different page with a different fix.
What the request id is for, and what it is not
Every response carries a request-id header — hyphen in the header, underscore
in the JSON body field, and the Python and TypeScript SDKs expose it as
_request_id on top-level response objects. It is an index into the vendor’s
logs for one attempt.
Three consequences people learn the expensive way. First, each retry mints a new id, so the id you see after the SDK’s automatic retries belongs to the last attempt, not the first — capture them per attempt if you want the full picture. Second, an id is not a class of error: searching the web for yours will return nothing, and the one printed in this page’s title identifies somebody else’s failed request, not a category. Third, an id without a timestamp and timezone is much weaker than an id with one. Support can look up a request; they cannot look up a feeling.
So the reporting recipe is short: one request id from a failed attempt, the UTC timestamp, the model, and whether a minimal request succeeded at the same moment. That last item is what separates “your platform is down” from “this payload breaks your platform,” and it decides which queue the ticket lands in.
How to confirm it’s fixed
For the transient case, re-run the original request and require it to succeed twice in a row at the same size — one success is indistinguishable from the internal fault clearing on its own while you were reading logs.
For the deterministic case the confirmation is stronger and you should insist on it: re-introduce the element your bisect identified and watch the 500 come back, then remove it again and watch it go. A fix you cannot toggle is a coincidence you have not caught yet.
Related errors
Four failures in this family look alike in a terminal and are not alike at all.
A 529 overloaded_error is platform-wide traffic rather than anything about
your request. A 504 timeout_error means processing started and ran out of
time. A
503 no healthy upstream
never reached the service — which is why it has no request id, the fastest way
to tell it from this page’s error. And if the refusal is about limits rather
than internal failure, start at
API Error: Rate limit reached. To sort
an unfamiliar one by status, envelope shape, and whether a request id is
present, use the
AI coding error triage tool.