Duplicate KID
URI: https://docs.ankatech.co/errors/duplicate-kid
HTTP Status: 409 Conflict
When you see this
You tried to create, import, or register a key under a key identifier (kid) that is already held by a key that still exists. Key identifiers are unique per tenant, and the occupancy question is asked of the identifier itself — not of the key's usability. A key that is suspended, retired, revoked, archived, pending destruction, or left with no usable material still holds its identifier, and so still produces this refusal.
A key that still exists answers this problem type in any status. A key that has been destroyed does not free its identifier either: a Key ID is never reused within its tenant, and a create under a destroyed key's identifier answers key-id-retired instead. Both are 409; branch on the type.
Common Causes
- Generating a new key with a
kidalready in use. - Importing a keystore whose alias maps to an existing
kid. - Re-running a provisioning script that creates the same
kidtwice. - Re-creating a
kidwhose key was suspended, retired, revoked, or archived — none of those release the identifier.
Response Example
{
"type": "https://docs.ankatech.co/errors/duplicate-kid",
"title": "Duplicate Key Identifier",
"status": 409,
"detail": "A key already exists under the identifier \"my-mlkem-key\" for this tenant.",
"instance": "/api/v3/key-management/keys",
"correlationId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": 1730000000
}
Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.
Which route answers with this type
This type originates in the Core API key plane, where key creation and keystore import detect the collision. Those routes are internal and issuer-gated: they are absent from the published OpenAPI document, carry no generated SDK or CLI method, and are not callable by a client. You reach that behaviour through the Admin API routes below, which relay the condition unchanged.
The Admin API routes that front them — POST /api/v3/admin/tenants/{tenantId}/keys, the route the Admin API key-management reference tells you to call, and the two console-facing key/keystore imports — refuse the same condition with the same 409 and the same type. Admin API relays Core API's type, title and detail under the relay contract, substituting only instance and its own correlation member. You can branch on type on either plane.*
Branch on the type
Admin API answers this conflict with duplicate-kid, not with the generic …/errors/conflict / Resource Conflict. Code that branches on the 409 status alone also works, because the status is the same.
The detail names the identifier you submitted and states that it is in use for this tenant. It states nothing further — not the status of the key holding it, not its material, not when it was created.
How to Resolve
- Choose a different, unique
kidfor the new key. - If you intended to replace the key material, use the rotation endpoint instead of creating a new key under the same
kid— rotation adds a new material version under the identifier you already have. - List existing keys (
GET /api/v3/key-management/keys) to confirm which identifiers are already taken, then re-submit. - Destroying the key that holds the identifier does not give it back: a destroyed key's identifier is retired, and a create under it answers
key-id-retired. Choose a new identifier instead.
For full schema definitions, examples, and interactive testing, see the Developer Hub Reference.