Semantic difference
GET reads: parameters go in the URL query string, and it should be safe and idempotent, so repeated calls do not change server state. POST submits: data goes in the body and may have side effects (create an order, change data).
Engineering differences
| Aspect | GET | POST |
|---|---|---|
| Param location | URL query | Request body |
| Cacheable | Yes | No |
| Browser history | Retained | Body not stored |
| Length limit | URL bound | Essentially none |
| Idempotent | Should be | Not guaranteed |
Two misuses
- GET that mutates: crawlers or prefetch can accidentally trigger deletes or charges;
- POST for pure queries: you lose caching and shareable links; list filters belong in GET.
One rule
Read with GET, write with POST; prefer idempotent safe repeats as GET, and use POST for state-changing submits.
Real-world cases: the cost of picking wrong
- Deleting via GET: browsers prefetch links and crawlers follow them, so data can vanish before anyone clicks. Any operation with side effects must use POST / PUT / DELETE.
- Searching via POST: the results page cannot be bookmarked, shared or cached, and refreshing prompts "resubmit the form". Filters and pagination belong in a GET query string.
- Putting secrets in a GET: tokens or phone numbers end up in browser history, proxy logs and server logs. Put sensitive data in the body or headers.
FAQ
Is POST safer than GET? Not safer — just a different parameter location; both must run over HTTPS. Is there a length limit on GET? The spec sets none, but browsers and servers usually cut off somewhere between 2 and 8KB, so very long payloads belong in POST. What does idempotent mean? Repeating it yields the same result; GET / PUT / DELETE should be idempotent, POST generally is not, so retries need an idempotency key. What is a form's default method? GET when method is omitted — always switch to POST before submitting sensitive data.
Re-balancing security and cacheability
Method choice also affects two things people overlook:
- Sensitive parameters and sessions: even with POST, keep tokens out of query strings that get logged — put them in headers or the body and mask them in logs;
- Caching and idempotency: GET being cacheable by CDNs and browsers is both a benefit and a risk; a personalised GET without
Cache-Control: privatecan be cached and served to someone else; - Retry semantics: GET retries safely by itself; POST needs an idempotency key, or one network timeout produces two orders;
- Prefetch and crawlers: browsers and crawlers follow links on a page, so any side-effecting GET can be triggered for you.
Batch and partial updates
Bulk creation is POST on a collection, partial updates PATCH, full replacement PUT, removal DELETE. A common symptom of muddled semantics is POST doing every write, which prevents uniform handling — per-method authorisation and rate limiting become impossible. GraphQL and gRPC express this differently, but "reads are idempotent, writes need an idempotency key" still holds.
Consistency across the API surface
- One semantic per resource: if one endpoint takes filters in the query string, others should too — mixing them defeats intuition and makes caching and logging policies hard to apply uniformly.
- Verify idempotency: for endpoints declared idempotent, prove it with an automated test that repeats the call and checks the result, rather than a line in the docs.
- Logs that can reconstruct: record query conditions, request bodies and response status with sensitive fields masked, or you cannot reconstruct a disputed request.
- Rate limits by method: reads usually tolerate higher frequency while writes need stricter limits — align limits with method semantics to avoid throttling normal queries.
- Errors that point at the cause: a misused method should return a clear status and message suggesting the correct one, so callers fix it immediately.
Picking a method is easy; keeping it consistent across the whole API — in docs, tests and operations — is the real work.