REST APIs are how modern applications talk to each other, and JSON is the language they speak. Requests carry JSON bodies, responses return JSON documents, and errors arrive as JSON too. In this lesson of our JSON tutorial series, we connect everything you have learned about the format to the way real web services actually use it, including headers, status codes, pagination, and error envelopes.
A typical exchange with a JSON API looks like this. The client sends:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
{ "name": "Ada Lovelace", "email": "ada@example.com" }And the server replies:
HTTP/1.1 201 Created
Content-Type: application/json
{ "id": 1042, "name": "Ada Lovelace", "email": "ada@example.com", "createdAt": "2026-07-20T09:30:00Z" }The header Content-Type: application/json tells the receiver how to interpret the body. Forgetting it on a request is a classic bug: many frameworks will then treat your carefully stringified body as form data and fail to populate the parsed request object. Likewise, clients should check the response Content-Type before calling .json() on it, because error pages from proxies often arrive as HTML.
async function createUser(user) {
const res = await fetch("https://api.example.com/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user)
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.message || `HTTP ${res.status}`);
}
return res.json();
}Note the two-step error handling: fetch only rejects on network failure, so you must check res.ok yourself, and the error body may not even be JSON.
Well-designed APIs use consistent response envelopes. Two widespread patterns:
{ "data": { "id": 1042, "name": "Ada" } }for single resources, and for collections:
{
"data": [ { "id": 1 }, { "id": 2 } ],
"pagination": { "page": 1, "perPage": 20, "total": 57 }
}Wrapping the payload in a data key leaves room for metadata and keeps the shape stable as the API evolves.
Errors should be JSON with a machine-readable code and a human-readable message:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "email is not a valid address",
"field": "email"
}
}Pair the body with the correct HTTP status: 400 for bad input, 401 for missing auth, 404 for unknown resources, 422 for semantic validation failures, and 500 for server faults.
/api/v1/) so response shapes can evolve safely.Content-Type: application/json.data, pagination, and structured error objects make APIs predictable.res.ok in fetch code; HTTP errors still resolve successfully.Next up: JSON Schema and Validation, where we make payload shapes enforceable instead of just conventional.