Skip to main content
Every LangWatch error, from the gateway data plane and from the management API alike, is returned in one OpenAI-compatible envelope:
This matches what the openai Python and TypeScript SDKs, plus the Anthropic SDK (which parses a superset), expect. Existing client code raises its usual typed exceptions unchanged.

Two planes, one envelope, one difference

type, code, message and meta mean the same thing on both LangWatch surfaces, and a client that reads only those four works against either. The surfaces are:
  • The control plane: the management API, /api/gateway/v1/* and /api/webhooks/v1/*. You create virtual keys, budgets, cache rules and webhook endpoints here.
  • The data plane: the OpenAI-compatible chat surface the gateway serves. Your application’s model calls go here.
They differ in exactly two ways, both consequences of the data plane being OpenAI-wire-shaped on purpose: Read the reason chain from the plane you called. Nothing else about the envelope moves, and the data plane’s extra tips, docs_url and fault fields are additive on both.
402 budget_exceeded is a data-plane refusal because a budget stops traffic, not administration. Creating or editing a budget over the control plane is never refused for being over cap.

Fields

Validation failures put the offending fields under meta: meta.target names the part of the request that failed (json, query, param) and meta.fields lists the offending paths. The per-violation chain carries one { code, message, meta } entry each, with meta.field, meta.type and, where the schema knows them, meta.expected and meta.received. That chain lives at error.meta.reasons on the control plane and at error.reasons on the data plane, as above. The gateway data plane adds three fields on the errors it can offer a next step for, most notably the 402 budget_exceeded: A client that reads only type, code, message and meta works unchanged against both planes; the three fields above are additive.
Clients must ignore unknown fields. Fields are added to this envelope over time and none of the additions above changes an existing one.

Response headers

Errors (like successes) always carry:
  • X-LangWatch-Gateway-Request-Id: req_<hex>, use this when filing support tickets. It is the same id that appears as gateway_request_id on spend events and webhook envelopes, so it joins a failing call to its billing records.
  • X-LangWatch-Provider, present when the error originated from an upstream provider (absent for gateway-internal errors).
  • X-LangWatch-Handled-Error, the top-level error.code, present on every LangWatch-authored error. A forwarded provider error is not LangWatch-authored, so this header is absent (or stripped, if the provider’s own response happened to set it) on those responses. Use it to tell the two apart rather than guessing from the body shape.

Type enum

Control-plane codes

The management and webhook APIs answer with the same envelope, and their type is the status class. Branch on code:

Budget-warning headers (not errors)

These are soft signals on successful responses:
  • X-LangWatch-Budget-Warning: <scope>:<pct>, a budget scope is at or over the 80 percent soft threshold. Multiple can be present.
A warn breach never turns into an error envelope; it’s only a header.

end_user_required (fail-closed attribution)

When a virtual key carries an active attributed-user budget template and a request arrives with no end-user id anywhere the gateway resolves one, the request is rejected with error.code = "end_user_required" rather than passing uncapped. Branch on the code: the fix is always on the request side, send the OpenAI user field or the X-LangWatch-End-User-Id header (X-Litellm-End-User-Id is accepted as an alias), and the error’s message and tips name exactly that. It is terminal for the request as sent; do not retry without adding the id.

Examples

Invalid key

Budget exceeded

Blocked tool

Upstream timeout after fallback exhaustion

Streaming errors

For SSE streaming, a terminal event: error frame carries the same envelope and the stream ends:
Clients that receive chunks and then an error frame should treat the response as incomplete (partial) and X-LangWatch-Gateway-Request-Id still identifies the session in traces.

Mid-stream code values

Once bytes are flowing, the HTTP status is already 200, so the distinguishing signal for clients is the code field inside the terminal frame:
The type on a streaming terminal frame always reflects the category (provider_error, guardrail_blocked), clients keying off type will already have a usable classification. The code is the granular discriminator if you need it (for example, a retry policy that distinguishes “upstream flaked, retry with a different VK” from “guardrail policy said no, don’t retry”).