Skip to content

BYOK Credential Self-Test Failed

URI: https://docs.ankatech.co/errors/byok-credential-self-test-failed
HTTP Status: 422 Unprocessable Entity

When you see this

A new access credential submitted to rotate a tenant's own key-protection backend (BYOK) did not pass verification against the key the tenant declared, so it was not stored. Before any credential replaces the one in force, the platform checks the new credential alone against the declared key, in this order:

  1. Existing material — runs only when the tenant holds at least one live key whose secret part the platform stores wrapped: any non-composite key except a public-only import (a deleted or destroyed key does not count). The most recent such key is unwrapped under the new credential and discarded. This proves the new credential reaches the key that actually protects the tenant's data, not merely a key with the same name. A tenant whose only keys are composite or public-only skips this check.
  2. Candidate round trip — always runs. A throwaway value is wrapped and then unwrapped under the new credential. This proves the credential authenticates and holds both the encrypt and the decrypt permission on the declared key.

Because the existing-material check is the first to contact the key service when it runs, any refusal at that step is reported as EXISTING_MATERIAL — an expired or mistyped credential included.

The credential in force is never used for the check, so a rotation works even after the old credential has been deactivated at the cloud provider. The response is not retryable as it stands: submitting the same credential again gives the same answer.

Common Causes

  • The credential is invalid, expired or deactivated, or its secret was mistyped.
  • The credential authenticates but is not permitted to use the declared key — for example an IAM policy, a key policy or a role binding that does not grant it.
  • The credential may decrypt but not encrypt. A decrypt-only credential (for example Google Cloud's Cloud KMS CryptoKey Decrypter role on its own) fails the candidate round trip, because the platform must also wrap new keys under it.
  • The credential belongs to a different account, project or directory, so it reaches a different key than the one holding the tenant's existing material.
  • The declared key no longer exists, the new credential belongs to an account that does not hold it, or the declaration cannot be used with it: that is not this type but BYOK Declared Key Unusable (KEY_ABSENT, DESCRIPTOR_INVALID, SECURITY_DOMAIN_ABSENT).

Response Example

{
  "type": "https://docs.ankatech.co/errors/byok-credential-self-test-failed",
  "title": "BYOK Credential Self-Test Failed",
  "status": 422,
  "detail": "The new credential did not pass verification against the declared key. The current credential remains in force.",
  "instance": "/api/v3/admin/tenants/7d1c9b40-52e8-4a36-9f07-c3b81e6d240a/key-backend/byok/credential",
  "timestamp": 1730000000,
  "extensions": {
    "requestId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "reason": "AUTHENTICATION_REFUSED",
    "check": "CANDIDATE_ROUND_TRIP"
  }
}

Content-Type: application/problem+json — the response follows RFC 9457 Problem Details. The response carries no Retry-After header.

Extension Values Meaning
reason REFUSED, AUTHENTICATION_REFUSED, ROUND_TRIP_MISMATCH What the backend answered, from the same closed vocabulary the connection test uses
check CANDIDATE_ROUND_TRIP, EXISTING_MATERIAL Which check was running when the refusal arrived — read it after reason

The body never names the key, the endpoint, the account or any credential field, and the credential you submitted is never logged or echoed.

How to Resolve

  1. Read reason first, against the connection test verdicts*. AUTHENTICATION_REFUSED means the key service did not accept the credential at all — it is expired, deactivated or mistyped — and the fix is the secret itself, whichever check it arrived with.
  2. Then read check, which names the step that was running when the refusal arrived:
    • EXISTING_MATERIAL with any reason other than AUTHENTICATION_REFUSED — the credential authenticates but cannot unwrap the tenant's existing keys: it lacks decrypt on the declared key, or it reaches a key of the same name in another account, project or directory. Use a credential from the account, project or directory that owns the declared key, granted decrypt on it.
    • CANDIDATE_ROUND_TRIP — the credential could not wrap and unwrap on the declared key. When the existing-material check ran first and passed, the credential can decrypt but not encrypt: grant it encrypt and decrypt on that key.
  3. Submit the corrected credential again. Nothing was changed: the credential in force, the tenant's declaration and its last recorded credential check are exactly as they were, and the refusal is recorded in the audit trail.

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

* restricted content — see higher-tier documentation