Skip to content
RungsySign in

Methods & Status Codes

GET/POST/PUT/PATCH/DELETE and what your API is really telling the caller with 201 vs 204 vs 202.

25 minDifficulty 1/5api · fundamentalsReviewedRead and accepted by a person.

Before this

Why this exists

Two APIs both "work". One returns 200 for everything with `{"error": null}` in the body. The other returns 201 with a Location header on create, 204 on delete, 409 on conflict. The second one can be consumed by a client that has never read its docs — and by caches, proxies and retry libraries that will never read them at all.

The mental model

The method is the verb, the path is the noun, and the status code is the one-word answer. "Delete the order." "Done, nothing to show you."

How it works

Two properties define every method

**Safe** means it doesn't change anything — GET and HEAD. **Idempotent** means doing it repeatedly ends in the same state as doing it once — GET, HEAD, PUT and DELETE. Everything safe is idempotent; the reverse isn't true, since DELETE changes state but repeats harmlessly. POST is neither. PATCH is neither by default, though you can write one that is. These two properties are the whole reason the methods differ.

The properties are promises other software relies on

This isn't documentation, it's a contract. Browsers prefetch GETs. Proxies cache them. HTTP client libraries retry idempotent methods automatically on connection failure. If your GET has side effects, something you don't control will trigger them — a link prefetcher, a security scanner, a retry after a flaky connection. The classic version of this bug is an admin panel with `GET /users/42/delete` links, discovered when a crawler works through the page.

PUT replaces, PATCH modifies

PUT carries the complete desired state of the resource: whatever you send is what it becomes, and fields you omit get cleared. That's what makes it idempotent — send it twice, same result. PATCH carries only a change. The distinction matters in practice: a client sending PUT with a partial object is silently deleting the fields it left out, which is one of the most common causes of mysterious data loss in an API.

Status codes are grouped by who owns the problem

**2xx** it worked. **3xx** look elsewhere. **4xx** the caller made a mistake, so retrying the same request won't help. **5xx** the server failed, so retrying might work. That last distinction is the one with operational teeth: a 5xx invites retries. Returning 500 for a validation error means clients hammer you with a request that can never succeed. The most-abused code is 200 — returning it with an error in the body defeats every generic client, every monitor, and every proxy.

The mechanism

The order matters. **401 before 403**: 401 means "I don't know who you are", 403 means "I know exactly who you are and you still can't". Sending 403 to an anonymous user tells them the resource exists. **404 vs 403** is a judgement call — returning 404 for a resource that exists but isn't yours hides its existence, which is often the right choice for private data. **409** is for a conflict with current state, like creating something that already exists.

flowchart TD
    A[Request arrives] --> B{Valid?}
    B -->|no| C[400 / 422]
    B -->|yes| D{Authenticated?}
    D -->|no| E[401]
    D -->|yes| F{Allowed?}
    F -->|no| G[403]
    F -->|yes| H{Exists?}
    H -->|no| I[404]
    H -->|yes| J{Conflict?}
    J -->|yes| K[409]
    J -->|no| L{Created something?}
    L -->|yes| M[201 + Location]
    L -->|no| N{Anything to return?}
    N -->|no| O[204]
    N -->|yes| P[200]
Diagram source for Methods & Status Codes.

What people get wrong

GET can't have side effects because it's read-only.
Nothing enforces it. The protocol declares GET safe; your code has to honour that. Safety is a promise you make to everything between you and the client. Break it and prefetchers, caches and crawlers will trigger your side effects.
Return 200 with an error object so the client can always parse the same shape.
Use the status code. Put detail in the body. Monitoring, retry logic, caches and load balancers all read the status code and none of them parse your body. A 200 error is invisible to all of them.
PUT and PATCH are interchangeable for updates.
PUT replaces the whole resource; omitted fields are cleared. PATCH applies a partial change. Sending a partial object via PUT silently wipes the fields you didn't include.
422 is just a fancier 400.
400 means the request was malformed — bad syntax, unparseable. 422 means it parsed fine but the values are semantically wrong. Broken JSON is a 400. Well-formed JSON with a negative quantity is a 422. The distinction tells the client whether to fix its serialisation or its data.

When not to use it

Your operation genuinely isn't CRUD — 'send this campaign', 'run this report'.
POST to an action-shaped endpoint. Contorting it into PUT for the sake of REST purity produces a worse API than an honest POST.
You need fine-grained error categories that HTTP codes don't distinguish.
The right status code plus a machine-readable error code in the body. Don't invent status codes; do add detail beneath them.

Terms

Safe
Doesn't modify state. GET and HEAD only.
Idempotent
Repeating it leaves the same end state. GET, HEAD, PUT, DELETE.
201 Created
A new resource now exists. Should carry a Location header pointing at it.
204 No Content
Success, and deliberately nothing in the body. Common for DELETE.
409 Conflict
The request clashes with the resource's current state — a duplicate create, or a lost-update conflict.
202 Accepted
Received and queued, not yet done. The honest answer when work happens asynchronously.

In an interview

What's the difference between PUT and PATCH?

  • PUT replaces the entire resource with the sent representation
  • omitted fields are cleared, which is why it's idempotent
  • PATCH applies a partial modification
  • sending a partial body via PUT causes silent data loss

A client gets a 500 from your API. What should it do, and how is that different from a 400?

  • 5xx means the server failed, so a retry with backoff may succeed
  • 4xx means the request itself is wrong, so retrying it unchanged is pointless
  • using 500 for validation errors causes clients to retry requests that can never succeed

Can you recall it?

What do 'safe' and 'idempotent' mean, and why does it matter to software other than your own?

Sources

Keep track of this

Add HTTP Semantics to your map and Rungsy will schedule reviews so you actually remember it.