Skip to content

Preconditions

Preconditions refer to the concept of a set of conditions that must be met before a particular operation (generally update of a resource) is performed.

For example, APIs often need to validate that a client and server agree on the current state of a resource before taking some kind of action on that resource. E.g., two processes updating the same resource in parallel could create a race condition, where the latter process “stomps over” the effort of the former one.

The ETag, If-Match, and If-None-Match headers provide a way to deal with this by allowing the server to send a checksum based on the current content of a resource; when the client sends that checksum back, the server can ensure that the checksums match before acting on the request.

When adding precondition checking to an API (ETag, If-Match, and If-None-Match headers), the behavior must match preconditions what is documented in RFC 9110.

If a server receives a conditional header it does not support, the service should return a 400 Bad Request response. A server should support all preconditions or none of them.

ETag (entity tag) is a response header.

A resource may provide an ETag header when retrieving a single resource when it is important to ensure that the client has an up-to-date resource before acting on certain requests:

200 OK
Content-type: application/json
ETag: "55cc0347-66fc-46c3-a26f-98a9a7d61d0e"

The ETag must be provided by the server on output, and values should conform to RFC 9110. Resources must support the If-Match header (and may support the If-None-Match header) if and only if resources provide the ETag.

ETags must be based on an opaque checksum or hash of the resource that guarantees it will change if the resource changes.

If-Match and If-None-Match are a request headers.

Services that provide ETags should support the If-Match and If-None-Match headers.

An example of using If-Match (the ETag returned from the request before is in the If-Match header):

GET /v1/publishers/{publisherId}/books/{bookId}
Accept: application/json
If-Match: "55cc0347-66fc-46c3-a26f-98a9a7d61d0e"

If the service receives a request to modify a resource that includes an If-Match header, the service must validate that the value matches the current ETag. If the If-Match header value does not match the ETag, the service must reply with 412 Precondition Failed.

If the user omits the If-Match header, the service should permit the request. However, services with strong consistency or parallelism requirements may require clients to send precondition headers all the time and reject the request with a 428 Precondition Required error if it does not contain an If-Match header.

If any conditional headers are supported for any operation within a service, the same conditional headers must be supported for all mutation methods (POST, PATCH, PUT, and DELETE) of any path that supports them, and should be supported uniformly for all operations across the service.

If any validator or conditional headers are supported for any operations in the service, the use of unsupported conditional headers must result in a 400 Bad Request error response. (In other words, once a service gives the client reason to believe it understands conditional headers, it must not ever ignore them.)

If a service receives a GET or HEAD request with an If-Match header, the service must proceed with the request if the ETag matches, or send a 412 Precondition Failed error if the ETag does not match.

If a service receives a GET or HEAD request with an If-None-Match header, the service must proceed with the request if the ETag does not match, or return a 304 Not Modified response if the ETag does match.

ETags can be either “strongly validated” or “weakly validated”:

  • A strongly validated ETag means that two resources bearing the same ETag are byte-for-byte identical.
  • A weakly validated ETag means that two resources bearing the same ETag are equivalent, but may differ in ways that the service does not consider to be important.

Resources may use either strong or weak ETags, as it sees fit, but should document the behavior. Additionally, weak ETags must have a W/ prefix as mandated by RFC 9110 Etag Comparison.

200 OK
Content-type: application/json
ETag: W/"55cc0347-66fc-46c3-a26f-98a9a7d61d0e"

Strong ETags must, and weak ETags should, be guaranteed to change if any properties on the resource change that are directly mutable by the client. Additionally, strong ETags should be guaranteed to change if the resource’s representation changes in a meaningful way (meaning the new representation is not equivalent to the old one).

Preconditions enable optimistic concurrency control, allowing multiple clients to work with the same resource without explicit locking.

When a service provides ETags, clients should use the If-Match header to prevent concurrent modification conflicts. The typical flow is:

  1. The client retrieves the resource and receives an ETag.
  2. The client includes the ETag value in the If-Match header when making a mutation request.
  3. The server validates that the ETag matches the current resource state.
  4. If the ETag has changed, the server must return 412 Precondition Failed, indicating another client has modified the resource.
  5. The client retrieves the updated resource, merges changes if needed, and retries the operation with the new ETag.

If no If-Match header is provided, the service should permit the request using last-write-wins semantics. However, services with strong consistency or parallelism requirements may require clients to always send ETags and reject requests without them using 400 Bad Request.

Example: Successful update with concurrency control

### Client retrieves the current resource
GET /books/123
ETag: "v1"
{
"id": "123",
"title": "Original Title",
"author": "Jane Doe"
}
### Client updates the resource with the ETag
PUT /books/123
If-Match: "v1"
Content-Type: application/json
{
"id": "123",
"title": "Updated Title",
"author": "Jane Doe"
}
### Server accepts the update
200 OK
ETag: "v2"
{
"id": "123",
"title": "Updated Title",
"author": "Jane Doe"
}

Example: Concurrent modification conflict

### Client A retrieves the resource
GET /books/123
ETag: "v1"
### Client B also retrieves the resource
GET /books/123
ETag: "v1"
### Client A successfully updates
PUT /books/123
If-Match: "v1"
...
200 OK
ETag: "v2"
### Client B attempts to update with stale ETag
PUT /books/123
If-Match: "v1"
Content-Type: application/json
{
"id": "123",
"title": "Different Title",
"author": "Jane Doe"
}
### Server rejects due to ETag mismatch
412 Precondition Failed
{
"message": "The resource has been modified by another client. Please retrieve the latest version and retry."
// ...rest of error...
}
  • HTTP Conditional Requests - Covers date-based conditional headers like If-Modified-Since and If-Unmodified-Since, which provide timestamp-based caching mechanisms complementary to ETag-based preconditions