Resource modelling and methods

Designing URLs around resources, mapping actions to HTTP methods, and the idempotency guarantees clients depend on.

Model nouns, not actions

REST is a style built on one idea: identify things with URLs, and use HTTP's methods to act on them. A resource is a noun — /orders/1042 — and the method carries the verb.

GET    /orders                 # the collection
POST   /orders                 # create one; server assigns the id
GET    /orders/1042            # a single order
PATCH  /orders/1042            # change part of it
DELETE /orders/1042            # remove it

GET    /orders/1042/items      # a sub-collection
PUT    /orders/1042/items/3    # replace one line item
MethodSafeIdempotentRequest bodyTypical response
GETYesYesNo200 with the representation
POSTNoNoYes201 with Location
PUTNoYesYes200 or 204
PATCHNoNo in generalYes200 with the updated resource
DELETENoYesOptional204, or 200 with a body
OPTIONSYesYesNo204 with Allow
💡
Idempotency is about server state, not about identical responses. Deleting the same order twice may return 204 then 404; the state after both calls is the same, so a client can safely retry.

URL design rules that age well

  • Plural nouns for collections, and an identifier for one member: /invoices/8842.
  • Never put a verb in the path — POST /orders/1042/cancel is a pragmatic exception worth documenting, not a default habit.
  • Use query parameters for filtering and searching a collection; they do not identify a resource.
  • Keep hierarchy shallow: /customers/7/orders/5/items/3 becomes unmanageable. Both ids are global enough to use two levels.
  • Prefer opaque identifiers (UUIDs) over sequential integers when exposing counts is a privacy risk.
  • Trailing slashes and case must be decided once — /Orders and /orders/ are different URLs to a cache.
# an action that does not fit CRUD: model it as a resource instead of a verb
POST /orders/1042/cancellation        # creates a cancellation
{ "reason": "customer_request" }

# if the client must not create duplicates on retry, use an idempotency key
POST /payments HTTP/1.1
Idempotency-Key: 8f14e45f-ea3b-4c2d-9f10-2c3d4e5f6a7b
Content-Type: application/json

{ "amount": 2500, "currency": "USD", "order_id": "1042" }

Representations and content negotiation

GET /orders/1042 HTTP/1.1
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
ETag: "7f2c9a"
Cache-Control: private, max-age=60

{ "id": "1042", "status": "shipped", "total": { "amount": 2500, "currency": "USD" } }
  • The resource is a concept; JSON and XML are representations of it. A well-behaved API can serve several.
  • Send Content-Type on every request with a body and Accept on every request that wants a specific format.
  • Return 406 Not Acceptable rather than silently ignoring an unsupported Accept value.
  • Emit ETag with the representation so clients can use conditional requests and optimistic concurrency.

FAQ

Is a verb in a URL always wrong?
No. Some operations are not CRUD — cancelling, publishing, retrying. Model them as a sub-resource when you can, and accept a verb when the alternative is contorted.
Should POST responses be cached?
Not by default. POST responses are not cacheable unless the response carries explicit freshness information, which is why returning the created resource in the body is the practical pattern.

Status codes, pagination and filtering HTTP methods

Last refreshed 2026-09-18.