Skip to content

Algorithm Not Admitted

URI: https://docs.ankatech.co/errors/algorithm-not-admitted
HTTP Status: 400 Bad Request

When you see this

The algorithm policy in force for the tenant — the tenant's own algorithm-availability policy, or the deployment policy a tenant without one inherits — does not admit the algorithm of the key you asked to create, rotate to or import. The request itself is well-formed; the policy refused it. Nothing is generated, rotated, stored or imported.

The type is distinct from Invalid Input so a caller tells a policy refusal from a malformed request by its type (RFC 9457 §3.1.1), never by parsing detail. Every other 400 on the same operations keeps its own type and carries neither extension member below.

Which route answers with this type

This type originates in the Core API key plane, where key creation, rotation, key import and keystore import judge the algorithm against the policy. Core API answers it directly on key creation (POST /api/v3/key-management/keys) to a caller holding key_management.generateKey; its rotation and import routes admit only the Admin API. None of these Core API operations is in a published OpenAPI document or has a generated SDK or CLI method. The published routes are the Admin API routes below, which relay the refusal with the same 400, the same type and both extension members:

Operation (operationId) Route
createKey POST /api/v3/admin/tenants/{tenantId}/keys
rotateKey POST /api/v3/admin/tenants/{tenantId}/keys/{kid}/rotations
importKey POST /api/v3/admin/tenants/{tenantId}/keys/import-key
importKeystore POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore

A keystore import is refused as a whole when any entry's algorithm is not admitted; its detail names the entry's alias. You can branch on type on either plane.

Extension members

Member Meaning
extensions.algorithm The catalogue name of the refused algorithm (for example P-384, X25519+ML-KEM-768), in its canonical spelling — never the spelling the request used
extensions.refusedBy The check of the policy that refused it, one token of the vocabulary below

detail is a single sentence naming the algorithm, the policy that refused it (with the template it was resolved from, when the policy names one) and the refusing check — except under a policy that admits nothing, where it says so and names no field (Algorithm ML-DSA-65 is not admitted: the algorithm policy in force for this tenant admits no algorithm.). Show it to the operator; branch on type and on extensions.refusedBy, never on detail.

The refusedBy vocabulary

The Core API owns this vocabulary as part of its public error contract. Every token matches ^[a-z][A-Za-z]{2,39}$. A check that is a field of the availability policy is named by that field's wire name.

refusedBy The policy refused the algorithm because
explicitAlgorithms A non-empty explicit algorithm list does not name it
allowedOperations A requested key operation is outside allowedOperations
allowedCategories Its category is outside allowedCategories
allowedFamilies Its family, or a component family of a composite, is outside allowedFamilies
allowedLevels Its security level is outside allowedLevels
minSecurityLevel Its security level is below minSecurityLevel
minSecurityStrengthBits Its security strength in bits — for a composite, the weaker component's — is below minSecurityStrengthBits (for example RSA-2048, 112 bits, under a 128-bit floor)
mandatoryStandards Its standards attribution does not overlap mandatoryStandards
allowedCompositeModes The composite mode is outside allowedCompositeModes
allowedKdfs The KDF of a composite KEM pair (the request's kdf, or the catalogue default when absent) is outside allowedKdfs
requireComposite It is a standalone algorithm the policy admits only inside a COMPOSITE (hybrid) key — on create, and on a SIMPLE→SIMPLE rotation
failClosed The policy in force admits no algorithm at all (deny-all, or every allow-list empty)

Response Example

The refusal as the Core API states it, for a create of my-p384-key under a policy whose allowedFamilies is [ML-KEM, ML-DSA]:

{
  "type": "https://docs.ankatech.co/errors/algorithm-not-admitted",
  "title": "Algorithm not admitted",
  "status": 400,
  "detail": "Algorithm P-384 is not admitted by the tenant algorithm policy; the refusing policy field is allowedFamilies.",
  "instance": "/api/v3/key-management/keys",
  "correlationId": "71fa3bc8-a559-49a1-8791-c1dc37aadd56",
  "timestamp": 1760090400,
  "extensions": {
    "algorithm": "P-384",
    "refusedBy": "allowedFamilies"
  }
}

The same refusal as the Admin API relays it to a tenant administrator:

{
  "type": "https://docs.ankatech.co/errors/algorithm-not-admitted",
  "title": "Algorithm not admitted",
  "status": 400,
  "detail": "Algorithm P-384 is not admitted by the tenant algorithm policy; the refusing policy field is allowedFamilies.",
  "instance": "/api/v3/admin/tenants/7b9c2d10-4e6f-4a21-9c33-0d1e5f7a8b42/keys",
  "timestamp": 1760090400,
  "extensions": {
    "algorithm": "P-384",
    "refusedBy": "allowedFamilies",
    "requestId": "71fa3bc8-a559-49a1-8791-c1dc37aadd56"
  }
}

Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.

The Core API emits a top-level correlationId; the Admin API emits extensions.requestId. Read the member your service emits — see Error Index & Overview.

Where the members come from

The Admin API relays the Core API's type, title and detail; instance, timestamp and extensions.requestId are its own (S2S Relay Envelope). The refusal is recognised from the upstream type alone, so it keeps its type even when the upstream detail did not survive (the Admin API then supplies its own sentence). The two extension members cross a trust boundary and are re-minted, never relayed as bytes:

  • algorithm is replaced by the Admin API's own catalogue entry for the name, so the relayed value is a name the platform publishes;
  • refusedBy is kept only when it has the vocabulary's shape (^[a-z][A-Za-z]{2,39}$).

A member that fails its check is omitted from the response — the type stays — and the Admin API logs one WARN naming the member, never its value.

What is not this type

  • An algorithm the catalogue does not know — for example an unknown composite pairing — is not a policy decision: it is Invalid Input, with no algorithm or refusedBy member, also under a deny-all policy.
  • A rotation-policy refusal (a downgrade, a family change the rotation policy forbids) is Rotation Security Downgrade or another 422, unchanged.
  • The rotation-admissibility preview reports a target the policy refuses as a verdict in its 200 response, not as this error.

Audit trail

The refusal is a denial, recorded once per act:

Caller Audit row
A licensed caller of the Core API's own key creation or rotation, holding a user token One crypto audit row with status DENIED, errorCode DENY_ALGORITHM_NOT_ADMITTED and the refused algorithm
Any caller of the Admin API (the Core API is reached service-to-service) One admin audit row with outcome DENIED — admin.keyalias.create for a create or an import, admin.keyalias.update for a rotation — whose details carry refusedAlgorithm, refusedBy, problemType, upstreamStatus and the relayed detail as reason. No crypto row is written for the service-to-service call

A malformed member the Admin API dropped is absent from the row as well. Auditors list these rows with status=DENIED on the admin timeline or outcome=DENIED on the unified feed; see Audit API endpoints*.

How to Resolve

  1. Read extensions.refusedBy to learn which check refused the algorithm, and detail for the policy (and template) in force.
  2. Choose an algorithm the policy admits — GET /api/v3/key-management/supported-algorithms lists the algorithms filtered by the tenant's availability policy.
  3. For requireComposite, create or rotate to a COMPOSITE (hybrid) key that pairs the algorithm.
  4. If the algorithm should be admitted, ask a tenant administrator to change the tenant's algorithm policy (or a platform administrator the deployment policy it inherits), then re-submit.

An existing key whose algorithm the floor no longer admits is not refused by this problem when it is used: an operation that applies protection with it answers Key Below Security-Strength Floor, and decrypting and verifying with it stay admitted.

For full schema definitions, examples, and interactive testing, see the Developer Hub Reference.

* restricted content — see higher-tier documentation