jsonhttpapirestmethods

HTTP Methods and JSON: GET, POST, PUT, PATCH, DELETE, QUERY Explained

·10 min read·APIs

HTTP methods are a contract, not a suggestion

Every HTTP request declares an intent through its method: read, create, replace, partially update, or delete. JSON is how the data for that intent gets carried — in the request body, the response body, or both. Using the right method, with the right JSON shape, is what makes a REST API predictable to every client that calls it, from a browser fetch to a generated SDK.

This guide walks through GET, POST, PUT, PATCH and DELETE, exactly how JSON fits each one, and the two properties — safe and idempotent — that determine whether a client (or a proxy, or a retry policy) can call a method again without asking permission first.

Safe and idempotent, defined once

Two words come up constantly in this space and are worth pinning down before the examples:

  • Safe — the method doesn't change server state. A client (or a search engine crawler, or a CDN) can call it freely without side effects.
  • Idempotent — calling it once or a hundred times with the same input leaves the server in the same state as calling it once. This is what makes automatic retries safe.
json
{
  "GET":    { "safe": true,  "idempotent": true  },
  "PUT":    { "safe": false, "idempotent": true  },
  "DELETE": { "safe": false, "idempotent": true  },
  "PATCH":  { "safe": false, "idempotent": false },
  "POST":   { "safe": false, "idempotent": false }
}

Notice PATCH and POST are the two methods that are not idempotent by default — retrying them can create a second resource or apply the same partial update twice. That's exactly why idempotency keys exist for POST (see JSONKit's guide on idempotency in JSON APIs) — the HTTP spec doesn't give POST that safety for free.

GET — read, no request body

GET fetches a resource. It should never carry a JSON request body — some clients and proxies silently strip it, so query parameters are the correct place for filters, not a body.

http
GET /orders/ord_42
json
{
  "id": "ord_42",
  "status": "shipped",
  "total": 149.97,
  "items": [{ "productId": "PROD-42", "quantity": 1 }]
}

For filtering a list, use query parameters — never a body — because GET requests must remain safe and cacheable:

http
GET /orders?status=shipped&limit=20

POST — create, JSON body, not idempotent

POST creates a new resource (or triggers an action that doesn't map cleanly to a resource, like sending an email). The request carries a JSON body describing what to create; a successful response is 201 Created with the new resource, including its server-assigned id.

http
POST /orders
Content-Type: application/json

{ "items": [{ "productId": "PROD-42", "quantity": 1 }] }
json
201 Created
Location: /orders/ord_43

{ "id": "ord_43", "status": "pending", "items": [{ "productId": "PROD-42", "quantity": 1 }] }

Because POST is not idempotent, calling it twice — say, after a network timeout hides the first response — can create two orders. That's the exact problem an Idempotency-Key header solves: the server recognizes the retry and returns the original order instead of creating a duplicate.

PUT — full replace, JSON body, idempotent

PUT replaces a resource entirely with the JSON you send. The body must represent the *whole* resource — any field you omit is typically treated as absent or reset, not left alone. Calling the same PUT twice produces the same end state, which is what makes it idempotent.

http
PUT /orders/ord_43
Content-Type: application/json

{ "status": "shipped", "items": [{ "productId": "PROD-42", "quantity": 1 }], "trackingNumber": "DLVY99" }
json
200 OK

{ "id": "ord_43", "status": "shipped", "items": [{ "productId": "PROD-42", "quantity": 1 }], "trackingNumber": "DLVY99" }

If you PUT a body missing trackingNumber, a strict full-replace implementation drops it — which is exactly why PUT is the wrong tool for updating a single field on a large resource.

PATCH — partial update, JSON body, not idempotent by default

PATCH updates *part* of a resource — you send only the fields that change. Unlike PUT, PATCH is not guaranteed idempotent by the spec, though many APIs implement it idempotently in practice by treating each field as a "set to this value" instruction rather than a running change.

The simplest and most common convention is JSON Merge Patch (RFC 7396): send an object with only the changed keys, and null explicitly removes a field.

http
PATCH /orders/ord_43
Content-Type: application/merge-patch+json

{ "status": "delivered", "trackingNumber": null }
json
200 OK

{ "id": "ord_43", "status": "delivered", "items": [{ "productId": "PROD-42", "quantity": 1 }] }

Only status changed and trackingNumber was removed; every other field — including items, which wasn't mentioned — stayed exactly as it was. That "only touch what's mentioned" behavior is precisely what PUT does not give you.

For more surgical operations — insert into the middle of an array, test-then-set, move a value — JSON Patch (RFC 6902) sends an array of explicit operations instead of a merged object:

json
[
  { "op": "replace", "path": "/status", "value": "delivered" },
  { "op": "remove",  "path": "/trackingNumber" }
]

DELETE — remove, usually no body, idempotent

DELETE removes a resource. It typically carries no request body, and a successful response is either 204 No Content (nothing to return) or 200 OK with a confirmation body. It's idempotent because deleting an already-deleted resource has the same end state as deleting it once — most APIs return 204 (or 404) either way, rather than erroring on the second call.

http
DELETE /orders/ord_43
json
204 No Content

Side-by-side

MethodRequest bodyIdempotentSafeTypical success
GETNoneYesYes200 OK
POSTFull resource to createNoNo201 Created
PUTFull resource (replace)YesNo200 OK
PATCHPartial change (merge patch or ops)Not guaranteedNo200 OK
DELETEUsually noneYesNo204 No Content

A newer method for safe queries with a body: HTTP QUERY

Earlier in this guide, GET's "no request body" rule came up as a real limitation: some reads genuinely need a complex filter — a GraphQL-style query, a large list of IDs, a multi-field search — that doesn't fit cleanly into a query string. Historically, teams worked around this by abusing POST for what is really a read, which throws away GET's safety and cacheability.

QUERY is a newer HTTP method, standardized through the IETF (draft-ietf-httpbis-safe-method-w-body), created specifically to close this gap: a method that is safe like GET — it doesn't change server state, and it's cacheable in principle — but that's explicitly allowed to carry a JSON request body, the way POST does.

http
QUERY /orders
Content-Type: application/json
Accept: application/json

{ "filter": { "status": "shipped", "total": { "gte": 100 } }, "sort": "-createdAt", "limit": 20 }
json
200 OK

{ "data": [ { "id": "ord_43", "status": "shipped", "total": 149.97 } ], "hasMore": false }

The response body and status codes work exactly like a GET response — this is a read, not a write.

Where it fits, and its current status

QUERY is aimed squarely at search-heavy and filter-heavy endpoints: complex list filters, GraphQL-like read queries, and any "read with a large or structured input" case that today gets forced onto POST. As of this writing it's still a draft standard — support is landing incrementally across HTTP servers, frameworks and proxies rather than being universally available, so check that your specific stack (server framework, load balancer, API gateway, client library) actually understands the QUERY method before relying on it in production. Until then, the practical options remain: GET with query parameters for simple filters, or POST for anything that needs a body but doesn't have official read semantics yet.

Choosing PUT vs PATCH in practice

Use PUT when the client naturally has the whole resource already — a settings form that always submits every field, or a client that fetched the full object, edited it, and is writing it back. Use PATCH when the client only knows about one field it wants to change — toggling a single boolean, updating a status — without needing to fetch and resend everything else first. Sending a PUT with a partial body to an API that does a true full-replace is a classic bug: fields you didn't include quietly disappear.

Where JSON Schema fits

Whatever method is in play, the JSON body is exactly what a JSON Schema describes and a validator like JSONKit's JSON Schema Validator checks: required fields for a POST body, the full resource shape for a PUT, and — with PATCH — a schema where every property is optional, since any subset can legitimately be sent. Modeling PATCH bodies with JSON Schema is worth doing deliberately: mark nothing as required, and note that null on merge-patch payloads means "delete this field," not "invalid value."

Frequently asked questions

Technically HTTP doesn't forbid it, but it's discouraged and unreliable — many HTTP libraries, proxies and caches ignore or strip a GET body. Use query parameters for anything GET needs to filter or shape a read.

Not by guarantee, but it can be in practice. If every operation in the patch is a "set to value" (as in JSON Merge Patch), applying it twice yields the same result — idempotent by implementation. If an operation is relative (like "increment by 1"), it isn't.

Because PUT means "replace the whole resource with what I'm sending." If your API implements PUT as a true full-replace, omitted fields are cleared or defaulted — that's expected, not a bug. Switch to PATCH if you only want to change specific fields.

Conventions vary, but two are common: 204 No Content (treat "already deleted" as success, matching DELETE's idempotent contract) or 404 Not Found. Either is defensible — document which one your API follows.

QUERY is a proposed HTTP method, still moving through IETF standardization, designed to be safe and cacheable like GET but able to carry a JSON request body like POST — meant for complex reads (rich filters, search, GraphQL-style queries) that don't fit in a query string.

Only after confirming your server framework, gateway and any proxies in between actually support it — as a draft standard, support is still rolling out unevenly across the ecosystem. Until it's reliably supported everywhere you deploy, GET with query parameters or a documented POST-as-read endpoint remain the safer defaults.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.