Your API currently accepts unconditional PUT requests, and you want every write to a sensitive resource to carry a precondition, rejecting the ones that do not with HTTP 428 Precondition Required. How would you roll that out across existing clients?
answer
- Support is additive; requiring it is breaking
- ETag on every read path first
- Warn phase with per-client attribution
- 428 body must name the header and the read endpoint
- PUT-as-create needs If-None-Match: *, not If-Match
basics
~20 sDecide which resources genuinely need it, ship the ETag on reads first, then run a warn phase where unconditional writes are accepted but counted per client. Once that count hits zero for the resources you are enforcing, flip them to 428 behind a flag with a documented, actionable error.
solid answer
~50 sEnforcement is a migration, not a switch. 1. **Scope it.** Mandate preconditions only where concurrent edits actually cause harm — mutable shared aggregates. Do not blanket-require it on writes that are already idempotent overwrites of caller-owned data. 2. **Publish validators first.** Every read path must return a stable `ETag` before any client can comply. 3. **Warn phase.** Accept unconditional writes but emit a metric labelled by client identity and resource, plus a deprecation signal, so you can see exactly who would break. 4. **Chase the tail.** Contact the remaining callers; the metric tells you when it is safe. 5. **Enforce.** Return **428** with a typed problem body naming the required header and linking the docs, per resource, behind a flag you can roll back in minutes. And only enforce what you can support: 428 tells the client to retry conditionally, so the error must be actionable, not just a refusal.
go deeper
Know that 428 means the server refuses to guess and wants a conditional request, and that clients must read the resource first to obtain a validator.
Describe the ordering — publish ETags, warn, then enforce — and why the error body must name the required header.
Own the migration: per-client attribution metrics, per-resource flags, deprecation signalling, and the PUT-as-create and header-stripping traps.
Set the policy for which resource classes require preconditions across the platform and weigh the added client burden against the data-loss risk it removes.
## Why enforcement is a separate decision from support Supporting `If-Match` is additive: clients that send it get protection, clients that do not are unaffected. *Requiring* it is a breaking change — every existing caller that writes unconditionally starts failing. So the design question splits in two: which resources justify the break, and how you get there without an outage. ## Which resources justify it Require preconditions where a blind overwrite causes real harm: multi-editor aggregates, state machines where a transition depends on current state, money and permission objects. Do not require them where the caller owns the data outright and a full replace is the intent (a user updating their own profile from one device), or on internal writers you control that already serialize by design. Over-mandating adds a round trip and a failure mode to every write for no gain, and teams route around it. ## The rollout **Publish validators first.** A client cannot comply before every read path returns a stable `ETag` — including list endpoints, if callers write objects they discovered through a collection. Any read that omits it becomes a dead end. **Warn phase with a metric.** Keep accepting unconditional writes, but count them by resource type and by client identity (API key, service name, user agent). This is the whole rollout: without per-client attribution you are guessing about blast radius. Add a deprecation signal on those responses — a `Deprecation`/`Sunset` header or a documented warning field — and announce a date. **Chase the tail.** The metric turns "is it safe yet?" into a fact. Usually a few forgotten integrations dominate the count; contact them directly rather than waiting. **Enforce per resource, behind a flag.** Flip one resource type at a time with fast rollback. Bulk-flipping everything is how you discover the batch job that runs monthly. ## Designing the 428 itself 428 exists precisely so a server can refuse an unconditional write instead of risking a lost update. The response must therefore be *instructive*: a problem document naming the header to send (`If-Match`), the endpoint to read the validator from, and a stable machine code. A bare 428 with an empty body sends the integrator to your support channel. Also decide the wildcard question. `If-Match: *` means "as long as the resource exists" — it prevents accidental creation but not lost updates. If you accept it as satisfying the requirement, say so; many APIs accept it for `DELETE`, where existence is the only real concern, and reject it for `PUT`. ## Traps worth naming - **Intermediaries strip headers.** Some proxies, SDKs and low-code connectors drop unknown request headers. Verify the header survives the path your clients actually use before enforcing. - **Creation via PUT.** If `PUT` also creates, an unconditional create is legitimate; the correct precondition there is `If-None-Match: *`, not `If-Match`. Enforcing `If-Match` blindly makes creation impossible. - **Non-browser callers.** Scripts and CLI users write ad hoc. Give them a documented two-step (`GET`, extract the `ETag`, send it) so the requirement is workable by hand. - **Retry semantics.** Clients must understand a 428 is fixed by reading first, not by retrying the same request; document it so nobody puts it in a blind retry loop. ## How you know it worked After enforcement the unconditional-write counter is zero and the 412 counter is non-zero but small. A suddenly large 412 rate means either a genuinely hot resource (revisit granularity) or a client refreshing its token without merging.
- A caller uses PUT to create a resource at a client-chosen URL. How does mandatory If-Match affect them, and what should they send instead?They cannot comply, because there is no existing version to reference, so enforcing If-Match would make creation impossible. The correct precondition for create-if-absent is If-None-Match: *, which succeeds only when the resource does not yet exist and guards against clobbering an existing one. The enforcement rule must accept either header depending on intent.
- How do you know when it is safe to switch from warning to enforcing?By the metric counting unconditional writes broken down by client identity and resource. When it reaches zero for a given resource and stays there across a full business cycle — including weekly and monthly batch jobs — that resource is safe to flip. Flip one resource at a time behind a flag so a missed caller is a rollback, not an incident.
saying these in an interview costs you the question
- Flipping every endpoint to 428 in one release without a warn phase or per-client metrics
- Returning 428 with no body, so integrators cannot tell which header is missing
- Enforcing If-Match on a PUT that is also used to create resources
- Assuming every client and proxy passes custom request headers through untouched
- Mandating preconditions on every write regardless of whether concurrent edits are possible