Data interchange in APIs: negotiation and versioning
Content-Type and Accept negotiation, envelope versus bare payloads, how to version a format without breaking clients, and an error contract worth keeping.
Negotiating the representation
The same resource can be returned in several representations. The client states a preference with Accept, the server declares what it actually sent with Content-Type, and a Vary header keeps caches honest.
GET /v1/orders/42 HTTP/1.1
Accept: application/json;q=1.0, application/vnd.acme.order+json;q=0.9, */*;q=0.1
HTTP/1.1 200 OK
Content-Type: application/vnd.acme.order+json; charset=utf-8
Vary: Accept
Cache-Control: private, max-age=60
{"id":42,"total":{"amount":"19.99","currency":"EUR"}}- A vendor media type such as
application/vnd.acme.order+jsonversions the schema without touching the URL. - Return
406 Not Acceptablewhen no offered type can be produced — silently returning JSON is worse. - Always state
charset=utf-8; for JSON it is the only valid choice, and saying so removes a class of parsing bugs.
Versioning and compatibility
| Change | Compatible? | Notes |
|---|---|---|
| Add an optional response field | Yes | Clients must ignore unknown fields |
| Add a required request field | No | Breaking: old clients never send it |
| Rename a field | No | Emit both names for a deprecation window |
| Widen a type (int to string) | No | Breaks typed clients; introduce a new field |
| Tighten validation | No | Requests that used to work now 400 |
| Remove a documented field | No | Announce, measure usage, then remove |
{
"id": 42,
"total": "19.99",
"totalAmount": "19.99",
"currency": "EUR"
}The duplicate-field pattern above is the pragmatic way to rename: publish both, deprecate one in the docs and with a Warning or Deprecation header, and remove it after usage telemetry reaches zero.
Envelope or bare payload, and the error contract
{
"data": { "id": 42, "total": "19.99" },
"meta": { "requestId": "b7f1", "durationMs": 12 },
"errors": []
}An envelope costs bytes and one level of nesting, and buys a place to put request IDs, pagination cursors and partial-failure details. Use it for list endpoints and any API with partial success; skip it for single-resource reads where the payload is the resource.
FAQ
Should I version in the URL or the media type?
/v2/) is simpler to route and cache and is what most teams can operate. Media-type versioning is more precise and allows per-representation versions, but needs stricter client discipline.Is a 204 response with no body better than a 200 with an envelope?
Related
Choosing a format: size, speed, readability and tooling Schema definition and validation
Last refreshed 2026-09-18.