openai Python and TypeScript SDKs, plus the Anthropic SDK (which parses a superset), expect. Existing client code raises its usual typed exceptions unchanged.
Response headers
Errors (like successes) always carry:X-LangWatch-Request-Id: grq_<ULID>, use this when filing support tickets.X-LangWatch-Provider, present when the error originated from an upstream provider (absent for gateway-internal errors).
Type enum
Budget-warning headers (not errors)
These are soft signals on successful responses:X-LangWatch-Budget-Warning: <scope>:<pct>, a budget scope is over its soft threshold. Multiple can be present.
warn breach never turns into an error envelope; it’s only a header.
Examples
Invalid key
Budget exceeded
Blocked tool
Upstream timeout after fallback exhaustion
Streaming errors
For SSE streaming, a terminalevent: error frame carries the same envelope and the stream ends:
error frame should treat the response as incomplete (partial) and X-LangWatch-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”).