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:
algorithmis replaced by the Admin API's own catalogue entry for the name, so the relayed value is a name the platform publishes;refusedByis 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
algorithmorrefusedBymember, 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
200response, 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
- Read
extensions.refusedByto learn which check refused the algorithm, anddetailfor the policy (and template) in force. - Choose an algorithm the policy admits —
GET /api/v3/key-management/supported-algorithmslists the algorithms filtered by the tenant's availability policy. - For
requireComposite, create or rotate to a COMPOSITE (hybrid) key that pairs the algorithm. - 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.