REST API conditional requests let a client ask the server to send or change a resource only when a stated condition is true. They reduce unnecessary response bodies, improve cache validation, and prevent one client from silently overwriting another client's newer update.
The server supplies a validator such as an ETag or Last-Modified value. The client returns that validator in a conditional header on its next request. The server then compares the value with the current representation before choosing a response.
Understand Conditional Request Headers
| Header | Role | Typical result |
|---|---|---|
| ETag | Identifies a specific representation version. | Sent with a successful response. |
| Last-Modified | States when the resource last changed. | Sent with a successful response. |
| If-None-Match | Continues only when no listed tag matches. | 304 for cached GET requests. |
| If-Match | Continues only when a listed strong tag matches. | 412 when an update is stale. |
| If-Modified-Since | Checks whether a resource changed after a date. | 304 when it did not change. |
| If-Unmodified-Since | Continues only when a resource has not changed after a date. | 412 when it changed. |
| If-Range | Requests a range only while a validator still matches. | 206 or a complete 200 response. |
Validate a Cached GET with an ETag
On the first request, the API returns the representation and a quoted entity tag. Treat the tag as an opaque server value; the client does not need to understand how the server generated it.
Example:
GET /api/articles/42 HTTP/1.1
Host: api.example.com
Accept: application/json
# The first request has no cached validator.
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=0, must-revalidate
ETag: "article-42-v7"
{"id":42,"title":"Conditional Requests","version":7}
The client stores both the response and the ETag. When it needs the resource again, it sends the tag through If-None-Match.
Example:
GET /api/articles/42 HTTP/1.1
Host: api.example.com
Accept: application/json
If-None-Match: "article-42-v7"
# Ask for a body only when the representation changed.
If the current tag still matches, the server returns 304 Not Modified without a message body. The client reuses its stored representation.
Response:
HTTP/1.1 304 Not Modified
ETag: "article-42-v7"
Cache-Control: private, max-age=0, must-revalidate
# A 304 response does not contain the resource body.
When a request contains both If-None-Match and If-Modified-Since, HTTP gives If-None-Match precedence. Prefer ETags when you can generate them reliably.
Prevent Lost Updates with If-Match
Conditional requests also protect writes. Suppose Priya and Oliver read version 7 of the same article. Priya saves first, so the server moves the resource to version 8. Oliver must not overwrite Priya's work using the stale version 7 data.
Send the previously received strong ETag in If-Match with the update request.
Example:
PUT /api/articles/42 HTTP/1.1
Host: api.example.com
Content-Type: application/json
If-Match: "article-42-v7"
{"title":"Safer REST API Updates"}
Because the current server tag is now version 8, the condition fails. The server returns 412 Precondition Failed and leaves the resource unchanged.
Response:
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json
ETag: "article-42-v8"
{"title":"Stale resource version","status":412}
The client can fetch the latest representation, show the conflict to the user, merge changes where appropriate, and retry with the new ETag.
Create a Resource Only When It Is Missing
Use If-None-Match: * when a PUT request should create a resource only if no current representation exists. This atomic check avoids a separate check-then-create race.
Example:
PUT /api/usernames/anaya HTTP/1.1
Host: api.example.com
Content-Type: application/json
If-None-Match: *
{"displayName":"Anaya Shah"}
The server creates the resource when the target is missing. If it already exists, the server responds with 412 Precondition Failed instead of replacing it.
Choose Strong or Weak ETags
A strong ETag identifies a byte-for-byte equivalent representation and appears as a quoted value such as "v8". A weak tag begins with W/ and indicates semantic equivalence even when the bytes are not identical.
- Use strong ETags for
If-Match, write protection, and range requests. - Use weak ETags for cache validation when minor representation differences do not matter.
- Generate a different validator whenever the selected representation changes.
- Keep quotation marks because entity-tag syntax requires them.
Example:
ETag: "invoice-91-v3"
ETag: W/"news-feed-2026-09-01"
# The W/ prefix marks the second validator as weak.
Use Last-Modified When Dates Are Sufficient
Last-Modified and date-based conditions are convenient when your data already has a reliable update timestamp. However, HTTP dates have one-second precision and may not distinguish rapid changes. ETags can represent versions more precisely.
Example:
GET /api/guides/15 HTTP/1.1
Host: api.example.com
If-Modified-Since: Tue, 01 Sep 2026 08:30:00 GMT
# The server returns 304 when the guide has not changed.
Implement Conditional Requests Correctly
- Generate a validator from the selected representation or its version.
- Return ETag or Last-Modified with successful GET and HEAD responses.
- Evaluate request preconditions before running the method when HTTP requires it.
- Return 304 for a matching cache-validation GET or HEAD request.
- Return 412 when a write precondition fails.
- Send a new validator after every successful change.
Example:
// Create a strong ETag from the stored version number.
const etag = `"article-${article.id}-v${article.version}"`;
// Reject a stale update before changing the database.
if (request.headers["if-match"] !== etag) {
response.status(412).set("ETag", etag).end();
return;
}
// Apply the update and return the validator for the new version.
const updated = await updateArticle(article, request.body);
response.set("ETag", `"article-${updated.id}-v${updated.version}"`);
response.json(updated);
This simplified example accepts one exact tag. Production code must parse header syntax correctly, handle lists and the wildcard, follow method-specific precedence rules, and evaluate strong or weak comparisons as defined by HTTP.
Conditional Request Best Practices
- Evaluate validators for the exact representation, including content encoding and negotiated format.
- Combine validators with suitable Cache-Control and Vary headers.
- Do not use predictable user-specific ETags as a tracking mechanism.
- Return no representation body with a 304 response.
- Use 412 for a failed precondition, not as a general validation error.
- Test concurrent updates so stale clients cannot replace newer data.
Conclusion
REST API conditional requests make reads more efficient and writes safer. Use ETag with If-None-Match to validate cached responses, and use a strong ETag with If-Match to reject stale updates. Add If-None-Match with a wildcard when creation must not overwrite an existing resource. Correct validators and status codes give clients a clear, standards-based way to handle caching and concurrency.