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.
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.