JSON Toolkit

What structure should an API response use

Bottom line: Bare objects are clean until you need metadata; full envelopes are safe but verbose. For most projects use HTTP status for outcomes and a stable envelope for data.

Intermediate~4 min readPublished 2026-10-30Updated 2026-10-30
Three API response shapes and their trade-offs

Bottom line: express outcomes with 4xx/5xx status codes, carry the payload in { "data": ..., "meta": ... }, and switch to an error envelope on failure; that is enough for internal APIs, and only public platforms with many integrators need JSON:API or OpenAPI.

Rule out the worst option first: always returning 200 with the outcome in a code field.

Three shapes

A. Bare object

{ "id": 1, "name": "demo", "price": 99 }

Clean, and HTTP semantics do the work (404 is 404). But the moment you need pagination, warnings or a trace id, there is nowhere to put them without changing the contract.

B. Stable envelope

{
  "data": { "id": 1, "name": "demo" },
  "meta": { "requestId": "abc", "took": 12 }
}

Payload and metadata live apart, so new metadata never reshapes data. Costs one level of nesting.

C. Everything envelope (avoid)

{ "code": 0, "message": "success", "data": {...} }

The shape is not the sin; the practice is. Status 200 for every outcome means CDNs, gateways, circuit breakers, retry policies and dashboards all see success. Every alert then needs custom parsing.

The recommendation

HTTP status for the outcome, envelope for the payload.

{
  "data": { "id": 1, "name": "demo" },
  "meta": { "requestId": "req_8f2a", "took": 12 }
}
{
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "Order does not exist or was deleted",
    "details": [{ "field": "id", "issue": "not_found" }]
  },
  "meta": { "requestId": "req_8f2a" }
}

Status 4xx or 5xx; code stable and machine-readable; message for humans; details for field-level form errors.

Hard rules

  1. List endpoints must carry pagination metadata. Without total or a cursor there is no pager.
  2. Empty lists return [], never null. null.map is a recurring outage.
  3. Timestamps in ISO 8601 with an offset. See what format for timestamps.
  4. One naming convention. snake_case or camelCase, converted once at the boundary.
  5. Money as integer minor units or strings. 99.99 is not exact in binary floating point.
  6. Do not conflate null with absent. See null or omit the field.

When to adopt a specification

  • Public platforms with many integrators: JSON:API or OpenAPI plus a shared envelope
  • Single-team internal APIs: the envelope above is enough
  • Strong contracts: Schema first, structures generated from it

Specifications save discussion and cost onboarding. For a five-person team, JSON:API is usually negative work.

FAQ

How should I choose between a bare object and an envelope?

Use an envelope when you need pagination, warnings or a requestId, so meta can grow without reshaping data. Return the bare object only when you need no metadata and can accept that adding fields later changes the contract.

Why should an API never return 200 for every outcome?

CDNs, gateways, circuit breakers, retry policies and dashboards all read the HTTP status to decide whether to cache, retry or alert. With 200 everywhere they treat failures as successes, and every alert needs custom parsing of the business code.

Should list endpoints carry pagination metadata?

Yes. Without total or a cursor there is no pager, and empty lists must return [] rather than null, or the frontend's .map call breaks.

Further reading