RESTful API Cheatsheet - REST API Design Reference

APIs rarely degrade because features are missing; they degrade because shapes drift. The same resource gets a different name in different endpoints, the same class of failure returns HTTP 200 with an errCode in one place and HTTP 500 in another, and every frontend integration has to re-derive the rules. This sheet breaks each decision out separately: how URLs encode resource hierarchy and collections, why nouns plural beat verbs, whether filters, pagination and sorting belong in query parameters, how methods map onto idempotency, which status code fits which situation, which cross-cutting concerns belong in headers, what a fixed response and error envelope look like, and which auth scheme fits which risk. Sequence matters too - settle the resource model and URL first, because they determine authorisation and caching; lock the error envelope next, because that is what frontend fallbacks branch on; choose the success wrapper last, since changing it is cheapest. The division of labour with the method sheet is clean: that one picks a verb, this one keeps an entire surface consistent.

Reference·46 commands·Last updated 2026-07-21

Typical Use Case

For back-end engineers designing, reviewing or implementing HTTP interfaces. The sheet settles the conventions in one pass: plural nouns for collections, IDs for single resources, query parameters for filtering, paging and sorting, and one line per method on safety and idempotency. Status codes come with their semantic edges, notably 401 versus 403 and 400 versus 422. Authentication covers Bearer tokens, API keys and the OAuth 2.0 authorization code flow, plus ETag conditional requests, data plus meta envelopes and batch sub-resources.

URL Design Spec 6

/api/v1/users
A collection resource, named with a plural noun
/api/v1/users/123
A single resource addressed by its ID
/api/v1/users/123/orders
A nested sub-resource of the parent
/api/v1/users?role=admin
Query string for filtering the result set
/api/v1/users?page=2&limit=20
Query string carrying pagination options
/api/v1/users?sort=-created_at
Sort parameter; a leading - means descending

HTTP Methods 8

GET /users
List resources; safe and idempotent
GET /users/123
Fetch a single resource
POST /users
Create a resource; not idempotent
PUT /users/123
Replace the entire resource; idempotent
PATCH /users/123
Partially update the resource
DELETE /users/123
Delete the resource; idempotent
HEAD /users/123
Fetch the response headers only, no body returned
OPTIONS /users
Discover which HTTP methods a resource supports, used by the CORS preflight

Common Status Codes 12

200 OK
The request succeeded
201 Created
The resource was created successfully
204 No Content
Success with no content in the response, e.g. after DELETE
301 Moved Permanently
The resource has moved permanently
400 Bad Request
The request options are wrong or malformed
401 Unauthorized
Unauthenticated: credentials are missing or invalid
403 Forbidden
Authenticated but not permitted to access it
404 Not Found
The resource does not exist
409 Conflict
Resource conflict, e.g. a duplicate create
422 Unprocessable Entity
Semantically invalid, e.g. field validation failed
429 Too Many Requests
Request rate exceeded, rate limiting was triggered
500 Internal Server Error
The service failed internally

Request Headers 5

Content-Type: application/json
The request body is JSON
Accept: application/json
The client expects a JSON response
Authorization: Bearer <token>
Bearer token authentication header
If-None-Match: "etag"
Conditional request for ETag cache validation
X-API-Key: abc123
Custom API key authentication

Response Format 5

{ "data": [...], "meta": {...} }
Standard list response envelope
{ "data": {...} }
Single-resource response envelope
{ "error": { "code": "...", "message": "..." } }
Error response envelope
{ "page": 2, "limit": 20, "total": 100 }
Pagination metadata
Link: <url>; rel="next"
Pagination link header (RFC 5988)

Auth Methods 5

Authorization: Basic base64(user:pass)
HTTP Basic authentication
Authorization: Bearer <jwt>
Bearer token / JWT authentication
X-API-Key: <key>
API key auth through a custom header
?api_key=<key>
API key auth passed as a query option
OAuth 2.0
The authorization code flow

Best Practices 5

Idempotency design
Design for idempotency: repeated GET/PUT/DELETE calls yield the same result
POST create returns 201 + Location
Return 201 plus a Location header on create, pointing at the new resource URI
Bulk operations: POST /users/batch
Batch create/update goes to its own sub-resource endpoint
RateLimit-Limit / Retry-After
Rate-limiting response headers tell the client its quota
HATEOAS
The response payload embeds links to related resources, hypermedia-driven

Parameter matrix

ParameterEffectExample
/api/v1/usersResource collection path, with the noun in plural./api/v1/users
/api/v1/users/123Locate a single resource by ID./api/v1/users/123
/api/v1/users/123/ordersNested sub-resource path./api/v1/users/123/orders
?role=adminFilter resources with query parameters./api/v1/users?role=admin
?page=2&limit=20Pagination parameters./api/v1/users?page=2&limit=20
?sort=-created_atSort parameter; the leading - means descending./api/v1/users?sort=-created_at
GET /usersFetch a resource list; safe and idempotent.GET /users
POST /usersCreate a resource; not idempotent.POST /users
PUT /users/123Fully update a resource; idempotent.PUT /users/123
PATCH /users/123Partially update a resource.PATCH /users/123
DELETE /users/123Delete a resource; idempotent.DELETE /users/123
HEAD /users/123Fetch only the response headers, no body.HEAD /users/123
OPTIONS /usersAsk which methods are supported — this is the CORS preflight.OPTIONS /users
200 OKThe request succeeded.GET /users/123 → 200
201 CreatedThe resource was created; pair it with a Location header.POST /users → 201 + Location
204 No ContentSuccess with an empty body, as after a delete.DELETE /users/123 → 204
301 Moved PermanentlyPermanent redirect for the resource.GET /old → 301
400 Bad RequestThe parameters are wrong or the payload is malformed.POST /users → 400
401 UnauthorizedNot authenticated: the credential is missing or invalid.no Authorization header -> 401
403 ForbiddenAuthenticated, but not allowed to touch this resource.regular user calls an admin endpoint -> 403
404 Not FoundNo such resource.GET /users/999 → 404
409 ConflictThe request collides with current state, such as a duplicate create.duplicate signup for the same account name -> 409
422 Unprocessable EntityThe syntax was fine but field validation failed.malformed email address -> 422
429 Too Many RequestsRate limit exceeded; the caller has been throttled.flood of calls from one client -> 429
500 Internal Server ErrorThe server failed internally.unhandled exception -> 500
Content-Type: application/jsonDeclare that the request body is JSON.Content-Type: application/json
Accept: application/jsonAsk for a JSON response.Accept: application/json
Authorization: Bearer ***Bearer token or JWT authentication.Authorization: Bearer <token>
If-None-Match: "etag"Conditional request that revalidates a cached copy with an ETag.If-None-Match: "v1"
X-API-Key: ***API key authentication carried in a custom header.X-API-Key: <key>
{ "data": [...], "meta": {...} }The conventional envelope for a list response.{ "data": [], "meta": {} }
{ "data": {...} }The conventional envelope for a single resource.{ "data": { "id": 1 } }
{ "error": { "code", "message" } }One error shape for every failure.{ "error": { "code": "INVALID_ARG" } }
{ "page", "limit", "total" }Pagination metadata.{ "page": 2, "limit": 20, "total": 100 }
Link: <url>; rel="next"Pagination links in a header, so clients can walk the pages.Link: </users?page=3>; rel="next"
Authorization: Basic ***HTTP basic authentication.Authorization: Basic <base64>
?api_key=<key>Pass the API key as a query parameter./users?api_key=<key>
OAuth 2.0 authorization code flowThe standard flow for authorizing a third-party client.authorization code flow
POST /users/batchBulk create and update get their own sub-resource endpoint.POST /users/batch
RateLimit-Limit / Retry-AfterRate limit headers telling the caller its quota and when to retry.Retry-After: 30
HATEOASResponses carry links to the related resources.{ "links": { "orders": "..." } }

Common pitfalls

SymptomAfter receiving a 401 the client keeps retrying and never gets in.

CauseThe API also returns 401 for "invalid credentials", so the client cannot tell whether to re-authenticate or retry.

FixReturn 401 when unauthenticated and 403 when authenticated but unauthorized, so the client can take different branches.

SymptomThe front end treats a 400 as a field validation failure, but the request body format is actually wrong.

CauseFormat errors and semantic errors share the same status code.

FixUse 400 for format or syntax errors and 422 for field-level business validation failures, distinguishing them in error.code.

SymptomSubmitting the same create request twice produces two identical records in the database.

CausePOST is not idempotent by itself, so the server cannot recognize a duplicate when the client retries on timeout.

FixUse 409 Conflict for duplicate creation and require the client to send an idempotency key or query before creating.

SymptomAfter a PUT update, fields the client did not send are cleared.

CausePUT is treated as a full replacement, writing back missing fields as defaults.

FixWhen only part of the fields needs changing, switch to PATCH and accept only the fields actually submitted.

SymptomAfter switching to PATCH the client still submits a full object and the server behaves like PUT.

CauseThe API implementation ignores partial-update semantics and overwrites the whole record.

FixIn the PATCH handler, update only the keys present in the request body and leave the rest unchanged.

SymptomThe collection path is written /user instead of /users, and clients parsing by one rule get everything wrong.

CauseURL naming is inconsistent, so resource collections and single resources are not distinguished.

FixUse plural nouns for collections and IDs to address single resources, and pin the version in a /v1/ prefix.

SymptomAfter hitting the rate limit the client does not back off and the API is crushed in an avalanche.

CauseThe 429 response does not tell the client how long to wait, so it can only retry densely.

FixInclude Retry-After and RateLimit-Limit in the 429 response so the client backs off as instructed.

Troubleshooting

  1. 1Confirm which HTTP methods an endpoint actually supports.

    OPTIONS /users

    A preflight request returns the allowed methods list, also the first entry point for diagnosing CORS and method restrictions.

  2. 2Check whether the client's cached copy is still valid.

    If-None-Match: "etag"

    Send the last ETag with the request; when the content is unchanged the server returns 304, saving the entire response body.

  3. 3Pin down which page pagination starts from and the total count.

    /api/v1/users?page=2&limit=20

    The { "page", "limit", "total" } in the response lets you verify that the server and client agree on page numbering.

  4. 4Verify the default list ordering against creation time, descending.

    /api/v1/users?sort=-created_at

    The leading - means descending; removing it means ascending, so you can confirm the sort direction matches the API contract.

  5. 5Confirm the new resource address after a successful create.

    POST /api/v1/users

    Success should return 201, and the Location header gives the URI of the new resource, which you can use to follow up with a GET.

  6. 6Tell an authentication failure apart from an authorization failure

    GET /api/v1/users/123

    No credential at all yields 401, while a valid credential that is still refused yields 403 — and that is how you know whether to fix the client token or the server policy.

  7. 7Confirm what a bulk endpoint actually does

    POST /users/batch

    Bulk work belongs on its own sub-resource rather than as extra verbs piled onto the collection, and the response should report success and failure item by item.

Command Examples

A list endpoint: filter, pagination and sort in one request

GET /api/v1/users?role=admin&page=2&limit=20&sort=-created_at Accept: application/json Authorization: Bearer <token>

Plural nouns for collections, a sub-path per ID, and filters, pagination and sorting as query parameters (a leading - on sort means descending). Wrapping the payload in data with pagination in meta keeps one response shape for every client.

Output

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [ { "id": 123, "role": "admin", "created_at": "2026-07-19T10:30:00Z" } ],
  "meta": { "page": 2, "limit": 20, "total": 100 }
}

Creation returns 201 plus a Location header

POST /api/v1/users Content-Type: application/json { "name": "tom" }

A successful POST must return 201 with a Location header so clients never have to parse the body to build the new URL. Returning 200 makes creation indistinguishable from a read in both caches and monitoring.

Output

HTTP/1.1 201 Created
Location: /api/v1/users/456

{ "data": { "id": 456, "name": "tom" } }

Cut whole-payload transfers with an ETag conditional request

GET /api/v1/users/123 If-None-Match: "a1b2c3d4"

The client echoes the ETag it last stored; when nothing changed the server replies 304 with no body. For polling-heavy mobile endpoints this drops traffic to zero and skips server-side serialization entirely.

Output

HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"

A fixed error envelope, with 401 and 403 kept distinct

GET /api/v1/users/999 Authorization: Bearer <expired-token>

401 means the credential is missing or expired and the client should re-authenticate; 403 means the credential is valid but under-scoped, and retrying is pointless. Collapsing both into 401 sends clients into an endless refresh loop.

Output

HTTP/1.1 403 Forbidden

{
  "error": {
    "code": "FORBIDDEN",
    "message": "token lacks scope users:read"
  }
}

Back off according to Retry-After after a rate limit

GET /api/v1/users # when the rate limiter trips: # HTTP/1.1 429 Too Many Requests # RateLimit-Limit: 100 # Retry-After: 30

A 429 without Retry-After forces clients to guess, which piles more load onto an already saturated service. Clients must treat 429 as a retryable state driven by the header, not as a terminal failure.

Common Pitfalls

  • Cursor pagination depends on a stable sort: new inserts or ties invalidate the next cursor, so clients stall on one page or silently drop rows. Prefer a composite (created_at, id) cursor with a consistent snapshot.
  • A POST without an Idempotency-Key or unique constraint turns a client retry after a timeout into a duplicate order or a second charge. Payment writes must be retry-safe; one network hiccup otherwise costs real money.
  • Local-time timestamps lose timezone and precision: clients in other zones read 2026-07-19T10:30:00Z as local time and daily reports stop reconciling. Store UTC RFC 3339 with Z or an offset, and keep money as integer minor units.
  • Passing the key as ?api_key= records it in browser history, proxy logs and the Referer header, which is effectively publishing the credential. Use the X-API-Key or Authorization header instead.
  • PUT is an idempotent full replace, so omitting fields wipes them. Clients wanting partial updates must use PATCH, or fields vanish without any error.
  • DELETE is idempotent, but delete-then-recreate makes the second DELETE return 404, which clients read as a failure. Return 204 or a receipt field when the client must confirm the outcome.

Tips

  • Use plural nouns for resource collections in URLs, e.g. /users not /user.
  • GET should be safe (no server state change); PUT/DELETE should be idempotent.
  • PUT fully replaces; PATCH partially updates — choose by scenario.
  • Put the API version in the URL path (/v1/) or a request header (Accept-Version).
  • Error responses should include a code, message, and optional details for easy client handling.

FAQ

How do I choose between 401, 403 and 404?

Split them by category: authentication, authorisation, or absence. 401 Unauthorized means authentication failed — no credentials, an invalid token, or an expired one; the client fix is to attach or refresh the credential. 403 Forbidden means authentication succeeded but the principal lacks permission, so retrying is pointless. 404 Not Found means the resource does not exist, and is also the deliberate choice when you want to hide existence rather than reveal "it exists but you may not have it". Their retry semantics differ completely: 401 is retryable with a new token, 403 is not, 404 means fix the URL or the id. A duplicate create is none of these — that is 409 Conflict.

Both PUT and PATCH change data — how should an API design choose between them?

It is about replacement semantics. PUT /users/123 is a full update: the complete representation must be supplied, and calling it twice leaves the same result (idempotent). PATCH /users/123 is a partial update: send only the fields you want changed and the rest stay as they are. DELETE /users/123 is likewise idempotent — repeating it changes nothing. GET must be safe and never alters server state, while POST /users creates and is non-idempotent. In practice PATCH is the easier client contract since only changed fields travel; PUT is friendlier to validation and caching but silently drops data if a field is omitted, which is why many teams define a full-required schema plus a partial-optional one.

For an invalid request, do I return 400 or 422?

Layer them by where the failure occurs. 400 Bad Request is for a malformed request: the JSON will not parse, the Content-Type is wrong, the syntax itself is broken and the server never got as far as reading the content. 422 Unprocessable Entity is for semantic failure — the structure parses but field validation fails: a bad email format, an out-of-range age, an illegal status value. The split matters to clients, because 400 means "do not resend this unchanged" while 422 means "fix the flagged fields and resend". Both should use the same envelope, { "error": { "code": "...", "message": "..." } }, so clients can branch on the code.

What should a create endpoint return, and how are batch writes and rate limiting handled?

A successful create should return 201 Created rather than 200, with a Location header pointing at the new resource URI; fetch it afterwards with HEAD /users/123 (headers only) or GET for the body. When there is nothing to return, use 204 No Content — the classic case being delete. Do not force batch creates or updates through an ordinary POST; use a sub-resource endpoint such as POST /users/batch. When throttling kicks in, return 429 Too Many Requests together with RateLimit-Limit for the quota and Retry-After for when to come back, so the client can back off. Including links to related resources (HATEOAS) lets clients discover the next action instead of hard-coding URLs.

How do I choose between the authentication options listed here?

Pick by client type and trust level. Authorization: Bearer <token> suits a separated front end and SPA; the token may be a JWT so the permissions travel with it. Authorization: Basic <base64> is only base64-encoded username and password — weak, fine for an intranet or quick debugging. X-API-Key: <key> uses a custom header and is the most common server-to-server credential; putting the key in ?api_key=<key> is convenient but leaks into access logs and Referer headers, so keep it out of production. Use OAuth 2.0 in the authorization code flow when third-party users must grant access. Pair these with Content-Type: application/json, Accept: application/json and If-None-Match: "etag" so an ETag hit serves from cache and saves bandwidth.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us