Technical

Document API Errors for Reliable Recovery

Explain API errors with status, domain problem, known side effects and safe recovery actions, supported by a runnable local policy simulator.

By Mohammad Alshaikhusain

Published

Sources checked

Useful error documentation tells the caller what failed, what is known about the operation's effects and what action is safe next. An HTTP status and a message such as “try again” are often insufficient. The answer may depend on the domain problem, the request method, the service's replay contract and whether the first request's outcome is known.

Document those decisions beside the request example and in a linked error reference. This helps a developer or coding agent recover without guessing. It does not prove that the documentation will be retrieved or cited by an answer engine.

This guide includes a local policy simulator using invented responses. It runs assertions without a server, credentials, network requests or actual waiting. The examples teach recovery decisions; they are not a production client or a test of a real API.

Describe the error as a decision

An error entry should answer five questions:

QuestionInformation to document
What happened?HTTP status and stable domain problem identifier
What caused this occurrence?Safe, relevant detail or field-level evidence
Could the operation have taken effect?Known rejection, accepted work or unknown outcome
What should the caller do?Correct input, repair access, reconcile state, wait or stop
Under which conditions may it retry?Replay guarantees, delay instructions and retry budget

The same status can represent different domain conditions. A 409 caused by a reused idempotency key is not necessarily repaired the same way as a 409 caused by stale resource state. The caller needs the actual conflict meaning.

Similarly, a transport timeout does not tell the caller whether a write reached the server. Treating it as a definite rejection can produce duplicate work if the client issues a new operation immediately.

Use the complete API example guide for tying the request, response state and verification together. An error reference should explain how to continue that task, not merely enumerate status codes.

Use a stable machine-readable problem identity

Problem Details defines a standard representation for HTTP error information. Its JSON media type is application/problem+json, and its type member identifies the problem. An API can define extensions for domain information. Human-readable detail remains useful, but clients should not depend on parsing its wording. Problem Details specification

Here is an original fictional validation problem:

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
  "type": "https://example.com/problems/invalid-event",
  "title": "Invalid event",
  "status": 422,
  "detail": "name must be a non-empty string",
  "instance": "/requests/example-001",
  "errors": [
    { "pointer": "/name", "code": "required" }
  ]
}

The errors member is an extension defined for this fictional problem. Its pointer and code give the caller a stable way to identify the invalid field. The reserved example.com address is an illustrative identifier, not a live problem-documentation service to call.

Keep the HTTP response status and body status consistent. The body's status field is advisory; it does not override the response received by HTTP software. Do not label every failure 200 and expect a body field to preserve ordinary HTTP semantics.

A real problem page should define the extension fields, valid values and recovery behavior. Avoid including credentials, entire sensitive payloads, private stack traces or internal infrastructure details just because the format can carry arbitrary JSON.

Distinguish two conflicts with the same status

Consider a fictional event API with two problem types:

ResponseDomain meaningRecovery direction
409 replay-conflictA key already identifies a different request bodyRecover the original operation and decide whether the new intent is a separate operation
409 stale-versionThe client tried to update an older resource versionRefresh current state and review the intended update

The first is not fixed by changing the request body repeatedly while keeping the same key. The second is not necessarily fixed by blindly copying the latest version field onto an old update. The client may need a user or business rule to resolve the conflict.

Write a complete example of each, including the original operation and the state needed for recovery. If your API requires a new key for a genuinely new operation, explain what establishes that new intent. If it supplies a receipt lookup for uncertain writes, link it where the retry question occurs.

These rules belong to the service contract. A generic client cannot infer every business consequence from 409 alone.

Make side-effect knowledge explicit

Use a small vocabulary for operation outcome:

  • Rejected before the operation: the service's documented contract establishes that the requested work did not occur.
  • Accepted: the service accepted work, but completion still needs verification.
  • Completed: the defined outcome was established.
  • Unknown: the caller lacks enough evidence to determine the outcome.

Do not assign these states solely by intuition. An API's validation and execution sequence can affect what the caller knows. In particular, a dropped connection after a write begins is not proof that nothing happened.

HTTP 202 is an acceptance state, not confirmation of completed processing. HTTP idempotence concerns the intended effect of repeated requests. Neither concept grants a universal retry guarantee to every POST request carrying a custom key. HTTP semantics

The error documentation should identify the actual replay boundary: key scope, expiry, payload comparison, persistence and behavior after service restarts or failover. If the service does not offer a relevant guarantee, show how the client can reconcile the uncertain outcome instead of promising safe automatic repetition.

Build a recovery matrix around your own contract

The following matrix describes the fictional simulator. It is deliberately conservative and is not a universal interpretation of all APIs.

ConditionSimulator actionImportant boundary
400 or 422Correct the requestRepeating identical invalid input does not address the cause
401Repair credential setupNo automatic credential-refresh flow is assumed
403 permission problemCheck permissionMore retries do not create authorization
403 with its defined rate-limit type, or 429Consider delayed retryOnly if the operation is eligible for safe replay
404Check resource and accessDo not assume every service reveals private resource existence
409 replay-conflictRecover the original operationDistinct from stale-state conflict
409 stale-versionRefresh state and reviewUpdating intent may need approval
Transport failure or 503 without replay assuranceReconcile unknown outcomeNo blind retry of an uncertain write
202Inspect acceptanceVerify completion separately
Unclassified responseInvestigateNo guessed recovery path

GitHub provides an instructive real-world example: its troubleshooting guidance discusses rate-limit responses and access-related cases rather than treating each status as one universal meaning. Follow its documented delay/reset rules for GitHub; do not substitute the toy simulator's fictional problem types. GitHub REST troubleshooting

A product's authentication guide should explain the credential and permission prerequisites referenced by the error page. See the authentication documentation guide for making that relationship explicit.

Respect delay instructions without shortening them

A retry policy needs both timing and a stopping point. The simulator accepts an already interpreted retry delay in seconds. If the required delay is 12 seconds, it preserves 12. If the delay is 120 seconds, it returns a defer action rather than shortening the wait to its 60-second immediate-work limit.

That distinction matters. A cap on how long your current worker can remain active is not permission to retry earlier than the service allows. Defer the operation to an appropriate scheduler or return a clear state to the caller.

The fixture does not parse raw Retry-After headers. A real implementation must handle the service's supported forms, including HTTP-date versus delay-seconds where relevant, and consider its clock and timeout behavior. It also needs a documented policy for invalid or absent values.

Our example uses a small fallback delay for eligible retries when no numeric delay is supplied. It is a teaching choice, not a recommendation to apply that timing to GitHub or any other provider. Follow the real service's documented requirements.

Run the local recovery simulator

Download error-recovery-simulator.mjs, inspect it, then run:

node error-recovery-simulator.mjs

The recorded runs used Node 23.11.0 and Node 24.19.0. It completed 17 scenario checks and seven additional assertions. The script uses Node's assertion module and synthetic inputs; it installs no dependencies and makes no external calls.

Its input includes the response status, optional problem type, method, attempt number, attempt budget, numeric delay and a flag stating whether the actual contract guarantees replay. That last flag is an assumption supplied by the caller. The simulator does not validate a real service's guarantee.

GET and HEAD are considered eligible by the toy policy, along with operations carrying an explicit replay guarantee. That is a narrow policy choice, not a complete list of HTTP methods defined as idempotent.

The attempt budget is three and includes the original attempt. Once an eligible failure reaches the budget, the returned action is stop-budget-exhausted. The simulator does not silently reset the counter by treating each retry as a new task.

Inspect the trace, not just the final label

One synthetic trace contains a 503 followed by a 202 for an operation whose replay guarantee is supplied as true:

Attempt 1: 503 -> retry the same operation after the chosen delay
Attempt 2: 202 -> inspect acceptance; do not claim delivery

There is no real wait or server behind that trace. It verifies the decision transitions. For an uncertain POST without replay assurance, the first action instead asks for reconciliation; the simulator never reaches an automatic second request.

The test also checks that the two 409 problem types lead to different actions, that a long required delay remains intact, and that an invalid attempt budget is rejected. These checks make the article's specific policy claims inspectable.

They do not prove full protocol compliance, reliable scheduling, distributed idempotency or production security. A real client needs integration tests against an authorized environment, including the state produced when a request times out after the server accepts it.

Keep error examples connected to the happy path

An error page should link to the exact operation, prerequisites and relevant version. A tutorial should link back to the error cases that change the next step. Avoid leaving the developer to search a global list of codes while holding an incomplete mental model of the request.

For each important operation, include at least one meaningful failure example beside the successful example. Explain which input or condition changed. That comparison makes the recovery action easier to follow than an unrelated catalog entry.

The integration tutorial guide covers setup and verification, while the context-preservation guide explains which qualifications belong beside the instruction itself.

Review questions before release

Should the client parse the error message?

Prefer a stable problem identifier and documented extension fields. Human detail can change with wording or localization. If a legacy API requires message parsing, document that limitation honestly rather than presenting it as a robust contract.

Should every 5xx response be retried?

No. Recovery depends on method, service contract, known effects, timing and budget. An uncertain non-idempotent write can require reconciliation even when a later retry might succeed technically.

Does an error example need a real secret?

No. Use placeholders or fake local values, and explain how a real credential is obtained safely. Never include an actual token just to make a screenshot or code sample look complete.

What should a test report establish?

Name the environment, scenario inputs, decisions and observed final state. Distinguish pure policy tests, simulated service behavior and actual integration tests. This article establishes the first, using the downloadable local simulator.

Sources and verification

References checked September 17, 2026: RFC9457 Problem Details, RFC9110 HTTP semantics and GitHub's REST troubleshooting guidance. Problem identifiers and recovery policy are original fictional examples. The simulator's 17 scenarios and seven additional assertions passed on Node 23.11.0 and Node 24.19.0; no real service, delay scheduler or production retry path was exercised.

Explore GEO with Jam

See how Jam approaches AI visibility research and content improvements for developer-tool teams.

Explore Jam for GEO