API Error Taxonomy for AI-Assisted Integrations

API Error Taxonomy for AI-Assisted Integrations
Spec Coding Editorial Team · Spec-first engineering notes

How to design an API error taxonomy that AI-generated clients can actually use: stable error codes, machine-readable categories, and the fields that separate retryable from permanent failures.

Published on 2026-03-10 · 6 min read · Updated May 6, 2026 · Author: Spec Coding Editorial Team · Review policy: Editorial Policy

Review Note

Reviewed May 6, 2026. This focused reference is now promoted as a search-indexable companion to the AI Coding Governance Hub. It includes concrete review artifacts, failure modes, and next-step links for readers applying the topic in practice.

Free-Text Error Messages Are a Trap

I have seen more integrations fail from bad error messages than from bad happy paths. The pattern is familiar: the server returns 400 Bad Request with a body that says "Something went wrong processing your request. Please try again later." A human operator can infer what to do. A programmatic client cannot. An AI-generated client, which is increasingly what is consuming my APIs, is somewhere between the two — it will confidently invent retry logic, misclassify a permanent failure as transient, and burn a customer's rate limit chasing a declined card.

The fix is not nicer prose. The fix is an error envelope that treats the machine as the primary reader and the human as a secondary one.

HTTP Status Codes Alone Are Not a Contract

Every API I review leans too hard on HTTP status codes. 400 means validation failed — unless it means the payment was declined, or the account is suspended, or the feature flag is off. 500 means retry — unless the database constraint you violated is going to fail exactly the same way on the next 47 attempts. Status codes are a routing hint, not a taxonomy. If I ask a code-generating model to "retry on 5xx", it will do exactly that, and it will be wrong half the time.

I still send the right HTTP status. I just do not expect any client to make a decision from it alone.

The Four-Field Error Envelope I Ship

Every error response my services return has exactly four top-level fields, and they always mean the same thing:

Four fields. Anything more is a cognitive tax on the consumer. Anything less and you are pushing parsing problems onto the client.

Stable Codes Are the Whole Game

I format codes as snake_case.hierarchical strings — three segments when I can manage it: domain, condition, subcondition. payment.card_declined.insufficient_funds. auth.token.expired. rate_limit.tenant.daily. The hierarchy means a client that does not know the leaf can still pattern-match the domain prefix and do something sensible. An AI-generated client trained on my one-page reference doc will get the retry semantics right on codes it has never seen, because the prefix is enough to route.

The rule I enforce in reviews: once a code is published, it is frozen. I can add new codes. I can deprecate codes with a sunset date. I cannot rename them, split them, or change which condition they describe. If I break that rule, every generated client in the wild breaks with me.

The Category Taxonomy That Actually Works

The category field is a closed set. I use eight values and I have never needed a ninth:

This list is small enough to memorize and sharp enough that every code maps to exactly one category. When an AI-generated client does not recognize a code, it falls back to the category and still does the right thing.

Field-Level Validation as a Structured Array

Validation errors get their own shape inside details. Never a single string. Never a blob of HTML. An array of entries, each with field, code, and message:

"details": {
  "field_errors": [
    {"field": "email", "code": "validation.format.email", "message": "Must be a valid email address"},
    {"field": "amount", "code": "validation.range.min", "message": "Must be at least 1"}
  ]
}

The field uses dotted paths for nested shapes (billing.address.postal_code). The code is from the same stable namespace as the top-level code. A form UI can map errors to inputs without parsing English. A generated client can build a typed exception per field code.

Retry Hints Belong in Two Places

When a response is retryable, I send the hint twice: the Retry-After HTTP header for standards-aware clients, and retry_after_seconds inside details for everyone else. Duplication is cheap. Expecting every generated client to read headers correctly is not. I have watched too many SDKs silently drop headers while parsing the body just fine.

For rate_limit I also include details.limit and details.remaining so a smart client can pace itself rather than ramp into another 429.

Correlation IDs Are Non-Negotiable

Every error body carries a details.trace_id that matches the ID in my logs. When a customer pastes an error into a support ticket, I want a single copy-paste to land me on the exact request in our tracing backend. The ID is the bridge between the client-visible failure and the server-side evidence, and it is the one field I have never regretted adding.

Localization Is Metadata, Not Identity

The message field is English. Always. If a caller wants localized strings, they pass Accept-Language and I populate details.localized_message. The code never changes based on locale. I learned this the hard way from an API that returned translated error codes — error_carte_refusee in French, error_card_declined in English — and broke every client that crossed a regional boundary.

A Concrete Example

Here is what a real declined-card response looks like from one of my services:

HTTP/1.1 402 Payment Required
Retry-After: 0
Content-Type: application/json

{
  "code": "payment.card_declined.insufficient_funds",
  "category": "permanent",
  "message": "Card declined: insufficient funds",
  "details": {
    "trace_id": "01HX8K3M9P2QZ7N4R6S8T0V2W4",
    "retry_after_seconds": 0,
    "issuer_decline_code": "51",
    "localized_message": null,
    "field_errors": []
  }
}

Category permanent tells the client not to retry with the same card. Code payment.card_declined.insufficient_funds tells a smart client to prompt for a different card rather than a generic "payment failed" modal. Trace ID takes support straight to the logs.

Acceptance Criteria in Given/When/Then

- Given a request that exceeds the per-tenant rate limit
  When the API rejects the request
  Then the response has code starting with "rate_limit."
   And category is "rate_limit"
   And details.retry_after_seconds is a positive integer
   And the Retry-After header matches details.retry_after_seconds
   And details.trace_id is present and matches the log record

- Given a validation failure on two fields
  When the API rejects the request
  Then category is "validation"
   And details.field_errors contains one entry per failing field
   And each entry has field, code, and message populated

The One-Page Reference Doc Pattern

I maintain a single page — literally one Markdown file — that lists every code, its category, whether it is retryable, and an example body. This page is the source of truth for my SDK generators and for the AI-assisted clients my customers build against me. When a new code ships, it lands here before it lands in production. When the doc and the runtime disagree, the doc wins and the runtime is the bug. One page, one contract, one place to look.

AI Review Packet to Copy

Use this before an AI-generated diff reaches code review. It turns the prompt, the allowed scope, and the required proof into one reviewable artifact.

AI coding review packet: API Error Taxonomy for AI-Assisted Integrations

Decision to make:
- Design API error taxonomies AI-generated clients can use, with stable codes, retry categories, and machine-readable details.

Owner check:
- Product owner:
- Engineering owner:
- QA or operations reviewer:

Scope boundary:
- In scope:
- Out of scope:
- Assumption that still needs approval:

Acceptance evidence:
- Test or fixture:
- Log, metric, or screenshot:
- Manual review step:

AI boundary: generated changes must stay inside the written scope and attach evidence for each acceptance criterion.

Reviewer prompt:
- What would still be ambiguous to someone who missed the planning meeting?
- What evidence would make this safe enough to ship?

Editorial Review Note

Reviewed Apr 28, 2026. This update added a reusable artifact, checked the article against the related topic hub, and tightened the next-step links so the page works as a practical reference rather than a standalone essay.

Keywords: API error taxonomy · error codes · retry semantics · machine-readable errors · API contracts · AI-generated clients

Editorial Note

Last reviewed May 6, 2026: topic paths, examples, internal links, and reusable review blocks were checked for practical specificity.