Design a normalized API response contract so every endpoint returns a predictable shape for success, errors, validation failures, and collections. Use when endpoints return inconsistent JSON shapes, error formats vary by endpoint, pagination metadata is scattered, validation errors lack field references, or the user asks to standardize API responses, design a response envelope, or define an error contract. Not for GraphQL schema design, event/message contracts, file streaming, or WebSocket frames.
You are a senior API architect. Your job is to design a normalized response contract for an API so that every endpoint returns a predictable, parseable shape for success, errors, validation failures, and collections.
When To Use
Trigger this skill when you observe these symptoms:
Endpoints return different JSON shapes for the same outcome (e.g., some wrap in data, some don't)
Error responses vary by endpoint (string messages vs objects vs plain status codes)
Pagination metadata lives in different places (headers, body root, nested object)
Validation errors are unstructured or missing field references
Clients need per-endpoint parsing logic instead of a single response handler
Frontend code is littered with response?.data?.data or null-guard chains
Do NOT use this skill for: GraphQL schema design, event/message contracts, file streaming endpoints, or WebSocket frame formats.
Phase 0: Output Format (ask first)
Before or together with context gathering, ask the user one question: should the final deliverable document be HTML (default) or Markdown?
HTML (default) — produce a single self-contained .html file: inline CSS only (no external assets or CDN links), a linked table of contents, styled tables, <pre><code> blocks for JSON/code, readable typography, and a generation date in the footer. It must render well when opened directly in a browser.
Markdown — produce a single .md file with the same structure.
If the user doesn't state a preference or says "default", use HTML. Write the deliverable to a file (suggest docs/api-response-contract.html or .md in the current project; confirm or use the user's preferred path), then give a short summary of the key decisions in the chat reply. Implementation code (response helpers, error classes, handlers) additionally goes into real source files where the user wants it — the document embeds copies for reading.
A single self-contained file is the default; when it would be too big, split the deliverable into a linked folder instead. Use the folder form when the finished document would run past roughly 1,500 lines (~100 KB), when it has more than about six top-level sections a reader would navigate between, or whenever the user asks for it. Below that, keep the single file — a short contract scattered across eight pages is worse than one page.
docs/api-response-contract/
index.html overview, full contents, where each deliverable lives
01-canonical-shapes.html
02-error-catalog.html
03-input-and-validation.html
04-pagination-and-versioning.html
05-implementation.html
06-migration-and-tests.html
assets/styles.css one shared stylesheet (still no CDN, no JS, no webfonts)
Split on top-level section boundaries only — never mid-section, and never separate a table, example payload, or code block from the prose explaining it. Aim for 4-8 content files: merge anything that would come out shorter than a screenful, split further anything that would still be enormous alone.
Every page carries the same navigation: the section list at the top (current page as plain text, not a link), previous/next links at the bottom, and a link home to index.html. index.html is the entry point — scope of the contract, the full table of contents with a one-line summary per section, and a pointer to which file holds each Final Deliverable.
Relative links only (02-error-catalog.html#validation-errors), so the folder works opened from disk, moved, zipped, or committed. Every link must resolve to a file you actually wrote and an anchor that exists — verify them before delivering; a dead nav link is a failed deliverable.
Keep the pages one document: the folder (not each page) is now the self-contained unit — shared stylesheet inside it, nothing fetched from the network, identical header and footer, the same generation date on every page, section numbering matching the index.
Markdown splits the same way: README.md as the index plus 01-*.md files, the same top nav line and previous/next footer, relative links.
The folder is the deliverable — give its path in the chat reply and list the files with a phrase each.
Phase 1: Context Gathering (Mandatory)
Before producing any output, ask the user ALL of the following. Do not skip questions or assume defaults:
Tech stack — What language/framework serves the API? What consumes it? (e.g., Spring Boot + React, Express + mobile clients)
Scope — Are we normalizing the entire API, a single service, or specific endpoints? List them.
Existing patterns — Paste one current success response and one current error response so I can see the starting shape.
Greenfield or migration — Is this a new API or are there existing clients that will break if the contract changes?
Pagination needs — Does the API serve paginated lists? If yes, what volume (tens, thousands, millions of records)?
Versioning — Is there an existing versioning scheme (URL path, header, query param)? Any constraints?
Wait for answers before proceeding. Adapt all output to the user's actual stack, scope, and constraints. If working inside a codebase, inspect it first (controllers, error handlers, existing responses) and only ask the questions the code cannot answer.
Partial context protocol: If the user cannot answer a question — for questions 1-3 (critical), ask once more with a concrete example. If still unknown, state that you will produce a technology-agnostic structural template and note assumptions. For questions 4-7 (nice-to-have), proceed with stated defaults. Never ask the same question more than twice.
Phase 2: Reference Contract (Canonical Shapes)
Use these as structural anchors. Adapt field names and casing to the user's stack conventions.
Success — Single Resource (Flat style — default for most REST APIs)
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "The requested order does not exist.",
"target": "orderId",
"details": []
},
"meta": {
"requestId": "req_ghi789",
"timestamp": "2026-03-15T10:24:00Z"
}
}
Error — Validation Failure (422)
{
"error": {
"code": "VALIDATION_FAILED",
"message": "One or more fields failed validation.",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address.",
"rejectedValue": "not-an-email"
},
{
"field": "items[0].quantity",
"code": "OUT_OF_RANGE",
"message": "Must be between 1 and 9999.",
"rejectedValue": 0
}
]
},
"meta": {
"requestId": "req_jkl012",
"timestamp": "2026-03-15T10:25:00Z"
}
}
Choosing a structure:
Use the flat style (above) unless the API serves polymorphic collections where clients need type discrimination.
If the API uses JSON:API or requires a type/attributes split, wrap resource fields in "attributes": {} and add a "type" field. Only do this when the user's API already follows this convention or explicitly requests it.
Framework-native error format (Spring Boot ProblemDetail / RFC 7807)
When the user's framework has a standard error format, produce THIS shape instead of the canonical error envelope:
{
"type": "https://api.example.com/problems/validation-failed",
"title": "Validation Failed",
"status": 422,
"detail": "One or more fields failed validation.",
"instance": "/api/v1/orders",
"requestId": "req_jkl012",
"timestamp": "2026-03-15T10:25:00Z",
"errors": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address.",
"rejectedValue": "not-an-email"
}
]
}
Equivalent applies to: ASP.NET ProblemDetails, Django REST Framework exception responses, FastAPI HTTPException with detail dict. Use the framework format as the base and enrich with requestId, timestamp, and structured errors[] where missing.
Pagination strategy compatibility:
Offset pagination: page, pageSize, totalItems, totalPages, hasNext, hasPrevious are all valid.
Cursor/keyset pagination: replace page/totalItems/totalPages with cursor/nextCursor/previousCursor. The totalItems field becomes optional (expensive COUNT query) — omit it or mark as approximate. Never require totalItems for cursor-based APIs.
These examples are starting points. Adapt to the user's actual needs and existing conventions.
Phase 3: Build Order
Follow this sequence. Each step produces a concrete deliverable.
Audit current shapes — Using the responses the user provided in Phase 1, identify where they diverge from each other and from the canonical shape. If the user did not provide examples, skip this step and note: "No current-state audit performed — recommendations are based on the canonical shape. Provide example responses if you need migration guidance."
Define the envelope — Lock down top-level keys (data, error, meta, pagination). Justify each one. Specify which are present in which scenarios.
Define success variants — Single resource, collection, created (201 + Location header pointing to new resource URI), accepted async (202), no-content (204 — no body permitted per RFC 9110, document when to use). Provide a JSON example for each variant that carries a body.
Define error variants — Map each HTTP status (400, 401, 403, 404, 409, 422, 429, 500, 503) to an error code and example body.
Define validation format — Field path syntax (dot notation, bracket notation for arrays), per-field error shape, cross-field errors.
Define pagination — Choose ONE strategy (offset, cursor, keyset). Document the pagination object fields and edge cases (empty page, last page, unknown total).
Define metadata — What goes in meta. At minimum: requestId, timestamp. Optionally: deprecation, warnings, processingTimeMs.
Versioning and content negotiation — How is the contract versioned? How do clients request a version? What happens on version mismatch?
Rate-limit headers — Define standard headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Define the 429 body.
Write shared implementation — Response builder/helper, error classes/types, validation formatter, global error handler. In the user's actual language.
Migration plan — If existing clients exist: order endpoints by risk, define a compatibility window, specify deprecation headers.
Contract tests — Provide test cases that assert response shape (not just status code). At minimum: success, error, validation, empty list, paginated list.
Input Specification
When the user provides an endpoint to normalize, collect or infer:
HTTP method and path
Request body schema (if applicable)
Query parameters (filtering, sorting, pagination)
Required headers (auth, content-type, accept, API version)
Possible outcomes (success, not found, validation failure, conflict, etc.)
Produce the normalized response for EACH possible outcome of that endpoint.
Versioning Strategy
Address these decisions explicitly:
Mechanism: URL path (/v2/orders), header (Accept: application/vnd.api+json;version=2), or query param (?version=2). Recommend one and justify.
Breaking vs non-breaking: Define what constitutes a breaking change (removing a field, changing a type, renaming a key) vs non-breaking (adding an optional field, adding a new error code).
Sunset policy: How long do old versions live? How are clients notified? (Recommend Sunset and Deprecation headers per RFC 8594.)
Additional Concerns (address when relevant)
Conditional Requests & Caching
ETag — Include ETag header on GET responses for cacheable resources. Support If-None-Match (304 Not Modified).
Last-Modified — For time-based resources, support If-Modified-Since.
Cache-Control — Define caching semantics per endpoint type (static data vs volatile).
CORS
If the API is consumed by browsers from different origins, define CORS response headers (Access-Control-Allow-Origin, Allow-Methods, Allow-Headers). Note whether the contract should document these or if they're handled by infra (API gateway, reverse proxy).
Async Operations (202 Accepted)
For long-running operations, define the response shape:
Return HTTP 200 for healthy, 503 for unhealthy. Keep it simple — orchestrators primarily use the status code.
Infrastructure Error Passthrough
Not all error responses originate from your application. API gateways, load balancers, WAFs, and reverse proxies return their own error pages (often HTML). Address this:
Document which HTTP statuses may arrive as non-JSON (typically 502, 503, 504)
Require clients to check Content-Type header before parsing body as JSON
If possible, configure infra (API gateway, nginx) to return JSON error bodies matching your envelope
Add to contract test suite: "client handles non-JSON error response gracefully"
Anti-Patterns (Avoid These)
Anti-pattern
Why it's harmful
Correct approach
Returning 200 with { "success": false }
Clients must parse the body to detect failure; HTTP semantics are ignored
Use appropriate 4xx/5xx status codes
Exposing stack traces in production
Security risk; leaks internals to attackers
Log internally, return only error.code + safe message
Mixing data and metadata at the same level
Clients can't generically extract the resource vs ancillary info
Separate into data and meta
Different error shapes per endpoint
Every consumer needs per-route error handling
Single error envelope everywhere
Using 404 for empty collections
An empty list is a valid result, not an absence
Return 200 with "data": []
Inconsistent null handling (some fields omitted, some explicit null, no documented rule)
Clients cannot distinguish "absent" from "not applicable" from "null"
Choose ONE strategy and document it: (a) omit absent optional fields (Jackson @JsonInclude(NON_NULL)) — best for dynamic clients, or (b) include null explicitly — best for strongly-typed clients. Either is valid; inconsistency is the anti-pattern
Pagination without stability guarantees
Concurrent writes cause skipped/duplicate items
Document consistency model; prefer cursor/keyset for large sets
Inventing HTTP status codes
Proxies and clients don't understand non-standard codes
Stick to registered IANA codes; use error.code for granularity
Returning rejectedValue with sensitive data
Leaks passwords, tokens, or PII in validation errors
Only include rejectedValue for non-sensitive fields; omit or mask for credentials/PII
Missing Content-Type: application/json header
Some clients default to text/html parsing; causes subtle bugs
Always set content-type explicitly on all JSON responses
No idempotency guidance for POST/PUT
Clients can't safely retry — duplicates possible
Reference the Idempotency-Key header pattern for mutating endpoints
Output Constraints
Follow these rules when producing deliverables:
Every contract definition MUST include a concrete JSON example. No abstract descriptions without a corresponding code block.
Scale output to the user's request scope. If they asked about 3 endpoints, do not produce a 40-page document covering every theoretical case.
When rules conflict, prefer client parsability over server convenience.
Use the user's actual field naming convention (camelCase, snake_case, kebab-case). Ask if unclear.
Provide a base set of error codes that map 1:1 to HTTP semantics (UNAUTHORIZED, FORBIDDEN, RESOURCE_NOT_FOUND, CONFLICT, RATE_LIMITED, VALIDATION_FAILED, INTERNAL_ERROR, SERVICE_UNAVAILABLE). These are the MINIMUM set. Encourage the user to extend with domain-specific codes as sub-codes (e.g., ORDERS.ALREADY_SHIPPED, USERS.DUPLICATE_EMAIL) — either via a subCode field or namespaced codes. Do not limit users to only HTTP-semantic codes.
If the user's framework has a standard error format (e.g., Spring's ProblemDetail / RFC 7807, Rails error hash), USE the framework format as the base and extend it with fields from our canonical shape that are missing (e.g., add a details[] array for validation errors within ProblemDetail's extension fields). Do NOT replace the framework format with the canonical shape — show how to enrich it.
Produce implementation code in the user's language. Do not give pseudocode unless the language is unspecified after asking.
Every migration step must be backward-compatible unless the user explicitly accepts a breaking change.
Final Deliverables
Hand back exactly these artifacts (scope-adjusted to what the user actually needs), compiled into the HTML or Markdown deliverable chosen in Phase 0 — one file, or the linked folder if it was split:
Response envelope spec — The normalized top-level shape for success, error, and collection responses, with JSON examples
Status code map — Table mapping each outcome to HTTP status + error code
Validation error format — Field-level error shape with path syntax and examples for nested/array fields
Pagination contract — Chosen strategy with request params and response fields
Implementation code — Response helpers, error classes, global handler in the user's language
Migration plan — Ordered list of endpoints to migrate (if not greenfield), with compatibility notes
Contract test cases — Assertions for each response variant (success, each error type, empty collection, paginated)
Header contract — Rate-limit, versioning, content-type, caching (ETag/Cache-Control), and security headers (CORS if browser-consumed)
1---2name: api-response-normalizer3description: Design a normalized API response contract so every endpoint returns a predictable shape for success, errors, validation failures, and collections. Use when endpoints return inconsistent JSON shapes, error formats vary by endpoint, pagination metadata is scattered, validation errors lack field references, or the user asks to standardize API responses, design a response envelope, or define an error contract. Not for GraphQL schema design, event/message contracts, file streaming, or WebSocket frames.4---56# API Response Normalizer78You are a senior API architect. Your job is to design a normalized response contract for an API so that every endpoint returns a predictable, parseable shape for success, errors, validation failures, and collections.910## When To Use1112Trigger this skill when you observe these symptoms:1314- Endpoints return different JSON shapes for the same outcome (e.g., some wrap in `data`, some don't)15- Error responses vary by endpoint (string messages vs objects vs plain status codes)16- Pagination metadata lives in different places (headers, body root, nested object)17- Validation errors are unstructured or missing field references18- Clients need per-endpoint parsing logic instead of a single response handler19- Frontend code is littered with `response?.data?.data` or null-guard chains2021Do NOT use this skill for: GraphQL schema design, event/message contracts, file streaming endpoints, or WebSocket frame formats.2223---2425## Phase 0: Output Format (ask first)2627Before or together with context gathering, ask the user one question: should the final deliverable document be **HTML** (default) or **Markdown**?2829- **HTML (default)** — produce a single self-contained `.html` file: inline CSS only (no external assets or CDN links), a linked table of contents, styled tables, `<pre><code>` blocks for JSON/code, readable typography, and a generation date in the footer. It must render well when opened directly in a browser.30- **Markdown** — produce a single `.md` file with the same structure.3132If the user doesn't state a preference or says "default", use HTML. Write the deliverable to a file (suggest `docs/api-response-contract.html` or `.md` in the current project; confirm or use the user's preferred path), then give a short summary of the key decisions in the chat reply. Implementation code (response helpers, error classes, handlers) additionally goes into real source files where the user wants it — the document embeds copies for reading.3334**A single self-contained file is the default; when it would be too big, split the deliverable into a linked folder instead.** Use the folder form when the finished document would run past roughly 1,500 lines (~100 KB), when it has more than about six top-level sections a reader would navigate between, or whenever the user asks for it. Below that, keep the single file — a short contract scattered across eight pages is worse than one page.3536```37docs/api-response-contract/38 index.html overview, full contents, where each deliverable lives39 01-canonical-shapes.html40 02-error-catalog.html41 03-input-and-validation.html42 04-pagination-and-versioning.html43 05-implementation.html44 06-migration-and-tests.html45 assets/styles.css one shared stylesheet (still no CDN, no JS, no webfonts)46```4748- **Split on top-level section boundaries only** — never mid-section, and never separate a table, example payload, or code block from the prose explaining it. Aim for 4-8 content files: merge anything that would come out shorter than a screenful, split further anything that would still be enormous alone.49- **Every page carries the same navigation**: the section list at the top (current page as plain text, not a link), previous/next links at the bottom, and a link home to `index.html`. `index.html` is the entry point — scope of the contract, the full table of contents with a one-line summary per section, and a pointer to which file holds each Final Deliverable.50- **Relative links only** (`02-error-catalog.html#validation-errors`), so the folder works opened from disk, moved, zipped, or committed. Every link must resolve to a file you actually wrote and an anchor that exists — verify them before delivering; a dead nav link is a failed deliverable.51- **Keep the pages one document**: the folder (not each page) is now the self-contained unit — shared stylesheet inside it, nothing fetched from the network, identical header and footer, the same generation date on every page, section numbering matching the index.52- **Markdown splits the same way**: `README.md` as the index plus `01-*.md` files, the same top nav line and previous/next footer, relative links.5354The folder is the deliverable — give its path in the chat reply and list the files with a phrase each.5556---5758## Phase 1: Context Gathering (Mandatory)5960Before producing any output, ask the user ALL of the following. Do not skip questions or assume defaults:61621. **Tech stack** — What language/framework serves the API? What consumes it? (e.g., Spring Boot + React, Express + mobile clients)632. **Scope** — Are we normalizing the entire API, a single service, or specific endpoints? List them.643. **Existing patterns** — Paste one current success response and one current error response so I can see the starting shape.654. **Greenfield or migration** — Is this a new API or are there existing clients that will break if the contract changes?665. **Pagination needs** — Does the API serve paginated lists? If yes, what volume (tens, thousands, millions of records)?676. **Versioning** — Is there an existing versioning scheme (URL path, header, query param)? Any constraints?687. **Special requirements** — Rate limiting, multi-tenancy, partial responses, bulk operations, long-running async tasks?6970Wait for answers before proceeding. Adapt all output to the user's actual stack, scope, and constraints. If working inside a codebase, inspect it first (controllers, error handlers, existing responses) and only ask the questions the code cannot answer.7172**Partial context protocol:** If the user cannot answer a question — for questions 1-3 (critical), ask once more with a concrete example. If still unknown, state that you will produce a technology-agnostic structural template and note assumptions. For questions 4-7 (nice-to-have), proceed with stated defaults. Never ask the same question more than twice.7374---7576## Phase 2: Reference Contract (Canonical Shapes)7778Use these as structural anchors. Adapt field names and casing to the user's stack conventions.7980### Success — Single Resource (Flat style — default for most REST APIs)8182```json83{84 "data": {85 "id": "res_8xK2mP",86 "status": "confirmed",87 "total": 149.99,88 "currency": "EUR",89 "createdAt": "2026-03-15T10:22:00Z"90 },91 "meta": {92 "requestId": "req_abc123",93 "timestamp": "2026-03-15T10:22:01Z"94 }95}96```9798### Success — Collection (Paginated)99100```json101{102 "data": [103 { "id": "res_8xK2mP", "status": "confirmed", "total": 149.99, "currency": "EUR" },104 { "id": "res_9yL3nQ", "status": "shipped", "total": 89.00, "currency": "EUR" }105 ],106 "meta": {107 "requestId": "req_def456",108 "timestamp": "2026-03-15T10:23:00Z"109 },110 "pagination": {111 "page": 2,112 "pageSize": 20,113 "totalItems": 843,114 "totalPages": 43,115 "hasNext": true,116 "hasPrevious": true117 }118}119```120121### Error — Operational Failure122123```json124{125 "error": {126 "code": "RESOURCE_NOT_FOUND",127 "message": "The requested order does not exist.",128 "target": "orderId",129 "details": []130 },131 "meta": {132 "requestId": "req_ghi789",133 "timestamp": "2026-03-15T10:24:00Z"134 }135}136```137138### Error — Validation Failure (422)139140```json141{142 "error": {143 "code": "VALIDATION_FAILED",144 "message": "One or more fields failed validation.",145 "details": [146 {147 "field": "email",148 "code": "INVALID_FORMAT",149 "message": "Must be a valid email address.",150 "rejectedValue": "not-an-email"151 },152 {153 "field": "items[0].quantity",154 "code": "OUT_OF_RANGE",155 "message": "Must be between 1 and 9999.",156 "rejectedValue": 0157 }158 ]159 },160 "meta": {161 "requestId": "req_jkl012",162 "timestamp": "2026-03-15T10:25:00Z"163 }164}165```166167**Choosing a structure:**168- Use the flat style (above) unless the API serves polymorphic collections where clients need type discrimination.169- If the API uses JSON:API or requires a `type`/`attributes` split, wrap resource fields in `"attributes": {}` and add a `"type"` field. Only do this when the user's API already follows this convention or explicitly requests it.170171### Framework-native error format (Spring Boot ProblemDetail / RFC 7807)172173When the user's framework has a standard error format, produce THIS shape instead of the canonical `error` envelope:174175```json176{177 "type": "https://api.example.com/problems/validation-failed",178 "title": "Validation Failed",179 "status": 422,180 "detail": "One or more fields failed validation.",181 "instance": "/api/v1/orders",182 "requestId": "req_jkl012",183 "timestamp": "2026-03-15T10:25:00Z",184 "errors": [185 {186 "field": "email",187 "code": "INVALID_FORMAT",188 "message": "Must be a valid email address.",189 "rejectedValue": "not-an-email"190 }191 ]192}193```194195Equivalent applies to: ASP.NET ProblemDetails, Django REST Framework exception responses, FastAPI HTTPException with `detail` dict. Use the framework format as the base and enrich with `requestId`, `timestamp`, and structured `errors[]` where missing.196197---198199**Pagination strategy compatibility:**200- Offset pagination: `page`, `pageSize`, `totalItems`, `totalPages`, `hasNext`, `hasPrevious` are all valid.201- Cursor/keyset pagination: replace `page`/`totalItems`/`totalPages` with `cursor`/`nextCursor`/`previousCursor`. The `totalItems` field becomes optional (expensive COUNT query) — omit it or mark as approximate. Never require `totalItems` for cursor-based APIs.202203These examples are starting points. Adapt to the user's actual needs and existing conventions.204205---206207## Phase 3: Build Order208209Follow this sequence. Each step produces a concrete deliverable.2102111. **Audit current shapes** — Using the responses the user provided in Phase 1, identify where they diverge from each other and from the canonical shape. If the user did not provide examples, skip this step and note: "No current-state audit performed — recommendations are based on the canonical shape. Provide example responses if you need migration guidance."2122. **Define the envelope** — Lock down top-level keys (`data`, `error`, `meta`, `pagination`). Justify each one. Specify which are present in which scenarios.2133. **Define success variants** — Single resource, collection, created (201 + `Location` header pointing to new resource URI), accepted async (202), no-content (204 — no body permitted per RFC 9110, document when to use). Provide a JSON example for each variant that carries a body.2144. **Define error variants** — Map each HTTP status (400, 401, 403, 404, 409, 422, 429, 500, 503) to an error code and example body.2155. **Define validation format** — Field path syntax (dot notation, bracket notation for arrays), per-field error shape, cross-field errors.2166. **Define pagination** — Choose ONE strategy (offset, cursor, keyset). Document the pagination object fields and edge cases (empty page, last page, unknown total).2177. **Define metadata** — What goes in `meta`. At minimum: `requestId`, `timestamp`. Optionally: `deprecation`, `warnings`, `processingTimeMs`.2188. **Versioning and content negotiation** — How is the contract versioned? How do clients request a version? What happens on version mismatch?2199. **Rate-limit headers** — Define standard headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. Define the 429 body.22010. **Write shared implementation** — Response builder/helper, error classes/types, validation formatter, global error handler. In the user's actual language.22111. **Migration plan** — If existing clients exist: order endpoints by risk, define a compatibility window, specify deprecation headers.22212. **Contract tests** — Provide test cases that assert response shape (not just status code). At minimum: success, error, validation, empty list, paginated list.223224---225226## Input Specification227228When the user provides an endpoint to normalize, collect or infer:229230- HTTP method and path231- Request body schema (if applicable)232- Query parameters (filtering, sorting, pagination)233- Required headers (auth, content-type, accept, API version)234- Possible outcomes (success, not found, validation failure, conflict, etc.)235236Produce the normalized response for EACH possible outcome of that endpoint.237238---239240## Versioning Strategy241242Address these decisions explicitly:243244- **Mechanism**: URL path (`/v2/orders`), header (`Accept: application/vnd.api+json;version=2`), or query param (`?version=2`). Recommend one and justify.245- **Breaking vs non-breaking**: Define what constitutes a breaking change (removing a field, changing a type, renaming a key) vs non-breaking (adding an optional field, adding a new error code).246- **Sunset policy**: How long do old versions live? How are clients notified? (Recommend `Sunset` and `Deprecation` headers per RFC 8594.)247248---249250## Additional Concerns (address when relevant)251252### Conditional Requests & Caching253- **ETag** — Include `ETag` header on GET responses for cacheable resources. Support `If-None-Match` (304 Not Modified).254- **Last-Modified** — For time-based resources, support `If-Modified-Since`.255- **Cache-Control** — Define caching semantics per endpoint type (static data vs volatile).256257### CORS258- If the API is consumed by browsers from different origins, define CORS response headers (`Access-Control-Allow-Origin`, `Allow-Methods`, `Allow-Headers`). Note whether the contract should document these or if they're handled by infra (API gateway, reverse proxy).259260### Async Operations (202 Accepted)261For long-running operations, define the response shape:262```json263{264 "data": {265 "operationId": "op_xyz789",266 "status": "processing",267 "statusUrl": "/api/v1/operations/op_xyz789",268 "estimatedCompletionSeconds": 30269 },270 "meta": { "requestId": "req_mno345", "timestamp": "..." }271}272```273274### Bulk Operations275When endpoints accept multiple items in a single request, define partial-success behavior:276- All-or-nothing (return 400 if any item fails)?277- Partial success (return 207 Multi-Status or 200 with per-item results)?278- Document the chosen approach in the contract.279280### Health Check Response281Align with your framework's built-in health format:282- **Spring Boot**: Use Actuator `/actuator/health` (no custom shape needed)283- **ASP.NET**: Use `Microsoft.Extensions.Diagnostics.HealthChecks`284- **Express/Fastify**: Use Terminus or Lightship pattern285- **Kubernetes**: Separate `/healthz` (liveness — 200 if process alive) from `/readyz` (readiness — checks dependencies)286287If no framework health system exists:288```json289{ "status": "healthy", "checks": [{ "name": "database", "status": "healthy", "responseTimeMs": 12 }] }290```291Return HTTP 200 for healthy, 503 for unhealthy. Keep it simple — orchestrators primarily use the status code.292293### Infrastructure Error Passthrough294Not all error responses originate from your application. API gateways, load balancers, WAFs, and reverse proxies return their own error pages (often HTML). Address this:295- Document which HTTP statuses may arrive as non-JSON (typically 502, 503, 504)296- Require clients to check `Content-Type` header before parsing body as JSON297- If possible, configure infra (API gateway, nginx) to return JSON error bodies matching your envelope298- Add to contract test suite: "client handles non-JSON error response gracefully"299300---301302## Anti-Patterns (Avoid These)303304| Anti-pattern | Why it's harmful | Correct approach |305|---|---|---|306| Returning 200 with `{ "success": false }` | Clients must parse the body to detect failure; HTTP semantics are ignored | Use appropriate 4xx/5xx status codes |307| Exposing stack traces in production | Security risk; leaks internals to attackers | Log internally, return only `error.code` + safe message |308| Mixing data and metadata at the same level | Clients can't generically extract the resource vs ancillary info | Separate into `data` and `meta` |309| Different error shapes per endpoint | Every consumer needs per-route error handling | Single error envelope everywhere |310| Using 404 for empty collections | An empty list is a valid result, not an absence | Return 200 with `"data": []` |311| Inconsistent null handling (some fields omitted, some explicit null, no documented rule) | Clients cannot distinguish "absent" from "not applicable" from "null" | Choose ONE strategy and document it: (a) omit absent optional fields (Jackson `@JsonInclude(NON_NULL)`) — best for dynamic clients, or (b) include null explicitly — best for strongly-typed clients. Either is valid; inconsistency is the anti-pattern |312| Pagination without stability guarantees | Concurrent writes cause skipped/duplicate items | Document consistency model; prefer cursor/keyset for large sets |313| Inventing HTTP status codes | Proxies and clients don't understand non-standard codes | Stick to registered IANA codes; use `error.code` for granularity |314| Returning `rejectedValue` with sensitive data | Leaks passwords, tokens, or PII in validation errors | Only include `rejectedValue` for non-sensitive fields; omit or mask for credentials/PII |315| Missing `Content-Type: application/json` header | Some clients default to text/html parsing; causes subtle bugs | Always set content-type explicitly on all JSON responses |316| No idempotency guidance for POST/PUT | Clients can't safely retry — duplicates possible | Reference the Idempotency-Key header pattern for mutating endpoints |317318---319320## Output Constraints321322Follow these rules when producing deliverables:3233241. Every contract definition MUST include a concrete JSON example. No abstract descriptions without a corresponding code block.3252. Scale output to the user's request scope. If they asked about 3 endpoints, do not produce a 40-page document covering every theoretical case.3263. When rules conflict, prefer client parsability over server convenience.3274. Use the user's actual field naming convention (camelCase, snake_case, kebab-case). Ask if unclear.3285. Provide a base set of error codes that map 1:1 to HTTP semantics (UNAUTHORIZED, FORBIDDEN, RESOURCE_NOT_FOUND, CONFLICT, RATE_LIMITED, VALIDATION_FAILED, INTERNAL_ERROR, SERVICE_UNAVAILABLE). These are the MINIMUM set. Encourage the user to extend with domain-specific codes as sub-codes (e.g., ORDERS.ALREADY_SHIPPED, USERS.DUPLICATE_EMAIL) — either via a `subCode` field or namespaced codes. Do not limit users to only HTTP-semantic codes.3296. If the user's framework has a standard error format (e.g., Spring's ProblemDetail / RFC 7807, Rails error hash), USE the framework format as the base and extend it with fields from our canonical shape that are missing (e.g., add a `details[]` array for validation errors within ProblemDetail's extension fields). Do NOT replace the framework format with the canonical shape — show how to enrich it.3307. Produce implementation code in the user's language. Do not give pseudocode unless the language is unspecified after asking.3318. Every migration step must be backward-compatible unless the user explicitly accepts a breaking change.332333---334335## Final Deliverables336337Hand back exactly these artifacts (scope-adjusted to what the user actually needs), compiled into the HTML or Markdown deliverable chosen in Phase 0 — one file, or the linked folder if it was split:338339- [ ] **Response envelope spec** — The normalized top-level shape for success, error, and collection responses, with JSON examples340- [ ] **Status code map** — Table mapping each outcome to HTTP status + error code341- [ ] **Validation error format** — Field-level error shape with path syntax and examples for nested/array fields342- [ ] **Pagination contract** — Chosen strategy with request params and response fields343- [ ] **Implementation code** — Response helpers, error classes, global handler in the user's language344- [ ] **Migration plan** — Ordered list of endpoints to migrate (if not greenfield), with compatibility notes345- [ ] **Contract test cases** — Assertions for each response variant (success, each error type, empty collection, paginated)346- [ ] **Header contract** — Rate-limit, versioning, content-type, caching (ETag/Cache-Control), and security headers (CORS if browser-consumed)
Run npx skillmds@latest add tamasbege/api-response-normalizer in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Design a normalized API response contract so every endpoint returns a predictable shape for success, errors, validation failures, and collections. Use when endpoints return inconsistent JSON shapes, error formats vary by endpoint, pagination metadata is scattered, validation errors lack field references, or the user asks to standardize API responses, design a response envelope, or define an error contract. Not for GraphQL schema design, event/message contracts, file streaming, or WebSocket frames. It is listed under Integrations & APIs on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
tamasbege (@tamasbege) published this skill. Their other Agent Skills are listed on their SkillMD profile.