DEV Community

Cover image for API Error Design for AI Agents: Errors They Can Recover From
Hassann
Hassann

Posted on Originally published at apidog.com

API Error Design for AI Agents: Errors They Can Recover From

Design Agent-Readable API Errors That Actually Recover

Your API returns 400 Bad Request with {"error":"invalid input"}. A developer checks the docs, spots the missing field, and fixes it. An agent has no actionable detail, retries the same payload, then reports that the API is broken.

Try Apidog today

Error responses are an interface. They must tell an agent what failed, whether retrying helps, and what to change. Without that information, recoverable failures become failed automations.

This guide covers the API side of agent recovery. Pair it with agent error-recovery logic for retries, backoff, and circuit breakers. Use Apidog to define, mock, and test failure responses alongside successful ones.

Agent-readable API error response diagram

The three questions every error must answer

An agent should be able to answer these questions without guessing:

  1. Is this the caller's fault or the server's?

    A 4xx means repeating an unchanged request will fail again. A 5xx may succeed later.

  2. Should I retry, and when?

    429 is retryable after waiting. 409 may require re-reading state. 422 requires changing the payload.

  3. What exactly should I change?

    “Validation failed” is not actionable. “customer.postal_code is required when country is US” is.

Answer all three consistently to prevent retry storms and unnecessary escalations.

Use a structured error format

Do not invent a new shape if you do not need one. RFC 9457 Problem Details for HTTP APIs provides a widely supported baseline:

{
  "type": "https://api.example.com/errors/validation-failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "The field 'customer.postal_code' is required when 'country' is 'US'.",
  "instance": "/v1/orders",
  "errors": [
    {
      "field": "customer.postal_code",
      "code": "required_conditional",
      "message": "Required when country is US. Provide a 5-digit or 9-digit US postal code.",
      "example": "94107"
    }
  ],
  "retryable": false,
  "next_action": "Add customer.postal_code to the request body and send again."
}
Enter fullscreen mode Exit fullscreen mode

The fields that matter most for agents are:

  • detail: Name the exact field and rule that failed for this request.
  • errors: Return every validation problem at once, with field paths that map to the submitted payload.
  • retryable: Make retry behavior explicit instead of requiring clients to infer it from status codes.
  • next_action: Give one clear instruction the agent can follow.

Google’s API error design guide reaches the same conclusion: expose structured details instead of burying actionable information in prose.

Say when to retry

For transient failures, provide a concrete delay.

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json
Enter fullscreen mode Exit fullscreen mode
{
  "type": "https://api.example.com/errors/rate-limited",
  "title": "Rate limit exceeded",
  "status": 429,
  "detail": "You have used 1000 of 1000 requests in the current minute window.",
  "retryable": true,
  "retry_after_seconds": 30,
  "next_action": "Wait 30 seconds before sending this request again. Do not retry sooner."
}
Enter fullscreen mode Exit fullscreen mode

The Retry-After header accepts seconds or an HTTP date. Seconds are usually easier for clients and agents to apply. Send the value in both the header and response body: conventional clients use the header, while models often rely on body content.

Apply the same pattern to 503 maintenance windows and 409 locked resources. If waiting is the right action, include the wait time.

For implementation details, see handling “rate limit exceeded” errors and implementing API rate limiting.

Structured API error example

Never leak internals—and never return nothing

Avoid both extremes:

  • Stack traces leak framework versions, file paths, query fragments, and sometimes secrets. They are a security problem and add noise agents cannot use. See testing APIs against untrusted input.
  • Empty errors such as a bodyless 500 or {"error":true} provide no recovery path.

Return a stable public error with a correlation ID instead:

{
  "type": "https://api.example.com/errors/internal",
  "title": "Internal error",
  "status": 500,
  "detail": "The order could not be created due to an internal error. No order was created.",
  "retryable": true,
  "retry_after_seconds": 5,
  "request_id": "req_01J8ZK3M2Q",
  "next_action": "Retry once after 5 seconds. If it fails again, stop and report request_id req_01J8ZK3M2Q."
}
Enter fullscreen mode Exit fullscreen mode

The key sentence is: “No order was created.” For failed writes, agents must know whether retrying could create a duplicate. If you cannot guarantee the outcome, support idempotency and document it. See idempotency keys for AI agents.

Make request_id resolvable in your logs using the practices in this API observability guide.

Put errors in your OpenAPI spec

If error responses are absent from OpenAPI, generated clients, mocks, and agent tools cannot reliably handle them.

responses:
  '201':
    description: Order created
    content:
      application/json:
        schema: { $ref: '#/components/schemas/Order' }

  '422':
    description: >
      Validation failed. Not retryable without changing the request body.
      The errors array names each invalid field.
    content:
      application/problem+json:
        schema: { $ref: '#/components/schemas/Problem' }

  '429':
    description: >
      Rate limited. Retryable. Wait for retry_after_seconds before sending again.
    content:
      application/problem+json:
        schema: { $ref: '#/components/schemas/Problem' }
Enter fullscreen mode Exit fullscreen mode

These descriptions are operational instructions, not decoration. When generating tools from a specification, as described in turning an OpenAPI spec into agent tools, the model reads them to understand failure cases.

Test failures, not only successes

Error-path coverage often disappears because failures take effort to reproduce. Mocks remove that effort.

Define failure responses in Apidog, switch the mock between them, and test agents against repeatable 422, 429, and 500 scenarios without affecting production. For the broader practice, see running agents against mocks instead of production.

Test at least these cases:

  • Several invalid fields: Return all validation errors together and verify the agent fixes them in one follow-up request.
  • Rate limit with a wait: Verify the agent waits at least retry_after_seconds.
  • Server failure during a write: Verify retries do not silently create duplicates.
  • Authentication failure: Verify the agent stops rather than retrying an invalid token. See least-privilege API keys for agents.
  • Malformed error body: Return invalid JSON and verify graceful degradation; proxies eventually produce these failures.

Save these as CI scenarios. Serializer refactors commonly break error handling while happy-path tests continue to pass.

Why better errors matter

Clear errors reduce cost in three measurable ways:

  • Fewer retries: {"error":"invalid input"} often triggers two or three duplicate attempts. A named missing field usually produces one corrected request.
  • Fewer escalations: An agent that knows the correction can complete the workflow instead of handing it to a person.
  • Faster debugging: Precise detail plus request_id turns log hunting into a single lookup.

They also help human developers. Specific error messages improve every integration, not only agent-driven ones.

Design the escalation path

Not every failure is recoverable. Missing scopes, closed accounts, and decisions requiring human approval should tell the caller:

  • what happened,
  • what a person must do next, and
  • which correlation ID to provide.

That message must surface where someone will read it. For example, Sharkly keeps an agent’s result and execution trace on its Task and routes items needing replies or review into an Inbox. A useful error message makes that handoff actionable; “invalid input” does not.

Do not make agents parse prose

Avoid inconsistent response wording such as:

{ "message": "Sorry, that didn't work. Please check your details and try again." }
Enter fullscreen mode Exit fullscreen mode

An agent can only guess what to do next. The problem gets worse when APIs return this body with a 200 OK, making the failure invisible to retry policies, dashboards, and alerts.

Use two rules:

  1. Give each distinct failure a stable machine-readable code, such as insufficient_funds.
  2. Never return an error with a success status code.

Checklist: agent-readable errors

  • Use one consistent structured format across the API.
  • Make detail name a specific field or condition.
  • Return all validation failures at once with field paths.
  • Include retryable on every error.
  • Include retry delays in seconds in both headers and bodies.
  • State whether failed writes created or changed anything.
  • Include a correlation ID that resolves in logs.
  • Never expose stack traces, framework strings, SQL, or internal identifiers.
  • Document error responses with agent-readable OpenAPI descriptions.
  • Mock each failure mode and run saved tests in CI.

Errors are an interface. Design them for the caller you have—which is increasingly a model that will follow exactly what your response tells it to do. Download Apidog to define error shapes and mock them before agents encounter them in production.

Frequently asked questions

Should I use RFC 9457 or my own error format?

Use RFC 9457 unless you already have a consistent production format. Consistency matters more than switching only part of an API to a standard. Add extensions such as retryable and next_action to either format.

Is next_action safe in an API response?

Yes, if the service generates it from fixed templates. Never echo user-provided text into next_action: agents interpret it as instruction, which creates a prompt-injection path. See testing APIs against untrusted input.

Should validation errors be 400 or 422?

Use 400 for malformed requests, such as invalid JSON. Use 422 when the request parses but fails validation or business rules. If your API already uses one status for both, document that behavior rather than changing it inconsistently.

How much detail is too much?

Include the field, failed rule, and an example value when useful. Exclude internal identifiers, query text, stack frames, and implementation details.

Do error messages count against the context window?

Yes. Repeated verbose errors add up across retries. Keep errors below a few hundred tokens. The same principle applies in trimming API responses for agents.

How do I stop retries for non-retryable errors?

Set "retryable": false, state that clearly in next_action, and enforce it in the tool wrapper. Do not rely on model judgment alone.

Top comments (0)