Precondition Required
URI: https://docs.ankatech.co/errors/precondition-required
HTTP Status: 428 Precondition Required
When you see this
You sent a write against a resource that already carries a stored value, and your request stated no precondition — it did not say which version of that value you were updating. The server refused the write rather than apply it, because applying it would silently overwrite whatever is there, including a change somebody else made between your read and your write. Nothing was persisted.
Two preconditions answer with this type:
versiononPUT /api/v3/admin/platform/settings/{key}, when the request body carries noversionmember and the setting already has an override row. The first override of a key is unaffected — see The first override is not refused.endpointFingerprinton a credential-only write whose credential is presented to an endpoint another write names: the ANKA-managed tier credential, a notification provider credential (an SMTP relay, a Microsoft Graph, Twilio or Gmail account), the deployment key-protection credential, an identity provider edit that writes a secret and omitstrustAnchors, and thetsa_client_credentialsetting. The write must carry the fingerprint the caller last read, andextensions.fieldnamesendpointFingerprint. Read the destination and send its fingerprint back; a fingerprint of an endpoint that has since moved is Credential Endpoint Changed (409).
The sections below describe the version precondition.
The precondition travels in the body, not in If-Match
RFC 6585 §3 illustrates this status with the conditional-request header If-Match. This platform carries the precondition as the version member of the request body instead. The rationale the status exists for is identical — refuse the lost update that a precondition-less write would cause, and tell the client to supply a precondition — and nothing in RFC 6585 prescribes the carrier. The settings PUT reads no If-Match header, so sending one neither satisfies this refusal nor changes it.
Not this type: the 409 it splits from
A 409 Conflict on the same operation means something different, and the difference is the whole reason these are two statuses rather than one:
409— you did send aversion, and it did not hold. Three paths reach it: the version does not match the stored row, another administrator wrote it, or a concurrent writer won at flush time — and one more where nothing moved at all, a non-zero version sent against a key that has no stored override yet, which is a stale read with no row to match. Itsdetailis the fixed literal "Setting was modified by another administrator; reload and retry" on every one of them, so read it as "your precondition did not hold" rather than literally. Re-reading is the right remedy in each case, because a fresh read is what replaces the version you sent.428— you sent noversionat all. Nobody modified anything. Telling you to "reload and retry" would name a concurrent administrator who does not exist, and send you to re-read a row that has not moved. What is missing is the precondition, not a fresh read.
Do not branch on 409 to detect a missing precondition, and do not treat this 428 as a lost update. The generic Conflict page stays in service for every other caller and is unchanged.
The first override is not refused
428 covers existing override rows only. Creating the first override of a key is a write with nothing to lose, so a version of 0 — or no version member at all — is accepted there and remains the correct way to create one. The refusal appears only once the key carries a stored override.
Response Example
{
"type": "https://docs.ankatech.co/errors/precondition-required",
"title": "Precondition Required",
"status": 428,
"detail": "This setting already carries an override; send the version you last read",
"instance": "/api/v3/admin/platform/settings/PKCS11_TOKEN_LABEL",
"timestamp": 1758556800,
"extensions": {
"requestId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
}
Content-Type: application/problem+json — the response follows RFC 9457 Problem Details. The example above is an Admin API body, where the correlation identity is extensions.requestId; the Core API and the PQC Handshake API instead emit a top-level correlationId. Read the member your service emits — see Error Index & Overview.
The detail is a fixed literal. It echoes neither the value you sent nor the version currently stored, so the refusal discloses nothing beyond what your own write attempt already established. Read the current version from the resource, never from this body.
How to Resolve
RFC 6585 §3 requires this response to explain how to resubmit successfully. For the settings PUT that is three steps:
-
Read the setting.
The response body carries the current
versionalongside the effective value. -
Send the value back with that
version. -
If the retry answers
409, the row moved between your read and your write. That is the other refusal, and the remedy there really is to re-read and retry — see Conflict.
Client authors: send version on every settings write, not only when a previous attempt was refused. Reading it and sending it back is the entire contract, and a client that always does it never reaches this status.
Caching
RFC 6585 §3 requires that a 428 response not be stored by a cache. This is satisfied for the whole API rather than per handler: every Admin API response carries Cache-Control: no-store, no-cache, must-revalidate, private, written by a header writer registered on the security filter chain.