Key Operations Narrowing Forbidden
URI: https://docs.ankatech.co/errors/key-ops-narrowing-forbidden
HTTP Status: 422 Unprocessable Entity
When you see this
A key rotation supplied an explicit key_ops set that drops an operation the current PRIMARY material permits. Within a key's fixed purpose family a rotation may only preserve or widen its operation set — never narrow it.
The gate is mandatory and not configurable. It runs after the purpose gate and the structural gate, and before any new key material is generated, so a refused rotation leaves the key exactly as it was.
Reported as a 422, not a 500
The refusal reaches the handler that owns it, so a rotation that would narrow keyOps receives this 422, never a generic 500.
Common Causes
- Rotating a key that permits
encryptanddecryptwhile supplyingkey_opsof["decrypt"]only. - Rotating a signing key that permits
signandverifywhile supplying["verify"]. - Copying a
key_opsarray from a different key, or from an older revision of a request, that happens to be narrower than the current material's.
Response Example
{
"type": "https://docs.ankatech.co/errors/key-ops-narrowing-forbidden",
"title": "Key Operations Narrowing Forbidden",
"status": 422,
"detail": "Rotation may not narrow key_ops. Dropped operations: decrypt.",
"instance": "/api/v3/admin/tenants/7c9e6679-7425-40de-944b-e07fc1f90ae7/keys/my-mlkem-key/rotations",
"correlationId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": 1730000000,
"extensions": {
"errorCode": "KEY_OPS_NARROWING_FORBIDDEN"
}
}
Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.
The detail names only the dropped operations. It never carries key material, the kid, a UUID or a tenant identifier; the kid you supplied is echoed in instance.
The example above shows the Core API shape — the Core API is where this refusal originates, and it carries the correlation identifier as the top-level correlationId. The Admin API relays the same refusal on its tenant-scoped rotation route (POST /api/v3/admin/tenants/{tenantId}/keys/{kid}/rotate), so on that path instance is the Admin API route and the correlation identifier is extensions.requestId — see Where extension members live.
Branch on type, not on errorCode
extensions.errorCode is emitted by the Core API and is convenient for a direct integration, but it does not survive the Admin API relay: the relay's problem view is a closed four-member record (type, title, status, detail), so extension members are dropped end to end.
type is the identifier to dispatch on. RFC 9457 §3.1.1 makes it the primary identifier of a problem, this type is a dedicated slug rather than a shared one, and it is relayed unchanged — so a consumer branching on type gets the same discrimination on both the direct and the relayed path, and never has to parse detail.
if ("https://docs.ankatech.co/errors/key-ops-narrowing-forbidden"
.equals(problemDetails.getType().toString())) {
// widen or omit key_ops, then resubmit
}
How to Resolve
- Read the current PRIMARY material's
key_ops(GET /api/v3/key-management/keys/{kid}/metadata). - Resubmit the rotation with
key_opsthat preserve or widen that set. - Or simply omit
key_ops. A rotation that omits it keeps the current operations within the key's purpose, which can never narrow them. It never takes the new algorithm's full allowed set: an RSA or EC key created for signing stays a signing key.
Narrowing an operation set is a separate action
Rotation exists to change key material, not to reduce a key's capability. If you genuinely need a narrower operation set, create a key with that set rather than trying to reach it through a rotation.
For full schema definitions, examples, and interactive testing, see the Developer Hub Reference.