Conflict
URI: https://docs.ankatech.co/errors/conflict
HTTP Status: 409 Conflict
When you see this
The server understood your request but cannot complete it because it would conflict with the current state of the target resource.
Common Causes
This is the generic conflict type: it is what you receive when the server has nothing narrower to say about the collision. Several conditions that a 409 can express carry a type of their own and never reach this page — branch on type, never on the 409 status alone.
Conditions that answer this type:
-
Concurrent modification lost the race
Another writer changed the resource between your read and your write. Nothing was written; re-read the resource and retry with the version it returns. -
Key rotation lost a concurrent write
This key's material version changed after it was read; retry to advance from the current material version. On this arm the version moving is the whole of what the server observed, and the refusal names no cause. It is reached when another write changed the primary material the rotation read — a metadata update, a revocation or an expiry transition can each do it — or when the material version the rotation was about to write turned out to exist already. Another rotation of the same key is not one of these: rotations of one key are applied one at a time, and the second one is refusedmaterial-version-movedbecause the key is no longer at the version it advances from. Which outcome a concurrent write produces depends on timing, not on the kind of write, and neither this type norconcurrent-modificationidentifies the writer. A write that leaves the material primary lands here whenever it falls between the rotation's read and its write. A write that retires the material or replaces it with a newer one lands here only if it had written but not yet committed when the rotation wrote. If it had already committed when the rotation re-read the primary material, the outcome is different: a revocation or expiry leaves no primary material to rotate, so the request answers404, not409. A key that was already revoked when the request arrived answers404as well. -
A precondition the target resource depends on is absent
For example, no active deployment policy exists for a tenant policy to follow, or a role is still held by live users and cannot be deleted. Thedetailnames the specific obstruction. -
The upstream conflict carried no usable description
On an orchestrated key route, the responding service kept its own sentence because the internal key service answered409with a body that identified nothing at all. On this branch the generic slug means "a conflict, unclassified" rather than "a generic conflict" — see the409fall-through.
Conditions that are a 409 but not this type — follow the type you received:
| Condition | Type you receive |
|---|---|
| The identifier is already held by a key that still exists — in any lifecycle status | duplicate-kid — Duplicate Key Identifier |
| The identifier belonged to a key this tenant destroyed; a Key ID is never reused within its tenant | key-id-retired — Key ID Retired |
The key was already in a lifecycle state that forbids the operation: revoking an already-revoked key, or archiving a key that is still active. Rotating a revoked key is not one of these — it has no primary material to advance, so it answers 404 | invalid-key-state — Key State Conflict |
| A competing writer advanced the alias naming a key's current primary material version between your read and your write, or, on a key rotation, held the key longer than the rotation's lock-wait bound | concurrent-modification — Concurrent Modification Conflict |
| On a key rotation, the key is no longer at the material version the rotation advances from, because another rotation advanced it first | material-version-moved — Material Version Moved |
| The operation is not admitted by the referenced key's purpose — encrypting with a signature-only key, for example | key-purpose-incompatible — Key Purpose Incompatible |
| The operation is not supported by the referenced key's algorithm | key-algorithm-incompatible — Key Algorithm Incompatible |
| A federated login asserted an email address already owned by another account in the tenant | federated-email-conflict — Federated Email Already Owned, described below |
On the two key-import routes a conflict over existing key material — a kid whose stored public material does not match the one you submitted — is relayed from the internal key service, so the type you receive is that service's classification rather than this one. Read type before deciding what to retry.
Not this type: the federated just-in-time provisioning conflict
A federated login whose identity provider asserts an email address already owned by another account in the tenant is also a 409, but it does not carry this generic type. It carries its own type https://docs.ankatech.co/errors/federated-email-conflict, title Federated Email Already Owned, so that the authentication service can classify it on the type member alone and route it to the operator instead of reporting it as a platform defect.
Do not treat a 409 on the federated provisioning path as a duplicate-resource conflict, and do not branch on this generic type to detect it. The two are separated deliberately:
errors/conflictremains in service for every other caller and is unchanged.errors/federated-email-conflictis raised only on the internal federated provisioning call, whose sole caller is the authentication service. On the browser surface it is projected asaccess_not_configured; on a non-navigation surface a programmatic caller receives403 admission-denied. Both are deliberate — see Federated Login Errors.
Response Example
{
"type": "https://docs.ankatech.co/errors/conflict",
"title": "Resource Conflict",
"status": 409,
"detail": "Rotation conflict: this key's material version changed after it was read. Retry to advance from the current material version.",
"instance": "/api/v3/admin/tenants/2f1c9d84-6b2e-4d3a-9f57-0a1b2c3d4e5f/keys/my-mlkem-key/rotations",
"timestamp": 1751500800,
"extensions": {
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
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.
title is a short human label paired with type, and it is not the classifier. This one type is emitted with several titles depending on the condition — Resource Conflict on the key, role and policy routes, Concurrent Modification Conflict on the exchange transitions, and Data Conflict or Conflict elsewhere. Branch on type; show detail.
When the conflict is detected by an internal upstream service, the rejection is relayed: the upstream's type, title and detail are re-presented, so whenever the internal service produced a narrower classification than this one, that is what reaches you. The responding service falls back to its own sentence — under this generic type — only when the upstream 409 body carries nothing propagatable. A key-protection refusal is recognised ahead of both and keeps its own type and status. In every case instance and extensions.requestId are those of the service you called, and detail is a single sentence, never a nested document. The per-operation table is in S2S Relay Envelope.
How to Resolve
- Read
typefirst, thendetail.typetells you which conflict occurred;detaildescribes the specific instance. The steps below cover the409family, and several of them are reached through one of the narrower types listed above rather than through this one. - For a rotation that lost a concurrent write
- Re-read the key to obtain its current material version, then retry the rotation from the version it returns.
- Bound the retry. Nothing was written, so the rotation may be re-attempted — but this type does not establish that a lost race is what happened. The unclassified conflict in the next step arrives under the same
typeand the same title, so neither member separates the two, and on a rotation its cause may instead be a key-protection backend that is not bound, which no number of retries resolves. Retry a bounded number of times, then stop and surface the refusal. - A retry that answers
404is consistent with a revocation or an expiry that was in flight during the first attempt and has since committed, leaving no primary material to rotate. Re-read the key's status rather than retrying further.
- For an unclassified conflict on an orchestrated key route
- Treat this type as a conflict, cause possibly unidentified. The responding service kept its own sentence because the internal key service answered
409with a body that classified nothing, so on this branch the generic type reports the shape of the upstream body rather than the cause. - Do not branch on
detailto tell it apart from the step above.detailis a human sentence and its wording is not a contract member, so a caller that matches on it breaks when the sentence is reworded. - Re-read the key before each retry instead, and let its state decide. If the key is revoked, archived or absent, the conflict was not a lost race and retrying cannot resolve it. If the tenant's key-protection backend is not bound, that is the cause, and it is resolved by completing key-protection setup — never by retrying. See the
409fall-through.
- Treat this type as a conflict, cause possibly unidentified. The responding service kept its own sentence because the internal key service answered
- For duplicate or mismatched keys
- Choose a new, unique
kid, or - Use the same algorithm and public-key material as the existing entry.
- Choose a new, unique
- For logically-deleted keys
- Select a different
kid, or - Permanently remove the old entry before reusing the ID.
- Select a different
- For invalid lifecycle states
- Check the key’s status via
GET /api/v3/key-management/keys/{kid}/metadata(see the Developer Hub Reference) - Only perform operations allowed in that state (see Key Lifecycle in the API Reference).
- Check the key’s status via
- For disallowed operations
- Ensure the key’s type and algorithm support the operation you’re requesting (encrypt, decrypt, sign, verify).
For full details on key-management rules and operations, see the Developer Hub Reference.