Introduction to Conditional Requests
Conditional requests let a client attach preconditions to an operation so the server only acts when the resource is in an expected state. Defined by RFC 7232 (and carried into RFC 9110), they solve two distinct problems: revalidating cached representations without transferring a full body, and preventing the “lost update” scenario when multiple clients modify the same resource. For API designers they are not optional optimizations but foundational tools for efficiency and data integrity.
1. Validators: ETag and Last-Modified
Conditional requests depend on validators — server-generated metadata describing a resource’s current state.
ETag (Entity Tag)
An ETag is an opaque identifier for a specific version of a representation, typically a hash of the content or a version number. Its value must be enclosed in double quotes.
Last-Modified
A Last-Modified timestamp records when the origin server believes the representation last changed. It has one-second resolution, which limits precision for rapidly changing resources and generally makes it a weaker signal than a strong ETag.
Servers should send both headers for cacheable resources when they can, since some clients support only one.
2. Strong vs. Weak Validators
HTTP uses strong comparison by default.
- Strong validators change whenever the representation’s bytes change, guaranteeing byte-for-byte identity. They are required for concurrency control and range requests.
- Weak validators, prefixed with
W/, indicate semantic equivalence and may not change for trivial differences such as whitespace or reformatting. They are acceptable for cache revalidation where minor variations do not matter.
| Strong ETag | Weak ETag (W/) |
Last-Modified | |
|---|---|---|---|
| Guarantees | Byte-for-byte | Semantic equivalence | Timestamp only |
| Resolution | Exact (byte-for-byte) | Semantic (ignores trivial changes) | One second |
| Concurrency control | Yes | No | Weaker |
| Cache revalidation | Yes | Yes | Yes |
3. Cache Revalidation with 304 Not Modified
The most common use of conditional requests is revalidating a stale cached copy on a GET or HEAD.
The client stores validators from the first response and replays them on the next request:
GET /v1/leads/42 HTTP/1.1Host: api.apiguide.devIf-None-Match: "8a9f0c2b"If-Modified-Since: Wed, 25 Oct 2023 19:17:59 GMTIf the resource is unchanged, the server returns a body-less response:
HTTP/1.1 304 Not ModifiedETag: "8a9f0c2b"If-None-Match uses weak comparison and takes precedence over If-Modified-Since when both are present. A 304 Not Modified response carries no body, saving bandwidth and origin work. See the caching guide for how this fits into the broader freshness model.
4. Concurrency Control with If-Match and 412
For state-changing methods (PUT, PATCH, DELETE), conditional requests prevent one client from silently overwriting another’s changes.
The client submits the ETag of the version it last read in an If-Match header, which uses strong comparison:
PUT /v1/leads/42 HTTP/1.1Host: api.apiguide.devIf-Match: "8a9f0c2b"Content-Type: application/json
{ "status": "qualified" }If the current ETag still matches, the write proceeds. If another process already updated the resource, the ETag differs and the server rejects the request:
HTTP/1.1 412 Precondition FailedA 412 Precondition Failed tells the client its copy is stale; it should re-fetch and retry. Some APIs also return 428 Precondition Required when a mutating request omits If-Match entirely, forcing clients to opt into safe updates.
If-Match: * succeeds as long as any current representation exists — useful with PUT to prevent creating a resource that already exists.
5. Timestamp-Based Preconditions
Two headers mirror the ETag-based conditionals using dates instead of entity tags. All HTTP dates are expressed in GMT.
If-Modified-Since: for GET/HEAD. The server returns the body only if the resource changed after the given date, otherwise304. Ideal for revalidating a cached entity that has no ETag.If-Unmodified-Since: for mutations. The request proceeds only if the resource has not changed since the given date, failing with412otherwise. It is the timestamp equivalent ofIf-Match.
6. Precedence Rules
When several precondition headers appear in one request, RFC 7232 defines a fixed evaluation order:
If-Match— if present,If-Unmodified-Sinceis ignored.If-Unmodified-Since.If-None-Match— if present,If-Modified-Sinceis ignored.If-Modified-Since.
Because ETag-based conditionals take precedence over date-based ones, sending both a validator pair is safe: the stronger check wins.
7. Implementation Guidance
- Generate ETags deterministically. Hash a canonical serialization (for example, JSON with sorted keys) so identical content always yields the same tag. Storing precomputed ETags avoids regenerating a full representation just to answer a conditional GET.
- Support conditional GETs everywhere. Emit
ETagandLast-Modifiedon all cacheable GET/HEAD endpoints and honor the matching conditionals. GitHub’s REST API, for example, does not count304responses against the primary rate limit. - Require
If-Matchon mutations. For PUT, PATCH, and DELETE, treat a missing precondition as a risk of lost updates. - Prefer standard headers. Avoid inventing custom conditional headers for business logic; they undermine interoperability. See the conditional requests patterns alongside the HTTP caching specification for the authoritative rules.