← APIS

REST API design rules for URLs, methods and fields

Rules for REST API resource naming, HTTP methods, idempotency, field casing and datetime formats, plus the style guide that keeps every endpoint consistent.

On this page
  1. Resource naming
  2. Sub-resources
  3. HTTP methods
  4. Idempotency and safe retries
  5. Field naming
  6. Dates and times
  7. Writing it down as a style guide

These are the rules for the request side of a REST API. They cover what the URLs look like, which HTTP method does what, how fields are named and how dates are written. The response side is covered in REST API response shape, pagination and errors.

Resource naming

Name resources with plural nouns. The collection lives at the plural path and a single resource sits under it with its identifier:

GET  /products        # list all products
GET  /products/42     # retrieve product 42
POST /products        # create a new product

Use lowercase throughout. Most servers treat URLs as case-sensitive, so mixed case leads to routing bugs. Separate words with hyphens, which are easier to read in a URL than camelCase or underscores:

# Correct
/shipping-addresses

# Avoid
/shippingAddresses
/shipping_addresses

Sub-resources

When one resource belongs to another, nest it so the URL shows who owns it:

GET  /orders/42/items      # list all items in order 42
GET  /orders/42/items/5    # retrieve item 5 within order 42
POST /orders/42/items      # add an item to order 42

Keep nesting to two levels at most. If you end up writing /a/1/b/2/c/3, give the deepest resource its own top-level endpoint, such as GET /items/5.

HTTP methods

The HTTP method says what the request does, so the URL only needs to name the resource.

MethodPurposeExample
GETRetrieve a resource or collectionGET /users, GET /users/42
POSTCreate a new resourcePOST /users
PUTReplace an entire resourcePUT /users/42
PATCHPartially update a resourcePATCH /users/42
DELETERemove a resourceDELETE /users/42

GET must never change server state. Use it to fetch data at any level of the resource tree.

POST creates a resource and the server picks its identifier. Return 201 Created with a Location header pointing at the new resource.

PUT replaces the whole resource. The client sends the full payload, and any field it leaves out is cleared or reset to its default.

PATCH changes only the fields in the request body. Use it when a client wants to change one attribute without fetching and resending the whole resource.

DELETE removes the resource. A repeat call to the same URL returns 404 once the resource is gone, or 204 if the server treats it as already deleted.

Keep verbs out of URLs. They repeat what the method already says:

# Correct
GET    /users
POST   /orders
DELETE /users/42

# Incorrect
GET    /getUsers
POST   /createOrder
POST   /orders/submit
DELETE /deleteUser/42

Idempotency and safe retries

A request can time out or lose its response even when the server did the work. An idempotent request can be retried without causing the action twice.

  • GET, HEAD and OPTIONS do not change state, so repeating them is always safe.
  • PUT sends the full resource, so sending it twice gives the same end state.
  • DELETE removes the resource once. The second call returns 404, but nothing extra happens.
  • PATCH is idempotent only if you build it that way. Applying the same partial update twice should give the same result as applying it once.

POST creates a new resource each time. Where a duplicate would do harm, such as a payment, an order or a new account, accept an Idempotency-Key header:

POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
    "amount": 2500,
    "currency": "GBP",
    "customer_id": 42
}

The server stores the key with the result of the first successful request. If the same key arrives again, it returns that stored response and does not run the operation again.

  • The client generates the key, usually a UUID. It needs to know the key before the request completes so it can retry with the same value.
  • Keys do not need to live forever. A 24-hour window covers most retries.
  • Document which endpoints accept a key and what a duplicate submission returns.

Field naming

Pick one casing for field names and use it everywhere. snake_case is the most common choice for JSON APIs, especially in Rails, Laravel and Python. It also maps directly to database column names:

{
    "id": 1,
    "first_name": "Jane",
    "last_name": "Smith",
    "email_address": "[email protected]",
    "created_at": "2026-01-15T10:30:00Z"
}

An API that has firstName in one endpoint, last_name in another and EmailAddress in a third makes client developers check the documentation for every field. The same casing applies in three places:

  • JSON response body keys.
  • Query parameters, so ?sort=created_at, not ?sort=createdAt.
  • The field names in validation error details, which must match the request and response payloads.

If you serve clients that strongly prefer camelCase, such as JavaScript front ends, you can offer a header or parameter that switches the response casing. Make that a documented option, applied to the whole response.

Dates and times

Send and accept every datetime in ISO 8601, in UTC, with a Z suffix:

2026-08-16T12:34:56Z
2026-01-01T00:00:00Z
  • Store and transmit in UTC. The client converts to the user's local timezone for display, so conversion happens once, at the edge.
  • Always include seconds. Add milliseconds only when you need sub-second precision: 2026-08-16T12:34:56.123Z.
  • Avoid the +0000 offset. PHP's DATE_ISO8601 constant produces 2026-08-16T12:34:56+0000, and the missing colon breaks strict ISO 8601 parsers. Use Z.
  • Convert offsets in query parameters to UTC before you query. If a client sends ?after=2026-08-16T12:00:00+01:00, normalise it and do not store the raw offset.

UTC timestamps in this format sort correctly as strings, so string order and time order are the same. Rows stored with different offsets, such as +01:00 and +05:30, can sort in the wrong order.

Writing it down as a style guide

Agree these decisions before the first endpoint and write them down:

AreaDecision
Resource namingPlural nouns, hyphens between words, lowercase
Field casingOne casing, applied everywhere
Pagination parametersThe same names on every list endpoint, with documented defaults and maximums
Response envelopeOne JSON shape for data, meta and links
Error envelopeOne JSON shape for all errors, including validation details
Datetime formatUTC ISO 8601 with a trailing Z
AuthenticationBearer tokens in the Authorization header
VersioningURL path versions (/v1/, /v2/) with a documented deprecation process

The envelopes and pagination are covered in REST API response shape, pagination and errors. Authentication is in REST API security, authentication and rate limiting, and versioning in Running a REST API: caching, versioning, deprecation and release.

Use the guide as a checklist when reviewing a new endpoint. Automate what you can: linters, tests that check response shapes against your OpenAPI spec, and CI checks that flag drift catch it before clients build on the wrong shape.