key — standard-base64 of exactly 32 raw
bytes (an AES-256 key). The field is named key, nothing else; there is
no byok_key_b64 field and an unknown field is silently ignored, so a
typo here quietly falls back to envelope mode.wrapped_dek and nonce_dek set to null; the
ciphertext is encrypted directly under your supplied key, no
per-request DEK to unwrap.AESGCM(your key) against nonce_payload + ciphertext.400 Bad Request with a detail string:detail | Cause |
"key must be valid base64" | The value isn't strict standard base64. Use standard base64 (+, /, =), not base64URL, and don't strip padding. |
"key must decode to 32 bytes (256 bits), got {n}" | It decoded, but to the wrong length. {n} is the actual byte count; a common cause is base64-ing a 64-char hex string (decodes to 64 bytes) instead of the 32 raw bytes. |
1234567891011121314151617181920212223import base64, os, requests from cryptography.hazmat.primitives.ciphers.aead import AESGCM byok_key = os.urandom(32) resp = requests.post( "https://api.kindi.me/api/v1/mask", headers={"Authorization": "Bearer mk_live_..."}, json={ "text": "Patient John Doe, MRN H123456", "key": base64.b64encode(byok_key).decode(), }, ).json() # No DEK to unwrap; decrypt the payload directly. assert resp["wrapped_dek"] is None assert resp["nonce_dek"] is None plain = AESGCM(byok_key).decrypt( base64.b64decode(resp["nonce_payload"]), base64.b64decode(resp["ciphertext"]), None, ) print(plain.decode())