Structured error handling means every failure your API returns is machine-readable, typed and consistent. A client can branch on it without parsing prose. The working recipe: a standard body shape (RFC 9457 problem details), the right HTTP status, a stable error code, a safe request ID, and an explicit retry signal.
TL;DR
- Return one error shape everywhere. RFC 9457's
type,title,status,detailandinstanceare the baseline; add extensions for your own fields. - Clients should switch on a stable
code, never on the HTTP status alone and never on message text. - Be precise about the caller's input and opaque about your system's internals. Log the detail server-side and return a request ID.
- Mark what is retryable. Retry transient failures with backoff and jitter; never retry permanent ones.
- For scraping and agent tools, treat "the page said no" as a typed result, not an exception.
Table of Contents
- Why a consistent error contract matters
- The error body: fields that earn their place
- Picking status codes
- Useful messages without leaking internals
- Retries: what the client should do
- Request IDs and structured logs
- Perspective
- Applying this to scraping, agents and RAG
- Try typed failures on a real API
- FAQ
- Sources
Why a consistent error contract matters
RFC 9457 exists because non-human clients have a hard time interpreting HTML error pages. It defines a JSON or XML problem-details format so a program can understand what went wrong and act on it.
AWS makes the same point from the client side: an explicit error contract lets clients centralize handling, automate retries and generate strongly typed SDKs. Without a contract, every integration reinvents string matching, and every message rewrite breaks someone.
Status codes alone are too coarse. They tell the client which class of problem occurred. The body tells it which problem, where, and what to change. For more on how bodies complement status codes, see this breakdown of API rate limiting decisions, where 429 handling is a good example.
The error body: fields that earn their place
Start from RFC 9457. It defines the application/problem+json media type with five optional members: type, title, status, detail and instance (summary). Add extensions for problem-specific data, such as an errors array with pointers for validation failures.
{
"type": "https://api.example.com/problems/invalid-field",
"title": "Invalid field",
"status": 422,
"code": "slug_contains_invalid_character",
"detail": "slug may contain only a-z, 0-9 and hyphens.",
"errors": [{ "pointer": "/slug", "constraint": "^[a-z0-9-]+$" }],
"request_id": "req_8f2c1d"
}
The code field matters most. A stable string a client can branch on, combined with a field path and a stated constraint, removes most support conversations. Paymos documents the same rule: switch on code, not on title or detail, which are human text and will be reworded.
Rules we apply:
codeis permanent. Add new codes; never rename old ones.detailis for humans and may change.- Multiple problems go in one response as a list, one entry per problem.
- Add a
retryableflag when the server knows the answer.
Picking status codes
A compact map, following Zuplo's guidance:
| Code | Use it for |
|---|---|
| 400 | Malformed syntax |
| 401 | Missing or invalid credentials |
| 403 | Authenticated but lacking permission |
| 404 | Resource does not exist |
| 409 | State conflict |
| 422 | Valid syntax, failed business validation |
| 429 | Rate limit, with Retry-After |
| 500 | Unhandled server error |
| 502 | Invalid upstream response |
| 503 | Maintenance or overload, with Retry-After |
| 504 | Upstream timeout |
A 401 must carry a WWW-Authenticate challenge, per the Siemens API guidelines. The 502/504 split matters for any API that wraps other systems: it tells the caller the fault is upstream, not in their request.
Useful messages without leaking internals
Good messages have two parts: the cause in terms of the caller's own request, and the fix. PartnerMatch's three habits are to name the field or value, say what good looks like, and keep one message per problem.
Nielsen Norman Group's error message guidelines say the same for humans: plain language, precise description, constructive advice.
The security rule is simple: be precise about the caller's input, opaque about the system's state. That includes the difference between "you may not access this" and "this does not exist" (source). RFC 9457's security section warns against exposing stack traces, SQL, internal hostnames and secrets, and recommends logging private detail server-side and returning a safe occurrence identifier. CWE-209 lists the usual leaks, including login endpoints that distinguish "invalid password" from "user not found".

Retries: what the client should do
The error body should let the client decide without guessing. The guidance from Zuplo:
- Retry 429, 500, 502, 503 and 504 only when the operation is safe to repeat or protected by an idempotency key.
- Respect
Retry-After. - Use exponential backoff with jitter and cap attempts or total time.
- Do not retry validation, auth or not-found failures.
Google Cloud adds that repeating non-idempotent operations can create duplicates or side effects, and that permanent authorization or configuration errors need a change before another attempt helps. Our note on idempotent web fetch covers why reads are the easy case.
Request IDs and structured logs
Generate an ID at the edge, pass it through every service, put it in logs and return it in the error. Microsoft's playbook describes a correlation ID as the glue binding a transaction together. Salesforce Engineering describes how a correlation ID in a header lets both developers and support query all related logs.

Log route, action, status, timing and retry count as named JSON fields. Never log secrets.
Perspective
Most scraping vendors hide the interesting part. A blocked page, a paywall, a timeout and an empty extraction all collapse into a 200 with empty content, or a generic 500. An agent cannot reason about that. It guesses.
We built Gyrence so every call returns a typed, discriminated-union response, failures included. A blocked fetch is a value with a reason, not an exception. Agents can branch on it, route it, or escalate it.
Web data is a storm. Pretending otherwise is how bills and pipelines surprise you. Naming failure modes is the honest design, and it is the one we ship.
Glen
Applying this to scraping, agents and RAG
Scraping has failures plain REST rarely has: blocked fetches, content that changed shape, extraction that returns nothing for a field. Treat these as first-class variants.
- Fetch: success, blocked, timeout and unsupported content type are different variants with different retry rules.
- Extract: a missing field is not a failed document. LandingAI recommends routing missing fields to a separate workflow and terminal errors to a dead-letter queue with the error code and document reference.
- Crawl: report per-URL outcomes, so one bad page does not hide 500 good ones.
See our patterns for discriminated union responses, agent web tool failure handling and typed scraping failures. If you document the contract in OpenAPI, discriminators model the variants cleanly.
Try typed failures on a real API
Gyrence exposes Search, Traverse, Fetch, Extract and Map, plus a hosted MCP endpoint, and every response is typed, failures included. Billing uses spending caps and bundled LLM extraction with no separate AI charge. Standard is $75 per month, Growth is $299 per month and Scale is $549 per month; Free, Founders and Pay-As-You-Go are also available on the pricing page.
For change monitoring with webhook alerts, see WebDoppler.
FAQ
Should I use RFC 9457 or a custom error format?
Use RFC 9457 as the base. It is a standard, clients and tooling already understand it, and it allows extension members. Add your own stable code and request_id on top.
Is the HTTP status code enough?
No. Status codes classify the failure, but many distinct problems share one code. A stable machine-readable code plus a field pointer lets clients react precisely.
Should error messages say exactly what went wrong?
About the caller's input, yes: name the field and the fix. About your system, no. Keep stack traces, hostnames and SQL out of responses and give a request ID for support instead.
Which errors should clients retry?
Transient ones such as 429, 502, 503 and 504, with backoff, jitter and a cap, and only for safe or idempotent operations. Permanent errors need a changed request first.
Sources
- RFC 9457: Problem Details for HTTP APIs
- Error Handling Patterns in Amazon API Gateway and AWS Lambda
- RFC 9457 guide, httpstatuses.net
- Designing API Error Responses Without Leaking State
- Error Handling, Paymos
- Best Practices for API Error Handling, Zuplo
- Error Reporting, Siemens
- API error design that speeds up integration
- Error-Message Guidelines, NN/G
- Sensitive Information in Error Messages: CWE-209
- Retry strategy, Google Cloud Storage
- Correlation IDs, Microsoft Engineering Fundamentals Playbook
- Correlation ID in your API, Salesforce Engineering
- Designing Fault-Tolerant Document Processing Pipelines, LandingAI

