Install
$ agentstack add skill-tamasbege-staff-engineer-skills-api-response-normalizer ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
API Response Normalizer
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?.dataor 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
.htmlfile: inline CSS only (no external assets or CDN links), a linked table of contents, styled tables, `` 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
.mdfile 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.htmlis 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.mdas the index plus01-*.mdfiles, 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?
- Special requirements — Rate limiting, multi-tenancy, partial responses, bulk operations, long-running async tasks?
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)
{
"data": {
"id": "res_8xK2mP",
"status": "confirmed",
"total": 149.99,
"currency": "EUR",
"createdAt": "2026-03-15T10:22:00Z"
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-03-15T10:22:01Z"
}
}
Success — Collection (Paginated)
{
"data": [
{ "id": "res_8xK2mP", "status": "confirmed", "total": 149.99, "currency": "EUR" },
{ "id": "res_9yL3nQ", "status": "shipped", "total": 89.00, "currency": "EUR" }
],
"meta": {
"requestId": "req_def456",
"timestamp": "2026-03-15T10:23:00Z"
},
"pagination": {
"page": 2,
"pageSize": 20,
"totalItems": 843,
"totalPages": 43,
"hasNext": true,
"hasPrevious": true
}
}
Error — Operational Failure
{
"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/attributessplit, 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,hasPreviousare all valid. - Cursor/keyset pagination: replace
page/totalItems/totalPageswithcursor/nextCursor/previousCursor. ThetotalItemsfield becomes optional (expensive COUNT query) — omit it or mark as approximate. Never requiretotalItemsfor 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 +
Locationheader 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
SunsetandDeprecationheaders per RFC 8594.)
Additional Concerns (address when relevant)
Conditional Requests & Caching
- ETag — Include
ETagheader on GET responses for cacheable resources. SupportIf-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:
{
"data": {
"operationId": "op_xyz789",
"status": "processing",
"statusUrl": "/api/v1/operations/op_xyz789",
"estimatedCompletionSeconds": 30
},
"meta": { "requestId": "req_mno345", "timestamp": "..." }
}
Bulk Operations
When endpoints accept multiple items in a single request, define partial-success behavior:
- All-or-nothing (return 400 if any item fails)?
- Partial success (return 207 Multi-Status or 200 with per-item results)?
- Document the chosen approach in the contract.
Health Check Response
Align with your framework's built-in health format:
- Spring Boot: Use Actuator
/actuator/health(no custom shape needed) - ASP.NET: Use
Microsoft.Extensions.Diagnostics.HealthChecks - Express/Fastify: Use Terminus or Lightship pattern
- Kubernetes: Separate
/healthz(liveness — 200 if process alive) from/readyz(readiness — checks dependencies)
If no framework health system exists:
{ "status": "healthy", "checks": [{ "name": "database", "status": "healthy", "responseTimeMs": 12 }] }
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-Typeheader 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": [] | | Incon
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: tamasbege
- Source: tamasbege/staff-engineer-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.