Error Code to HTTP Status Mapping
so1 beginner 3 min read
ELI5
The error code is the colour on the label; the HTTP status is the post-office tier. Same letter, different envelopes — both must agree, otherwise the receiver can’t sort the mail.
Technical Deep Dive
Canonical Mapping
Per ADR-004:
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | session invalid/missing |
FORBIDDEN | 403 | authenticated but not permitted |
BAD_REQUEST | 400 | malformed request (missing field) |
VALIDATION_ERROR | 400 | typed validation failed (Zod) |
NOT_FOUND | 404 | resource absent |
CONFLICT | 409 | state conflict (Idempotency-Key mismatch — see so1-009) |
GITHUB_ERROR | 502 | upstream GitHub failed |
N8N_ERROR | 502 | upstream n8n failed |
MCP_ERROR | 502 | MCP tool/server failed |
INTERNAL_SERVER_ERROR | 500 | unhandled exception |
SERVICE_UNAVAILABLE | 503 | dependency down (secrets, queue) |
Selection Flow
flowchart TD fail[failure detected] --> who{whose fault?} who -- client auth missing --> u[UNAUTHORIZED 401] who -- client auth ok, perm no --> f[FORBIDDEN 403] who -- client payload bad --> kind{shape vs values?} kind -- shape --> br[BAD_REQUEST 400] kind -- values --> ve[VALIDATION_ERROR 400] who -- not found --> nf[NOT_FOUND 404] who -- state collision --> co[CONFLICT 409] who -- upstream service --> svc{which?} svc -- github --> ge[GITHUB_ERROR 502] svc -- n8n --> ne[N8N_ERROR 502] svc -- mcp --> me[MCP_ERROR 502] who -- bff bug --> ise[INTERNAL_SERVER_ERROR 500] who -- dependency down --> sa[SERVICE_UNAVAILABLE 503]400 Variants Distinction
BAD_REQUEST is structural (missing field, wrong content-type). VALIDATION_ERROR is semantic (branch is present but not a valid git ref). Clients can show different remediation: “fix request shape” vs “fix this field”.
502 Choice for Upstreams
ADR-004 elects 502 (Bad Gateway) for all upstream failures because the BFF acts as the gateway. Clients can therefore retry with the same intent; the failure was not theirs.
Key Terms
- Code → stable, machine-readable string from the canonical table.
- HTTP status → tier-level signal carried in the response line.
- Bad Gateway (502) → standard semantic for “I called somebody else and they failed me”.
Q&A
Q: A user lacks access to a GitHub repo — FORBIDDEN or GITHUB_ERROR?
A: FORBIDDEN (403). The decision was made by the BFF’s RBAC, not relayed from GitHub. Use GITHUB_ERROR only when GitHub itself returned an error code.
Q: What status does a Zod validation throw produce?
A: VALIDATION_ERROR with HTTP 400; the offending field path goes into error.details.
Q: Are 5xx codes safe for clients to retry? A: 502 and 503 are typically transient and retriable; 500 indicates a BFF bug — retry only with backoff and surface to engineering.
Examples
The GitHub adapter (so1-005) catches a 403 from api.github.com due to a revoked PAT. Because BFF authorization passed but the broker call failed, it raises GITHUB_ERROR (502) with details.upstream.statusCode=403 — not FORBIDDEN.
neighbors on the map
- Request Routing & Edge Resolution debugging why a slug returns 404 instead of redirecting
- ProtocolMessage Envelope adding a new wire message type
- Run Outcome Classification interpreting a History row's status pill