Error & status code reference 

    Basil fails closed and never panics on the request path: a bad input, a denied caller, or a flaky backend turns into a clean status code, not a crashed broker. What each code means and what to do about it:

    Wire status codes 

    CodegRPCMeaning / your move
    UNAUTHORIZEDPermissionDeniedPolicy said no, or the key doesn't exist. Deliberately indistinguishable and detail-free (no key enumeration). Check the audit reason for the real cause.
    UNAUTHENTICATEDUnauthenticatedMissing peer credentials (SO_PEERCRED): the broker can't attest the caller, so it fails closed before policy runs.
    INVALID_REQUESTInvalidArgumentMalformed input, wrong op for the key's class, or an algorithm mismatch. A client/config bug; fix the call.
    PAYLOAD_TOO_LARGEResourceExhaustedOver an encrypt/payload cap. Raise the limit (Limits) or chunk the data.
    UNSUPPORTED / UNSUPPORTED_ALGORITHMUnimplementedThe op or algorithm isn't backed here (usually a catalog/backend mismatch). Re-run basil config check.
    DECRYPT_FAILEDInvalidArgumentAEAD/unseal authentication failed: wrong key, tampered ciphertext, or mismatched AAD. Opaque on purpose: no oracle distinguishing the cause.
    BACKEND_UNAVAILABLEUnavailableThe backend is unreachable. Likely transient infra, so retry and check Vault/OpenBao health.
    BACKEND_ERROR / INTERNALInternalThe backend rejected the op, or an internal invariant (e.g. a misconfigured publicPath) tripped. Check logs; usually a config issue.

    Admin ops (explain, revoke) return the same InvalidArgument gRPC code with the token INVALID_ARGUMENT rather than INVALID_REQUEST; the meaning is identical. Two narrower reasons round out the set: revoke without a configured store returns NO_REVOCATION_STORE (FailedPrecondition), and a sealed-invocation response-protection failure returns INVOCATION_RESPONSE_PROTECTION_FAILED (Internal).

    📝 Failures are isolated per key

    Each key resolves independently, so a backend that's down fails only the ops routed to it. Keys on a healthy backend keep serving. One bad backend doesn't take down the broker.

    💡 Why some errors tell you so little

    UNAUTHORIZED and DECRYPT_FAILED are intentionally terse, hiding whether a key exists or why a decrypt failed. When you need the why, it's in your audit log (for authorization) or the broker's tracing output (for backend/crypto), not in the caller-facing status.

    Where to go next