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| Method | Safe | Idempotent | Request body | Typical response |
|---|---|---|---|---|
GET | Yes | Yes | No | 200 with the representation |
POST | No | No | Yes | 201 with Location |
PUT | No | Yes | Yes | 200 or 204 |
PATCH | No | No in general | Yes | 200 with the updated resource |
DELETE | No | Yes | Optional | 204, or 200 with a body |
OPTIONS | Yes | Yes | No | 204 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/cancelis 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/3becomes 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 —
/Ordersand/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-Typeon every request with a body andAccepton every request that wants a specific format. - Return
406 Not Acceptablerather than silently ignoring an unsupportedAcceptvalue. - Emit
ETagwith 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.
Related
Status codes, pagination and filtering HTTP methods
Last refreshed 2026-09-18.