Best Practices for Designing HTTP APIs: Status Codes, JSON Payloads, Resource Naming, and Error Handling
This guide outlines comprehensive backend API design recommendations, covering appropriate HTTP status codes, JSON request bodies, unique resource identifiers, timestamp formats, consistent URL structures, nested relationships, error message schemas, caching, pagination, versioning, security, and clear documentation with executable examples.
Return appropriate status codes
Each response should use a suitable HTTP status code. Successful responses use:
200 : GET succeeded, or DELETE/PATCH synchronous completion.
201 : POST synchronous completion.
202 : POST, DELETE, or PATCH asynchronous request accepted.
206 : Partial GET response (example shown).
For client errors and server exceptions, refer to the full HTTP response code specification.
Provide all available resources
When the response code is 200 or 201, return the full set of visible resources, including attributes for PUT/PATCH/DELETE requests. Example:
$ curl -X DELETE \
https://service.com/apps/1f9b/domains/0fd4
HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
...
{
"created_at": "2012-01-01T12:00:00Z",
"hostname": "subdomain.example.com",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"updated_at": "2012-01-01T12:00:00Z"
}If the status code is 202, do not return the full resource set. Example:
$ curl -X DELETE \
https://service.com/apps/1f9b/dynos/05bd
HTTP/1.1 202 Accepted
Content-Type: application/json;charset=utf-8
...
{}Use JSON data in request bodies
PUT, PATCH, and POST request bodies should be JSON, not form‑encoded. Example:
$ curl -X POST https://service.com/apps \
-H "Content-Type: application/json" \
-d '{"name": "demoapp"}'
{
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "demoapp",
"owner": {
"email": "[email protected]",
"id": "01234567-89ab-cdef-0123-456789abcdef"
},
...
}Provide a unique identifier for resources
Assign each resource an id attribute, preferably a globally unique UUID in the 8‑4‑4‑4‑12 format, e.g., "01234567-89ab-cdef-0123-456789abcdef".
Provide standard timestamps
Include created_at and updated_at fields using ISO‑8601 UTC format, e.g., "2012-01-01T12:00:00Z". Omit if not applicable.
Use ISO‑8601 international time format
All returned time values must be in UTC ISO‑8601 format, such as:
"finished_at": "2012-01-01T12:00:00Z"Use unified resource paths
Resource naming
Use plural nouns for resource names to keep URLs consistent.
Action naming
When a special action is required, use an actions prefix, e.g., /resources/:resource/actions/:action . Example:
/runs/{run_id}/actions/stopUse lowercase letters for paths and attributes
Paths should be lowercase with hyphens as separators, e.g., service-api.com/users . Attribute names use lowercase with underscores, e.g., service_class .
Nested foreign‑key relationships
Embed related objects instead of exposing only foreign‑key IDs. Example of nested owner object:
{
"name": "service-production",
"owner": {
"id": "5d8201b0...",
"name": "Alice",
"email": "[email protected]"
},
...
}Support convenient indirect references without ID
Allow resources to be accessed by both name and ID, e.g.:
$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prodBuild error messages
Return structured error objects containing a machine‑readable id , a human‑readable message , and optionally a url for more information. Example:
HTTP/1.1 429 Too Many Requests
{
"id": "rate_limit",
"message": "Account reached its API rate limit.",
"url": "https://docs.service.com/rate-limits"
}Support ETag caching
Include an ETag header in all responses and honor If-None-Match in subsequent requests.
Use an ID to track each request
Include a Request-Id header (a UUID) in every response to aid debugging and tracing.
Range pagination
For large result sets, paginate using the Content-Range header.
Show rate‑limit status
Return the remaining request tokens via the RateLimit-Remaining header.
Specify acceptable header version
Clients must explicitly set the API version using the Accept header, e.g., Accept: application/vnd.heroku+json; version=3 .
Reduce path nesting
Limit deep nesting by exposing top‑level resources and using shallow paths, e.g., /orgs/{org_id} , /apps/{app_id} , /dynos/{dyno_id} .
Provide a machine‑readable JSON schema
Use tools like prmd to generate and verify a JSON schema with prmd verify .
Provide human‑readable documentation
Generate Markdown documentation from the schema using prmd doc for developers.
Provide executable examples
Include ready‑to‑run command‑line examples so users can copy‑paste directly, e.g.:
$ export TOKEN=... # acquire from dashboard
$ curl -is https://[email protected]/usersDescribe stability
Document the API's stability level (prototype, development, production) and follow the Heroku API compatibility policy for versioning.
Require secure transport
All API access must be over HTTPS.
Use friendly JSON output
Print pretty‑printed JSON with line breaks for readability, e.g.:
{
"beta": false,
"email": "[email protected]",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"last_login": "2012-01-01T12:00:00Z",
"created_at": "2012-01-01T12:00:00Z",
"updated_at": "2012-01-01T12:00:00Z"
}Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.