Skip to content

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.

Newly visible, not newly enforced

This gate has always run and this type has always been declared, but the rotation handler wrapped every refusal and re-reported it as a 500. Since PRD §128 the refusal reaches the handler that owns it, so you now receive the 422 that was intended all along. The rule itself did not change.

Common Causes

  • Rotating a key that permits encrypt and decrypt while supplying key_ops of ["decrypt"] only.
  • Rotating a signing key that permits sign and verify while supplying ["verify"].
  • Copying a key_ops array 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/key-management/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 7807 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

  1. Read the current PRIMARY material's key_ops (GET /api/v3/key-management/keys/{kid}/metadata).
  2. Resubmit the rotation with key_ops that preserve or widen that set.
  3. Or simply omit key_ops. A rotation that omits it inherits the new algorithm's full allowed operation set, which can never narrow the current one and is therefore always safe.

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.