{ }HttpStatus.com

428 Precondition Required

The origin server requires the request to be conditional, typically to prevent a lost update from concurrent edits.

Defined in RFC 6585 §3

What 428 means

RFC 6585 defines 428 to address the "lost update" problem: two clients read the same resource, both edit it locally, and whichever writes second silently overwrites the first client's changes with no error at all. A server can require every write to be conditional, using headers like If-Match or If-Unmodified-Since, and reject any write that omits them with 428, forcing clients into a safe read-then-write pattern.

The typical flow is optimistic concurrency control: a client first performs a GET and records the resource's current ETag, then sends a subsequent PUT, PATCH, or DELETE with If-Match set to that ETag. If the header is missing entirely, the server returns 428 rather than guessing whether the client is working from stale data. If the header is present but the ETag no longer matches, the server returns 412 Precondition Failed instead.

That distinction is the key thing to remember: 428 means "you sent no precondition at all," while 412 means "you sent one, but it does not match the current state." Together they let an API implement safe concurrent editing without resorting to pessimistic, lock-based writes.

Common causes

  • A client sends a PUT, PATCH, or DELETE without an If-Match or If-Unmodified-Since header on a resource that requires conditional writes.
  • The API enforces optimistic concurrency control and the client skipped the required get-then-conditional-write flow.
  • A caching proxy or SDK strips conditional headers before forwarding the request to the origin.
  • Client code was written against an older version of the API that did not yet require preconditions.

How to fix a 428

If you are the client (browser user or API caller)

  • GET the resource first, capture its current ETag or Last-Modified value, then resend the write with If-Match or If-Unmodified-Since set.
  • Update the SDK or HTTP client so it automatically attaches conditional headers on every mutating request.
  • Verify that intermediate proxies or gateways are not stripping conditional headers before they reach the origin.

If you run the server

  • Document clearly which endpoints require conditional requests and which headers they accept.
  • Return the resource's current ETag or Last-Modified value in the 428 response so the client can retry immediately without an extra GET.
  • Apply 428 only to genuinely concurrency-sensitive endpoints, to avoid adding unnecessary friction to read-mostly resources.

Example

PUT /documents/42 HTTP/1.1
Host: api.example.com
Content-Type: application/json

{"title": "Updated title"}

HTTP/1.1 428 Precondition Required
Content-Type: application/json

{"error": "this resource requires an If-Match header", "etag": "\"a1b2c3\""}
The write omitted If-Match, so the server refuses it and returns the current ETag to retry with.

Try it live

Our free status responder returns a real HTTP 428 you can point tests, monitors or a browser at.

GET https://mcp.httpstatus.com/status/428

Related status codes

Tools for debugging this