git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/api-error-contracts" ~/.claude/skills/intense-visions-harness-engineering-api-error-contracts-362c53 && rm -rf "$T"
agents/skills/claude-code/api-error-contracts/SKILL.mdAPI Error Contracts
ERROR CONTRACTS DEFINE THE MACHINE-READABLE STRUCTURE, HUMAN-READABLE MESSAGE, AND ACTIONABLE REMEDIATION FOR EVERY FAILURE MODE — CONSISTENT ERROR RESPONSE DESIGN LETS CLIENTS HANDLE ERRORS PROGRAMMATICALLY WITHOUT PARSING FREE-TEXT OR REVERSE-ENGINEERING FAILURE SEMANTICS.
When to Use
- Designing the error response shape for a new API or service before writing any handlers
- Reviewing a PR that returns unstructured error strings or inconsistent error shapes across endpoints
- Establishing an API style guide section on error response standards for a team or organization
- Auditing an existing API whose clients report confusion about how to distinguish error categories
- Choosing between application-level error codes (e.g.,
) and HTTP status codes for client routingCARD_DECLINED - Building SDKs or client libraries that need to surface typed, catchable error objects to callers
- Implementing error monitoring and alerting where machine-readable codes are required to classify alerts
- Documenting all possible error conditions for each endpoint in an OpenAPI specification
Instructions
Key Concepts
-
Machine-readable error codes — An application-level error code (
) is distinct from the HTTP status code. It identifies the specific failure type within a category so clients can branch on it without parsing free-text messages. Example: two Stripe"code": "INSUFFICIENT_FUNDS"
responses may have402
or"code": "card_declined"
— the HTTP status routes to the payment-failure handler; the code selects the message shown to the user."code": "expired_card" -
Human-readable messages — The
field is for developers reading logs or API explorer output, not for display in end-user interfaces. It should be complete and unambiguous:message
rather than"The card ending in 4242 was declined by the issuer"
. Avoid exposing internal implementation details, stack traces, or database error messages."Card error" -
Actionable remediation — Every error response should answer: "What should the caller do next?" The
field (per RFC 9457) or a dedicated"detail"
field communicates the next step:"suggestion"
,"Retry with a different payment method"
, or"Check that the field matches ISO 8601 format"
. Errors without remediation guidance shift the debugging burden to the caller."Contact support with reference ID abc-123" -
Error taxonomy — Group error codes into categories that map to HTTP status classes: authentication errors (
), authorization errors (401
), validation errors (403
), business-rule violations (422
/409
), and server faults (422
). A taxonomy prevents code proliferation and makes documentation searchable. Publish the full taxonomy in API reference docs; include a500
URI (per RFC 9457) so clients can link to the documentation for each error type.type -
Consistent envelope structure — Every error response from every endpoint must use the same JSON shape. Mixing
,{ "error": "..." }
, and{ "message": "...", "errors": [...] }
across endpoints breaks SDK code generation and forces client-side shape detection. Choose one envelope (RFC 9457 Problem Details is the recommended standard) and enforce it at the framework/middleware level.{ "code": ..., "description": ... } -
Error reference IDs — For server-side faults (
), include a unique5xx
orinstance
field that correlates the response to a specific log entry. This enables support teams to locate the root cause without asking users to reproduce the issue. Example:traceId
or"instance": "/errors/7f3a-bc91-..."
."traceId": "abc-123-xyz"
Worked Example
Stripe's error contract is one of the most studied in production APIs. It uses a consistent envelope across all failure modes:
Payment declined (402 Payment Required):
POST /v1/charges Authorization: Bearer sk_example_... Content-Type: application/x-www-form-urlencoded amount=2000¤cy=usd&source=tok_chargeDeclined
HTTP/1.1 402 Payment Required Content-Type: application/json { "error": { "type": "card_error", "code": "card_declined", "decline_code": "insufficient_funds", "message": "Your card has insufficient funds.", "param": "source", "charge": "ch_3N..." } }
Invalid API key (401 Unauthorized):
HTTP/1.1 401 Unauthorized Content-Type: application/json { "error": { "type": "authentication_error", "code": "api_key_invalid", "message": "No such API key: sk_example_****abc.", "doc_url": "https://stripe.com/docs/error-codes/api-key-invalid" } }
Missing required parameter (400 Bad Request):
HTTP/1.1 400 Bad Request Content-Type: application/json { "error": { "type": "invalid_request_error", "code": "parameter_missing", "message": "Missing required param: amount.", "param": "amount", "doc_url": "https://stripe.com/docs/error-codes/parameter-missing" } }
Stripe's taxonomy (
card_error, authentication_error, invalid_request_error, api_error, idempotency_error) maps cleanly to HTTP status ranges. The code field gives a programmatic subcategory; param identifies the offending field; doc_url links to remediation. SDK clients switch on error.type for top-level routing and error.code for specific handling — without any free-text parsing.
Anti-Patterns
-
Free-text error strings only.
or{ "error": "Something went wrong" }
forces clients to parse English prose to determine the error type. Localization, rewording, or phrasing changes in the message silently break clients that pattern-match on strings. Fix: always include a machine-readable{ "message": "Invalid input" }
field alongside the human-readablecode
.message -
Inconsistent envelope shape across endpoints. When one endpoint returns
and another returns{ "error": { "message": "..." } }
and a third returns{ "errors": ["..."] }
, client error handling requires per-endpoint special cases. Fix: enforce a single error shape at the middleware or gateway layer so every response is shaped identically before leaving the server.{ "status": "error", "reason": "..." } -
Leaking internal error details. Including
,"sqlState": "23000"
, or"stackTrace": "..."
in error responses exposes implementation internals, aids attackers, and creates API surface that clients may start depending on. Fix: log internal details server-side and surface only a"internalMessage": "NullPointerException at line 42"
for correlation. The public error contains only what is safe and useful for the caller.traceId -
Omitting actionable remediation. An error like
without a{ "code": "RATE_LIMITED" }
header or aRetry-After
explaining when to retry is incomplete. The caller either retries immediately (worsening the rate-limit problem) or gives up unnecessarily. Fix: every error response should include what the caller should do next — retry timing, which parameter to fix, or where to get help.detail
Details
Error Code Naming Conventions
Error codes should use consistent casing (SCREAMING_SNAKE_CASE for application codes is common;
snake_case is used by Stripe and GitHub). Codes must be stable across API versions — changing CARD_DECLINED to PAYMENT_DECLINED is a breaking change for any client that switches on the code. Prefix codes by domain when the taxonomy is large: AUTH_TOKEN_EXPIRED, PAYMENT_CARD_DECLINED, VALIDATION_FIELD_REQUIRED.
Error Documentation Requirements
Every published error code should be documented with: the HTTP status code it accompanies, a description of when it occurs, the fields present in the response for this code, and recommended client action. This documentation is the contract — treat removals or renames as breaking changes.
Real-World Case Study: Twilio Error Contracts
Twilio maintains a published error dictionary at
twilio.com/docs/api/errors with over 600 documented error codes. Each code has a dedicated page with: description, possible causes, and suggested resolution steps. When Twilio's REST API returns an error, it includes "code": 21211 (for example) alongside the HTTP 400 status. Clients look up the code in documentation or switch on it directly. Twilio's developer surveys show that APIs with published error taxonomies reduce average debugging time by 40-60% compared to APIs that return only HTTP status codes and free-text messages. The combination of stable machine-readable codes and linked documentation is the highest-leverage investment in API error design.
Source
- Creating Good API Errors in REST, GraphQL, and gRPC — APIs You Won't Hate
- RFC 9457 — Problem Details for HTTP APIs
- Stripe API Error Reference
- Twilio Error and Warning Dictionary
- Microsoft REST API Guidelines — Error Responses
Process
- Define the error envelope structure: choose RFC 9457 Problem Details or a documented custom shape, and enforce it at the framework level across all endpoints.
- Create an error taxonomy document listing every application-level error code, its HTTP status, its description, and its recommended client action.
- For each endpoint, document all possible error codes in the OpenAPI spec under the appropriate
/4xx
response schemas.5xx - Implement an error serialization middleware that maps internal exceptions to the canonical error envelope — never let raw exception types reach the serializer.
- Run
to confirm skill files are well-formed and related skills are correctly cross-referenced.harness validate
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
- related_skills: api-problem-details-rfc, api-status-codes, api-validation-errors, api-bulk-operations
Success Criteria
- Every error response across all endpoints uses the same JSON envelope structure.
- All error responses include a stable, machine-readable
field alongside the human-readablecode
.message - No error response exposes internal stack traces, SQL errors, or raw exception messages.
- Every
response includes a5xx
ortraceId
field for log correlation.instance - The full error taxonomy is published in API documentation with remediation guidance for each code.