A Field Guide to LLM API Error Messages
Inference APIs return a small, stable set of failures, and most integrations handle them with a blanket retry that makes two of them worse and hides a third. Knowing which is which takes about ten minutes and saves an outage. The shape of an error Both major dialects return a JSON body with a structured error object alongside the HTTP status. In the OpenAI dialect it is {"error": {"message", "type", "param", "code"}} ; Anthropic returns {"type": "error", "error": {"type", "message"}} . The status tells you the class; the type or code field tells you what to do, and it is the field most client code discards. Log both, and log the request id header — every provider issues one, and it is the only thing a support conversation can proceed from. The distinction that organises everything below is not client-versus-server, which is what the status code nominally encodes. It is will the identical request succeed later? Three answers exist: yes after a wait (capacity and rate conditions), no until something changes in the request (validation, auth, model identity), and no until something changes outside the request entirely (a billing state, a retired snapshot, a regional restriction). Only the first is retryable, the second belongs in an alert on your own deploy, and the third needs a human. Several genuinely different conditions share a status code across that boundary, which is why classifying on status alone produces a retry policy that is wrong in both directions — hammering a wall in one place and giving up on a transient blip in another. Error messages themselves are prose written for a human and are the worst thing to branch on. They get reworded without notice, they are sometimes localised, and the same underlying condition is phrased differently by two providers. Match on the status and the type field, keep the message for the log, and if you must string-match — some providers put the only useful detail in the message — treat that branch as a known liability and cov