{"openapi":"3.1.0","info":{"title":"ANKASecure API – Enterprise Post-Quantum Cryptography","description":"Enterprise-grade quantum-resistant cryptography API with hybrid classical+PQC encryption, zero-copy streaming for large files, and NIST-standardized algorithms.\n\n✓ Hybrid cryptography (X25519+ML-KEM) for crypto-agility\n✓ Streaming operations for multi-GB files without buffering\n✓ Implements ML-KEM (FIPS 203) and ML-DSA (FIPS 204)\n✓ Multi-tenant key isolation with complete lifecycle management\n✓ Interoperability with external PKI systems\n\nAlgorithms: AES · RSA · ECC · ML-KEM · ML-DSA · Falcon · SLH-DSA\n","contact":{"name":"ANKATech Support","url":"https://ankatech.co","email":"info@ankatech.co"},"version":"3.3.0","x-category":"Security","x-long-description":"ANKASecure Core API is the industry's first enterprise-grade post-quantum cryptography API offering true hybrid encryption (NIST SP 800-227), zero-copy streaming, and seamless crypto-agility.\n\nKEY FEATURES:\n\n• Quantum-Resistant Security: NIST-approved ML-KEM (FIPS 203) and ML-DSA (FIPS 204) with backwards-compatible hybrid modes.\n\n• Zero-Copy Streaming: Encrypt, decrypt, sign, and verify multi-gigabyte files without buffering. Ideal for cloud storage, IoT, and data pipelines.\n\n• Hybrid Cryptography: Combine classical (X25519, Ed25519) with post-quantum (ML-KEM, ML-DSA) in a single operation.\n\n• Multi-Tenant Isolation: Tenant-specific keystores with usage tracking, license enforcement, and audit trails.\n\n• Interoperability: Verify signatures and encrypt to external public keys without importing them.\n\n• Complete Key Lifecycle: Generate, rotate, revoke, and archive keys with immediate effect. PKCS#12/PKCS#7/PEM import support.\n\nUSE CASES:\nGovernment/Defense · Financial Services · Healthcare · Cloud Providers · IoT/Edge · Supply Chain\n\nALGORITHMS:\nSymmetric: AES (128/192/256-GCM) | Classical: RSA, ECC | Post-Quantum: ML-KEM, ML-DSA, Falcon, SLH-DSA | Hybrid: X25519+ML-KEM, Ed25519+ML-DSA\n\nSTANDARDS:\nNIST FIPS 203/204 · NIST SP 800-227 · NIST CSWP 39 · RFC 7516/7515 · OWASP REST API Security\n","x-website":"https://ankasecure.co","x-apisguru-categories":["security","cryptography","post-quantum","enterprise"]},"servers":[{"url":"https://{host}:{port}","description":"Parametrised ANKASecure Server (https://host:port)","variables":{"host":{"description":"Hostname or IP address of the server","default":"demo.ankatech.co"},"port":{"description":"TCP port on which the server listens","default":"443"}}}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Crypto Limits","description":"Discovery of the crypto payload limit (read-only)"},{"name":" Key Query","description":"Retrieve key metadata and list stored keys."},{"name":" Secure","description":"Endpoints to manage and perform cryptographic operations with symmetric\nand post-quantum/classical asymmetric keys (AES, RSA, ECC, ML-KEM, ML-DSA, Falcon, etc.).\n"},{"name":" Algorithm Catalog","description":"Catalog of supported cryptographic algorithms for crypto-agility decisions."},{"name":"Interoperability","description":"Continuous interoperability operations for cryptographic collaboration with\nexternal systems using caller-supplied keys (not persisted in keystore).\nSupports signature verification and encryption for cross-platform integration\nwithout importing keys into ANKASecure infrastructure.\n"},{"name":"License Metrics","description":"Operational metrics for license validation monitoring"},{"name":" Secure Streaming","description":"Endpoints to manage fully streaming-based cryptographic operations\nwith symmetric and post-quantum/classical asymmetric keys (AES, RSA, ECC, ML-1024,\nML-DSA, Falcon, etc.).\n"},{"name":"Migration","description":"Migration operations for legacy PKCS#7/CMS data: analysis of a PKCS#7 structure and\nits conversion to JOSE under keys the tenant already holds. These endpoints support\ntransitioning legacy cryptographic assets to ANKASecure's post-quantum infrastructure.\n"}],"paths":{"/api/v3/migration/stream/convert-pkcs7-to-jose":{"post":{"tags":["Migration"],"summary":"Convert PKCS#7 to JOSE (streaming)","description":"Converts PKCS#7/CMS files of any size to modern JOSE (JWE/JWS), streaming the result.\n\nThis is a **format conversion** (PKCS#7 container → JOSE container), not an algorithm conversion.\nClassical algorithms (RSA, ECDSA, AES) are preserved or upgraded (CBC→GCM).\n\n**Supported conversions** — every signer and every recipient of the input is carried:\n\n- SignedData (N signers) → detached JWS (RFC 7797), one signature per signer key\n- EnvelopedData (M recipients) → JWE carried as multipart/mixed, one recipient per recipient key\n- SignedAndEnvelopedData (RFC 2315 section 11: N outer signers, M recipients) → JWE whose\n  plaintext is an **attached** JWS with one signature per outer signer key\n\n**Serialization** (`serialization`, exact case; detached streaming output is always\nJSON-serialized, so the single-actor form is not RFC 7515 section 7.1 Compact):\n\n- `AUTO` (default): the single-actor form for at most one signer and at most one\n  recipient (a flattened detached JWS; the multipart JWE with a one-recipient header);\n  General otherwise (a `signatures` array; a `recipients` array).\n- `COMPACT`: the single-actor form; more than one signer or recipient is refused with 422\n  `INCOMPATIBLE_SERIALIZATION` and zero body bytes.\n- `JSON`: General, also for a single actor.\n\nAny other value is refused with 400 `invalid-input` naming `serialization`, before the\nupload is read.\n\n**Response format varies by PKCS#7 type:**\n\n**SignedData** returns `application/octet-stream`; one signer under AUTO:\n```json\n{\"protected\":\"eyJhbGc...\",\"signature\":\"dGVz...\"}\n```\nTwo signers:\n```json\n{\"signatures\":[{\"protected\":\"eyJhbGc...\",\"signature\":\"dGVz...\"},{\"protected\":\"eyJhbGc...\",\"signature\":\"c2ln...\"}]}\n```\n\n**EnvelopedData** returns `multipart/mixed`:\n```\n------ankatech-<UUID>\nContent-Type: application/jose+json\n\n{\"protected\":\"...\",\"recipients\":[...],\"iv\":\"...\"}\n\n------ankatech-<UUID>\nContent-Type: application/octet-stream\n\n<binary ciphertext stream>\n------ankatech-<UUID>--\n```\n\n**Size and cost:**\n\n- There is no size limit on the uploaded file.\n- EnvelopedData is converted in a single pass with bounded memory, whatever the number\n  of recipients.\n- The signed shapes read the uploaded file once per signer plus once more, because\n  RFC 2315 places the signer metadata after the content and each signature is computed\n  in its own pass: SignedData costs **N + 1** passes, SignedAndEnvelopedData **N + 3**\n  (a keyless structure pass, a verification pass, one signing pass per signer and the\n  emission). Expect the wall-clock time to scale with the signer count.\n\n**Abort contract:** every failure that can be decided before the first payload\nbyte is answered as an HTTP status with an `application/problem+json` document.\nA failure raised while the output is being emitted is answered the same way while\nthe response is still uncommitted (its bytes have not left the server's response\nbuffer); once the response is committed, a failure ends the response **without\ncompleting its framing** and no problem document is written into the body — so a\nresponse that stops short is a failure, not a truncated success.\n\n**Important:**\n- Response does NOT include metadata (signers, recipients, algorithm details)\n- For metadata: Use POST /api/v3/migration/stream/analyze-pkcs7 first\n- The output is an input POST /api/v3/crypto/stream/reencrypt and\n  /crypto/stream/resign accept. For an output with several recipients or several\n  signatures, `sourceKid` names the recipient, or the signature, that is replaced,\n  and every other entry is kept.\n\n**Prerequisites:** Import signer/recipient keys via POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore\n\n**Input:** multipart/form-data with 'metadata' (JSON) and 'file' (binary PKCS#7)\n","operationId":"convertPkcs7ToJoseStream","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/Pkcs7ConversionStreamRequest"},"file":{"type":"string","format":"binary"}},"required":["file","metadata"]}}}},"responses":{"200":{"description":"Conversion successful AND the response completed. The body is application/octet-stream (a detached JWS, for SignedData) or multipart/mixed (a JWE, for EnvelopedData and SignedAndEnvelopedData); for SignedAndEnvelopedData the JWE plaintext is an attached JWS, carrying the signed content in its payload.\n\nAbort contract: the 200 status line is negotiated before the first payload byte, so the status alone is not a statement of success. Once a byte has been written, a failure ends the response without completing its framing — the chunked terminator or the closing multipart boundary is never sent — and no problem document is written into the body. A response that stops short is the failure signal: treat a short read as an error and discard the partial output.\n\nSize and cost: no size limit. EnvelopedData is converted in a single pass with bounded memory; the signed shapes read the uploaded file once per signer plus once (SignedData N + 1 passes, SignedAndEnvelopedData N + 3), because RFC 2315 places the signer metadata after the content.","content":{"*/*":{}}},"400":{"description":"Bad Request: invalid request metadata (a serialization or targetFormat outside its vocabulary, exact case, refused before the upload is read; a decryptionKid whose certificate matches none of the recipients; a member the metadata part does not define, at its root or in algorithmPolicy, named), or the file is not a valid PKCS#7/CMS structure. Returned as a response status before any body byte. A named decryptionKid's Capability Grant is decided, and its stateful constraints (max_usage, rate_limit, revoke_on_use) charged, before that key is resolved, as on the crypto plane: a decryptionKid refused here has already been charged one use of that grant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Member the body does not define":{"description":"Member the body does not define","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Unsupported member 'validateCertificates' in the request body.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"serialization outside AUTO, COMPACT and JSON":{"description":"serialization outside AUTO, COMPACT and JSON","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"serialization must be one of [AUTO, COMPACT, JSON] (exact case); received 'json'.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Invalid input":{"description":"Invalid input","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"The streaming metadata or file part is malformed.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Invalid PKCS#7 structure":{"description":"Invalid PKCS#7 structure","value":{"type":"https://docs.ankatech.co/errors/invalid-pkcs7","title":"Invalid PKCS#7 Structure","status":400,"detail":"The provided file is not a valid PKCS#7/CMS structure (expected DER-encoded SignedData, EnvelopedData, or SignedAndEnvelopedData).","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}}}}}},"401":{"description":"Unauthorized: missing, expired, or invalid JWT token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Missing or expired bearer token":{"description":"Missing or expired bearer token","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required: provide a valid bearer token.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}}}}}},"403":{"description":"Forbidden: the token is valid but lacks the required 'migration.convertPkcs7' scope, or the caller's tenant is in a state that does not admit cryptographic operations (SUSPENDED, CLOSED), or the calling Application or Cryptographic Actor is SUSPENDED / DISABLED (effective-status containment, as on the crypto plane — the refusal stands even for an already-issued, still-valid JWT, and precedes every Capability Grant decision). Also 403, before any body byte and any actor is resolved and with no extensions.actor, when the token carries no Cryptographic Actor, or the calling Actor holds no Capability Grant at all for an operation the upload needs (REENCRYPT for its recipients, RESIGN for its signers), or none to REENCRYPT from the named decryptionKid - answered the same whether or not that key exists. Also 403, before any body byte, when a leg of one of the upload's signers or recipients is refused, first by the Capability Grant resolver (the calling Actor holds no active grant for the leg's decision - REENCRYPT from the recipient that opens the content to every recipient, itself included, and RESIGN from every signer's key to itself, as /stream/reencrypt and /stream/resign decide theirs; DECRYPT, ENCRYPT and SIGN grants admit no conversion, and a REENCRYPT grant covers only the recipients it names beside the opening one; a REENCRYPT or RESIGN grant is requested with exactly two assetKids, so an upload with N recipients needs N-1 pair grants, one REENCRYPT [opener, recipient] per other recipient (any of them also covers the opener's own pair), a single-recipient upload the in-place REENCRYPT grant, requested with the kid twice (assetKids [opener, opener]) and stored as [opener], and each signer the in-place RESIGN [signer, signer]; there is no wildcard REENCRYPT or RESIGN grant - or a grant constraint is violated, or its exchange context is suspended), then by the tenant's key-lifecycle policy (the opening recipient on the DECRYPT row, every recipient on the REENCRYPT row, every signer on the VERIFY then the RESIGN row). Without a decryptionKid the recipient that opens the content is the FIRST one, in encoded order, whose key can decrypt, and every REENCRYPT decision is taken from it: a caller whose grants name a later recipient's key must name that key in decryptionKid. For a leg refusal, extensions.actor names that actor by its index in its set, its role (signer | recipient) and the refused leg (sign | decrypt | encrypt); the detail is the refusing gate's generic detail and no kid is named","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Insufficient scope":{"description":"Insufficient scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Access denied: the 'migration.convertPkcs7' scope is required for this operation.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"A grant constraint refuses re-signing for the first signer":{"description":"A grant constraint refuses re-signing for the first signer","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: Grant's max_usage limit reached (100)","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"actor":{"index":0,"role":"signer","leg":"sign"}}}},"Key-lifecycle policy refuses re-signing for the second signer":{"description":"Key-lifecycle policy refuses re-signing for the second signer","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: not permitted for this key","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"actor":{"index":1,"role":"signer","leg":"sign"}}}}}}}},"404":{"description":"Not Found: the referenced decryption/signing kid, or its certificate, does not exist for this tenant; a kid owned by another tenant yields the identical 404, so there is no cross-tenant existence oracle. The body renders fixed strings and names no kid. Raised at the certificate lookup, before any private key is retrieved, and returned as a response status before any body byte. A named decryptionKid's Capability Grant is decided, and its stateful constraints (max_usage, rate_limit, revoke_on_use) charged, before that key is resolved, as on the crypto plane: a decryptionKid answered 404 has already been charged one use of that grant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Recipient or signer kid not resolvable for this tenant":{"description":"Recipient or signer kid not resolvable for this tenant","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"The requested resource does not exist or is not accessible. Reference the correlationId for support.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}}}}}},"409":{"description":"Conflict: the environment's key-protection backend is not configured, so no key can be unwrapped. The refusal happens at the key-material seam, before any payload byte, and is returned as a response status. extensions.setupState names the setup step that is missing. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when a signer's or recipient's key is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>): no signature or encryption is produced with such a key. The refusal is decided before any output exists, and names the refused actor under <code>extensions.actor</code> (<code>index</code>, <code>role</code>, <code>leg</code>), never its key. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Key-protection backend not configured for this environment":{"description":"Key-protection backend not configured for this environment","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key-protection backend for this environment has not been configured yet. Complete the environment setup before creating keys.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"setupState":"KEY_BACKEND_UNCONFIGURED"}}},"Signer key at its usage limit":{"description":"Signer key at its usage limit","value":{"type":"https://docs.ankatech.co/errors/key-material-usage-exhausted","title":"Key Material Usage Exhausted","status":409,"detail":"Operation 'sign' is refused: the key's current material has reached its usage limit. Rotate the key to protect new data; what it already protected can still be decrypted and verified.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"code":"KEY_MATERIAL_USAGE_EXHAUSTED"}}}}}}},"422":{"description":"Unprocessable Entity. extensions.errorCode: INCOMPATIBLE_SERIALIZATION (COMPACT with more than one signer or recipient), ACTOR_LIMIT_EXCEEDED (more signers and recipients together than one conversion accepts), UNSUPPORTED_SIGNER_IDENTIFIER / UNSUPPORTED_RECIPIENT_TYPE, LEGACY_ALGORITHM_REJECTED, INCOMPATIBLE_TARGET_FORMAT; missing-private-key with unresolvedActors[] naming EVERY actor without a tenant key (MISSING_SIGNER_KEY, MISSING_RECIPIENT_KEY, MISSING_RECIPIENT_PRIVATE_KEY, or MISSING_ACTOR_KEYS when they differ); unprocessable-entity with errorCode AMBIGUOUS_ACTOR_KEY when an actor's certificate is held under more than one tenant key admissible for its role; a content-encryption algorithm whose parameters encode a key size its RFC does not define (UNSUPPORTED_CONTENT_ENCRYPTION: RC2 effective key bits outside 1 to 1024, RFC 2268 section 6; a CAST5 keyLength outside 40 to 128 bits in 1-octet increments, RFC 2984 section 2.1), refused before any key is looked up or used; decryption-failed with one body whichever decryption step failed, raised at the content (a PKCS #1 v1.5 encrypted key that does not unwrap is replaced by a random key, RFC 3218 section 2.3.2): on an EnvelopedData that is while the output is being emitted, exactly as for a corrupted ciphertext, so it is this problem document while the response is still uncommitted and an aborted response once it is committed; unprocessable-entity when the CMS signature of a signer does not verify; every signer that is re-signed is verified, and the detail names the index of the first one that fails (an outer SignedAndEnvelopedData signer is verified under the very certificate its tenant key was bound to). The outer signers of a SignedAndEnvelopedData are verified in the RFC 2315 section 11.2 form only (SIGNED_AND_ENVELOPED_DIGEST_NOT_ENCRYPTED for a plain-form encryptedDigest, UNSUPPORTED_SIGNED_AND_ENVELOPED_SIGNER_KEY for a non-RSA signer, SIGNED_AND_ENVELOPED_WITHOUT_SIGNER when there is no outer signer), before any signature is computed and before any body byte. A missing key is answered at every input size, as a response status before any body byte; a content-decryption failure is answered this way only while the response is still uncommitted, and otherwise aborts the response per the 200 abort contract","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"RC2 parameters encoding an undefined effective key size":{"description":"RC2 parameters encoding an undefined effective key size","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The PKCS#7 RC2-CBC content-encryption parameters do not encode an effective key size RFC 2268 section 6 defines (1 to 1024 bits). Re-export the file and upload it again.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_CONTENT_ENCRYPTION"}}},"COMPACT requested for three signers":{"description":"COMPACT requested for three signers","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"serialization COMPACT represents at most one signer and one recipient; the PKCS#7 input carries 3 signer(s) and 0 recipient(s). Use AUTO or JSON.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"INCOMPATIBLE_SERIALIZATION"}}},"More signers and recipients than one conversion accepts":{"description":"More signers and recipients than one conversion accepts","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The PKCS#7 input carries 14 signer(s) and 3 recipient(s); a conversion names at most 16 signers and recipients together.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"ACTOR_LIMIT_EXCEEDED"}}},"Signer identified by SubjectKeyIdentifier":{"description":"Signer identified by SubjectKeyIdentifier","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 signer at index 1 is identified by a SubjectKeyIdentifier. Only signers identified by issuer and serial number can be converted.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_SIGNER_IDENTIFIER"}}},"Signer on a legacy SHA-1 digest":{"description":"Signer on a legacy SHA-1 digest","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 signer at index 1 uses a legacy hash algorithm: digest SHA-1 (OID: 1.3.14.3.2.26). This is considered insecure. Set algorithmPolicy.allowLegacyHash=true to allow.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"LEGACY_ALGORITHM_REJECTED"}}},"A signer and a recipient without a tenant key":{"description":"A signer and a recipient without a tenant key","value":{"type":"https://docs.ankatech.co/errors/missing-private-key","title":"Missing Key for a PKCS#7 Signer or Recipient","status":422,"detail":"One or more signers or recipients of the PKCS#7 structure have no key of this tenant admissible for what they need: a signer a key that signs, the decrypting recipient a key that decrypts and encrypts, any other recipient a key that encrypts. Each one is listed in unresolvedActors with its requirement; import through importEndpoint a key admissible for it, with its private part (a certificate imported alone only verifies), and retry.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"MISSING_ACTOR_KEYS","unresolvedActors":[{"index":1,"role":"signer","requirement":"sign","issuerDN":"CN=Legacy Legal Signer,O=Example Corp,C=US","serialNumber":"0x1B2C3D4E5F607182"},{"index":2,"role":"recipient","requirement":"encrypt","issuerDN":"CN=Records Office,O=Example Corp,C=US","serialNumber":"0x5A6B7C8D9E0F1021"}],"importEndpoint":"POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore"}}},"SignedAndEnvelopedData outer signer without an RSA key":{"description":"SignedAndEnvelopedData outer signer without an RSA key","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The outer signer at index 1 of this PKCS#7 SignedAndEnvelopedData does not hold an RSA key. RFC 2315 section 11.2 defines the digest encryption of this structure for rsaEncryption only.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_SIGNED_AND_ENVELOPED_SIGNER_KEY"}}},"Missing signer key (SignedData or SignedAndEnvelopedData)":{"description":"Missing signer key (SignedData or SignedAndEnvelopedData)","value":{"type":"https://docs.ankatech.co/errors/missing-private-key","title":"Missing Key for a PKCS#7 Signer or Recipient","status":422,"detail":"One or more signers or recipients of the PKCS#7 structure have no key of this tenant admissible for what they need: a signer a key that signs, the decrypting recipient a key that decrypts and encrypts, any other recipient a key that encrypts. Each one is listed in unresolvedActors with its requirement; import through importEndpoint a key admissible for it, with its private part (a certificate imported alone only verifies), and retry.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"MISSING_SIGNER_KEY","unresolvedActors":[{"index":0,"role":"signer","requirement":"sign","issuerDN":"CN=Legacy Signing CA,O=AnkaTech,C=US","serialNumber":"0x4A3F2E1D5B6C7A8B"}],"importEndpoint":"POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore"}}},"Missing recipient private key (EnvelopedData)":{"description":"Missing recipient private key (EnvelopedData)","value":{"type":"https://docs.ankatech.co/errors/missing-private-key","title":"Missing Key for a PKCS#7 Signer or Recipient","status":422,"detail":"One or more signers or recipients of the PKCS#7 structure have no key of this tenant admissible for what they need: a signer a key that signs, the decrypting recipient a key that decrypts and encrypts, any other recipient a key that encrypts. Each one is listed in unresolvedActors with its requirement; import through importEndpoint a key admissible for it, with its private part (a certificate imported alone only verifies), and retry.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"MISSING_RECIPIENT_PRIVATE_KEY","unresolvedActors":[{"index":0,"role":"recipient","requirement":"decrypt","issuerDN":"CN=John Doe,O=AnkaTech,C=US","serialNumber":"0x4A3F2E1D5B6C7A8B"}],"importEndpoint":"POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore"}}},"Decryption failed (wrong recipient key or corrupted ciphertext)":{"description":"Decryption failed (wrong recipient key or corrupted ciphertext)","value":{"type":"https://docs.ankatech.co/errors/decryption-failed","title":"Decryption Failed","status":422,"detail":"PKCS#7 decryption failed. The content could not be decrypted with the private key of the decrypting recipient: either that key does not match the public key the file was encrypted to, or the file's encrypted key, encrypted content or content-encryption algorithm cannot be processed. Verify that the correct private key has been imported via POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"DECRYPTION_FAILED","possibleCauses":["Private key does not match the public key used for encryption","PKCS#7 data is corrupted or truncated","Wrong recipient key selected in multi-recipient PKCS#7"],"resolution":"Verify the correct private key is imported via POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore"}}},"Signer certificate held under two signing keys of the tenant":{"description":"Signer certificate held under two signing keys of the tenant","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The PKCS#7 signer at index 0 with issuer CN=Legacy Legal Signer,O=Example Corp,C=US and serial 0x1B2C3D4E5F607182 matches more than one key of this tenant admissible to sign, so it cannot be resolved to a single key. Remove the duplicate certificate import and retry.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"AMBIGUOUS_ACTOR_KEY"}}},"Signer signature does not verify":{"description":"Signer signature does not verify","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"PKCS#7 signature verification failed for the signer at index 2.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Incompatible targetFormat":{"description":"Incompatible targetFormat","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"targetFormat 'JWS' requires a signed structure; the supplied PKCS#7 is 'EnvelopedData' — use 'JWE' or 'AUTO'.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"INCOMPATIBLE_TARGET_FORMAT"}}}}}}},"429":{"description":"Too Many Requests, from one of two controls, told apart by `type`. (1) The streaming concurrency bound is full, either on this instance or for this tenant across the deployment: answered before the upload is read, because a conversion holds a worker thread for its whole duration and reads the upload once per signer plus once. (2) A key-protection rate limit or a cloud KMS quota refused the unwrap: the backend is healthy and deliberately refusing, so this is neither a setup problem nor an outage; raised at the key-material seam before any payload byte. Both are returned as a response status, and the Retry-After header carries the interval to wait","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming concurrency bound full for this tenant":{"description":"Streaming concurrency bound full for this tenant","value":{"type":"https://docs.ankatech.co/errors/streaming-concurrency-limit","title":"Too Many Concurrent Streaming Requests","status":429,"detail":"Too many concurrent streaming requests. At most 32 may be in flight on this instance and at most 8 for any one tenant across the deployment. Retry shortly.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Key-protection unwrap rate limit exceeded":{"description":"Key-protection unwrap rate limit exceeded","value":{"type":"https://docs.ankatech.co/errors/key-protection-rate-limited","title":"Too many key-protection requests","status":429,"detail":"Too many key-protection operations were attempted. Please retry after the indicated interval.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}}}}}},"503":{"description":"Service Unavailable: either the caller's tenant, Application or Actor effective status could not be verified (the containment fails CLOSED — the request is refused rather than admitted while the status source (the platform's status records and the administration API that maintains them) is unreachable), or the status of the Exchange Context a Capability Grant decision depends on could not be verified (fail-closed), or the key-protection backend is transiently unavailable or misconfigured. Both are decided before any payload byte and returned as a response status. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key-protection backend is temporarily unavailable. Please retry shortly.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"500":{"description":"Internal Server Error: a genuinely-unexpected server fault (NOT a client-caused condition — those map to the 4xx above). Returned as a response status while the response is still uncommitted; once the response is committed the same fault aborts the response per the 200 abort contract","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/migration/stream/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/migration/stream/analyze-pkcs7":{"post":{"tags":["Migration"],"summary":"Analyze PKCS#7 structure (streaming, >5MB files)","description":"Introspects PKCS#7/CMS binary data of ANY size and returns structured metadata, parsing incrementally.\n\nReturns metadata including signers, recipients, algorithms, certificates, and conversion feasibility.\n\n**Supports** SignedData, EnvelopedData and SignedAndEnvelopedData (RFC 2315 section 11,\nwhose signers are read from the outer `signerInfos`), with any number of signers and\nrecipients. The response is the one POST /api/v3/migration/analyze-pkcs7 answers for\nthe same file: `supportStatus`, `recommendations.migrationPath` and\n`recommendations.requiredKeys` are equal on the two routes. Nothing is decrypted and no\nkey is needed.\n\n**Input:** multipart/form-data with a `metadata` part followed by a `file` part\n(binary PKCS#7).\n\n**Part order is enforced**, as on every other streaming endpoint: `metadata`\nMUST precede `file`. The parse is sequential and single-pass, so the payload\ncannot be reached without consuming what precedes it and there is nothing to go\nback to; a `file`-first body is refused with\n`400 .../errors/multipart-part-order` before any payload byte is read.\n","operationId":"analyzePkcs7Stream","requestBody":{"description":"Sequential multipart body: the `metadata` part FIRST, then `file`.\n\n**Part order is enforced.** The seam parses the body single-pass and streams `file` straight through, so it cannot reach the payload without consuming what precedes it and cannot rewind to pick up a `metadata` part that arrives later. A `file`-first body — and a body carrying no `metadata` part at all — is refused with `400 .../errors/multipart-part-order`, before any payload byte is read.\n\n`metadata` is therefore REQUIRED on this endpoint. It is a JSON object that defines no member (send `{}`): analysis decrypts nothing and validates no certificate path or timestamp, so it takes no key and no option. Any member is refused with `400 .../errors/invalid-input` naming the member (never its value), before any payload byte is read.\n\n`file` has no size limit: the parse is incremental, so heap is a function of the chunk size and not of the payload.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/Pkcs7AnalysisStreamRequest"},"file":{"type":"string","format":"binary","description":"Binary PKCS#7/CMS data. No size limit."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"200":{"description":"Analysis successful: every signer and every recipient, the JOSE form a conversion produces and every tenant key it needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pkcs7AnalysisResponse"},"examples":{"Three signers (SignedData): converted to a General JSON JWS":{"description":"Three signers (SignedData): converted to a General JSON JWS","value":{"detectedFormat":{"standard":"PKCS#7","structure":"SignedData","contentTypeOID":"1.2.840.113549.1.7.2","encoding":"BER","payloadEmbedded":true},"signers":[{"index":0,"issuerDN":"CN=Legacy Audit Signer,O=Example Corp,C=US","serialNumber":"0x7E6D5C4B3A291807","signatureAlgorithmOID":"1.2.840.10045.4.3.2","signatureAlgorithm":"SHA256withECDSA","digestAlgorithmOID":"2.16.840.1.101.3.4.2.1","digestAlgorithm":"SHA-256","certificatePresent":true,"certificatePEM":"-----BEGIN CERTIFICATE-----\nMIIBizCCATKgAwIBAgIIfm1cSzopGAcwCgYIKoZIzj0EAwIwQjELMAkGA1UEBhMC\nVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEcMBoGA1UEAwwTTGVnYWN5IEF1ZGl0\nIFNpZ25lcjAeFw0yNTAxMDEwMDAwMDBaFw0zNTAxMDEwMDAwMDBaMEIxCzAJBgNV\nBAYTAlVTMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxHDAaBgNVBAMME0xlZ2FjeSBB\ndWRpdCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASn1qVamP1ZTGeY\n/MlX+/IeYeFTS2JP6qbPLzpy8y0JWzE6wcl719iEpGyjA1FrSk7AKAyLeknlxDn3\nAT3z6Hy1oxIwEDAOBgNVHQ8BAf8EBAMCBsAwCgYIKoZIzj0EAwIDRwAwRAIgX9sG\nk552dX5ve7sezUBvtBf1NnvhheMA/c+SN9owxLMCIG5x4ffxy8cFvwQwFlbbglO6\nJqe7Bsx46pZtNBNfdNdy\n-----END CERTIFICATE-----","certificateChainLength":1,"keyUsage":["digitalSignature","nonRepudiation"],"timestampPresent":false,"timestampTSA":null,"timestampGenTime":null},{"index":1,"issuerDN":"CN=Legacy Finance Signer,O=Example Corp,C=US","serialNumber":"0x4A3F2E1D5B6C7A8B","signatureAlgorithmOID":"1.2.840.113549.1.1.11","signatureAlgorithm":"SHA256withRSA","digestAlgorithmOID":"2.16.840.1.101.3.4.2.1","digestAlgorithm":"SHA-256","certificatePresent":true,"certificatePEM":"-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIISj8uHVtseoswDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE\nBhMCVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEeMBwGA1UEAwwVTGVnYWN5IEZp\nbmFuY2UgU2lnbmVyMB4XDTI1MDEwMTAwMDAwMFoXDTM1MDEwMTAwMDAwMFowRDEL\nMAkGA1UEBhMCVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEeMBwGA1UEAwwVTGVn\nYWN5IEZpbmFuY2UgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAupFb0w5XUIfnzsUqqzVU4fc3fDAE7ieGvnJJH3MuEGtupOQWgTZvsiMSRCTf\n/tT0mnP8lkcp0CjV3b4cOfGwsiEAXZNlc3CqllUbnysd6/WQ7Dit31SrKbMamyMW\nXt8SRnJ8Om3kYn7NQNR1zMHL2k9syLwS4+IU7QZWbfzqvnPXoi92YTAJ8FJN2qoV\nckO23weMEGDkmXKQYBtc7zplO758mcZbJ1Sf35MiaSTBAkzdeuNLEWB3KPlr1zuI\nw2Ozc8oIQRceozm1MSqM783bXWFUYGlg7UrmaGxSuPEuK+rXZdh2aJg2Fzl0Vzpd\n72B1I0b2HESvxhVeSIDN5gAoFwIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCBsAwDQYJ\nKoZIhvcNAQELBQADggEBADcLCFdrEyXlnBvxiALIVhIDKX4/mJ5FA8HDEAeSLzoy\nZAfr3dmmEWI9XJhuGfQoUX/epRgK0EpYxjXEcbfeZ7IV+dr9z8njb0ZIHXXoIN+z\nUGwRGOr5tJS1akBhmog0RrAoj86mGb2vAFnljkjzng1QjtaZKokz9uLOS16Fs2ql\nVXwpJdgqfQ6Ce6YB1Z9hk0b1yKF2aRd7yeOczHy4d8gKmB4aw/Jm5pq3wX5Pi+Wf\nTgmkcgYw9chCofKQveNFwRR0AelLzyIMxdq497DmcN9h85NtdoRFL+0XIby+ImgI\nGwmgfB0pbE5aQl4Cg9yHuX4Hms2ZTyR9ERoYG20uosI=\n-----END CERTIFICATE-----","certificateChainLength":1,"keyUsage":["digitalSignature","nonRepudiation"],"timestampPresent":false,"timestampTSA":null,"timestampGenTime":null},{"index":2,"issuerDN":"CN=Legacy Legal Signer,O=Example Corp,C=US","serialNumber":"0x1B2C3D4E5F607182","signatureAlgorithmOID":"1.2.840.113549.1.1.10","signatureAlgorithm":"RSASSA-PSS","digestAlgorithmOID":"2.16.840.1.101.3.4.2.1","digestAlgorithm":"SHA-256","certificatePresent":true,"certificatePEM":"-----BEGIN CERTIFICATE-----\nMIIDGDCCAgCgAwIBAgIIGyw9Tl9gcYIwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UE\nBhMCVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEcMBoGA1UEAwwTTGVnYWN5IExl\nZ2FsIFNpZ25lcjAeFw0yNTAxMDEwMDAwMDBaFw0zNTAxMDEwMDAwMDBaMEIxCzAJ\nBgNVBAYTAlVTMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxHDAaBgNVBAMME0xlZ2Fj\neSBMZWdhbCBTaWduZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9\ntVKd+zgmiLiLDB2qaZSmqJIVXR1nFWJjGKCuybl4MG9YitO1yILuZUIk43txL+jY\nDCfQ/AQeXbM274YV3KCuu5s69d+3H4un471KKSNtc6Htkpx8MwFtZEXvWhfhyENU\ny7H+2a52H9jYG+9UpQVP5yiv2LuTSqBOkQ0oeYpr0mGJuE5mDWkR4v0hDFUDgvPm\nxXFZmd87jzudsgj0L4Rqn8ceDoStjCFPSOSj02AXRUgQkPo8oP2syN6EMYBotlaa\nTtxIpaMlhnFvmrn12/bVt17EeLo45nepNq6jxoefIZbE4zDhuwDJBevTeC2XsKn8\nWb0UkHMowxwaB7qgBO4jAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIGwDANBgkqhkiG\n9w0BAQsFAAOCAQEAJIFUAW2rZi0HBirbsOghJu/LQ3Y29TZn6llSWXmnDehFCd4A\nW6Jv4hBny+yIGqabR+2HQk3LXhXZI/7Tyh/JjvzwWyXUvAsGlEcJzI0Ddy7iDV3P\n51/hsDm+xhVHd2EETW28BjL+ZyDDFzGVjr1B/Oxc4VBpguvP5e9CtTmYFZC7WDuG\n0/zQClwGKS64BN/1DhdKxPtaKSM5d0PE5yFNWFkK7JObpB1wlbeOHl1hmJicArEZ\nMWfYavuHYTemFzDaPtNqzlrHZnvN1FhEAy4RpjJTBF8nSU/fcN8m3OwDvbFbnk3n\nHdRxu94YFkJ4EkalMAPApgSXaHeYe3cih/80mQ==\n-----END CERTIFICATE-----","certificateChainLength":1,"keyUsage":["digitalSignature","nonRepudiation"],"timestampPresent":false,"timestampTSA":null,"timestampGenTime":null}],"recipients":[],"supportStatus":{"supported":true,"reasons":[]},"recommendations":{"migrationPath":"CONVERT_TO_JWS_JSON","requiredKeys":[{"purpose":"signing","issuerDN":"CN=Legacy Audit Signer,O=Example Corp,C=US","serialNumber":"0x7E6D5C4B3A291807","algorithm":"SHA256withECDSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this signer"},{"purpose":"signing","issuerDN":"CN=Legacy Finance Signer,O=Example Corp,C=US","serialNumber":"0x4A3F2E1D5B6C7A8B","algorithm":"SHA256withRSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this signer"},{"purpose":"signing","issuerDN":"CN=Legacy Legal Signer,O=Example Corp,C=US","serialNumber":"0x1B2C3D4E5F607182","algorithm":"RSASSA-PSS","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this signer"}],"estimatedComplexity":"HIGH"},"limits":{"maxActors":16}}},"Two recipients (EnvelopedData): one recipient's private key decrypts, the other needs a key admissible for encryption":{"description":"Two recipients (EnvelopedData): one recipient's private key decrypts, the other needs a key admissible for encryption","value":{"detectedFormat":{"standard":"PKCS#7","structure":"EnvelopedData","contentTypeOID":"1.2.840.113549.1.7.3","encoding":"BER","payloadEmbedded":true},"signers":[],"recipients":[{"index":0,"issuerDN":"CN=Records Office,O=Example Corp,C=US","serialNumber":"0x5A6B7C8D9E0F1021","keyEncryptionAlgorithmOID":"1.2.840.113549.1.1.1","keyEncryptionAlgorithm":"RSA","certificatePresent":false,"certificatePEM":null},{"index":1,"issuerDN":"CN=Treasury Archive,O=Example Corp,C=US","serialNumber":"0x2F4E6D8C0B1A3928","keyEncryptionAlgorithmOID":"1.2.840.113549.1.1.1","keyEncryptionAlgorithm":"RSA","certificatePresent":false,"certificatePEM":null}],"encryption":{"contentEncryptionAlgorithmOID":"2.16.840.1.101.3.4.1.42","contentEncryptionAlgorithm":"AES-256-CBC","ivLength":16},"supportStatus":{"supported":true,"reasons":[]},"recommendations":{"migrationPath":"CONVERT_TO_JWE_JSON","requiredKeys":[{"purpose":"encryption","issuerDN":"CN=Records Office,O=Example Corp,C=US","serialNumber":"0x5A6B7C8D9E0F1021","algorithm":"RSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this recipient (a certificate imported alone only verifies and is not admissible as a recipient)"},{"purpose":"encryption","issuerDN":"CN=Treasury Archive,O=Example Corp,C=US","serialNumber":"0x2F4E6D8C0B1A3928","algorithm":"RSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this recipient (a certificate imported alone only verifies and is not admissible as a recipient)"}],"decryptionRequirement":"ONE_RECIPIENT_PRIVATE_KEY","estimatedComplexity":"HIGH"},"limits":{"maxActors":16}}}}}}},"400":{"description":"Bad Request: the uploaded file is not a valid PKCS#7/CMS structure; a `metadata` part that is absent or arrives after `file` (`.../errors/multipart-part-order`); unreadable `metadata` JSON, or a `metadata` member (the part defines none) (`.../errors/invalid-input`, naming the member). PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Member the body does not define":{"description":"Member the body does not define","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Unsupported member 'validateCertificates' in the request body.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Invalid PKCS#7 Structure":{"description":"Invalid PKCS#7 Structure","value":{"type":"https://docs.ankatech.co/errors/invalid-pkcs7","title":"Invalid PKCS#7 Structure","status":400,"detail":"The provided file is not a valid PKCS#7/CMS structure. Expected formats: SignedData (.p7m, .p7s), EnvelopedData (.p7e), or SignedAndEnvelopedData with DER encoding.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"6f1d0d1a-2c9f-4a5e-9b3f-0f2a7c5e81d4","timestamp":1738117563,"extensions":{"expectedFormats":["SignedData (.p7m, .p7s)","EnvelopedData (.p7e)","SignedAndEnvelopedData"],"expectedEncoding":"DER (binary)","hint":"For JSON/JWK keys, use POST /api/v3/admin/tenants/{tenantId}/keys/import-key instead"}}},"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"401":{"description":"Unauthorized: missing, expired or invalid JWT token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Missing bearer token":{"description":"Missing bearer token","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to access this resource.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"b7c4a0e2-5d18-4f6a-8c31-9e0b2d7f44aa","timestamp":1738117563}}}}}},"403":{"description":"Forbidden: the token does not carry the migration.analyzePkcs7 scope, or the calling Application or Cryptographic Actor is SUSPENDED / DISABLED (effective-status containment — the refusal stands even for an already-issued, still-valid JWT); the tenant's own lifecycle state is NOT consulted on this route","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Scope missing from token":{"description":"Scope missing from token","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to perform this action.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"2a9e7c40-13b5-4de8-a0f7-6c1d8b3e57c9","timestamp":1738117563}}}}}},"503":{"description":"Service Unavailable: the calling Application/Actor effective status could not be verified. The check fails CLOSED — the request is refused rather than admitted while the status source (the platform's subject-status record, and the administration API that maintains it) is unreachable. This handler is otherwise pure compute over the caller's own PKCS#7. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"Unprocessable Entity: valid PKCS#7 carrying an actor the platform cannot identify: a signer identified by SubjectKeyIdentifier (UNSUPPORTED_SIGNER_IDENTIFIER), a recipient that is not RSA key transport named by issuer and serial number, including a key-transport recipient identified by SubjectKeyIdentifier (UNSUPPORTED_RECIPIENT_TYPE), a content type outside the three structures; or a content-encryption algorithm whose parameters encode a key size its RFC does not define (UNSUPPORTED_CONTENT_ENCRYPTION: RC2 effective key bits outside 1 to 1024, RFC 2268 section 6; a CAST5 keyLength outside 40 to 128 bits in 1-octet increments, RFC 2984 section 2.1), refused before any key is looked up or used. extensions.errorCode names the condition and the detail names the actor's index","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"RC2 parameters encoding an undefined effective key size":{"description":"RC2 parameters encoding an undefined effective key size","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The PKCS#7 RC2-CBC content-encryption parameters do not encode an effective key size RFC 2268 section 6 defines (1 to 1024 bits). Re-export the file and upload it again.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_CONTENT_ENCRYPTION"}}},"Signer identified by SubjectKeyIdentifier":{"description":"Signer identified by SubjectKeyIdentifier","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 signer at index 1 is identified by a SubjectKeyIdentifier. Only signers identified by issuer and serial number can be converted.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_SIGNER_IDENTIFIER"}}},"Recipient that is not RSA key transport":{"description":"Recipient that is not RSA key transport","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 recipient at index 0 is a KeyAgreeRecipientInfo. Only KeyTransRecipientInfo (RSA) is supported.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_RECIPIENT_TYPE"}}},"Recipient identified by SubjectKeyIdentifier":{"description":"Recipient identified by SubjectKeyIdentifier","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 recipient at index 1 is not a key-transport recipient named by issuer and serial number. Only KeyTransRecipientInfo (RSA) is supported.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_RECIPIENT_TYPE"}}}}}}},"500":{"description":"Internal Server Error: the upload could not be read, or an unattributable fault occurred","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Unattributable server fault":{"description":"Unattributable server fault","value":{"type":"https://docs.ankatech.co/errors/crypto","title":"Crypto Operation Error","status":500,"detail":"A cryptographic operation failed. Reference the correlationId for support.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"9e3a1b57-6f04-4c92-bd18-72c5a0e6f331","timestamp":1738117563}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/migration/stream/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/migration/convert-pkcs7-to-jose":{"post":{"tags":["Migration"],"summary":"Convert PKCS#7/CMS to JOSE format","description":"Converts legacy PKCS#7/CMS files to modern JOSE (JWE/JWS) format.\n\nThis is a **format conversion** (PKCS#7 container → JOSE container), not an algorithm conversion.\nClassical algorithms (RSA, ECDSA, AES) are preserved or upgraded (CBC→GCM).\n\n**Supported conversions** — every signer and every recipient of the input is carried:\n\n- SignedData (N signers) → JWS with one signature per signer key\n- EnvelopedData (M recipients) → JWE with one recipient per recipient key\n- SignedAndEnvelopedData (RFC 2315 section 11: N outer signers, M recipients) → JWE\n  whose plaintext is a JWS with one signature per outer signer key\n\nA kid names ONE entry: two SignerInfos (or two RecipientInfos) that resolve to the\nsame tenant key produce one signature (or one recipient) for that key, and both\nactors are reported against it in `signers[]` / `recipients[]`.\n\n**Serialization** (`serialization`, exact case):\n\n- `AUTO` (default): Compact (RFC 7515 section 7.1 / RFC 7516 section 7.1) for at most one\n  signer and at most one recipient; General JSON (RFC 7515 section 7.2.1 / RFC 7516\n  section 7.2.1) otherwise, on both layers of a nested result.\n- `COMPACT`: Compact; more than one signer or recipient is refused with 422\n  `INCOMPATIBLE_SERIALIZATION` before any key is looked up.\n- `JSON`: General JSON serialization, also for a single actor.\n\nAny other value is refused with 400 `invalid-input` naming `serialization`.\n\n**Process** (every refusal precedes the first use of a private key, except the three\nthat need the decrypting recipient's key: a decryption failure, an outer\nSignedAndEnvelopedData signer that does not verify, and SignedAndEnvelopedData\ncontent-encryption parameters the named cipher cannot use):\n\n1. Parse the PKCS#7 structure; refuse an unidentifiable actor or more actors than one\n   conversion accepts (`ACTOR_LIMIT_EXCEEDED`)\n2. Check `serialization` against the actor counts, and refuse a legacy SHA-1/MD5 signer\n   unless `algorithmPolicy.allowLegacyHash` is true\n3. Verify the CMS signature of every signer of a SignedData — always (the outer\n   signers of a SignedAndEnvelopedData are verified in step 6, under the\n   content-encryption key, before anything is signed)\n4. Resolve every signer and recipient to a tenant key by issuer and serial number; one\n   422 names every actor without one (`unresolvedActors`)\n5. Admit every key-lifecycle leg (decrypt, encrypt per recipient, sign per signer)\n6. Decrypt once with one recipient's private key (the `decryptionKid` recipient, or the\n   first recipient in encoded order whose key admits decryption), then re-sign and\n   re-encrypt\n\n**Prerequisites:** Import the signer and recipient keys via\nPOST /api/v3/admin/tenants/{tenantId}/keys/import-keystore. POST\n/api/v3/migration/analyze-pkcs7 lists every key the file needs.\n\n**Next steps:** the token can be taken to PQC algorithms with POST\n/api/v3/crypto/reencrypt or POST /api/v3/crypto/resign. A token with several\nrecipients or several signatures is accepted there as it is: `sourceKid` names the\nrecipient, or the signature, that is replaced, and every other entry is kept.\n","operationId":"convertPkcs7ToJose","requestBody":{"description":"PKCS#7/CMS conversion request with Base64-encoded data and options","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pkcs7ConversionRequest"},"examples":{"SignedData with three signers (AUTO: General JSON JWS)":{"summary":"Convert a three-signer SignedData","description":"No decryptionKid: nothing is decrypted. AUTO yields a General JSON JWS with one signature per signer.","value":{"pkcs7Data":"MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSABIIB...","serialization":"AUTO","targetFormat":"AUTO","algorithmPolicy":{"allowLegacyHash":false}}},"EnvelopedData with two recipients, decrypting recipient named":{"summary":"Convert a two-recipient EnvelopedData","description":"decryptionKid names the recipient whose private key decrypts; the other recipient needs a tenant key admissible for encryption (a key imported with its private part, or the encryption projection of a DUAL import). AUTO yields a General JSON JWE with one entry per recipient.","value":{"pkcs7Data":"MIIGHgYJKoZIhvcNAQcDoIIGDzCCBgsCAQAxggE...","decryptionKid":"treasury-archive-rsa-2019","serialization":"AUTO","targetFormat":"AUTO"}},"SignedAndEnvelopedData with one signer and one recipient (COMPACT)":{"summary":"Convert an RFC 2315 section 11 SignedAndEnvelopedData to a Compact JWE(JWS)","description":"No decryptionKid: the first recipient in encoded order whose key admits decryption decrypts. COMPACT is admitted because the file carries one signer and one recipient.","value":{"pkcs7Data":"MIIHkwYJKoZIhvcNAQcEoIIHhDCCB4ACAQExggE...","serialization":"COMPACT","targetFormat":"AUTO","algorithmPolicy":{"allowLegacyHash":false}}}}}},"required":true},"responses":{"200":{"description":"PKCS#7 conversion successful. joseToken is the Compact or the General JSON serialization, as serialization says, carrying one signature per signer key and one recipient per recipient key; signers[] and recipients[] map each actor to its kid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pkcs7ConversionResponse"},"examples":{"Two signers: General JSON JWS":{"description":"Two signers: General JSON JWS","value":{"joseToken":"{\"payload\":\"eyJkYXRhIjoiUVc1cmFWTmxZM1Z5WlE...\",\"signatures\":[{\"protected\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImZpbmFuY2Utc2lnbmVyLXJzYS0yMDIwIn0\",\"signature\":\"kTn9mQ2vXw...\"},{\"protected\":\"eyJhbGciOiJQUzI1NiIsImtpZCI6ImxlZ2FsLXNpZ25lci1wc3MtMjAyMSJ9\",\"signature\":\"b3RoZXJzaWc...\"}]}","serialization":"JSON","structure":"JWS"}},"One signer: Compact JWS":{"description":"One signer: Compact JWS","value":{"joseToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImZpbmFuY2Utc2lnbmVyLXJzYS0yMDIwIn0.eyJkYXRhIjoiUVc1cmFWTmxZM1Z5WlE...kTn9mQ2vXw...","serialization":"COMPACT","structure":"JWS"}}}}}},"400":{"description":"Bad Request: invalid Base64; a serialization or targetFormat outside its vocabulary (exact case), refused before the file is decoded; a decryptionKid whose certificate matches none of the recipients; a member the body does not define, at its root or in algorithmPolicy (invalid-input naming the member, never its value); or a file that is not a valid PKCS#7/CMS structure. A named decryptionKid's Capability Grant is decided, and its stateful constraints (max_usage, rate_limit, revoke_on_use) charged, before that key is resolved, as on the crypto plane: a decryptionKid refused here has already been charged one use of that grant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Member the body does not define":{"description":"Member the body does not define","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Unsupported member 'validateCertificates' in the request body.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"serialization outside AUTO, COMPACT and JSON":{"description":"serialization outside AUTO, COMPACT and JSON","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"serialization must be one of [AUTO, COMPACT, JSON] (exact case); received 'json'.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Invalid Base64":{"description":"Invalid Base64","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"pkcs7Data is not valid Base64.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563}},"Invalid PKCS#7 Structure (JSON file provided)":{"description":"Invalid PKCS#7 Structure (JSON file provided)","value":{"type":"https://docs.ankatech.co/errors/invalid-pkcs7","title":"Invalid PKCS#7 Structure","status":400,"detail":"The provided file is not a valid PKCS#7/CMS structure. Expected formats: SignedData (.p7m, .p7s), EnvelopedData (.p7e), or SignedAndEnvelopedData with DER encoding.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563,"extensions":{"expectedFormats":["SignedData (.p7m, .p7s)","EnvelopedData (.p7e)","SignedAndEnvelopedData"],"expectedEncoding":"DER (binary)","hint":"For JSON/JWK keys, use POST /api/v3/admin/tenants/{tenantId}/keys/import-key instead"}}}}}}},"401":{"description":"Unauthorized: missing, expired, or invalid JWT token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Missing or expired bearer token":{"description":"Missing or expired bearer token","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication required: provide a valid bearer token.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563}}}}}},"403":{"description":"Forbidden: the token is valid but lacks the required 'migration.convertPkcs7' scope, or the caller's tenant is in a state that does not admit cryptographic operations (SUSPENDED, CLOSED), or the calling Application or Cryptographic Actor is SUSPENDED / DISABLED (effective-status containment, as on the crypto plane — the refusal stands even for an already-issued, still-valid JWT, and precedes every Capability Grant decision). Also 403, before any actor is resolved and with no extensions.actor, when the token carries no Cryptographic Actor, or the calling Actor holds no Capability Grant at all for an operation the upload needs (REENCRYPT for its recipients, RESIGN for its signers), or none to REENCRYPT from the named decryptionKid - answered the same whether or not that key exists. Also 403 when a leg of one of the upload's signers or recipients is refused, first by the Capability Grant resolver (the calling Actor holds no active grant for the leg's decision - REENCRYPT from the recipient that opens the content to every recipient, itself included, and RESIGN from every signer's key to itself, as /reencrypt and /resign decide theirs; DECRYPT, ENCRYPT and SIGN grants admit no conversion, and a REENCRYPT grant covers only the recipients it names beside the opening one; a REENCRYPT or RESIGN grant is requested with exactly two assetKids, so an upload with N recipients needs N-1 pair grants, one REENCRYPT [opener, recipient] per other recipient (any of them also covers the opener's own pair), a single-recipient upload the in-place REENCRYPT grant, requested with the kid twice (assetKids [opener, opener]) and stored as [opener], and each signer the in-place RESIGN [signer, signer]; there is no wildcard REENCRYPT or RESIGN grant - or a grant constraint is violated, or its exchange context is suspended), then by the tenant's key-lifecycle policy (the opening recipient on the DECRYPT row, every recipient on the REENCRYPT row, every signer on the VERIFY then the RESIGN row). Without a decryptionKid the recipient that opens the content is the FIRST one, in encoded order, whose key can decrypt, and every REENCRYPT decision is taken from it: a caller whose grants name a later recipient's key must name that key in decryptionKid. For a leg refusal, extensions.actor names that actor by its index in its set, its role (signer | recipient) and the refused leg (sign | decrypt | encrypt); the detail is the refusing gate's generic detail and no kid is named. Every actor has resolved by then, and nothing has been decrypted or signed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Insufficient scope":{"description":"Insufficient scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Access denied: the 'migration.convertPkcs7' scope is required for this operation.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563}},"Calling Actor holds no REENCRYPT grant naming the third recipient":{"description":"Calling Actor holds no REENCRYPT grant naming the third recipient","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: Actor has a grant for this capability but not for the requested asset","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"actor":{"index":2,"role":"recipient","leg":"encrypt"}}}},"Key-lifecycle policy refuses re-encrypting to the third recipient":{"description":"Key-lifecycle policy refuses re-encrypting to the third recipient","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: not permitted for this key","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"actor":{"index":2,"role":"recipient","leg":"encrypt"}}}}}}}},"503":{"description":"Service Unavailable: either the caller's tenant, Application or Actor effective status could not be verified (the containment fails CLOSED — the request is refused rather than admitted while the status source (the platform's status records and the administration API that maintains them) is unreachable), or the status of the Exchange Context a Capability Grant decision depends on could not be verified (fail-closed), or the key-protection backend that unwraps the actor keys is transiently unavailable (with Retry-After), misconfigured (without Retry-After), or changing its binding for the tenant (with Retry-After). Each is decided before any output is produced. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key-protection backend is temporarily unavailable. Please retry shortly.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/migration/convert-pkcs7-to-jose"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"404":{"description":"Not Found: the referenced decryption/signing kid (or the matching certificate) does not exist for this tenant. A kid belonging to another tenant yields an identical 404 (no cross-tenant existence oracle). A named decryptionKid's Capability Grant is decided, and its stateful constraints (max_usage, rate_limit, revoke_on_use) charged, before that key is resolved, as on the crypto plane: a decryptionKid answered 404 has already been charged one use of that grant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Recipient kid not found":{"description":"Recipient kid not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Not Found","status":404,"detail":"No key or certificate found for kid 'legacy-rsa-recipient-2024'.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563}}}}}},"409":{"description":"Conflict: the environment's key-protection backend is not configured, so no tenant key can be unwrapped. Refused when the first actor key is used, before any output is produced. extensions.setupState names the setup step that is missing. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when a signer's or recipient's key is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>): no signature or encryption is produced with such a key. The refusal is decided before any output exists, and names the refused actor under <code>extensions.actor</code> (<code>index</code>, <code>role</code>, <code>leg</code>), never its key. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Key-protection backend not configured for this environment":{"description":"Key-protection backend not configured for this environment","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key-protection backend for this environment has not been configured yet. Complete the environment setup before creating keys.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"setupState":"KEY_BACKEND_UNCONFIGURED"}}},"Signer key at its usage limit":{"description":"Signer key at its usage limit","value":{"type":"https://docs.ankatech.co/errors/key-material-usage-exhausted","title":"Key Material Usage Exhausted","status":409,"detail":"Operation 'sign' is refused: the key's current material has reached its usage limit. Rotate the key to protect new data; what it already protected can still be decrypted and verified.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"code":"KEY_MATERIAL_USAGE_EXHAUSTED"}}}}}}},"413":{"description":"Payload Too Large: decoded PKCS#7 data exceeds the configured synchronous limit (use POST /api/v3/migration/stream/convert-pkcs7-to-jose for larger files)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Decoded payload over 5MB":{"description":"Decoded payload over 5MB","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Decoded PKCS#7 data exceeds the 5MB synchronous limit. Use the streaming endpoint for larger files.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563}}}}}},"422":{"description":"Unprocessable Entity, decided before the first private key is used except for a decryption failure and an outer SignedAndEnvelopedData signer that does not verify, which need the decrypting recipient's key. extensions.errorCode: INCOMPATIBLE_SERIALIZATION (COMPACT with more than one signer or recipient), ACTOR_LIMIT_EXCEEDED (more signers and recipients together than one conversion accepts), UNSUPPORTED_SIGNER_IDENTIFIER / UNSUPPORTED_RECIPIENT_TYPE (an actor that cannot be identified), LEGACY_ALGORITHM_REJECTED (a SHA-1/MD5 signer without allowLegacyHash), INCOMPATIBLE_TARGET_FORMAT; missing-private-key with unresolvedActors[] naming EVERY actor without a tenant key (errorCode MISSING_SIGNER_KEY, MISSING_RECIPIENT_KEY, MISSING_RECIPIENT_PRIVATE_KEY, or MISSING_ACTOR_KEYS when they differ); unprocessable-entity with errorCode AMBIGUOUS_ACTOR_KEY when an actor's certificate is held under more than one tenant key admissible for its role (the detail names the actor's index, issuer and serial, never a kid); a content-encryption algorithm whose parameters encode a key size its RFC does not define (UNSUPPORTED_CONTENT_ENCRYPTION: RC2 effective key bits outside 1 to 1024, RFC 2268 section 6; a CAST5 keyLength outside 40 to 128 bits in 1-octet increments, RFC 2984 section 2.1), refused before any key is looked up or used; unprocessable-entity when the CMS signature of a signer does not verify (always checked, naming the first failing index; an outer SignedAndEnvelopedData signer is verified under the very certificate its tenant key was bound to, so a signature that verifies only under another certificate the upload carries is this refusal too); decryption-failed with one body whichever decryption step failed, raised at the content: a PKCS #1 v1.5 encrypted key that does not unwrap, or unwraps to a key of the wrong length, is replaced by a random key (RFC 3218 section 2.3.2), so it fails where a corrupted ciphertext fails. The outer signers of a SignedAndEnvelopedData are verified in the RFC 2315 section 11.2 form only (the encryptedDigest encrypted under the content-encryption key, RSA signers only): a plain-form encryptedDigest is SIGNED_AND_ENVELOPED_DIGEST_NOT_ENCRYPTED and a non-RSA signer UNSUPPORTED_SIGNED_AND_ENVELOPED_SIGNER_KEY; a SignedAndEnvelopedData with no outer signer is SIGNED_AND_ENVELOPED_WITHOUT_SIGNER","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"RC2 parameters encoding an undefined effective key size":{"description":"RC2 parameters encoding an undefined effective key size","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The PKCS#7 RC2-CBC content-encryption parameters do not encode an effective key size RFC 2268 section 6 defines (1 to 1024 bits). Re-export the file and upload it again.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_CONTENT_ENCRYPTION"}}},"COMPACT requested for three signers":{"description":"COMPACT requested for three signers","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"serialization COMPACT represents at most one signer and one recipient; the PKCS#7 input carries 3 signer(s) and 0 recipient(s). Use AUTO or JSON.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"INCOMPATIBLE_SERIALIZATION"}}},"More signers and recipients than one conversion accepts":{"description":"More signers and recipients than one conversion accepts","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The PKCS#7 input carries 14 signer(s) and 3 recipient(s); a conversion names at most 16 signers and recipients together.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"ACTOR_LIMIT_EXCEEDED"}}},"Signer identified by SubjectKeyIdentifier":{"description":"Signer identified by SubjectKeyIdentifier","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 signer at index 1 is identified by a SubjectKeyIdentifier. Only signers identified by issuer and serial number can be converted.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_SIGNER_IDENTIFIER"}}},"Signer on a legacy SHA-1 digest":{"description":"Signer on a legacy SHA-1 digest","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 signer at index 1 uses a legacy hash algorithm: digest SHA-1 (OID: 1.3.14.3.2.26). This is considered insecure. Set algorithmPolicy.allowLegacyHash=true to allow.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"LEGACY_ALGORITHM_REJECTED"}}},"A signer and a recipient without a tenant key":{"description":"A signer and a recipient without a tenant key","value":{"type":"https://docs.ankatech.co/errors/missing-private-key","title":"Missing Key for a PKCS#7 Signer or Recipient","status":422,"detail":"One or more signers or recipients of the PKCS#7 structure have no key of this tenant admissible for what they need: a signer a key that signs, the decrypting recipient a key that decrypts and encrypts, any other recipient a key that encrypts. Each one is listed in unresolvedActors with its requirement; import through importEndpoint a key admissible for it, with its private part (a certificate imported alone only verifies), and retry.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"MISSING_ACTOR_KEYS","unresolvedActors":[{"index":1,"role":"signer","requirement":"sign","issuerDN":"CN=Legacy Legal Signer,O=Example Corp,C=US","serialNumber":"0x1B2C3D4E5F607182"},{"index":2,"role":"recipient","requirement":"encrypt","issuerDN":"CN=Records Office,O=Example Corp,C=US","serialNumber":"0x5A6B7C8D9E0F1021"}],"importEndpoint":"POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore"}}},"Signer certificate held under two signing keys of the tenant":{"description":"Signer certificate held under two signing keys of the tenant","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The PKCS#7 signer at index 0 with issuer CN=Legacy Legal Signer,O=Example Corp,C=US and serial 0x1B2C3D4E5F607182 matches more than one key of this tenant admissible to sign, so it cannot be resolved to a single key. Remove the duplicate certificate import and retry.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"AMBIGUOUS_ACTOR_KEY"}}},"Signer signature does not verify":{"description":"Signer signature does not verify","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"PKCS#7 signature verification failed for the signer at index 2.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"SignedAndEnvelopedData outer signer without an RSA key":{"description":"SignedAndEnvelopedData outer signer without an RSA key","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The outer signer at index 1 of this PKCS#7 SignedAndEnvelopedData does not hold an RSA key. RFC 2315 section 11.2 defines the digest encryption of this structure for rsaEncryption only.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_SIGNED_AND_ENVELOPED_SIGNER_KEY"}}},"Missing recipient private key (EnvelopedData)":{"description":"Missing recipient private key (EnvelopedData)","value":{"type":"https://docs.ankatech.co/errors/missing-private-key","title":"Missing Key for a PKCS#7 Signer or Recipient","status":422,"detail":"One or more signers or recipients of the PKCS#7 structure have no key of this tenant admissible for what they need: a signer a key that signs, the decrypting recipient a key that decrypts and encrypts, any other recipient a key that encrypts. Each one is listed in unresolvedActors with its requirement; import through importEndpoint a key admissible for it, with its private part (a certificate imported alone only verifies), and retry.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1738117563,"extensions":{"errorCode":"MISSING_RECIPIENT_PRIVATE_KEY","unresolvedActors":[{"index":0,"role":"recipient","requirement":"decrypt","issuerDN":"CN=John Doe,O=AnkaTech,C=US","serialNumber":"0x4A3F2E1D5B6C7A8B"}],"importEndpoint":"POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore"}}},"Decryption Failed (wrong recipient key or corrupted ciphertext)":{"description":"Decryption Failed (wrong recipient key or corrupted ciphertext)","value":{"type":"https://docs.ankatech.co/errors/decryption-failed","title":"Decryption Failed","status":422,"detail":"PKCS#7 decryption failed. The content could not be decrypted with the private key of the decrypting recipient: either that key does not match the public key the file was encrypted to, or the file's encrypted key, encrypted content or content-encryption algorithm cannot be processed. Verify that the correct private key has been imported via POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563,"extensions":{"errorCode":"DECRYPTION_FAILED","possibleCauses":["Private key does not match the public key used for encryption","PKCS#7 data is corrupted or truncated","Wrong recipient key selected in multi-recipient PKCS#7"],"resolution":"Verify the correct private key is imported via POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore"}}},"Incompatible targetFormat":{"description":"Incompatible targetFormat","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"targetFormat 'JWS' requires a signed structure; the supplied PKCS#7 is 'EnvelopedData' — use 'JWE' or 'AUTO'.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563,"extensions":{"errorCode":"INCOMPATIBLE_TARGET_FORMAT"}}},"SignedAndEnvelopedData signer digest not encrypted (non-RFC 2315 form)":{"description":"SignedAndEnvelopedData signer digest not encrypted (non-RFC 2315 form)","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The outer signer at index 0 of this PKCS#7 SignedAndEnvelopedData carries its encryptedDigest unencrypted, a form outside RFC 2315 section 11.2, which requires it encrypted under the content-encryption key. Only the RFC 2315 form can be converted.","instance":"/api/v3/migration/convert-pkcs7-to-jose","timestamp":1738117563,"extensions":{"errorCode":"SIGNED_AND_ENVELOPED_DIGEST_NOT_ENCRYPTED"}}}}}}},"429":{"description":"Too Many Requests: a key-protection rate limit or a cloud KMS quota refused the unwrap of an actor key. The backend is healthy and deliberately refusing, so this is neither a setup problem nor an outage. The Retry-After header carries the interval to wait","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection unwrap rate limit exceeded":{"description":"Key-protection unwrap rate limit exceeded","value":{"type":"https://docs.ankatech.co/errors/key-protection-rate-limited","title":"Too many key-protection requests","status":429,"detail":"Too many key-protection operations were attempted. Please retry after the indicated interval.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}}}}}},"500":{"description":"Internal Server Error: a genuinely-unexpected server fault (NOT a client-caused condition — those map to 4xx above)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/migration/convert-pkcs7-to-jose","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/migration/analyze-pkcs7":{"post":{"tags":["Migration"],"summary":"Analyze PKCS#7/CMS file structure","description":"Introspects PKCS#7/CMS binary data and returns structured metadata including\nsigners, recipients, algorithms, certificates, and conversion feasibility.\n\nSupports SignedData, EnvelopedData and SignedAndEnvelopedData (RFC 2315 section 11,\nwhose signers are read from the outer `signerInfos`). Nothing is decrypted, no\ncertificate path or timestamp is validated and no key is needed, so `pkcs7Data` is\nthe whole request.\n\n**Every actor is reported.** `signers` and `recipients` list each one in encoded\norder, and the count of either never makes the structure unsupported:\n\n- `recommendations.migrationPath` is the JOSE form a conversion under\n  `serialization` AUTO produces — `_COMPACT` for at most one signer and at most one\n  recipient, `_JSON` (General JSON serialization, RFC 7515 section 7.2.1 /\n  RFC 7516 section 7.2.1) otherwise.\n- `recommendations.requiredKeys` names every signer (`signing`) and every recipient\n  (`encryption`) by the issuer and serial number the tenant key's certificate must\n  carry. For an encrypted structure, `decryptionRequirement` states that the private\n  key of one recipient is needed as well.\n- `supportStatus` runs every check a conversion runs WITHOUT a key and reports each\n  refusal with the conversion's own code, so `supported` is false exactly when the\n  conversion would refuse the structure before any key: `ACTOR_LIMIT_EXCEEDED`; a\n  SignedAndEnvelopedData with no outer signer (`SIGNED_AND_ENVELOPED_WITHOUT_SIGNER`),\n  a non-RSA outer signer (`UNSUPPORTED_SIGNED_AND_ENVELOPED_SIGNER_KEY`), a plain-form\n  encryptedDigest (`SIGNED_AND_ENVELOPED_DIGEST_NOT_ENCRYPTED`) or a content cipher it\n  does not decrypt (`UNSUPPORTED_CONTENT_ENCRYPTION`); and `INVALID_PKCS7` for what\n  the conversion answers 400 `invalid-pkcs7` (a SignedData with no signer or no\n  encapsulated content). A SHA-1/MD5 signer is `LEGACY_ALGORITHM_REJECTED` with\n  severity WARNING: the conversion refuses it unless `algorithmPolicy.allowLegacyHash`\n  is true. An RFC 3161 timestamp is reported as `TIMESTAMP_DETECTED` (INFO).\n- `limits.maxActors` is that bound: the most signers and recipients, together, one\n  conversion accepts. It is the crypto plane's one actor bound, the value the\n  conversion enforces and the same one a multi-key JOSE token is held to, published\n  here so a client knows it before converting.\n","operationId":"analyzePkcs7","requestBody":{"description":"PKCS#7/CMS analysis request with Base64-encoded DER data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pkcs7AnalysisRequest"},"examples":{"Analyze a SignedData file":{"description":"Analyze a SignedData file","value":{"pkcs7Data":"MIIGHgYJKoZIhvcNAQcCoIIGDzCCBgsCAQAxggE..."}}}}},"required":true},"responses":{"200":{"description":"PKCS#7 analysis successful: every signer and every recipient, the JOSE form a conversion produces and every tenant key it needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pkcs7AnalysisResponse"},"examples":{"Three signers (SignedData): converted to a General JSON JWS":{"description":"Three signers (SignedData): converted to a General JSON JWS","value":{"detectedFormat":{"standard":"PKCS#7","structure":"SignedData","contentTypeOID":"1.2.840.113549.1.7.2","encoding":"BER","payloadEmbedded":true},"signers":[{"index":0,"issuerDN":"CN=Legacy Audit Signer,O=Example Corp,C=US","serialNumber":"0x7E6D5C4B3A291807","signatureAlgorithmOID":"1.2.840.10045.4.3.2","signatureAlgorithm":"SHA256withECDSA","digestAlgorithmOID":"2.16.840.1.101.3.4.2.1","digestAlgorithm":"SHA-256","certificatePresent":true,"certificatePEM":"-----BEGIN CERTIFICATE-----\nMIIBizCCATKgAwIBAgIIfm1cSzopGAcwCgYIKoZIzj0EAwIwQjELMAkGA1UEBhMC\nVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEcMBoGA1UEAwwTTGVnYWN5IEF1ZGl0\nIFNpZ25lcjAeFw0yNTAxMDEwMDAwMDBaFw0zNTAxMDEwMDAwMDBaMEIxCzAJBgNV\nBAYTAlVTMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxHDAaBgNVBAMME0xlZ2FjeSBB\ndWRpdCBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASn1qVamP1ZTGeY\n/MlX+/IeYeFTS2JP6qbPLzpy8y0JWzE6wcl719iEpGyjA1FrSk7AKAyLeknlxDn3\nAT3z6Hy1oxIwEDAOBgNVHQ8BAf8EBAMCBsAwCgYIKoZIzj0EAwIDRwAwRAIgX9sG\nk552dX5ve7sezUBvtBf1NnvhheMA/c+SN9owxLMCIG5x4ffxy8cFvwQwFlbbglO6\nJqe7Bsx46pZtNBNfdNdy\n-----END CERTIFICATE-----","certificateChainLength":1,"keyUsage":["digitalSignature","nonRepudiation"],"timestampPresent":false,"timestampTSA":null,"timestampGenTime":null},{"index":1,"issuerDN":"CN=Legacy Finance Signer,O=Example Corp,C=US","serialNumber":"0x4A3F2E1D5B6C7A8B","signatureAlgorithmOID":"1.2.840.113549.1.1.11","signatureAlgorithm":"SHA256withRSA","digestAlgorithmOID":"2.16.840.1.101.3.4.2.1","digestAlgorithm":"SHA-256","certificatePresent":true,"certificatePEM":"-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIISj8uHVtseoswDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE\nBhMCVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEeMBwGA1UEAwwVTGVnYWN5IEZp\nbmFuY2UgU2lnbmVyMB4XDTI1MDEwMTAwMDAwMFoXDTM1MDEwMTAwMDAwMFowRDEL\nMAkGA1UEBhMCVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEeMBwGA1UEAwwVTGVn\nYWN5IEZpbmFuY2UgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAupFb0w5XUIfnzsUqqzVU4fc3fDAE7ieGvnJJH3MuEGtupOQWgTZvsiMSRCTf\n/tT0mnP8lkcp0CjV3b4cOfGwsiEAXZNlc3CqllUbnysd6/WQ7Dit31SrKbMamyMW\nXt8SRnJ8Om3kYn7NQNR1zMHL2k9syLwS4+IU7QZWbfzqvnPXoi92YTAJ8FJN2qoV\nckO23weMEGDkmXKQYBtc7zplO758mcZbJ1Sf35MiaSTBAkzdeuNLEWB3KPlr1zuI\nw2Ozc8oIQRceozm1MSqM783bXWFUYGlg7UrmaGxSuPEuK+rXZdh2aJg2Fzl0Vzpd\n72B1I0b2HESvxhVeSIDN5gAoFwIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCBsAwDQYJ\nKoZIhvcNAQELBQADggEBADcLCFdrEyXlnBvxiALIVhIDKX4/mJ5FA8HDEAeSLzoy\nZAfr3dmmEWI9XJhuGfQoUX/epRgK0EpYxjXEcbfeZ7IV+dr9z8njb0ZIHXXoIN+z\nUGwRGOr5tJS1akBhmog0RrAoj86mGb2vAFnljkjzng1QjtaZKokz9uLOS16Fs2ql\nVXwpJdgqfQ6Ce6YB1Z9hk0b1yKF2aRd7yeOczHy4d8gKmB4aw/Jm5pq3wX5Pi+Wf\nTgmkcgYw9chCofKQveNFwRR0AelLzyIMxdq497DmcN9h85NtdoRFL+0XIby+ImgI\nGwmgfB0pbE5aQl4Cg9yHuX4Hms2ZTyR9ERoYG20uosI=\n-----END CERTIFICATE-----","certificateChainLength":1,"keyUsage":["digitalSignature","nonRepudiation"],"timestampPresent":false,"timestampTSA":null,"timestampGenTime":null},{"index":2,"issuerDN":"CN=Legacy Legal Signer,O=Example Corp,C=US","serialNumber":"0x1B2C3D4E5F607182","signatureAlgorithmOID":"1.2.840.113549.1.1.10","signatureAlgorithm":"RSASSA-PSS","digestAlgorithmOID":"2.16.840.1.101.3.4.2.1","digestAlgorithm":"SHA-256","certificatePresent":true,"certificatePEM":"-----BEGIN CERTIFICATE-----\nMIIDGDCCAgCgAwIBAgIIGyw9Tl9gcYIwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UE\nBhMCVVMxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEcMBoGA1UEAwwTTGVnYWN5IExl\nZ2FsIFNpZ25lcjAeFw0yNTAxMDEwMDAwMDBaFw0zNTAxMDEwMDAwMDBaMEIxCzAJ\nBgNVBAYTAlVTMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxHDAaBgNVBAMME0xlZ2Fj\neSBMZWdhbCBTaWduZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9\ntVKd+zgmiLiLDB2qaZSmqJIVXR1nFWJjGKCuybl4MG9YitO1yILuZUIk43txL+jY\nDCfQ/AQeXbM274YV3KCuu5s69d+3H4un471KKSNtc6Htkpx8MwFtZEXvWhfhyENU\ny7H+2a52H9jYG+9UpQVP5yiv2LuTSqBOkQ0oeYpr0mGJuE5mDWkR4v0hDFUDgvPm\nxXFZmd87jzudsgj0L4Rqn8ceDoStjCFPSOSj02AXRUgQkPo8oP2syN6EMYBotlaa\nTtxIpaMlhnFvmrn12/bVt17EeLo45nepNq6jxoefIZbE4zDhuwDJBevTeC2XsKn8\nWb0UkHMowxwaB7qgBO4jAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIGwDANBgkqhkiG\n9w0BAQsFAAOCAQEAJIFUAW2rZi0HBirbsOghJu/LQ3Y29TZn6llSWXmnDehFCd4A\nW6Jv4hBny+yIGqabR+2HQk3LXhXZI/7Tyh/JjvzwWyXUvAsGlEcJzI0Ddy7iDV3P\n51/hsDm+xhVHd2EETW28BjL+ZyDDFzGVjr1B/Oxc4VBpguvP5e9CtTmYFZC7WDuG\n0/zQClwGKS64BN/1DhdKxPtaKSM5d0PE5yFNWFkK7JObpB1wlbeOHl1hmJicArEZ\nMWfYavuHYTemFzDaPtNqzlrHZnvN1FhEAy4RpjJTBF8nSU/fcN8m3OwDvbFbnk3n\nHdRxu94YFkJ4EkalMAPApgSXaHeYe3cih/80mQ==\n-----END CERTIFICATE-----","certificateChainLength":1,"keyUsage":["digitalSignature","nonRepudiation"],"timestampPresent":false,"timestampTSA":null,"timestampGenTime":null}],"recipients":[],"supportStatus":{"supported":true,"reasons":[]},"recommendations":{"migrationPath":"CONVERT_TO_JWS_JSON","requiredKeys":[{"purpose":"signing","issuerDN":"CN=Legacy Audit Signer,O=Example Corp,C=US","serialNumber":"0x7E6D5C4B3A291807","algorithm":"SHA256withECDSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this signer"},{"purpose":"signing","issuerDN":"CN=Legacy Finance Signer,O=Example Corp,C=US","serialNumber":"0x4A3F2E1D5B6C7A8B","algorithm":"SHA256withRSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this signer"},{"purpose":"signing","issuerDN":"CN=Legacy Legal Signer,O=Example Corp,C=US","serialNumber":"0x1B2C3D4E5F607182","algorithm":"RSASSA-PSS","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this signer"}],"estimatedComplexity":"HIGH"},"limits":{"maxActors":16}}},"Two recipients (EnvelopedData): one recipient's private key decrypts, the other needs a key admissible for encryption":{"description":"Two recipients (EnvelopedData): one recipient's private key decrypts, the other needs a key admissible for encryption","value":{"detectedFormat":{"standard":"PKCS#7","structure":"EnvelopedData","contentTypeOID":"1.2.840.113549.1.7.3","encoding":"BER","payloadEmbedded":true},"signers":[],"recipients":[{"index":0,"issuerDN":"CN=Records Office,O=Example Corp,C=US","serialNumber":"0x5A6B7C8D9E0F1021","keyEncryptionAlgorithmOID":"1.2.840.113549.1.1.1","keyEncryptionAlgorithm":"RSA","certificatePresent":false,"certificatePEM":null},{"index":1,"issuerDN":"CN=Treasury Archive,O=Example Corp,C=US","serialNumber":"0x2F4E6D8C0B1A3928","keyEncryptionAlgorithmOID":"1.2.840.113549.1.1.1","keyEncryptionAlgorithm":"RSA","certificatePresent":false,"certificatePEM":null}],"encryption":{"contentEncryptionAlgorithmOID":"2.16.840.1.101.3.4.1.42","contentEncryptionAlgorithm":"AES-256-CBC","ivLength":16},"supportStatus":{"supported":true,"reasons":[]},"recommendations":{"migrationPath":"CONVERT_TO_JWE_JSON","requiredKeys":[{"purpose":"encryption","issuerDN":"CN=Records Office,O=Example Corp,C=US","serialNumber":"0x5A6B7C8D9E0F1021","algorithm":"RSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this recipient (a certificate imported alone only verifies and is not admissible as a recipient)"},{"purpose":"encryption","issuerDN":"CN=Treasury Archive,O=Example Corp,C=US","serialNumber":"0x2F4E6D8C0B1A3928","algorithm":"RSA","importHint":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this recipient (a certificate imported alone only verifies and is not admissible as a recipient)"}],"decryptionRequirement":"ONE_RECIPIENT_PRIVATE_KEY","estimatedComplexity":"HIGH"},"limits":{"maxActors":16}}}}}}},"400":{"description":"Bad Request: invalid Base64; a member the body does not define (invalid-input naming the member, never its value); or a file that is not a valid PKCS#7/CMS structure","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Member the body does not define":{"description":"Member the body does not define","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Unsupported member 'validateCertificates' in the request body.","instance":"/api/v3/migration/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400}},"Invalid PKCS#7 Structure":{"description":"Invalid PKCS#7 Structure","value":{"type":"https://docs.ankatech.co/errors/invalid-pkcs7","title":"Invalid PKCS#7 Structure","status":400,"detail":"The provided file is not a valid PKCS#7/CMS structure. Expected formats: SignedData (.p7m, .p7s), EnvelopedData (.p7e), or SignedAndEnvelopedData with DER encoding.","instance":"/api/v3/migration/analyze-pkcs7","timestamp":1738117563,"extensions":{"expectedFormats":["SignedData (.p7m, .p7s)","EnvelopedData (.p7e)","SignedAndEnvelopedData"],"expectedEncoding":"DER (binary)","hint":"For JSON/JWK keys, use POST /api/v3/admin/tenants/{tenantId}/keys/import-key instead"}}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Forbidden: the token does not carry the migration.analyzePkcs7 scope, or the calling Application or Cryptographic Actor is SUSPENDED / DISABLED (effective-status containment — the refusal stands even for an already-issued, still-valid JWT, and `detail` then reads `application-suspended` / `actor-suspended`); the tenant's own lifecycle state is NOT consulted on this route","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the calling Application/Actor effective status could not be verified. The check fails CLOSED — the request is refused rather than admitted while the status source (the platform's subject-status record, and the administration API that maintains it) is unreachable. This handler is otherwise pure compute over the caller's own PKCS#7. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: decoded PKCS#7 data exceeds the configured migration upload limit (default 5 000 000 bytes)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"Unprocessable Entity: valid PKCS#7 carrying an actor the platform cannot identify: a signer identified by SubjectKeyIdentifier (UNSUPPORTED_SIGNER_IDENTIFIER), a recipient that is not RSA key transport named by issuer and serial number, including a key-transport recipient identified by SubjectKeyIdentifier (UNSUPPORTED_RECIPIENT_TYPE), a content type outside the three structures; or a content-encryption algorithm whose parameters encode a key size its RFC does not define (UNSUPPORTED_CONTENT_ENCRYPTION: RC2 effective key bits outside 1 to 1024, RFC 2268 section 6; a CAST5 keyLength outside 40 to 128 bits in 1-octet increments, RFC 2984 section 2.1), refused before any key is looked up or used. extensions.errorCode names the condition and the detail names the actor's index","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"RC2 parameters encoding an undefined effective key size":{"description":"RC2 parameters encoding an undefined effective key size","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"The PKCS#7 RC2-CBC content-encryption parameters do not encode an effective key size RFC 2268 section 6 defines (1 to 1024 bits). Re-export the file and upload it again.","instance":"/api/v3/migration/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_CONTENT_ENCRYPTION"}}},"Signer identified by SubjectKeyIdentifier":{"description":"Signer identified by SubjectKeyIdentifier","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 signer at index 1 is identified by a SubjectKeyIdentifier. Only signers identified by issuer and serial number can be converted.","instance":"/api/v3/migration/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_SIGNER_IDENTIFIER"}}},"Recipient that is not RSA key transport":{"description":"Recipient that is not RSA key transport","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 recipient at index 0 is a KeyAgreeRecipientInfo. Only KeyTransRecipientInfo (RSA) is supported.","instance":"/api/v3/migration/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_RECIPIENT_TYPE"}}},"Recipient identified by SubjectKeyIdentifier":{"description":"Recipient identified by SubjectKeyIdentifier","value":{"type":"https://docs.ankatech.co/errors/unsupported-pkcs7-format","title":"Unsupported PKCS#7 Format","status":422,"detail":"PKCS#7 recipient at index 1 is not a key-transport recipient named by issuer and serial number. Only KeyTransRecipientInfo (RSA) is supported.","instance":"/api/v3/migration/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"errorCode":"UNSUPPORTED_RECIPIENT_TYPE"}}}}}}},"500":{"description":"Internal Server Error: unexpected error during analysis","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/migration/analyze-pkcs7","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/interoperability/verify":{"post":{"tags":["Interoperability"],"summary":"Verify a detached flattened-JSON JWS with a supplied public key (stream).","description":"Accepts two multipart parts:\n• <code>metadata</code> – JSON (<em>SignatureUtilityApi</em>)\n  containing the caller's detached-JWS object (<code>signatureBase64</code>)\n  <strong>and</strong> the corresponding <code>publicKey</code>.\n• <code>file</code> – the raw payload that was signed.\n\nThe controller ignores any <code>kid</code> inside the JWS header\nand uses the public key supplied by the caller for streaming\nverification. It answers 200 with a <em>VerifySignatureResponse</em>\ncarrying <code>valid</code> (<code>true</code> or <code>false</code>) and one\n<code>signatureResults</code> entry for the one signature the supplied key verifies:\nits <code>algorithm</code> is the one the caller declared, and it carries no\n<code>kid</code> and no <code>materialVersion</code>, because the key is not a platform\nkey. One supplied key verifies one signature, so a detached JWS signed by several keys\nis refused with 422 and <code>extensions.errorCode</code>\n<code>JWS_SINGLE_SIGNATURE_REQUIRED</code> before any signature is verified: verifying it\nneeds a key per signer. A JWS signed by several platform keys is verified by\n<code>POST /api/v3/crypto/stream/verify</code>.\n\n<strong>Accepted <code>alg</code> values.</strong> The JWS was signed by a third\nparty, so the protected <code>alg</code> is honoured (RFC 7515 §4.1.1): it selects\nthe verification scheme and must be a registered JWS algorithm that fits the\nsupplied key.\n• RSA key: <code>RS256</code>, <code>RS384</code>, <code>RS512</code>\n  (RSASSA-PKCS1-v1_5) and <code>PS256</code>, <code>PS384</code>, <code>PS512</code>\n  (RSASSA-PSS, MGF1 over the same hash, salt of the hash length; RFC 7518 §3.3, §3.5).\n• EC key: the one ECDSA <code>alg</code> of its curve, with the fixed-length\n  <code>r||s</code> signature of RFC 7518 §3.4 — <code>ES256</code> (P-256),\n  <code>ES384</code> (P-384), <code>ES512</code> (P-521), and <code>ESB256</code>,\n  <code>ESB384</code>, <code>ESB512</code> on brainpoolP256r1, brainpoolP384r1 and\n  brainpoolP512r1 (the fully-specified brainpool ECDSA names RFC 9864 registers for\n  COSE; JOSE defines no brainpool identifier).\n• Ed25519 or Ed448 key: <code>EdDSA</code> (RFC 8037) or the curve's fully-specified\n  name, <code>Ed25519</code> or <code>Ed448</code> (RFC 9864).\n• ML-DSA and the other post-quantum signing keys: the parameter-set name, e.g.\n  <code>ML-DSA-65</code>.\n\nRefused with 422 before any verification: <code>none</code>, an absent\n<code>alg</code>, an HMAC <code>alg</code> such as <code>HS256</code>, a composite\n<code>alg</code> (a composite signature is not a single-key signature), and an\n<code>alg</code> that does not fit the key, such as <code>ES256</code> on a P-384 key.\n","operationId":"verifyInteroperable","requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`metadata` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past the payload to find the part that tells it what to do with it.\n\n`metadata` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/SignatureUtilityApi"},"file":{"type":"string","format":"binary","description":"Raw payload that was originally signed."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/interoperability/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"200":{"description":"Verification completed: `valid` is true when the signature verifies under the supplied public key and false when it does not.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifySignatureResponse"},"examples":{"Valid signature":{"description":"Valid signature","value":{"valid":true,"signatureResults":[{"algorithm":"ML-DSA-87","verified":true}]}},"Signature does not verify":{"description":"Signature does not verify","value":{"valid":false,"signatureResults":[{"algorithm":"ML-DSA-87","verified":false}]}}}}}},"400":{"description":"Invalid key, JWS, or algorithm. PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Malformed Base64":{"description":"Malformed Base64","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"'publicKey' is not valid Base64.","instance":"/api/v3/interoperability/verify","timestamp":1745121200}},"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/interoperability/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"422":{"description":"Unprocessable Entity: the protected `alg` is not a JWS algorithm for the supplied key (`none`, an absent `alg`, an HMAC or composite `alg`, or an `alg` of another curve or family), or an ECDSA signature is not its curve's fixed length; or the JWS carries several signatures, which one supplied key cannot verify (`extensions.errorCode` `JWS_SINGLE_SIGNATURE_REQUIRED`); or the supplied public key is not the parameter set or curve the request's `alg` names (an `ML-DSA-44` key sent as `ML-DSA-65`). Refused before any signature is verified; the `detail` names neither the `alg` nor the key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Detached JWS signed by two keys, one key supplied":{"description":"Detached JWS signed by two keys, one key supplied","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The supplied public key verifies one signature; this JWS carries several, and verifying them needs one key per signer","instance":"/api/v3/interoperability/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1790418442,"extensions":{"errorCode":"JWS_SINGLE_SIGNATURE_REQUIRED"}}},"ES256 presented on a P-384 key":{"description":"ES256 presented on a P-384 key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Failed to verify signature.","instance":"/api/v3/interoperability/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1790418442}}}}}},"401":{"description":"Unauthorized – missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Forbidden: the calling Application or Cryptographic Actor is SUSPENDED or DISABLED. The effective-status containment chain applies to this route, so the refusal stands even for an already-issued, still-valid JWT — the token is not re-issued on a suspend and the check is per-request. `detail` names the subject leg that denied (`application-suspended` / `actor-suspended`); the tenant's own lifecycle state is NOT consulted on this route.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the Application/Actor effective status could not be verified. The check fails CLOSED — a request is refused rather than admitted while the status source (the platform's subject-status record, and the administration API that maintains it) is unreachable. This route is otherwise pure compute over caller-supplied key material. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the `metadata` part exceeded the 2 MiB small-part cap. `file` has NO size limit, so the only producer of this status on this path is the seam's small-part cap (`.../errors/multipart-part-too-large`). This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Internal Server Error – unexpected verification failure.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/interoperability/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/interoperability/encrypt":{"post":{"tags":["Interoperability"],"summary":"Encrypt data with a provided public key (stream).","description":"Accepts two multipart parts:\n• <code>metadata</code> – JSON (<em>CryptoUtilityApi</em>) with\n  <code>kty</code>, <code>alg</code>, <code>publicKey</code>\n• <code>file</code> – binary plaintext\nReturns a streaming <code>application/octet-stream</code> response\nwithout persisting the key.\n","operationId":"encryptInteroperable","requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`metadata` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past the payload to find the part that tells it what to do with it.\n\n`metadata` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/CryptoUtilityApi"},"file":{"type":"string","format":"binary","description":"Plaintext file (binary). No size limit."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/interoperability/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"200":{"description":"Plaintext encrypted successfully (stream).","content":{"application/octet-stream":{}}},"400":{"description":"Invalid key material or malformed multipart, or an `alg` that names no parameter set (`NTRUPRIME-L5`, the bare family `BIKE`), refused as `.../errors/invalid-input`. PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Missing publicKey":{"description":"Missing publicKey","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"'publicKey' field is required.","instance":"/api/v3/interoperability/encrypt","timestamp":1745120900}},"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/interoperability/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"422":{"description":"Unprocessable Entity: the supplied public key decodes, but it is not the parameter set or curve the request's `alg` names (an `NTRUPRIME-761` key sent as `NTRUPRIME-857`). The key is held to the name it was labelled with, because the usage record carries that name. Refused before any plaintext is encrypted, while the response is still uncommitted; the `detail` names neither the `alg` nor the key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"NTRUPRIME-761 key labelled NTRUPRIME-857":{"description":"NTRUPRIME-761 key labelled NTRUPRIME-857","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Failed to encrypt data.","instance":"/api/v3/interoperability/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1790418442}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Forbidden: the calling Application or Cryptographic Actor is SUSPENDED or DISABLED. The effective-status containment chain applies to this route, so the refusal stands even for an already-issued, still-valid JWT — the token is not re-issued on a suspend and the check is per-request. `detail` names the subject leg that denied (`application-suspended` / `actor-suspended`); the tenant's own lifecycle state is NOT consulted on this route.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the Application/Actor effective status could not be verified. The check fails CLOSED — a request is refused rather than admitted while the status source (the platform's subject-status record, and the administration API that maintains it) is unreachable. This route is otherwise pure compute over caller-supplied key material. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the `metadata` part exceeded the 2 MiB small-part cap. The plaintext has NO size limit (`.../errors/multipart-part-too-large`). This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Internal Server Error: unexpected error during utility encryption.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/interoperability/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/verify":{"post":{"tags":[" Secure"],"summary":"Verify a JWS signature (auto-detecting, JAdES-aware)","description":"Verifies a JWS and reports the cryptographic verdict plus key metadata. Serialization is AUTO-DETECTED — both RFC 7515 §7.1 Compact (three dot-separated segments) and §7.2 JWS JSON Serialization inputs are accepted with no `serialization` parameter. Every key signs with ONE signature: a COMPOSITE signature is one value over the RFC 7515 signing input (the ML-DSA and classical component signatures concatenated), named by `kid`, `ank_kv` and the composite `alg` in its protected header; it is valid only when both components verify.\n\n**A JWS signed by several keys.** A General JWS (RFC 7515 §7.2.1) may carry one signature per key, each naming its key in its own protected header; a composite key is one of them, with its one value. The token is first classified from its headers alone, before any key is looked up: a root member besides `payload` and `signatures`, a key named by two signatures, an HMAC `alg`, or more signatures than the deployment admits in one token is refused with 422, and signatures declaring different `b64` values with 400. Then every signer is gated in token order — every Capability Grant decision for `VERIFY` (a grant covering several signers is charged once), then every key-lifecycle decision on the `VERIFY` row at the version its `ank_kv` names (PRIMARY when it names none) — and the first refusal is a 403 that names no key and carries no verdict. Only then is each signature verified under its own key. `valid` is `true` only when EVERY signature verifies: a token with any failing signature is invalid, never a partial success.\n\n**Response.** `signatureResults` carries one entry per signature, in token order — its `kid`, the key's catalogue `algorithm`, the `materialVersion` its key resolved to, and its own `verified` verdict. A JWS signed by one key answers one entry. No member names \"the\" key of the token.\n\n**Qualified timestamp (JAdES B-T, informational).** When the verified JWS carries an embedded RFC 3161 qualified timestamp (JAdES `sigTst`), the response surfaces an informational `qualifiedTimestamp` block — `present`, `trustStatus` (VALID/SIGNATURE_INVALID/IMPRINT_MISMATCH/UNTRUSTED/REVOKED/EXPIRED), `genTime`, `serialNumber`, `policyOid`. The token is judged by checks in a fixed order and the first that fails decides `trustStatus`: the token parses and carries its signer's certificate (else `UNTRUSTED`); its own signature and certificate identifiers verify (else `SIGNATURE_INVALID`); its RFC 3161 message imprint declares SHA-256, SHA-384 or SHA-512 with absent or NULL parameters and a digest of that length (else `UNTRUSTED`); the imprint is the digest, under the token's own algorithm, of the ASCII signature value presented in this JWS (else `IMPRINT_MISMATCH` — the token time-stamps other bytes); only then is the TSA certificate judged, again first failure deciding: the signer's extended key usage (else `UNTRUSTED`), the signer's validity at `genTime` (else `EXPIRED`), a configured trust anchor (else `UNTRUSTED`), the path at `genTime` (else `EXPIRED` or `UNTRUSTED`; a signer that is itself a configured self-signed anchor passes here and skips the next check), and, only when the deployment checks revocation (the default), its revocation by RFC 3161 section 4 (else `REVOKED`, or `UNTRUSTED` when undetermined under the deployment's hard-fail revocation posture, the default); `VALID` when all pass, and `UNTRUSTED` when a certificate check cannot be completed at all. The token's age is not judged: its `genTime` was compared with the clock when the time-stamping authority's reply was received at signing, so a stamp verifies `VALID` however long ago it was issued. The three token fields are reported only for a verdict of the TSA-certificate checks, and are absent for every earlier one. Verification makes no call to the time-stamping authority and does not depend on the tenant's time-stamping settings. This block is purely informational and NEVER flips the boolean `valid`: a cryptographically valid signature with an absent, untrusted, or revoked timestamp still returns `valid = true`. The block is omitted for a compact or an unstamped JWS.\n","operationId":"verifySignature","requestBody":{"description":"JSON object with one field: the typed JWS General JSON Serialization\n(RFC 7515 §7.2) payload to verify.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyJwsRequest"},"examples":{"SIMPLE key (RSA-3072, PS256)":{"description":"SIMPLE key (RSA-3072, PS256)","value":{"jwsToken":{"payload":"eyJkYXRhIjoiU0dWc2JHOGdRVzVyWVEiLCJpYXQiOjE3OTA2NTYyNDEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSJ9","signatures":[{"protected":"eyJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInR5cCI6IkpXVCIsImFsZyI6IlBTMjU2IiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwia2lkIjoibXktcnNhLTMwNzIta2V5In0","signature":"wdF8GP8DwiIwD06LJCicETh1trDvid__nvPN4pAJV90cf6NI8P8ujrNx4uPRXzumcf1e6rSUuOnyL2KQWLO8DQP0_1x2sK8SaeY6td93tV44hKm3x55nAon6yunxFTHNWAC3p-Dn_xXCDzKFoWu0WtYwXVRhKITB-fJ-jZEtgl4emB4yGxIzlFFkzCs_nxGTydOG9Z6mvnf0sQLHyIAS9O2PltJhKhBYxb6S80Nm6nIWVXigOB71QCszqDkGgedWxayHzb7NgIhyOPzTCy2lXGHQYdd0mlh8PdZAcMTgpXaeTpKx__hL9WV4LVFZpBQcIccGqDThBu7w7ZDn3ioEhov7mPxvIkx028xKRP51HeEk4z-cbcXsWHJmpqet16CIH1-3PPJTDeA5FZcXUu4xlTrvhmURmldpuIKMvvFa3T_ftlbSOMsZVPwIGZrGvOU6hyVzVrZ5dd_-DpO_M1LjO6RixUEWmOYIFO0lakXJt6V3EHOP7zvE4Zvb_khHc1lN"}]}}},"Three signers (General JSON, one signature per key)":{"description":"Three signers (General JSON, one signature per key)","value":{"jwsToken":{"payload":"UXVhcnRlcmx5IGJvYXJkIHJlc29sdXRpb24gMjAyNi1RMw","signatures":[{"protected":"eyJhbGciOiJQUzI1NiIsImtpZCI6ImJvYXJkLWNoYWlyLXJzYSIsInZlciI6IjIuMCIsImFua19rdiI6MiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwiaWF0IjoxNzkwNjU2MjQwfQ","signature":"Qm9hcmQgY2hhaXIgUlNBLVBTUyBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg"},{"protected":"eyJhbGciOiJFUzI1NiIsImtpZCI6InRyZWFzdXJlci1lYy1wMjU2IiwidmVyIjoiMi4wIiwiYW5rX2t2IjoxLCJ0aWQiOiI3ZDNhOWMxZS00YjJmLTRlOGEtOWM3My0yNzUwMDAwMDAwMTEiLCJpYXQiOjE3OTA2NTYyNDB9","signature":"VHJlYXN1cmVyIEVDRFNBIFAtMjU2IHNpZ25hdHVyZSBvdmVyIHRoZSByZXNvbHV0aW9uIGJ5IHRoZSB0cmVhc3VyZXI"},{"protected":"eyJhbGciOiJNTC1EU0EtNjUiLCJraWQiOiJzZWNyZXRhcnktbWxkc2EiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjMsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImlhdCI6MTc5MDY1NjI0MH0","signature":"U2VjcmV0YXJ5IE1MLURTQS02NSBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg"}]}}},"COMPOSITE key (Ed25519+ML-DSA-44, one composite signature)":{"description":"COMPOSITE key (Ed25519+ML-DSA-44, one composite signature)","value":{"jwsToken":{"payload":"SGVsbG8gQW5rYQ","signatures":[{"protected":"eyJhbGciOiJNTC1EU0EtNDQtRWQyNTUxOSIsImtpZCI6Imludm9pY2Utc2lnbmVyLTIwMjUiLCJhbmtfa3YiOjEsImlhdCI6MTc5MDY0NzEwNn0","signature":"DA3fv0AHKs1Zy4Z6gsTnoVfZU6qkGjTcUPP7RS7arzhtY_tbughY7Fq6dvNWXKioGsCbhsQrHyNvYPwPk9ja4yDYyimGKgApHesalAjjQGa1tlQOD6G2P2vLfOp1t6UBCQGeNb5MbQ-zynRqjmD6mIQf9xYfbDoT9kuMFVhdiAiO7J0sBND_VCG4gOkxAv-pIpyX8eYHEf_W5_2J5I3c9t3x_ej4-5LGAri6k81AQ8uXrRwTJeOn-DqTwOBv8Z91jwP5dTXfNnGzNaUdOP6rjDhw3YaptSWsgFGyMuBoZ13VLfihT0H5u1DEkJfCRy4Er8mxMYuGUhFvyXpsNPB10mJ9CK96u_EsL8RI291EsfijJNGU6Rxqsb6cTtd6kyGfEzYJofubxaPGGUBiLwNbISkgIRiL8sR_EK9M-1DUCj5lvO84UCJo6vpC5FNhCinXFuOst6_DGQfQYJ5etvkc0MTiyGFU01a-9n1Hs8FR46ch1cExq5SoTUeu22O2-CM2v8JBpodxW6kpIyIdKOLIL7-K2-ORR0EXR8LY_CdwRmE_8lpaFgfSyUeS8_V-CiHLb2jGc2BGlE5GO5WfwiZUp6TcV-9xdTcy8ID7qbvcONTxTOcVwrfGO-NZVoweXDHm6y5Aj-fpZQbbJboMY8fdT5LScHvjT87UbEz0q3RCGPuuyVmpwYf8wZdhcp-KCEYHYX6l0WAcVh9e4PLP0khLwY1Hex-uGXHUx9J2Hrq0SqV8jGNrz2iAwrEo8BU0gK7yISyPUFQ1z47gSBFzV0kXyi5Z79nRriwpCuANg_pCR5IdTC2QR2VRyNeaG-VT_IBLDkpsFiqISbHf83KTO7tuc7b95Qt2KSI6zEaJi27_sAZC-RPAdKHUogNqYsI9Px0f1l7o_r8w2TmZZPa8txEpg_BJQlIjCuh9jTmaiXoG835lb20VWs3qRWl3dKUI8CgMwa9c7pvrzR2hJHoxNF-RxVuKDt35ACSnJXLLjdcR4Ykp2sjKM3kuNntskTCS_FUDmMUQNa_Jqnt2484FphoTkF3xfgAIyPWi4Qzy99XlbaJ6IwCOwrrnpPd7HM3J6H5j3VhX0Fy5cCxgiDnCxtbx7qk0MY2ucy6oo2ojHI63_vkkSTZ-IvW3VdAEf1RcHPl5sxIBnzTsD3zRcPIASRytKKF_lGE1TVEk-3xepjNNnKzy7ivcf5hznV5YNuNVHL749tHgF50t0sZjj2JBTDQrt-KMZF3hSNyaljjmPJQXSInl35A-Fdy0ZbWTupl-OP1A4-PUR-6S1ztmYtmwocghkGPzz8qyfJHecG_4k1pSXJKeOIw501dkvk39ga5Fpza1fn_X-0CWSDUuJ-drVah-RvhhakilqUbFGUdB41kYmPMqRAllM4CY5wKLwqynSGf9Pa-UGPsPfZCZf8FR5hlKPbYOe8f_hY2Sm167g5nrq3xB4EX8fIR4xgTfS35CAZkRzYr264o7leR_i6TYv3VcFV0L2ySUOJ03N6gfdSgTkH-X9CgsQbVVxsXMbDT4f4jyuBT9DFwYYyGB6oym5287bVsV3JMJfqThABy4xw9-G9CAj4_93duRBTFxsofPN5XwlXtb4jlIWhCsV-_rK20ja5QE_Sy9etNSJaemWmokYV09pxOHP_phvkJO9NotnsFWxMkusQdYEgvp6Xg6ajAReMIardVkk_Lf_WGNy8kTtgfn2nbkxeeVciLXwDuq5SiEDAvXKrBEqQd5tcM5F9c9Br-7D_4HIk9k8MQgJFNA6eytDMstj4Z4GMXmHwkDy-IT_IdQc6WowIWZ7ADP-msRK3A7eapuHZ_WKgbDpYe_DDxkVxaXd6DHPBD74EmUS1kvrkOt-IxTcl1OOAN1DqjIsU-O33u4XARXuY20nvflfJhbD4PRgZMC_154riOWRN99I5MTsOgHNOZCmEwsEPhyQnQTC_ZQPF9xxcrKiwExGwStTV-HAX_M4HhIElpIhgkr0IgOH1I9GMC5yP1gtlIxCA82c1DQvT1l_tHA-nwmsJIXcGE3HyNNcdK8qKepyVTbzwuQtfAGVNWH8fEO494zsD8sRUtNSTIsX3snqNBzqliB8mXtPdA0SxLV0lkqkHQ1G5wGDfgGhkzIwkUnxShQn2uSKHpkC220z_p4RHcn2frkvWU8TgfXY0klhx0U1aAZ6yHYhW2NsHYFHiIGqMpScHkJ92_mPLc4OF-NaddHZJLNMNzN5BTB2wu_W_Yq5jmdb64IpNaN9-Md7_edU3htUGwHiuqcqIc5FMjzxZM3ehxPp0kqTLuNMcONhm4YHPWud1dN-ZmvCIVANNhmZC_tr4Pa5pS62BO1LuYn04eZ9MBggnS_j-CnqcN9vkh9oN-XadmNPY-MRHHEDp-e6Qx-Of4AjFNH8AzugG1ELcGM6n0_H3Xbq3b9eKELSPpdgP37FmHQCSLedsjDNeDWTd9-Mw0CrvXIH3008asmUGSD8abNZxLQQvcYk1LqVDvmzb6BUQDWNJY8zMGdetmKjL2T3rPoT00FC1cgDRxw4k2fX3q2FwkABlqiXmjK_Sw88kLV5YfZqSIPAEWLVfZC3fZjdb1QJDukpYM3K5bfLQyiAI2Y9fWBuMaTMzALYmTvGvlJztX5WzWKar_XGy4ZLYVjLY_MGarxuJChJiMBPmsFYmRbaw9XLjvxZVeoscU05JbVoDkQz7w31EYm7ZTtT1jmkf7DlLrvItMGNAr0TRuWtG4k2j7Q4JgzEAEea8SU7me9law2Uv0g5BdZdqJCLKsBxSUsw3A0w1XN2EdxE63CHu4Nj8bwaHVl1HO_1Y3ZxBzym7MQPb_o8Ep8XjTrPqhgwL3djIWACKTJsWU6am51QXNvxOGuUhjiYFX0FsrGZJeyOn6Yf4kpvIPMfP9uZ0lgMIFBAPSfeMy5PQXIIYERoXMG0CgIDzazVUr7Y5qfa1plxJLpxJQ9XsFcVyuTmKgkNs2mjhTnUguQ1N59Q4989WCaFNH0aJDBapfFoEuhvYIMLnppfIsLbJ58VO9vALiAjKoWQ3R_LS8cHuMuClCqkdlQ7yf6YC-qGqCLNDMqOAahLhblhjuVwC3J-pO_jahRo8Di3JSXvHrfCRrSDh7M40sQEilNW3uRoKSztLjJ0974BBghYWJjaHWEqLC0ur_L2-H2Ahc1OlteZnyCoLzIz9_l8fwFCUJZYmZndXqRkrnF2OPr-wAAAAAAAAAAAAAAABAiM0R2EnVgC05OvIWLpr22s7bSnheK2FoOmcWmTNt6wxb79PyRYeMFUzle-mUPh5-aF00dlow8WZXR7KsD9zYI6D8F"}]}}}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.verifySignature` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for VERIFY on a signing key, or a grant constraint is violated, or its exchange context is suspended; or a signing key's lifecycle state does not permit VERIFY; or the tenant or the calling Actor is not in a state that permits cryptographic operations. For a JWS signed by several keys, every signer is decided in token order and the first refusal answers; the body names no key and carries no verdict. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/verify","timestamp":1745120400}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/verify","timestamp":1745120403}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"Verification completed: one `signatureResults` entry per signature, in token order; `valid` is true only when every signature verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifySignatureResponse"},"examples":{"Three signers, one invalid (valid=false, every verdict reported)":{"description":"Three signers, one invalid (valid=false, every verdict reported)","value":{"valid":false,"warnings":[],"signatureResults":[{"kid":"board-chair-rsa","algorithm":"RSA-3072","materialVersion":2,"verified":true},{"kid":"treasurer-ec-p256","algorithm":"EC-P256","materialVersion":1,"verified":false},{"kid":"secretary-mldsa","algorithm":"ML-DSA-65","materialVersion":3,"verified":true}]}},"Three signers, all valid":{"description":"Three signers, all valid","value":{"valid":true,"warnings":[],"signatureResults":[{"kid":"board-chair-rsa","algorithm":"RSA-3072","materialVersion":2,"verified":true},{"kid":"treasurer-ec-p256","algorithm":"EC-P256","materialVersion":1,"verified":true},{"kid":"secretary-mldsa","algorithm":"ML-DSA-65","materialVersion":3,"verified":true}]}},"Valid SIMPLE signature (RSA-3072, PS256)":{"description":"Valid SIMPLE signature (RSA-3072, PS256)","value":{"valid":true,"warnings":[],"signatureResults":[{"kid":"my-rsa-3072-key","algorithm":"RSA-3072","materialVersion":1,"verified":true}]}},"Valid COMPOSITE signature (both components verify)":{"description":"Valid COMPOSITE signature (both components verify)","value":{"valid":true,"warnings":[],"signatureResults":[{"kid":"invoice-signer-2025","algorithm":"Ed25519+ML-DSA-44","materialVersion":1,"verified":true}]}},"COMPOSITE signature with one component broken (valid=false, never 422)":{"description":"COMPOSITE signature with one component broken (valid=false, never 422)","value":{"valid":false,"warnings":[],"signatureResults":[{"kid":"invoice-signer-2025","algorithm":"Ed25519+ML-DSA-44","materialVersion":1,"verified":false}]}},"Valid signature with JAdES qualified timestamp (informational — never flips 'valid')":{"description":"Valid signature with JAdES qualified timestamp (informational — never flips 'valid')","value":{"valid":true,"warnings":[],"signatureResults":[{"kid":"my-mldsa-signing-key","algorithm":"ML-DSA-87","materialVersion":3,"verified":true}],"qualifiedTimestamp":{"present":true,"trustStatus":"VALID","genTime":"2026-07-05T10:20:00Z","serialNumber":"84215045","policyOid":"1.3.6.1.4.1.99999.1.1"}}},"Timestamp imprint does not match the signature (IMPRINT_MISMATCH, token fields withheld)":{"description":"Timestamp imprint does not match the signature (IMPRINT_MISMATCH, token fields withheld)","value":{"valid":true,"warnings":[],"signatureResults":[{"kid":"my-mldsa87-signing-key","algorithm":"ML-DSA-87","materialVersion":3,"verified":true}],"qualifiedTimestamp":{"present":true,"trustStatus":"IMPRINT_MISMATCH"}}}}}}},"400":{"description":"Bad Request:\n• Malformed JWS or missing <code>jws</code> field, OR\n• A 'crit' naming any extension in the protected header (this route understands none; a composite key signs with one signature), or a 'crit' member in a signature's 'header' or in the shared 'header' - 'crit' MUST be integrity protected (RFC 7515 section 4.1.11). Refused before any key is resolved, OR\n• Key does not support the requested operation (e.g., trying to verify with a sign-only key), OR\n• A JWS signed by several keys whose signatures declare different 'b64' values: every signature signs the same payload (RFC 7797 section 3). Refused before any key is gated\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid input":{"description":"Invalid input","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"JWS string is not valid Base64 or missing.","instance":"/api/v3/crypto/verify","timestamp":1745120800}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'sign-only-key-001' does not support operation 'verify'","instance":"/api/v3/crypto/verify","timestamp":1745120800}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Not Found: key not found or unsuitable for verification.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key not found":{"description":"Key not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource not found","status":404,"detail":"Key 'unknownKid' does not exist or lacks verify capability.","instance":"/api/v3/crypto/verify","timestamp":1745120801}}}}}},"409":{"description":"Conflict: key is in a disallowed state (e.g., revoked).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid key state":{"description":"Invalid key state","value":{"type":"https://docs.ankatech.co/errors/conflict","title":"Conflict","status":409,"detail":"Key 'myVerifyKeyKid' is revoked and cannot verify signatures.","instance":"/api/v3/crypto/verify","timestamp":1745120802}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/verify","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"413":{"description":"Payload Too Large: JWS envelope exceeds the configured maximum size.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JWS too large":{"description":"JWS too large","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload too large","status":413,"detail":"JWS exceeds the maximum allowed size (5 000 000 bytes).","instance":"/api/v3/crypto/verify","timestamp":1745120803}}}}}},"422":{"description":"Unprocessable Entity: the JWS cannot be verified as presented. `unprocessable-entity` with a key-neutral `extensions.errorCode`, for a JWS signed by several keys, decided from its headers alone BEFORE any key is looked up or gated: `JWS_ROOT_MEMBER_FORBIDDEN` (a root member besides `payload` and `signatures` — the General syntax only, RFC 7515 §7.2.1), `JOSE_ACTOR_KID_REPEATED` (two signatures name one key), `JOSE_SYMMETRIC_ACTOR` (an HMAC `alg`, or a signer that resolves to a symmetric key, refused after its gate), `JOSE_ACTOR_LIMIT_EXCEEDED` (more signatures than the deployment admits in one token). `JWS_SIGNATURE_MISSING`: no signature at all. `composite-wire-shape-mismatch`: a composite key's signature value is not the shape its pairing produces (for example longer than the pairing allows). `composite-component-algorithm-mismatch`: the protected `alg` is not the composite algorithm of the key material `kid` and `ank_kv` select. `unprocessable-entity`: a SIMPLE JWS that is malformed, or whose `alg` or signature length is not its key's. A well-formed composite JWS whose components do not both verify is not a 422: it answers 200 with `valid=false`.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Two signatures naming one key":{"description":"Two signatures naming one key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The token names one key in more than one of its entries: every recipient or signature of a multi-key token names a distinct key","instance":"/api/v3/crypto/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1790409353,"extensions":{"errorCode":"JOSE_ACTOR_KID_REPEATED"}}},"A root header beside the signatures (a two-signature composite form)":{"description":"A root header beside the signatures (a two-signature composite form)","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The JWS carries a member at its root besides 'payload' and 'signatures': the General JSON syntax carries each signature's headers and value inside 'signatures' (RFC 7515 §7.2.1)","instance":"/api/v3/crypto/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1790409353,"extensions":{"errorCode":"JWS_ROOT_MEMBER_FORBIDDEN"}}},"Composite signature value longer than its pairing allows":{"description":"Composite signature value longer than its pairing allows","value":{"type":"https://docs.ankatech.co/errors/composite-wire-shape-mismatch","title":"Composite Wire Shape Mismatch","status":422,"detail":"The composite signature value is longer than ML-DSA-44-Ed25519 allows","instance":"/api/v3/crypto/verify","correlationId":null,"timestamp":1790409353,"extensions":null}},"Composite JWS whose protected alg names another pairing":{"description":"Composite JWS whose protected alg names another pairing","value":{"type":"https://docs.ankatech.co/errors/composite-component-algorithm-mismatch","title":"Composite Component Algorithm Mismatch","status":422,"detail":"Protected alg does not match the composite algorithm of the key material ank_kv selects; the JWS declares 'ML-DSA-44-ES256'","instance":"/api/v3/crypto/verify","correlationId":null,"timestamp":1790409353,"extensions":null}},"SIMPLE JWS whose protected alg is not the key's":{"description":"SIMPLE JWS whose protected alg is not the key's","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Failed to verify signature.","instance":"/api/v3/crypto/verify","correlationId":null,"timestamp":1790409353,"extensions":null}}}}}},"500":{"description":"Internal Server Error: unexpected error during verification.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/verify":{"post":{"tags":[" Secure Streaming"],"summary":"Verify a detached-JWS (streaming).","description":"Two <code>multipart/form-data</code> parts:\n• <code>metadata</code> – JSON (<em>VerifyDetachedJwsStreamRequest</em>)\n  with a detached-JWS: the flattened <code>{protected, signature}</code> JSON\n  (RFC 7797, <code>\"b64\":false</code>, <code>\"crit\":[\"b64\"]</code>).\n• <code>file</code> – the binary payload originally signed.\nExtracts <code>kid</code> and <code>alg</code> from each signature's protected header,\nverifies, and returns a <em>VerifySignatureResponse</em> once the payload has been read.<br><br>\nSupports SIMPLE keys and COMPOSITE keys (one composite signature, named by\n<code>kid</code>, <code>ank_kv</code> and the composite <code>alg</code> in its\nprotected header; valid only when both components verify).<br><br>\n<strong>A detached JWS signed by several keys</strong> is the General body\n<code>{\"signatures\":[{protected, signature}, ...]}</code> (RFC 7515 §7.2.1), each\nsignature <code>b64:false</code> (RFC 7797). It is classified from its headers alone\n(422 for a root member besides <code>signatures</code>, a key named twice, an HMAC\n<code>alg</code>, or more signatures than the deployment admits; 400 when a signature\ndoes not declare the unencoded payload the others declare), then EVERY signer is gated\n— every VERIFY Capability Grant in token order, then every VERIFY key-lifecycle\ndecision — before the <code>file</code> part is opened: a refusal is a 403 naming no key,\nwith no verdict. The payload is then read ONCE into every signature, and\n<code>signatureResults</code> reports each signature's verdict, in token order;\n<code>valid</code> is true only when every one verifies.<br><br>\n<strong>Memory.</strong> The payload is read once, but what each signer's verification\nholds is its algorithm's: an ECDSA, RSA-PSS or ML-DSA signature, and every composite\nsignature (which signs a pre-hash of the payload), keeps a fixed state while the\npayload streams; a pure EdDSA (Ed25519, Ed448), SLH-DSA or Falcon signature keeps the\nwhole payload in memory until it is judged, because those schemes read the message\nmore than once. A payload verified under k signers of those families is held k times.\n","operationId":"verifySignatureStream","requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`metadata` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`metadata` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/VerifyDetachedJwsStreamRequest"},"file":{"type":"string","format":"binary","description":"Binary payload originally signed. No size limit."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `stream.verifySignatureStream` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for VERIFY on a signing key, or a grant constraint is violated, or its exchange context is suspended; or a signing key's lifecycle state does not permit VERIFY; or the tenant or the calling Actor is not in a state that permits cryptographic operations. For a JWS signed by several keys, every signer is decided in token order before the payload is read, and the first refusal answers; the body names no key and carries no verdict. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/stream/verify","timestamp":1745120400}}}}}},"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/verify","timestamp":1745120403}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict: the tenant's key-protection backend is not configured - never declared, or declared but not yet bound. Durable: complete the setup rather than retrying. No Retry-After is sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/verify","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"200":{"description":"Verification completed once the payload was read: one `signatureResults` entry per signature, in token order; `valid` is true only when every signature verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifySignatureResponse"},"examples":{"Three signers, one invalid (valid=false, every verdict reported)":{"description":"Three signers, one invalid (valid=false, every verdict reported)","value":{"valid":false,"warnings":[],"signatureResults":[{"kid":"board-chair-rsa","algorithm":"RSA-3072","materialVersion":2,"verified":true},{"kid":"treasurer-ec-p256","algorithm":"EC-P256","materialVersion":1,"verified":false},{"kid":"secretary-mldsa","algorithm":"ML-DSA-65","materialVersion":3,"verified":true}]}},"One signer (COMPOSITE key, both components verify)":{"description":"One signer (COMPOSITE key, both components verify)","value":{"valid":true,"warnings":[],"signatureResults":[{"kid":"invoice-signer-2025","algorithm":"Ed25519+ML-DSA-44","materialVersion":1,"verified":true}]}}}}}},"400":{"description":"Malformed JWS, invalid Base64, or key does not support the requested operation. A <code>signature</code> member that is not base64url, whatever its length, is this 400 (<code>invalid-input</code>, <code>The detached JWS signature is not valid base64url.</code>) for a SIMPLE and a COMPOSITE key alike. A detached JWS whose protected <code>crit</code> names an extension other than <code>b64</code> (RFC 7797, the one this route implements) or a parameter the header does not carry, or that carries <code>crit</code> in its unprotected <code>header</code>, is a 400 (not a 422), refused before any key is resolved (RFC 7515 section 4.1.11); so is a body or protected header that repeats a member name, and a <code>b64</code> that is not a JSON boolean. For a detached JWS signed by several keys, a signature whose protected header names no <code>kid</code> is this 400, the status <code>/verify</code> answers for the same token: a token of several signatures is classified by one reader on both planes, and a signer it cannot name is a malformed token, before any key is looked up. PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"The detached JWS is not the RFC 7797 detached form this route reads, refused before any key is resolved, for a SIMPLE and a COMPOSITE key alike. <code>composite-wire-shape-mismatch</code>: the protected header does not set <code>b64</code> to the JSON boolean <code>false</code> and list it in <code>crit</code> (RFC 7797 sections 3 and 6), or the body is not the flattened or the General serialization (a <code>signatures</code> member beside the flattened members, a member the serialization does not define), or a Compact token carries a payload. A General body with ONE entry (<code>signatures: [{protected, signature}]</code>, what a PKCS#7 streaming conversion emits for one signer under <code>serialization: JSON</code>) is read exactly like the flattened one. <code>unprocessable-entity</code> with <code>extensions.errorCode</code> <code>JWS_SIGNATURE_MISSING</code>: no signature at all (an empty <code>signatures</code> array, no or an empty <code>signature</code>). <code>unprocessable-entity</code> with a key-neutral <code>extensions.errorCode</code>, for a <code>signatures</code> array of more than one entry, decided from the headers before any key is looked up: <code>JWS_ROOT_MEMBER_FORBIDDEN</code> (a root member besides <code>signatures</code>), <code>JOSE_ACTOR_KID_REPEATED</code> (one key named twice), <code>JOSE_SYMMETRIC_ACTOR</code> (an HMAC <code>alg</code>, or a signer that resolves to a symmetric key, refused after its gate), <code>JOSE_ACTOR_LIMIT_EXCEEDED</code> (more signatures than the deployment admits in one token). <code>unprocessable-entity</code>: a Compact token that cannot be parsed; the protected header of a JWS signed by ONE key that names no <code>kid</code> (this route's own reading of a single signature; a signature of a JWS signed by several keys that names none is the 400). <code>unprocessable-entity</code> with <code>Failed to verify signature.</code>: a well-formed <code>signature</code> value longer than the named key signs, for a SIMPLE and a COMPOSITE key alike.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Detached JWS without b64:false":{"description":"Detached JWS without b64:false","value":{"type":"https://docs.ankatech.co/errors/composite-wire-shape-mismatch","title":"Composite Wire Shape Mismatch","status":422,"detail":"Detached JWS must set “b64” to false and list it in “crit”.","instance":"/api/v3/crypto/stream/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}},"Two signatures naming one key":{"description":"Two signatures naming one key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The token names one key in more than one of its entries: every recipient or signature of a multi-key token names a distinct key","instance":"/api/v3/crypto/stream/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JOSE_ACTOR_KID_REPEATED"}}}}}}},"404":{"description":"kid not found or unsuitable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Unexpected failure.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/sign":{"post":{"tags":[" Secure Streaming"],"summary":"Sign data (streaming).","description":"Consumes two <code>multipart/form-data</code> fields:<br>\n1. <code>metadata</code> — JSON body <em>SignStreamRequest</em><br>\n2. <code>file</code> — binary data to sign<br><br>\nProduces a detached-JWS in JSON format streamed as <code>application/octet-stream</code>.\nEvery key type writes the same flattened <code>{protected, signature}</code> document\n(RFC 7797, <code>\"b64\":false</code>, <code>\"crit\":[\"b64\"]</code>); only the header differs:<br>\n• SIMPLE keys: one signature, named by <code>kid</code> and <code>alg</code><br>\n• COMPOSITE keys: one composite signature (the ML-DSA and classical component signatures\n  in one value) over the RFC 7797 unencoded payload, under the protected header\n  <code>{alg, b64, crit, kid, ank_kv, iat}</code>: the composite <code>alg</code>, the\n  RFC 7797 members, <code>kid</code>, <code>ank_kv</code>, and <code>iat</code> whenever\n  this deployment's JWS local clock issues it. This operation takes no\n  <code>validityDays</code>, so the header carries no <code>exp</code>.\n","operationId":"signDataStream","requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`metadata` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`metadata` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/SignStreamRequest"},"file":{"type":"string","format":"binary","description":"Binary data to be signed. No size limit."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `stream.signStream` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for SIGN on the addressed key, or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/stream/sign","timestamp":1745120400}}}}}},"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/sign","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/stream/sign"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict: the tenant's key-protection backend is not configured - never declared, or declared but not yet bound. Durable: complete the setup rather than retrying. No Retry-After is sent. It is also answered as <code>key-material-expired</code> when the key's current material is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>); a material in both states is answered as expired. Nothing is produced and the key's state is unchanged: it still decrypts and verifies what it protected, and rotating it restores protection. Durable - do not retry. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/stream/sign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/crypto/stream/sign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/stream/sign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/sign","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"200":{"description":"Data signed successfully (stream).","headers":{"Crypto-Policy-Info":{"description":"Base64URL-encoded JSON payload (snake_case) describing key\nselection and policy warnings for the current request.\nDecode and deserialise into the `CryptoPolicyInfo` schema.\n","style":"simple","schema":{"$ref":"#/components/schemas/CryptoPolicyInfo"}}},"content":{"application/octet-stream":{"examples":{"SIMPLE key output (RSA-3072, PS256, detached)":{"description":"SIMPLE key output (RSA-3072, PS256, detached)","value":{"protected":"eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6Im15LXJzYS0zMDcyLWtleSIsInZlciI6IjIuMCIsImFua19rdiI6MSwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwiaWF0IjoxNzkwNjU2MjQwfQ","signature":"DOcxA9VrbL_UUZeJ7UNGaT4tvXaPYJVeGYJMqVnzrsBrumtnB7RN-YeMRIDlijN9YP1jBadLybT0AwR5wLxOZ-g4nQ2gWQDBlouE4WyIB5H_MWl2cajjGXcNpcCM5r51kX1YLp5cvGgAO3DqQ81SDinIkthR_tGEH3TmElGCrpxuQrE0lm-BJRj_xypnxv3d88eYJxneymMP0dOokvWUCUfuajXHyLLwh2nNLPYLudRLDTxKhXwk_vsELvq9bEcf0u0ACV1HpQGdjWf6ggzt4ziHPuXEzzzQFAl4ALVSD9wNETT70uaFra0mwXptsLn4cfv5aq7WRv6m_xbWeCVyZhNnZOMqbIwCNzwhyv_Tp1-HpVxsOIehN2bGrBSp07X000qyNREDbAa0CxLl-5AnvF8g_oN-nuAt1dStwMb769wo27SECtg1REot-Mk1Xi7eZ5bnQhkcmJeULtzxLdT-6hk-Obe2HTTF1J9S6GSmDZ1GQc0e5hL3eHfDHyqmBbGZ"}},"COMPOSITE key output (Ed25519+ML-DSA-44, one composite signature, detached)":{"description":"COMPOSITE key output (Ed25519+ML-DSA-44, one composite signature, detached)","value":{"protected":"eyJhbGciOiJNTC1EU0EtNDQtRWQyNTUxOSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6Imludm9pY2Utc2lnbmVyLTIwMjUiLCJhbmtfa3YiOjEsImlhdCI6MTc5MDY0NzEwNn0","signature":"0ccDmL5hN4p4Oj-5B4s3ejygp0s6Fbd5xY4ORMpiyT6ri59zk_HdJ6oLDuOHcPGEzG4L0iitO8lcnKbggCvezhpXPJ8-7mlnttG6aJxUVZk0PykuI6A62L5m4Gfn2jybgf3SJ_l6Yb4nL6My51qCxAuTPIyvLN5GTvyTp6rPwGJMX_X5rD5HETF9xrxG_j3Da1bkKPVIUNupmPH68gGOnfP_iH026aFfcrZRhPwj4WVhYPBNLUYqun2A-LNLZ7Ib4LKnXsUdbbE7FjrsiwDhnlgrKq18t468hv-mAqRy-6UCW4o5tVXpqlkg6SAuW42Q0MHZdfYP-NcGbLoytzoKP9LpvCGc-BJpS3HCyaqNQOh0oEEPxeLpqpciFi-woaiwjsVvzkpghIeKVWPnBTlwUFEndIG7LbSBJwO2j-nP7jwYIQ0TTp8Urjo3CA1abo71TmiGOp3pMIp4vf0kxzcorgPRBFofT2g-iAKvTrZ7qXdVp8oO0wXiQN6B2fEzeNzBJb20xeZS-IaEhTgKdlh4OslRrbjmVQ9IJSYzQKMpXvoIUiYyx3f0qPNP70R3neMMPRQpOPoka3wrotf9LxCjUw_G3tDcQaeuMv7AbWh9x_KYOc_Pf_RPOKvG8it2DhKrUMU8ro62vmq7EHCJ3vJ3i-86mu54jaGdkwZxvWck-KQh0JIngvAxvTB-OBoyxkLf8z7-rUYK55T2zci-78wiAi-tno9lAWMOqh5d3hF4EPgQAtwtxtLg53rxqIlSNRDAWWrUqxofod81U1AlFESXMmnVqKg9faEIsGorcUcFKAROWTdFbtU56KjSavzGx6y0GSOy3_zzN8EClWAaVANiCTHXSFCcsmtro4V0jjRDn1N-syF5gCAt9k-b_H63h7Q89asOyS66VRzKyK6JBIc-QHXIvMW35JJ_PPS37yDsnoQIH5zVX9rn6vfykjtYZrhpgO00OK-dA7-IQljSv-4cwROyE2XRGR6-CcTsJp2Dgvoal6xHfT6SZNH4rQoSJG6U2Y_vbYSasRdf-X0guycpnFoR2zdJcBrM0aUdBmvZkhT93D3XeQrzZNmjjk5wt37bEABm0MAoL0a5L5Rlk0CuuECrgouUPDuCoilEU96BwY5dNmsalfJOAO682BuqQ0-yemujMpozs3JDhJ2aYecqFet7H0axuL_mWQJoULriCRMVABoJjHiorf8BvqccKT9NuatFovLEXBTxxJbrVgFaREaAvegge9y1NtzSyE7SVFsEjSQVHVNWvlWs3UD6FdTT1DWMPZSEmfZJuiA9b7SOrKVIm3XucAHwvX9CRiHeAdYGi0SVNyQ0I_--vBvcPSl-_a0nOntySP9afZNhskX_o9EdwzCvcercq0Vo7g580UI6Afq7Jf8lzBfOQ3gXq8HxjRwp0lRVyKOdFe0vRPs-WfxGmmJp-YqtnYinWlkTtcVNo-E9f5NO7Cd4SXchHIypvSIoA7CMHxBqW1QsotPJWq-HGAY3dVdlWMKlXHSy5ZLRnI0MjIiM93uEAfq6o5nZZNhnyXtnolsiS6nPqpS_Q51bg5lclDSTz-EqOjQStNYQK-PQmoPhzZoWExigZeKsGe4RejURITXySoyFf79kKqbmmk_OpEsqd-B5YJBRuAGRlLySXi2AnNnQkU3etfP8rkBi2vP85TFZmaPn4AP5NweLNQpHktk1gYPyt9fBVy-IY4pcl0v-Pvrcw_7orkSM0fJDKmL1YKHzDZ_zofqSTzKYdH4Ex24_SNApj7sk-Ylo7Rk7eYtSZH4sIa2m349kpTxf-Woyhiv-iDRgTlf-y-zCJzph2kRhKKtr1sKKXElv68BYRrFWgLNREfo3lJBaiDsV3YBexzKVhOX1z5orK5Afik_g_fbnTCv90-GMAVAypgYuRQ8NlI44B0mbyucINch1psf7txcM_fn47121E9QFMk7S-0PV9jx9l3nAct8BC5xpF8gXHB3d62ZeJKkhc3usGn67Z73uShLa5pxb6Eel4QS6lv2xYspagaIqajplIHry0dk3hOL1beP8dUPwytCvhgGxw1kq15l7ovAZOqI2nvDI22nheONJzKwe9CKUGBnJk8VDtLRmVzRPjfwcULsHz4sNkDc0Lq6TRhOpShha39j6zS56Ft5typ737H3H4fR22W7RP1TwOFLM9_5Mvg-SkGANMLueHEy85Zp3Dx3HMYl5ukXoEFhZxF86qmoklz0pa1l0bxHgVCojJAOXogF-jAYeV5Yt4KX28Ad7uXKv00gACDle9hPmd2o-XDrG2YMknrb6IDX80iIY9PVVxpY6-qiGWg6ulZbuJC3F_LKfBfw6z2GFKTxR4dbsf67Aj-NoZt8Obcbu8fjD27A5Rlmb8deZjO60gthRHKJrNGa8Lbuv02u6Qb3bh3Efw_tDDs72fiqLAhDq-QUuQyGKZHsQVbwleX3-PFnD5FSTGmtWjK0TCd1lYz6IwOdGXXhqeXebG3qEUBi7bKgVLd3v5i2LjdJyYomIUQwmoSYQcxdQgBBLD33Ve0GZlBm51woHG-wHEamWLiG6eq0q2EaukHIvac5O_t6xdf7sWhKsmInwepzU560dGn6xeIwLcfPltevC-5lOiykOENnfLFl-SS6OkHTxGUQgIqg-_8O9xM_6cbTHbv3mSi-8xpob8G1rXHfOcfBW2-Fc_bqrMP0ZPqf6h87qsHXOwL9B8zg9dAwPoby3pNigGEIlefRZ9vtqGMEvFS4idn7hBr-wkFStY0Ue6ezi999Y0_oNAr7PoI34Zayno64gERqIvVrjkscyInekLaWwehEBd5om7vyYXDGntc-xU8UavlVV1hy166UMS0SmZ1GvXjnlfFfAtjvMhSIJCddL7618fWVvD7YxKgqrEEmIGEHzXSIqtq_KhQ3znbSwAnrBiMmfgjprOQA_CsjHK3qEKvIO2Q21-RdaMwI-ovq52tustROQbwMMSgZ_9VU7JdSxNLuSXh-xHN2em7CD00G6lOQ7hSZNzjPSIjoL2dujJA2Ucd-sppy77D1i5toRAqW8A9ataWf1vnti7FvSsAQOOBYnDY2mVATocD9BcbJX48d1pi3A4dutSVoWgTlwv3aYn03u8d53A4MUGh9oaZfLzeYEDDxEVHuClq7RGCc6fpS-0dbZ3REpMzVDU3uFiaCisrrA8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkTHSxWPpJiF7gH8Cg1upjSc3m0JJH2xh2QIzAJ8mwKUusytjSb4Lr9Dm-BAKh61kDUm_Iu4KDjuEMmuEhJvyLGz4ME"}}}}}},"400":{"description":"Malformed multipart, invalid data, or key does not support the requested operation. PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"kid not found or unsuitable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"Unprocessable Entity (`.../errors/unprocessable-entity`): the request was understood but the signing engine refused to produce a signature with this key. This operation reads no signature, so its refusal is never an integrity failure. Answered with zero payload bytes while the response is still uncommitted; after commit the stream is truncated instead: the stream fails closed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Signature refused by the signing engine":{"description":"Signature refused by the signing engine","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Failed to sign data.","instance":"/api/v3/crypto/stream/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"500":{"description":"Unexpected failure during streaming signing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/sign-encrypt":{"post":{"tags":[" Secure Streaming"],"summary":"Sign-then-encrypt data (streaming, nested JWE(JWS)).","description":"Consumes a <code>multipart/form-data</code> request with a JSON\n<em>SignEncryptStreamRequest</em> metadata part ({signKid, encryptKid,\nvalidityDays?}) and a binary <code>file</code> part. The payload is signed with\n<code>signKid</code> and the resulting compact JWS is encrypted with\n<code>encryptKid</code>, so the signature is carried encrypted. <code>signKid</code>\nand <code>encryptKid</code> MUST be different. Each leg is dispatched by its own\nkey, so every pairing of a symmetric, an asymmetric and a composite key is\naccepted - the nine sign-kind × encrypt-kind pairs, including a symmetric key on\none leg and an asymmetric key on the other, and a composite key on either leg or\nboth. What each key must be is the operation of its leg: <code>signKid</code> a\nkey that signs (a composite signature key, never a composite KEM key) and\n<code>encryptKid</code> a key that encrypts (a composite KEM key, never a\ncomposite signature key); a key that does not support its leg's operation is 400\n<code>invalid-input</code>, answered before any response byte.\nA composite <code>signKid</code> produces the same inner attached JWS a SIMPLE\nkey does: one composite signature under one protected header\n<code>{alg, kid, ank_kv, iat[, exp]}</code>: <code>iat</code> whenever this\ndeployment's JWS local clock issues it (<code>ankasecure.jws.local-clock.enabled</code>\nand <code>include-iat</code>, both on by default), and <code>exp</code> =\n<code>iat</code> + <code>validityDays</code> × 86400 when\n<code>validityDays</code> is sent. The response is the nested ciphertext streamed\nas <code>multipart/mixed</code> (JWE header + ciphertext).\n","operationId":"signEncryptStream","requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`metadata` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`metadata` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/SignEncryptStreamRequest"},"file":{"type":"string","format":"binary","description":"Plaintext file (binary). No size limit."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/sign-encrypt","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/stream/sign-encrypt"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict: the tenant's key-protection backend is not configured - never declared, or declared but not yet bound. Durable: complete the setup rather than retrying. No Retry-After is sent. It is also answered as <code>key-material-expired</code> when the key's current material is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>); a material in both states is answered as expired. Nothing is produced and the key's state is unchanged: it still decrypts and verifies what it protected, and rotating it restores protection. Durable - do not retry. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/sign-encrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"200":{"description":"Nested JWE(JWS) produced (stream).","content":{"multipart/mixed":{"examples":{"Composite signer (P-256+ML-DSA-65) and ML-KEM-768 recipient, validityDays 365":{"description":"Part 1 is the outer JWE header of the recipient key; the inner composite JWS, whose protected header is {alg, kid, ank_kv, iat, exp} with exp - iat = 31536000, travels encrypted inside part 2.","value":"------ankatech-4f9c2e7a\nContent-Type: application/jose+json\n\n{\n  \"protected\": \"eyJhbGciOiJNTC1LRU0tNzY4IiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6ImFyY2hpdmUtcmVjaXBpZW50LW1sa2VtNzY4IiwidmVyIjoiMi4wIiwiYW5rX2t2IjoxLCJ0aWQiOiI3ZDNhOWMxZS00YjJmLTRlOGEtOWM3My0yNzUwMDAwMDAwMTEifQ\",\n  \"iv\": \"tgBLRsiJOfQXH8Rxou7wQA\",\n  \"ciphertext\": null,\n  \"tag\": null,\n  \"recipients\": [\n    {\n      \"header\": {\n        \"alg\": \"ML-KEM-768\",\n        \"kid\": \"archive-recipient-mlkem768\"\n      },\n      \"encrypted_key\": \"f9BLotOHNhz_ec883RAGh_osDsg33nS1GOyiLAqoP60b2W5OLIyTVCDhu1F9iuR5zDpOnaoGezFs-u5Es33767rxXCW_0IeaSaZrZtFlfq8KzO-MTJi9MjmDBR-3UauR9ASq1kkWgppjm02la-TlmCBIVGb-NHafYZ1JNYa9w4zDZ07kEHSlIeisEZCwk3tp4qAwif43_ZKgFeex3M4R91PwI286yJfgeKK_t5Ei_j8pAm1IYYJEzbywIVwRYxQp0JblW_g3210p37zwq_oOiJ7Uz1H-SImOIw_jUDnmJT5MILVCIoGHViL1ibexoKwFI8dli5wiJpLUyykkjFwrqEmNmRBJ5WQrZgr05GpIf6-Bp9x5Ciu_pU2Afeoa7bZ0XDRo0PleMy1cbOXyz6b28ZpZ5czgfUO3uZAVSj747IWDSr67eUkDNeCfG-Bco4y1m1tCJguhhN1ETjU4hnPjZ77wgPiSxfLhWXMmsMq_gkFYmqO_dCQIjnrtz5C1vy2EDvAjJL4IDHloQko8rcQck9VcdEfVhG_iNnjQ_Bd-Wr-6Dau0Pb0-KpZucHGqqacqK7tGcowioh7cR3h4LoyPPOqcNK0aJfA_XdgMsx90-kH4hG22nE1hctrlyh800ehUMrQEmqA2jJ8xeW6WMT4PiL92UdUVui34xFJiCVoCFjRLNY2Qm7qdV_6fEcgFvws96t7JWtWQdZAVqsT-J5pFIRuMOQfxw1g5h_Jp8SjlgpVqYh_WkC-zWL4cOLGW6-BvPGGAsX-5QD_5VKOTjWape7BsoYvK4P9e3Kj2rS8vk2GUi1yJ7XbBqYPt_Cg95a03MGP5o-Bdd9_32c4c83h8wIJysgt4qeZjY2Jfd3dfutR6HRFfhVx6KOXsbAnAaqCoHPzNvTAhvSLHIBP8ggSrtt1v5C7K_Jl38abiuUymRRL4djAjx1pKROUdBmB-yqYd21l92SNOoReVQFq4AzuNEVuqR6CqK2wboHqQ6becHuA6dvCFsTbLg5b5mKIIIP_QbfTJzkBg-wBJ6wCuJkHUYqv08w5tDfGs25EcqniOYWLSXgxULevKBtTv6ePnnxICC4M4tfeq_jePRy_O2Yys99tq8H4ZFBjNs6WF6F3q5XlKjUfLs0nl5l59SnCIQmwkZIYfzB6bv2TSOHSwZL0sKsWkc4qjm8kwdaWtO2BnGSax2JoQC9U-TqAzwYd8nF-q7Gtx4EcE3d4uN6OZJl9uVIL4uxo5Zl0z17s6Cq-eyMuflEgPKR7k2ETeBD_E7Pl_KkUJQ65TpeRqVVmRE3eFZDDhAeRBnbxsul_Ivx0ZQnP7FjiYyXATh4tlamWM3fMWH8eW5XATh3E1Mb86Q8z3Ean0ZfZ0zqEal7EozkdyM9l3xxgugqKfmTSSbQjppt_TA0zflXGYHtrj2U9HUYLM2jeq1iNawFDB-zLXuM05NegT8XlraYsgjfjbDp1Vgb4qOcmxBjKwNlfxOJ7vSflMyN_YslJCkUAU\"\n    }\n  ]\n}\n------ankatech-4f9c2e7a\nContent-Type: application/octet-stream\n\n<binary envelope + ciphertext + tag>\n------ankatech-4f9c2e7a--\n"}}}}},"400":{"description":"Malformed metadata, same signKid/encryptKid, invalid input, or key does not support the requested operation: a `signKid` that does not sign or an `encryptKid` that does not encrypt, a composite KEM key named as `signKid` or a composite signature key named as `encryptKid` included (`.../errors/invalid-input`, before any response byte). PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Missing scope or cross-tenant key reference.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"signKid or encryptKid not found / unsuitable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"Unprocessable Entity (`.../errors/unprocessable-entity`): the request was understood and cannot be processed: the signing engine of the inner leg or the encryption engine of the outer leg refused the operation. This operation reads no ciphertext, so its refusal is never a ciphertext integrity failure. Answered with zero payload bytes while the response is still uncommitted; after commit the stream is truncated instead: the stream fails closed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Inner signing engine refused the operation":{"description":"Inner signing engine refused the operation","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Failed to sign data.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"500":{"description":"Unexpected failure during streaming sign-encrypt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/resign":{"post":{"tags":[" Secure Streaming"],"summary":"Re-sign data (streaming).","description":"Verifies a detached-JWS (<code>oldJws</code>) under <code>oldKid</code>—\nthe header's <code>kid</code>, which <code>sourceKid</code> may name and must then\nequal, or the key <code>sourceKid</code> names when the header lacks one—and\nimmediately signs the same payload with\n<code>newKid</code>, in one read of the payload. The response is\n<strong>multipart/mixed</strong>: PART&nbsp;1 (<code>application/octet-stream</code>)\nis the new detached-JWS JSON and the trailing PART&nbsp;2 (<code>application/json</code>) is\nthe <code>VALID</code> <code>StreamVerdict</code>. Nothing is written until the source verified\nand the new signature exists: a source signature that does not verify is 400\n<code>invalid-input</code> with no body part, the status of every re-sign on every key flavour\nand both planes.<br><br>\n<strong>A detached JWS signed by several keys</strong> (the General body, one signature per\nkey): <code>sourceKid</code> is REQUIRED and names the ONE signer replaced, and\n<code>newKid</code> must be none of the kept signers (400 naming the member, before any key is\ngated). The caller holds <code>RESIGN</code> on <code>sourceKid</code> →\n<code>newKid</code> and <code>VERIFY</code> on every kept signer; every signer is judged on the\nVERIFY lifecycle row at its protected <code>ank_kv</code> and <code>newKid</code> on the RESIGN\nrow, all before any payload byte is read. The payload is then read ONCE into every signature\nand the new one together; the new signature is completed only when EVERY signature verified.\nPART&nbsp;1 is the General detached JWS with the new signature, under a protected header\nbuilt fresh for <code>newKid</code>, at the replaced position and every other signature\n(<code>protected</code>, <code>header</code>, <code>signature</code>) as received. A member\ngiven twice in <code>metadata</code> is 400 naming it. When the deployment requires an RFC 3161\ntimestamp on every new signature, the new signature carries its <code>sigTst</code> in its own\nentry's <code>header</code>, as a single-key re-sign carries it; every other signature keeps its\n<code>header</code>, and so its own timestamp, unchanged.<br><br>\nThe replaced signature's key and its replacement must be of one class: both symmetric, or\nboth asymmetric (a composite key is asymmetric). A pair whose classes differ is refused with\n400 <code>key-operation-incompatible</code> after the grant, lifecycle and capability checks and\nbefore any payload byte is read; a JWS signed by several keys admits no symmetric signer at all\n(422 <code>JOSE_SYMMETRIC_ACTOR</code>). Over a pair of one class:<br>\n• SIMPLE → SIMPLE<br>\n• SIMPLE → COMPOSITE<br>\n• COMPOSITE → COMPOSITE<br>\n• COMPOSITE → SIMPLE<br>\nEvery key signs with one signature (a composite key's is one composite value), so a source\nsigned by one key carries one signature and its result one, and a source signed by several\nkeys carries one signature per key and so does its result. A composite source is named by its\nprotected header, so <code>sourceKid</code> may not supply a composite key for a signature\nwhose header names none.\n","operationId":"resignDataStream","requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`metadata` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`metadata` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/ResignDetachedJwsStreamRequest"},"file":{"type":"string","format":"binary","description":"Binary data to be re-signed. No size limit."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `stream.resignStream` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for RESIGN on the source and target keys - or, for a JWS signed by several keys, for VERIFY on one of the kept signers - or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations, or a key's lifecycle does not admit its leg. Answered before any payload byte is read; the body names no key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/stream/resign","timestamp":1745120400}}}}}},"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/resign","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/stream/resign"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict: the tenant's key-protection backend is not configured - never declared, or declared but not yet bound. Durable: complete the setup rather than retrying. No Retry-After is sent. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when the target key (<code>newKid</code>), or a recipient a re-encryption of a multi-recipient token keeps, is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>): nothing is re-encrypted or re-signed under such a key. The source key (<code>oldKid</code>) is not refused for either: what a key produced stays readable after it stops protecting. The refusal is decided before any output exists. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature. <code>conflict</code>: the key was written between the request's admission and its use; retry the request.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/stream/resign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/crypto/stream/resign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/stream/resign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/resign","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"200":{"description":"multipart/mixed: PART 1 the new detached-JWS JSON (for a JWS signed by several keys, the General body with the one signature replaced), trailing PART 2 the VALID StreamVerdict. A source that does not verify is a 400 before any part is written.","headers":{"Crypto-Policy-Info":{"description":"Base64URL-encoded JSON payload (snake_case) describing key\nselection and policy warnings for the current request.\nDecode and deserialise into the `CryptoPolicyInfo` schema.\n","style":"simple","schema":{"$ref":"#/components/schemas/CryptoPolicyInfo"}}},"content":{"multipart/mixed":{"examples":{"Three signers: signature 2 of 3 replaced + verdict":{"description":"Three signers: signature 2 of 3 replaced + verdict","value":"--boundary\nContent-Type: application/octet-stream\n\n{\"signatures\":[{\"protected\":\"eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6ImJvYXJkLWNoYWlyLXJzYSIsInZlciI6IjIuMCIsImFua19rdiI6MiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwiaWF0IjoxNzkwNjU2MjQwfQ\",\"signature\":\"Qm9hcmQgY2hhaXIgUlNBLVBTUyBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg\"},{\"protected\":\"eyJhbGciOiJNTC1EU0EtNjUiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJraWQiOiJ0cmVhc3VyZXItbWxkc2EtNjUiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImlhdCI6MTc5MDY1NjI0MX0\",\"signature\":\"VHJlYXN1cmVyIE1MLURTQS02NSBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbiwgcmVwbGFjaW5nIHRoZSBQLTI1NiBvbmU\"},{\"protected\":\"eyJhbGciOiJNTC1EU0EtNjUiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJraWQiOiJzZWNyZXRhcnktbWxkc2EiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjMsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImlhdCI6MTc5MDY1NjI0MH0\",\"signature\":\"U2VjcmV0YXJ5IE1MLURTQS02NSBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg\"}]}\n--boundary\nContent-Type: application/json\n\n{\"verdict\":\"VALID\",\"operation\":\"resign\",\"correlationId\":\"a1b2c3\",\"timestamp\":1736500000000}\n--boundary--\n"},"SIMPLE target output (ML-DSA-44) + verdict":{"description":"SIMPLE target output (ML-DSA-44) + verdict","value":"--boundary\nContent-Type: application/octet-stream\n\n{\n  \"protected\": \"eyJhbGciOiJNTC1EU0EtNDQiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJraWQiOiJteS1tbGRzYS00NC1rZXkiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImlhdCI6MTc5MDY1NjI0MX0\",\n  \"signature\": \"hAzvnNIpecDXfD4eUIHEbPcqBlYYhpYJGaQNJJnC1u9Wp8LT8xZGgAF9xs2nHmrF_YFxvPeZJCVO7l1GEVCb8OlY_41Ldm6atcJBxuHYUgX9MWdzh-vyWgrklEfQG1jcLv34K48BmUCOHCAzgmY5rtKKGd1AFmR7lSML9-5Y6rKFtvGPPMEwiwjyiQp5JWT09c7gREj3tjVzGkiWVm8Vkh2VVeEqbRIP_tS2HngkOzeY8kARuqWNqc0GJFJDUmlwvVrAh7p4OvLJ6kE8dJFjomzClWjeeK_zQQjBUZckZbmoNueUVLStPfsUWby8ubtC5mQ2zFoy4ZG4dGy72c5gSrYTZyI3dF7C4LI99-FvYbsEi6C9oCUQQfR2PCWP8vih4UHL-0zwLRsZ_U3eKgwuSHwSGY5AJC_g5hqfbgKHZQwERuQjD_Eu6-Fy-KOtiHzzTPdXdwt06UTWFDv_chAwBg--ctNT7tKd4MIo1LUSCRVHhsLa8nUd0t1XQmEp6N4xSzavxFCDeSXBoi4KtnQg6W2fjr5F7YxZvUWUDqxVodO7ZOqLp54RytVzdqolv9sdpm3ZAE-4pbQ8_064Hxp1Cd0oHW_NU8voarLTFGjhatbKe1PTFr6JUnNNAgd8-qzOi7w3VlH9M_48jgJ0Xs_qAjW7dsvtsdGhbLuGVixddzhyFSjBLHWQTCBb-7d6oHQ6DeJ7RBXHZyP-J7Miojn1JXfN54fuz02LeXbh3GZnk_PZOsQapyuSyqfHBpdswuw1Hcci-NzCOy9JKdUkGWUCE2FR5fPr5maXqijTn7a24E6wQq5SbMp1quKe_Ub9f63hwjBGFT-lTUX1DuKu9Xy1u9d0VKaqy9YZv8RZpp3mE9myelv-EYQzuax6rTAGLLJoRGB9TPLiQZ3nMnhWpIxU44Z_grQwJnjhprJl0u2k7r30d1LJik0_J_PeiLMkWrHNTY1e1x3xd2vvUUrkWeUaawQCHRFC_a9x2C1ti0Q94OI-k-PA3HF8kLB5R19TiF3XNDVE2jDCkkaJGCww0Q0MeRkmDD90GHf9Wg8hvrLBLCgvAT7qNmWMrUBmj-1keYpXeojDHaJDUuSxEl028c1Z0i25PtbMj5NSIw3UW-MvpYpNIy9XOgupFUxdbfQm8vuE8iGRmPR8xM6IENMtuzQZzWc0y6bZbyFEOuErla9xytndyflpAkeQ252pT7TaRteVQSoCXn-M3akykr6504FQl9FiUq1ENbfhwlAT29uQBhKq1G5ols9u-huQsiNQgvCHr5QW22jSaF7vL5fksTGrv9VgSEgjwyuyG5mNSHm2iXpt8R4_aKip9_lY5yFGBoWdxyWsZIvuj-oDYJGEgHE4PihoNyYrw9olKssrFFcjqOdHZiozWrrnbQmX9vk6FO7ohzFnveBgM1iTId-q0bFlIir15L0ISNLiQpkFVGaAqUTpQvEchMCkmX1ATqHZa0z3MRIpIyphKr8zqhRodRJGfYtQ7WGJ2-6-7uozpvx6ATCXUUNVxsrOBtkJjKR8sbZivaXM9D9h7urp__z2_gs6RNH4MBQz8JHl7ct5RAyOZs2NaddAlA3EGJ587jvLjzNKsG5qobpasCEByiF6r_b1w02yx3KVLqlb4EHDYceUTYcbpPJaKFNEuq8dfFvdO348oQDRJ6MiyNgt9wB655hgTU2vQmllIPyP_EhC7BrmqKzxSHXTZDdNhuvWaMyQulA2Aad-ssuTgDz54AWhXqlOHHe4PQMKJg40nZzwXCSt8iMWJqATLw_PfnpNZmsMImAD2IvfFSg8jvGJ_7603hdhb7aIsDPHsJOxKWndpcJ8xbK9eEoDGmGG6BzXbIUza8bdBuqvh0wdBtLjtaTBuUwXhbIKBWPsPvI9ClPBxNaHGylUjw53eAR-cO6eCVahZX3YsvX2JD_UPWwKGuKpHC-2KKX1Zky84_pZKNGNn2PtbzxhdTgxTMnBTSzGDETQ56ftETRAxMNUJ7iFZ44uCaln3wDV3cW56d5bBkpvhCxeEa2q1kqMtt-Q9raF1X9uVwp1Iw4rdkBAq34likmLMkQfCzQhQdm7nbuvxj4dfGO4rr6eXevIowb5enK8k5nz8fReqaFFfa-kDurtN1-gE2ccjnIRUpZnoMfHG9TOH_XzGa-bBthaPoOXiiK6hoqquSf1AuZYXfjoQi35vCe1oiJZF1xUH-nszMODWfopo2KB14jGqXtAflUAJgH72xgQk9yRMb3gVX0QzxV7fem12ijC-I78kp2hAUgX93nFbbxTC7n1UHZVY2t3nARgI91X-mdpz0h_mYHN1IKKj0lCJu4iycCnJVwRwx85Yr776ncQNhW2dixscC4AxUuRLb56DmKKRyVzZE6NWblxx22RpSby-9MPy0rz6hqa2i4C6LLRpFkleHjlpmg09xn5teRstrZ1uEtGMOZSP3tjxKgZIOnho8oQO64HNmY470XjEh-DlN_7qEzbqYGRqfneazC0WOlhRJBkj6KdmRO132D-K12_Z2-PJABzP9TXbXz0ManfABCsX6JmWnhT17N04AIjwSF8K_9SLXugn-nxfs2QL9rGyd9mJiWrxLsAZAF5Hvp4SoAIEVXpsahty4axXxQBkHDLAdalJpvpj9Gbp4uOz8ulHHhGqSBjr8Ffm4SpfJSNSE9YmQHOGePv_0qVPyVvObb65Qq4368IC4vcUdIXx3Q-JP-qNdZH3pzqhg_6bveGRfuV0vHowQEosfNnng-x4Sb42ZnRtAN7XNYk26qd4ObH47kq4B7WricF1D7DlQg79BDtnN_XSXW9ciQBEhUU94IkF9kHAzO8YaAKzxjUtGIEwGeYGVYKZGwQf2bF-3mueX9dPRIyNuPn3lp0_3k0iQZDl52QWIVuYpnftsav7ZoNiqPj4FLlgzE6kreQe6btz6xE3XjxEsjrAs441SaJQfCdvb1aJwlA1-q2ZaLjACzpTasz43rM2OLWo92Otm8O4fuHCE2crkQpgJO_rclYsfuSd6mogg-jNKvE3XEGCRN2bZ73_qq8uIhNsR866i55rMFAtKaJdkiGrwmvAMQkNPmdqIgIm65ycgynW2yy8BBv6zMVy40C1hXmLGFAUxdC8UQjZnWBn7LH0gcPH1ZdY2dobJufpcbP4ufu_wwcHicrQ0xQVWJwdY2stsELIys3QkxPXWVweYWYra67ztbZ5-sAAAAAAAAAAAAAAAAAAAAAAAgaKj8\"\n}\n--boundary\nContent-Type: application/json\n\n{\"verdict\":\"VALID\",\"operation\":\"resign\",\"correlationId\":\"a1b2c3\",\"timestamp\":1736500000000}\n--boundary--\n"},"COMPOSITE target output (P-256+ML-DSA-44) + verdict":{"description":"COMPOSITE target output (P-256+ML-DSA-44) + verdict","value":"--boundary\nContent-Type: application/octet-stream\n\n{\n  \"protected\": \"eyJhbGciOiJNTC1EU0EtNDQtRVMyNTYiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJraWQiOiJpbnZvaWNlLXNpZ25lci0yMDI2IiwiYW5rX2t2IjoxLCJpYXQiOjE3OTA2NDcxMDZ9\",\n  \"signature\": \"SsiMsmO4GVzUAm113L0o68sjhjZsgKZ-wPz4Q22d5jdU4OMGsLTEc-PN-p0Uc_CGWwLz8I7G0XwYCjkOQfqWQbWA7HZjngzg4UJQ2zJKFGdte70WSoFbYXPFUr25Mo0Y-1-pjoOFRihU2g5YqdUXeVJcOTTfSBjEotkqXBZwXlgEZXlhfhXh_gJ7Gt6S6byajPu6mpfl2c3wHHps-XJSAkKNZPax5Boj7bGbXybu2ni8CBMmFeBrdLqIdsxpVkCYgw5yE096AvMIAzqpkvam2EEruVDNVaTK7qnpK6Ppd9IWyyFCyKsJQA-aIqDkQh2XfjlmMaReRHeJqfb3WshfI1C8rKqRLVb3j1rmvzGoQ7RaaGfjDAosD3AcO8KCNXMjIsu7rHVTLWEZpq8uiSgepee4Zp-2TUp5OuyzhN9SCNolrYP6sKwAwgd0D3U4N7ZXJgVIlo3AFuQ9ptr-apGAtB9gw42UQfYWIp9Wf7sYhzlNL7ob1GzBaQwv8_og9qhI5Yp6WdPDCw4qmRlrCWFZ6QkDVSckf6y0a2cohzN-9SEYAJbxFQB9shh8-scH9nkMvUBpF1s2lASsQd9jTpY5iBAE6aIi7DTcInYmr2RH9sB3OPCotJdwlhPKAZDNWkLMrBiNwcJuLd3nQJXTO965D5DbmjKrqAcGpNpmjZHB1ZyAfLIHELSYHc9XMmEPove25BnYxUwQNw4gpoFdW2cMy3wP6Hk_fzpSalDDk_h6k8B5EsM0ONHtisGhxgvvI8kefBL_VIzx8UOWDj0LD0mGqguTH88SJT3AuCUinXUnI7Y_yhc9MnC-YnrAW9woyotQ7hBcbBsMkDOp5Bjc1rEbFqk7oFzPQyD6JiNSNj3QFGD9xOvsbbEF8CqDodbXIh1rNFvsgnRNxbFdVgBCBg_gfOeA8kkDl63JrCFbiGPmtSH-XtqXRZze2vney8O5Aqr9z7EwEvRYPFB9jDYXaYUU3Uud2w-iONE_OIU8pTksziR_KL4XnbjZaGTUV7AAXzvlKiC21sgxdCHDZVQpaWq27Tsld-jU1KWPvAMW1e4RhWFENQN7eaq0_q-n5kIwUb5WFUL_3b3dB89XAfUJedYX-gAuvCb8CXPwdcmlELcvHvsUp2w72hobxs0A09bewUOej093xEm_iucnX5iPQlKMsqKNIPGFZSzv4YUhL_Z-b0ZKDSr9rK3YApYOGzyn-5FpSItTcVCA2J9S0TJyZSUncUk6-dFEqAq76NOpBQPhB1xjVNPluaXtZ4LzoBe1FC8ROrgroClO2X3CgIA0X1N8TyBD_jN5f4UoVIlxDDSBtdiFYu-OtfkG0PDTGVf-Z7a1k_6uzoVl4Jzu-oF9P8G4V74_k2Sta_23ivkt-0xMqDFVyXW2QrCFhr3qM6WT8jezuZpilnzkjCVxlEX5CW--zudEQUfKUeA38XmCyaPavmiXhky5wmNSZRnsKQuqJRGXtZhAxcEQl8zAtp4R9dPkQUVuOwwtKNppZWf66x7Hz-pGXzGp2p0JV1mz_jMP0PvafMwcEwjBt29A78JIqRulnsaizhkSp_Ugg5ifjksn64IK6rJlEoksa1Tmb2Cqb-MdJw80AtNHskxSATVCL-S_G-RhF6FQSfvuge_aYGeyIjnv7GsZpniI_jfyGOjt0SQ-azFPAPuNmhPqejZIwfJN41sayf4_KlymNw-GpjAklspyqAktt13Bf3SMfXuSRFcJciU7X0DQbAuXr8TJeRGOjluV-WvhtWIlb8V53kOQbgbTtEuqz4K0cj69vJCGq1_4d8aWevYur_H-AQxjW7Uf4DTGUEn5ekts3IEBSCFKHTEs6EzBv5YJ7wWtNb2UEtdDiUrAS9lXDJ0HYi7HDZ9jDwkpS_T-M61Q3oQqYZjWc2bMwywcXEJS4GSGbfJNkfmvyW5uktTgR7MvI2I0LsoZKlIkkiebgNXcS58wH7hVJ7hOPwj4HhY717eZrR84WuPxJIWdLrSV86kg1nONFL_6dCqOQevoDGFz6nwgVrKZT7FIPH7DYosJkcUosLzgSo5pt12FNoDeKQs1E2gSw8LCceMj0yxOx2nHmndmn1l26-Augk5HNU3gBsY1Qvkn5_JzSm7R6Hwbmj5yIghPkaJtasbMc4aiqw9aNHzraX6UXUOPnygZnM_XrE5AhXGJwDD98LL2b7i0oCnJjF8atfdjaX7-ObPNMsZCPmH7lDQMB41vHW1NvEtuKKGcJO2YKoEe9hArItsujJ7NqWvunxzvEmOBubPd-ewFfc_fvXD184tCG1Y8uhP9ZmRBl96nx-gs0-iSB44Bgqz4-KYc9o2yYuwqN0y_oO3npoXJc9qmmfIBJrEGio6gKUzhlMs8XZ_yz00v22nhZZ_zbFMVHnubpLuKdYeHt1VYVWCQ_ueIblBXLNambWc92O7vi7sGKvSsY73oNcNYLfW9oU0RxrIn2FbXr1Qk0ZkbU_L2TttK7u5UVNCHvlSqIsMIDSzz5kP-aNcDdBgqcGQkzupgM7n-9amN-cTop1jq-KUyZdqIjyDxXY12fZfSJwIoxUoGPmyCKUOsXdjQeRjNQzpG1m_zlwf6uY3x4A4l0XORorbSb1ss4iJic_JEgUW1edHK9TXmcfLdAGiQpkPEx0VllMH3TasGuI7o-TEGU1FcS7wcgL52NWbY_3zs9o0EJ0svyRcfyB4SfjC_DPHjuYyQGET9HVay8vzTF8P-osVxjCRt7oZMSvJ0bkG_WOm2OjwBH1mhXaEfXCeg1guE7OD7Suyixf-9ylOp7oscCSPyVEWLl1BcrJFsBmW6coN_ayLV2HxvB34axXMS3rpfIRG0JzFpHGVb8_AmOXR5K0FAFMwSpm41GcqLubLx7MNqCulXtk13IDLvGO8yAClSz_Tm0MaL-MRFbTivvGiDYi-J7HAT5KNIjHgzJw_1UmQT6prpKLrD6EDf0vkG7vppW00XfDbIPuMdXqJ8Xd1oZ-Ll4HjayECe1K_48pB3m2uol1S2EUGUUHN_o4Ci9PTDVPXI9-9qI0pY2O-IZ0LAr2yBuvviPe2LfvAyC1c7-ldW6ddHAsA5vOjTwv5CHbY0x-eC7XNmGYfV4ZQCh6f33A_HGv67vJAdMkRGXW1yh5jO1eLq9Q0cTU6DmbK3xNfa9AECGB0hKkFfZH2ltbsEGhw-U1Ved6O68_QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4aJzMwRQIhAPNVngpmkxUCy-yZ23RVVMoOa1-5VS2_elGbzSIF5BS9AiBJFg-h-bieGhZokjCiumeiwF1OzDpFfaMnJr8P8FJr-w\"\n}\n--boundary\nContent-Type: application/json\n\n{\"verdict\":\"VALID\",\"operation\":\"resign\",\"correlationId\":\"a1b2c3\",\"timestamp\":1736500000000}\n--boundary--\n"}}}}},"400":{"description":"Invalid Base64, a source signature that does not verify (for a JWS signed by several keys, ANY of its signatures - every one is verified and nothing is signed or written when one fails), or a key that does not support the requested operation. A source and target key of different classes - one symmetric (HMAC), the other asymmetric (a composite key is asymmetric) - yield <code>.../errors/key-operation-incompatible</code>, refused after the grant, lifecycle and capability checks and before any payload byte is read; the problem names no key. For a JWS signed by several keys, also: <code>sourceKid</code> absent or naming no signer, <code>newKid</code> naming a signer the operation keeps, or a <code>metadata</code> member given twice - the detail names the member and no key. A source JWS whose protected <code>crit</code> names an extension other than <code>b64</code> (RFC 7797, the one this route implements) or a parameter the header does not carry, or that carries <code>crit</code> in its unprotected <code>header</code>, is a 400 (not a 422), refused before any key is resolved (RFC 7515 section 4.1.11); so is a body or protected header that repeats a member name, a <code>b64</code> that is not a JSON boolean, and a JWS signed by several keys whose signatures do not all declare the unencoded payload (RFC 7797 section 3). PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"A source signature does not verify: nothing is written":{"description":"A source signature does not verify: nothing is written","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"The old signature is not valid for the provided data.","instance":"/api/v3/crypto/stream/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}},"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}},"HMAC source, Ed25519 target":{"description":"HMAC source, Ed25519 target","value":{"type":"https://docs.ankatech.co/errors/key-operation-incompatible","title":"Key Operation Incompatible","status":400,"detail":"Unsupported re-sign: symmetric <--> asymmetric keys are not permitted","instance":"/api/v3/crypto/stream/resign","correlationId":"3f2b8e41-6c0d-4a77-9e15-b8d24c7a90f3"}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"The detached JWS is not the RFC 7797 detached form this route reads, refused before any key is resolved, for a SIMPLE and a COMPOSITE key alike. <code>composite-wire-shape-mismatch</code>: the protected header does not set <code>b64</code> to the JSON boolean <code>false</code> and list it in <code>crit</code> (RFC 7797 sections 3 and 6), or the body is not the flattened or the General serialization (a <code>signatures</code> member beside the flattened members, a member the serialization does not define), or a Compact token carries a payload. A General body with ONE entry (<code>signatures: [{protected, signature}]</code>, what a PKCS#7 streaming conversion emits for one signer under <code>serialization: JSON</code>) is read exactly like the flattened one. <code>unprocessable-entity</code> with <code>extensions.errorCode</code> <code>JWS_SIGNATURE_MISSING</code>: no signature at all (an empty <code>signatures</code> array, no or an empty <code>signature</code>). <code>unprocessable-entity</code> with a key-neutral <code>extensions.errorCode</code>, for a <code>signatures</code> array of more than one entry, decided from the headers before any key is looked up: <code>JWS_ROOT_MEMBER_FORBIDDEN</code> (a root member besides <code>signatures</code>), <code>JOSE_ACTOR_KID_REPEATED</code> (one key named twice), <code>JOSE_SYMMETRIC_ACTOR</code> (an HMAC <code>alg</code>, or a signer or <code>newKid</code> that resolves to a symmetric key, refused after its gate), <code>JOSE_ACTOR_LIMIT_EXCEEDED</code> (more signatures than the deployment admits in one token). <code>unprocessable-entity</code>: a Compact token that cannot be parsed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Detached JWS without b64:false":{"description":"Detached JWS without b64:false","value":{"type":"https://docs.ankatech.co/errors/composite-wire-shape-mismatch","title":"Composite Wire Shape Mismatch","status":422,"detail":"Detached JWS must set “b64” to false and list it in “crit”.","instance":"/api/v3/crypto/stream/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}},"One key named by two signatures":{"description":"One key named by two signatures","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The token names one key in more than one of its entries: every recipient or signature of a multi-key token names a distinct key","instance":"/api/v3/crypto/stream/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JOSE_ACTOR_KID_REPEATED"}}}}}}},"404":{"description":"oldKid/newKid not found.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Unexpected failure.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/reencrypt":{"post":{"tags":[" Secure Streaming"],"summary":"Re-encrypt data (streaming).","description":"Decrypts the incoming ciphertext using <code>oldKid</code> (from the JWE header)\nand re-encrypts it on the fly with <code>newKid</code> (provided via query parameter).\nReturns a <strong>multipart/mixed</strong> response: the new JWE header part,\nthe re-encrypted ciphertext part, and a trailing <code>application/json</code>\n<code>StreamVerdict</code> part emitted AFTER the source AES-GCM tag is verified at\nend-of-stream (an <code>INVALID</code> verdict signals a tampered source; no committed\nciphertext is promotable). All key-selection metadata is returned in the single\n`Crypto-Policy-Info` response header.<br><br>\nThe two keys must be of one class: both symmetric, or both asymmetric (a composite\nkey is asymmetric). A pair whose classes differ is refused with 400\n<code>key-operation-incompatible</code> after the grant, lifecycle and capability\nchecks and before any body byte is read; the source key's class is the one of the\nmaterial its <code>ank_kv</code> pins. Over a pair of one class:<br>\n• SIMPLE → SIMPLE (single recipient → single recipient)<br>\n• SIMPLE → COMPOSITE (single recipient → multiple recipients; the SIMPLE source is asymmetric)<br>\n• COMPOSITE → COMPOSITE (multiple recipients → multiple recipients)<br>\n• COMPOSITE → SIMPLE (multiple recipients → single recipient; the SIMPLE target is asymmetric)<br>\nEvery pair is accepted on this route exactly as on the buffered <code>/reencrypt</code>.<br><br>\n<strong>A JWE addressed to several keys</strong> (no <code>kid</code> in\n<code>protected</code>, a <code>kid</code> in every recipient header, the recipient set\nrecorded in the protected <code>ank_kvs</code>): the <code>sourceKid</code> query\nparameter names the ONE recipient replaced, whose <code>encrypted_key</code> alone is\nunwrapped; the new header keeps every other recipient in its position with\n<code>newKid</code> at the replaced one, under a FRESH content-encryption key (the\nreplaced key cannot read the output), each kept recipient re-wrapped at the PRIMARY its\nwrite gate judged, as the new <code>ank_kvs</code> records. The caller needs a Capability Grant for\n<code>REENCRYPT</code> from the replaced key to <code>newKid</code> and to every kept\nrecipient, and no <code>DECRYPT</code> grant; every grant, then every key-lifecycle\ndecision (replaced key on DECRYPT, <code>newKid</code> on REENCRYPT, kept recipients on\nENCRYPT), is decided before any body byte. A recipient entry outside the protected set is\nrefused (<code>JWE_RECIPIENT_SET_UNPROTECTED</code>), and a source whose tag fails at\nend-of-stream leaves the output without its tag and the verdict <code>INVALID</code>.\nThe recipient set is authenticated by that same source tag, and on this route the tag\nis verified only at the end of the stream: the re-wrapped keys and the re-encrypted\ncontent are streamed before it is. What bounds who can receive the content before a tag\nfailure is therefore the caller's <code>REENCRYPT</code> grant from the replaced key to\nevery kept recipient, decided before any body byte, not the tag.\n","operationId":"reencryptDataStream","parameters":[{"name":"newKid","in":"query","description":"KID of the key the payload is re-encrypted TO. For a JWE addressed to several keys it takes the position of the replaced recipient, and must not be one of the recipients the operation keeps (400 naming `newKid`). Given at most once.","required":true,"schema":{"type":"string","maxLength":255,"pattern":"^[A-Za-z0-9._-]+$"},"example":"acme-archive-r4"},{"name":"sourceKid","in":"query","description":"The recipient this re-encryption replaces, named by its key identifier. Required when the JWE is addressed to several keys (no `kid` in `protected`, a `kid` in every recipient header): it must name exactly one recipient, whose `encrypted_key` alone is unwrapped. When the header names one key it is optional and, present, must equal it; when the header names none it is required and names the key the payload is decrypted FROM. Otherwise 400 naming this parameter and no key. Given at most once.","schema":{"type":"string","maxLength":255,"pattern":"^[A-Za-z0-9._-]+$"},"example":"acme-archive-r2"}],"requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`header` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`header` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"header":{"$ref":"#/components/schemas/JwetGeneralJsonHeaderReencrypt"},"file":{"type":"string","format":"binary","description":"Original ciphertext envelope (binary)."}},"required":["file","header"]},"encoding":{"header":{"contentType":"application/jose+json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `stream.reencryptStream` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for REENCRYPT on the source and target keys (for a JWE addressed to several keys, also from the replaced key to every recipient kept), or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/stream/reencrypt","timestamp":1745120400}}}}}},"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/reencrypt","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/stream/reencrypt"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict, one of two durable conditions told apart by the problem type. <code>key-protection-backend-not-configured</code>: the tenant's key-protection backend was never declared, or is declared but not yet bound - complete the setup rather than retrying. <code>invalid-key-state</code>: the source or the target composite key's material version records no usable key-derivation function, so no combiner can be selected for it; the material must be rotated. The target is admitted before any source private key is used. Both are answered before any payload byte, and no Retry-After is sent. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when the target key (<code>newKid</code>), or a recipient a re-encryption of a multi-recipient token keeps, is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>): nothing is re-encrypted or re-signed under such a key. The source key (<code>oldKid</code>) is not refused for either: what a key produced stays readable after it stops protecting. The refusal is decided before any output exists. <code>conflict</code>: the key was written between the request's admission and its use; retry the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/reencrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Composite material records no usable KDF":{"description":"Composite material records no usable KDF","value":{"type":"https://docs.ankatech.co/errors/invalid-key-state","title":"Invalid key state","status":409,"detail":"Composite key 'old-composite' material version 2 cannot be used because it records no key-derivation function from [HKDF-SHA256, HKDF-SHA384, HKDF-SHA512, CatKDF, CasKDF]; rotate the material","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403,"extensions":{"code":"INVALID_KEY_STATE"}}}}}}},"422":{"description":"Unprocessable Entity, answered with zero payload bytes. <code>unprocessable-entity</code> with the JWET-header detail: the <code>header</code> part is not a well-formed JWET header. A <code>crit</code> is part of the same 422, and its detail names the defect and where it sits (never a value from the header): a <code>crit</code> in <code>protected</code> naming any extension (this route understands none), or a <code>crit</code> in any <code>recipients[i].header</code> - <code>crit</code> MUST be integrity protected (RFC 7516 section 4.1.13). A recipient without <code>encrypted_key</code> is the empty key of <code>alg</code> <code>dir</code> (RFC 7516 section 7.2.1) and is accepted; a member that is present but not a string is this 422. <code>composite-kdf-mismatch</code>: the protected <code>anka_kdf</code> of a composite JWET is absent, is not a key-derivation function this platform defines, or is not the one the source key material records; the material is the only source of the combiner (NIST SP 800-227 section 4.6.3 downgrade) and the header is a cross-check, so the refusal is raised before any decapsulation. <code>composite-wire-shape-mismatch</code> and <code>composite-component-algorithm-mismatch</code>: the recipient count or a per-position <code>alg</code> does not match the key's components. An AES-GCM tag failure is NOT reported here: it is only detectable at the end of the stream, after plaintext may already have been written, so it is the INVALID verdict of the trailing part. This contract covers <code>/stream/decrypt</code> and <code>/stream/reencrypt</code>; <code>/stream/decrypt-verify</code> reports every outer decrypt failure in its verdict. A JWET header addressed to several keys is refused with this status BEFORE any authorization or lifecycle check when its shape is one no key accounts for, each with a key-neutral <code>extensions.errorCode</code>: <code>JWE_RECIPIENT_KID_MISSING</code>, <code>JOSE_ACTOR_KID_REPEATED</code>, <code>JOSE_SYMMETRIC_ACTOR</code>, <code>JOSE_ACTOR_LIMIT_EXCEEDED</code>, <code>JWE_SHARED_KID_NOT_COMPOSITE</code>, and <code>JWE_RECIPIENT_SET_UNPROTECTED</code> when its recipients are not exactly the set its protected <code>ank_kvs</code> records. After the grant and lifecycle decisions, <code>JWE_COMPOSITE_RECIPIENT</code> when the replaced recipient, <code>newKid</code> or a kept recipient is a composite key, and <code>JOSE_SYMMETRIC_ACTOR</code> when <code>newKid</code> or a kept recipient is a symmetric key; all before any body byte. A single-recipient JWE whose <code>alg</code> and <code>kid</code> sit only in its unprotected recipient header (the General layout of RFC 7516 section 7.2.1, which a PKCS#7 streaming conversion emits for <code>serialization</code> <code>JSON</code>) is read after its <code>kid</code> is authorised and resolved in the caller's tenant: its <code>alg</code> must be exactly the key-management algorithm the platform uses for that key, so the key decides how the content-encryption key is unwrapped and the header only repeats it. Any other <code>alg</code> is refused with this status and <code>extensions.errorCode</code> <code>JWE_ALG_KEY_MISMATCH</code> before any body byte; for a symmetric or composite key that layout is refused with <code>JWE_ALG_NOT_PROTECTED</code>. For a single-recipient JWE, a header parameter name present both in <code>protected</code> and in its recipient header is refused with this status as a malformed header, whatever the two values (RFC 7516 section 7.2.1: the header locations are disjoint); for a composite JWE or one addressed to several keys the same overlap is a 400, refused by the classification of its recipients. A single-recipient JWE whose <code>alg</code> and <code>kid</code> are in <code>protected</code> is read from there, as on the buffered routes: its recipient carries its <code>encrypted_key</code> alone (nothing at all for <code>alg</code> <code>dir</code>). A member the header does not define is refused with this status before any key is looked up, on every shape (one key, a composite key, several keys): <code>JWE_ROOT_MEMBER_FORBIDDEN</code> for a root member besides <code>protected</code>, <code>recipients</code>, <code>iv</code>, <code>ciphertext</code> (absent or null: it travels in the body, and a value is this 422 as a malformed header) and <code>tag</code> (absent or null when it is the body's last 16 octets, or the Base64URL tag when the body is the ciphertext alone) and, on a composite key's header, <code>anka_composite_mode</code> - a JSON <code>aad</code> member and a shared <code>unprotected</code> header included - and <code>JWE_RECIPIENT_MEMBER_FORBIDDEN</code> for a recipient member besides <code>header</code> and <code>encrypted_key</code>.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"A JSON aad member at the JWE root":{"description":"A JSON aad member at the JWE root","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The JWE carries a member at its root besides 'protected', 'recipients', 'iv', 'ciphertext' and 'tag': this platform reads no JSON 'aad' and no shared 'unprotected' header (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ROOT_MEMBER_FORBIDDEN"}}},"General-layout recipient whose alg is not the key's own":{"description":"General-layout recipient whose alg is not the key's own","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This JWE carries its key-management algorithm only in the unprotected per-recipient header, and that algorithm is not the one the platform uses for the key its kid names. An algorithm outside the integrity-protected header is accepted only when it is exactly that key's own key-management algorithm.","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_KEY_MISMATCH"}}},"General-layout recipient addressed to a symmetric or composite key":{"description":"General-layout recipient addressed to a symmetric or composite key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This operation reads the key-management algorithm of a JWE addressed to a symmetric or composite key from its integrity-protected header, and this JWE carries its alg only in the unprotected per-recipient header (the General JSON layout, RFC 7516 section 7.2.1). Only a JWE addressed to one asymmetric key may carry its alg there.","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_NOT_PROTECTED"}}},"Recipient 2 of 3 names no key":{"description":"Recipient 2 of 3 names no key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"A recipient of the JWE carries no 'kid' in its header: a JWE addressed to several keys names the key of every recipient (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_RECIPIENT_KID_MISSING"}}},"Composite KDF mismatch":{"description":"Composite KDF mismatch","value":{"type":"https://docs.ankatech.co/errors/composite-kdf-mismatch","title":"Composite KDF Mismatch","status":422,"detail":"Composite JWE for key 'old-composite' declares anka_kdf 'HKDF-SHA256' but the key material records 'CatKDF'","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}},"Composite recipient count mismatch":{"description":"Composite recipient count mismatch","value":{"type":"https://docs.ankatech.co/errors/composite-wire-shape-mismatch","title":"Composite Wire Shape Mismatch","status":422,"detail":"Composite material has 2 components but the wire has 1 recipients","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}},"Malformed JWET header":{"description":"Malformed JWET header","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"JWET is not valid.","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"multipart/mixed: new JWE header + re-encrypted ciphertext + trailing StreamVerdict (VALID/INVALID after the source AES-GCM tag check).","headers":{"Crypto-Policy-Info":{"description":"Base64URL-encoded JSON payload (snake_case) describing key\nselection and policy warnings for the current request.\nDecode and deserialise into the `CryptoPolicyInfo` schema.\n","style":"simple","schema":{"$ref":"#/components/schemas/CryptoPolicyInfo"}}},"content":{"multipart/mixed":{"examples":{"SIMPLE key response":{"summary":"Two-boundary framing: PART 1 is a nested JWET (inner boundary); the trailing verdict rides a DISTINCT outer boundary","description":"SIMPLE key response","value":"------ankatech-outer1\nContent-Type: multipart/mixed; boundary=----ankatech-inner1\n\n------ankatech-inner1\nContent-Type: application/jose+json\n\n{\"protected\":\"…\",\"iv\":\"…\",\"tag\":null,\"ciphertext\":null,\n \"recipients\":[{\"encrypted_key\":\"…\"}]}\n------ankatech-inner1\nContent-Type: application/octet-stream\n\n<binary envelope + ciphertext>\n------ankatech-inner1--\n------ankatech-outer1\nContent-Type: application/json\n\n{\"verdict\":\"VALID\",\"operation\":\"reencrypt\",\"correlationId\":\"a1b2c3\",\"timestamp\":1736500000000}\n------ankatech-outer1--\n"},"COMPOSITE key response":{"summary":"COMPOSITE recipients live inside the nested JWET's jose+json header; PART 1 is still one opaque JWET under the inner boundary","description":"COMPOSITE key response","value":"------ankatech-outer1\nContent-Type: multipart/mixed; boundary=----ankatech-inner1\n\n------ankatech-inner1\nContent-Type: application/jose+json\n\n{\n  \"protected\": \"eyJlbmMiOiJBMjU2R0NNIiwia2lkIjoibmV3LWNvbXBvc2l0ZSIsImFua2Ffa2RmIjoiSEtERi1TSEEyNTYifQ\",\n  \"recipients\": [\n    {\n      \"header\": {\"alg\": \"X25519\"},\n      \"encrypted_key\": \"McTdzamGVz9DHwNSZQldGNbLt5fJpP86HNW37HAiH1I\"\n    },\n    {\n      \"header\": {\"alg\": \"ML-KEM-768\"},\n      \"encrypted_key\": \"Sjkiwn9900-6F-fzMkLB-lmqq_TTCBPPZkqrvquKoDNata8H99w_DgLCkhILWJqY9vwQpscw9ZKNgLD39ynUVXh_qisFAil1hEcOU9xXQTLgoMWOQlejz9CVCjrIpI6w_bya7fe2Y_5Ge2QWDSanWrYks66XxRhNyh91oqgJqhET_iuJ4lVr4YvfH2vGUfW-DUnVd5zOCdr3LHftiLvEwNy7u6BEPaZOG_8WdRMJEmz8Iu3L-iCnjSQZrnUUZRPWKDXJlZv91Sbqkw8ftcWzWEKcCMChPXjfiGQsQdOrkEw2ZSFSgsm1z230BydCWUOXXNbXt58uBWXDDsiqLDxG7JSTz8nR41Pb9FhQsnR_eJ7qUPWJfE6iF4uIjeQzEqQtKgadjIWfysdeZEj3P2KO3niMY39ua9-zorUGmFwaAvM7zisJxnuGtVZbRmuQ52wt5LW5VqInn0DHXaGOK7-91zoSp1Vh9yV_s2NmjZRj1etK8Xw42-doRtvnMq3w4i_VJXqCl5guVtxVpk6Y4pfO0K8SsBHfG3qFbIrWP5v6alUhqdGE2-reJipKeBFEfOnwXHEBcJUCFgLo7Yk-v8F0DbkzNW_bmZ1Mk5DA_NLMFXxjwM2YR8tmRAXw2zWsk7-xDsfbtKTEogV1JBhh8YgbvP7lMjOVM8j0RRzGx7FbOtqFqvGFQckbwn0zh-AH36a7I9SZFGMiaOhxbzUSsnSHPa9fyWWlukO_nQ2X2aANP7KveNpdq6NA0k3tzgWDZQbkEjFBMlmQbnlP3WAHmJ1uqLBpGmk5R_p6KL16WjjFxtBMFj9YUn1q35F6HbNMVpyffJINove34kodh-eX39yZW_mbD9qq3zHnjg4lWeF1vm6fhrKOMsf48wMLJtTNOxV5ocRYxjVOtzCsno8d3ZNpn8WnF4ywkT_j6e5-ywXjiYlGzBqF0fx0stmK8DHd6b0OWxk42bE1LmhpcvYTMeY2F57WydxJxoscnljexHtFHYQceq0Qk6VoBmZ_krETFWzvYJKqTJZ3NmYPjtVJ3sA4O5n9-Ga6AChtw6VLyX8zaCuEBeP8dvyuSW-rFu14Q15l39GmLLccHbz4LbZk2RLzTsdjR1vlxu1FsQCc9cQYthO9vqXQsN38Wqk3jpmn8IZ--gO7vqjv1MlFECYW9qf_wbFM3QR_8iatNAwHZEDofCrEJFWKehLQsAfty49UeDIa8YrCQolmgNN0yw2_hch8Y8QHC8JqrFuM6FHiT9OfOY0iF_W-b4z-zIQPdD8jAk-pn3XWmUr9jgN0Rpa4mnjq7egjyM3bdCmWXG-dx8Qrox1ZnR97gnkgGa7AFqXuDk6z8AxB_EfGBXOK6S5P19i8LcTNlQhGpTz53DX18M60nxulCRtKr_gJikzSilHdAVp8w5syAYyIW1388X1byeDAT6ZSiABEswD1uiobsMl9maI\"\n    }\n  ],\n  \"iv\": \"ffwbFr27XUieM4CQ\",\n  \"ciphertext\": null,\n  \"tag\": null,\n  \"anka_composite_mode\": \"COMPOSITE_KEM_COMBINE\"\n}\n------ankatech-inner1\nContent-Type: application/octet-stream\n\n<binary envelope + ciphertext>\n------ankatech-inner1--\n------ankatech-outer1\nContent-Type: application/json\n\n{\"verdict\":\"VALID\",\"operation\":\"reencrypt\",\"correlationId\":\"a1b2c3\",\"timestamp\":1736500000000}\n------ankatech-outer1--\n"}}}}},"400":{"description":"Malformed multipart, invalid JWE, or key does not support the requested operation. A source and target key of different classes - one symmetric, the other asymmetric (a composite key is asymmetric) - yield `.../errors/key-operation-incompatible`, refused after the grant, lifecycle and capability checks and before any body byte is read; the problem names no key. `sourceKid` absent where it is required (a JWE addressed to several keys, or a header naming no key), naming no recipient of the JWE, or differing from the one key the header names; `newKid` absent, or naming a recipient the operation keeps; either member given more than once, or not 1 to 255 characters of letters, digits and `. _ -` - the problem names the member and no key, and no grant or lifecycle decision is taken. For a JWE addressed to several keys, also: a member name present both in `protected` and in a recipient header (RFC 7516 section 7.2.1), or a recipient `kid` that is not 1 to 255 characters of letters, digits and `. _ -` (the kid rule every JOSE header kid is read under; the detail does not echo it). PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}},"Symmetric source, composite target":{"description":"Symmetric source, composite target","value":{"type":"https://docs.ankatech.co/errors/key-operation-incompatible","title":"Key Operation Incompatible","status":400,"detail":"Unsupported re-encryption: symmetric <--> asymmetric keys are not permitted","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"3f2b8e41-6c0d-4a77-9e15-b8d24c7a90f3"}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"KID not found or unsuitable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Unexpected failure.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/encrypt":{"post":{"tags":[" Secure Streaming"],"summary":"Encrypt data (streaming).","description":"Consumes two <code>multipart/form-data</code> fields:<br>\n1. <code>metadata</code> – JSON body <em>EncryptStreamRequest</em><br>\n2. <code>file</code> – binary plaintext<br><br>\nProduces a <strong>multipart/mixed</strong> response where:<br>\n• Part 1 (<code>application/jose+json</code>) is a JWE General-JSON header with\n  detached ciphertext.<br>\n• Part 2 (<code>application/octet-stream</code>) is the proprietary envelope\n  <code>[wkLen]‖wrappedKey‖[ivLen]‖iv‖ciphertext‖tag</code> streamed on-the-fly.<br><br>\nOutput format depends on key type:<br>\n• SIMPLE keys: JWE General JSON with single recipient<br>\n• COMPOSITE keys: JWE General JSON with multiple recipients (one per component)\n","operationId":"encryptStream","requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`metadata` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`metadata` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"metadata":{"$ref":"#/components/schemas/EncryptStreamRequest"},"file":{"type":"string","format":"binary","description":"Plaintext file (binary). No size limit."}},"required":["file","metadata"]},"encoding":{"metadata":{"contentType":"application/json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `stream.encryptStream` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for ENCRYPT on the addressed key, or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/stream/encrypt","timestamp":1745120400}}}}}},"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/encrypt","timestamp":1745120403}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict, one of two durable conditions told apart by the problem type. <code>key-protection-backend-not-configured</code>: the tenant's key-protection backend was never declared, or is declared but not yet bound - complete the setup rather than retrying. <code>invalid-key-state</code>: the composite key's material version records no usable key-derivation function, so no combiner can be selected for it; the material must be rotated. Both are answered before any payload byte, and no Retry-After is sent. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when the key's current material is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>); a material in both states is answered as expired. Nothing is produced and the key's state is unchanged: it still decrypts and verifies what it protected, and rotating it restores protection. Durable - do not retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/encrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Composite material records no usable KDF":{"description":"Composite material records no usable KDF","value":{"type":"https://docs.ankatech.co/errors/invalid-key-state","title":"Invalid key state","status":409,"detail":"Composite key 'my-hybrid-key' material version 2 cannot be used because it records no key-derivation function from [HKDF-SHA256, HKDF-SHA384, HKDF-SHA512, CatKDF, CasKDF]; rotate the material","instance":"/api/v3/crypto/stream/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403,"extensions":{"code":"INVALID_KEY_STATE"}}}}}}},"200":{"description":"Plaintext encrypted successfully.","headers":{"Crypto-Policy-Info":{"description":"Base64URL-encoded JSON payload (snake_case) describing key\nselection and policy warnings for the current request.\nDecode and deserialise into the `CryptoPolicyInfo` schema.\n","style":"simple","schema":{"$ref":"#/components/schemas/CryptoPolicyInfo"}}},"content":{"multipart/mixed":{"examples":{"SIMPLE key response":{"description":"SIMPLE key response","value":"------ankatech-b6e0f4c3\nContent-Type: application/jose+json\n\n{\"protected\":\"…\",\"iv\":\"…\",\"tag\":null,\"ciphertext\":null,\n \"recipients\":[{\"encrypted_key\":\"…\"}]}\n------ankatech-b6e0f4c3\nContent-Type: application/octet-stream\n\n<binary envelope + ciphertext + tag>\n------ankatech-b6e0f4c3--\n"},"COMPOSITE key response":{"description":"COMPOSITE key response","value":"------ankatech-b6e0f4c3\nContent-Type: application/jose+json\n\n{\n  \"protected\": \"eyJlbmMiOiJBMjU2R0NNIiwia2lkIjoiY29tcG9zaXRlLWtleSIsImFua19rdiI6MSwiYW5rYV9rZGYiOiJIS0RGLVNIQTI1NiJ9\",\n  \"recipients\": [\n    {\n      \"header\": {\"alg\": \"P-256\"},\n      \"encrypted_key\": \"BBbGCTSC_x8Xg_H3QoX7az1tcH9LX56R7PAIVExVjwJiGqpS1sYiDo9fUeHvnf6k3Y27l8Nayu2kTkbUiV-5_-A\"\n    },\n    {\n      \"header\": {\"alg\": \"ML-KEM-768\"},\n      \"encrypted_key\": \"prAIHbfkxlE2mQylck4nHWNfETmxG8LuZazp17hpPGF8rWG8ouIMqFmxPe7tJK2PuWOheEqMk2M0_RDbK_SPzkG6zMs5pJKPgK8qywjhybZ7r-Ie18dOqlfHqxR7rN0tzgD6YLzEPFyagz6MtHAnhvZdKFH-gcawrfcORFHil9fmldc3rxVTKacwRftVWkLqFkTNG38bVev8azorjLvtjyOluWpkJyDELytHPXW5oaYiSaZwmbc4z3_bTdjgOBDwWIMYqbvjSg69WsO-FaX72_ZJQ_eSZiJCMS418ETeOzCSLPZEB0FvFD0Z-8Tg9B_Z0aW6cxrIyMCBdHDeFBB_taU5K0HGcsS1IMoBAsS9pq3H42S_D4vFY0AqiM-jadQ3MXfpeVOLoJxJJe-Q-1_IJujYwqPjV47A97Jo5udTE53hcqP8xoOJiTgcaMonAW9aZXx0vqvtHhoDP1D-XdkMWw_NfxR1Fa53U1eBpcyTEpPde11vquY5j9TOdYdvlQsrkq5TJ0fjZHkJ-niZDOwyxQWNfLCUmyaI2-GiERXUWe7cy6wyCW7rMjH51dVfdUi7Ouhm75SR4BvLH5YKfmAQEQl0FHaRFaC-zv8-MkxnMScTsjnBfqm7eOCD9_r3uOUrumU_OMod4Fe6wAQRNBe3ah5-QQvo5Z_vqkIUQrRaco-2p5xVIMKSq4u8DkOZtccSkKK5ANe_tHVDv7X-C0i8FOkyBmaZSD8Zr5CXobN1dlHzazjHGfsRRsdxYqEZHtZnp6Xqy3uLSGtHWqjC6ThBLLnnw5y54arKwvNoakGniOOo0F8vQ53q71u7G_UZsb21_S4VAZ4lrUVE2hfnmx_-oLZkzag_1uUQzyPzfKgyvHR11MtQpt92NxYXfS_DXAr3EULaQ2W9aU-qP_31BqKq1oNYX5_fjWph424Tg0P_L368oXH1gDJbRYGGHo-KxucpTU81n0LZEo6gNqxwNll6GlFoLjUqEvjDcTIDa4bX4v25sWtDZq0hu1l5CbiydBMGRemlRDa2w52V9SPK7GqtP7PQWGouJ6Uj5YrEWXWEjC910D4UYjjQlMAzLQAwbsSjgTGoNtwvXqT188G0n6k9hrmBCtzalWcfhPKG3zPL4i3Nvmaa5_o9ZKAS0oWlpwGp0vvFFYiY_x1KfT5FhDRTTHWBAcxCB9ngay0mv-naXDSITmgqvt4EYRKQlbtUWxzr4cLR-s0nPHf0wuU_-zv0YF3CFYjJDHNKxKtVvIf9hVou8Ok6ARfYjte-9U3RIFaKFuSWQlPVjdBnyLe7bDRuF1vVyrkxTtULmvy34angxXJcMYSQ0TesmcRPvLvDQdt4QZpFMaQ1UT3lnKnJO12X-hbnkGSBpTEHVi9Imdo4hbD9o9sFJkOLyrzX2OcpI0z2j3r5XY6zb-4XfjOLkAEB0ZbsR5Z14Aob3a_B12pHsuM\"\n    }\n  ],\n  \"iv\": \"MopNsAH8_6V86gb2\",\n  \"ciphertext\": null,\n  \"tag\": null,\n  \"anka_composite_mode\": \"COMPOSITE_KEM_COMBINE\"\n}\n------ankatech-b6e0f4c3\nContent-Type: application/octet-stream\n\n<binary envelope + ciphertext + tag>\n------ankatech-b6e0f4c3--\n"}}}}},"400":{"description":"Malformed multipart, invalid JSON/Base64, or key does not support the requested operation. PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"kid not found or unsuitable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"Unprocessable Entity (`.../errors/unprocessable-entity`): the request was understood but the key's cipher refused to perform the encryption. This operation reads no ciphertext, so its refusal is never a ciphertext integrity failure. Answered with zero payload bytes while the response is still uncommitted; after commit the stream is truncated instead: the stream fails closed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Encryption refused by the key's cipher":{"description":"Encryption refused by the key's cipher","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Failed to encrypt data.","instance":"/api/v3/crypto/stream/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"500":{"description":"Unexpected failure during streaming encryption.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/decrypt":{"post":{"tags":[" Secure Streaming"],"summary":"Decrypt data (streaming).","description":"The request is <strong>multipart/mixed</strong> with two parts:\n<ol>\n  <li><code>application/jose+json</code> — JWE General-JSON header\n      (detached ciphertext).</li>\n  <li><code>application/octet-stream</code> — proprietary binary\n      envelope followed by the ciphertext and GCM tag.</li>\n</ol>\nThe response is <strong>multipart/mixed</strong>:\nPART&nbsp;1 (<code>application/octet-stream</code>) is the decrypted plaintext and\nthe trailing PART&nbsp;2 (<code>application/json</code>) is a\n<code>StreamVerdict</code> emitted AFTER the end-of-stream AES-GCM tag is verified\n(<code>{verdict,operation,reason,correlationId,timestamp}</code>). Because the GCM\ntag is only known at end-of-stream, a tamper is reported through an\n<code>INVALID</code> verdict in PART&nbsp;2 — never a silently truncated success.<br><br>\n<strong>Supports SIMPLE keys</strong> (single recipient),\n<strong>COMPOSITE keys</strong> (one recipient per component, the key named by the\n<code>kid</code> in <code>protected</code>; decryption uses all of them to derive the\ncontent encryption key) <strong>and JWEs addressed to several keys</strong> (one\nrecipient per key, each naming its key in its own header, no <code>kid</code> in\n<code>protected</code> - the General JSON JWE of RFC 7516 section 7.2.1 the PKCS#7\nconversion emits for several recipients).<br><br>\n<strong>A JWE addressed to several keys is read with the ONE recipient the caller\nnames</strong> in the <code>recipientKid</code> query parameter (RFC 7516 section 5.2\nleaves the choice to the application). Everything below happens on the request, before\nany payload byte is read or written, in this order: the header is classified from its\npublic members alone (422, or 400 for a member name in both <code>protected</code> and\na recipient header); <code>recipientKid</code> must name one of its recipients (400\nnaming the parameter and no key); ONE Capability Grant decision for DECRYPT and ONE\nkey-lifecycle decision are taken on that key only, at the material version the\nintegrity-protected <code>ank_kvs</code> records for it (the PRIMARY when it records\nnone) - a refusal is the generic 403, which names no key; a composite key as the named\nrecipient is refused with 422; and only that recipient's <code>encrypted_key</code> is\nunwrapped. No other recipient is ever gated, resolved or tried, and there is no\nfallback after a refusal or an unwrap failure. For a single-recipient or composite JWE\n<code>recipientKid</code> is optional and, when present, must name the one key the JWE\nnames.\n","operationId":"decryptStream","parameters":[{"name":"recipientKid","in":"query","description":"The recipient of the JWE this decryption reads, named by its key identifier. Required when the JWE is addressed to several keys (no `kid` in `protected`, a `kid` in every recipient header): exactly one grant and one lifecycle decision are taken, on this key only, and only its `encrypted_key` is unwrapped. Optional for a single-recipient or composite JWE; when present it must name the one key the JWE names. Given at most once.","schema":{"type":"string","maxLength":255,"pattern":"^[A-Za-z0-9._-]+$"},"example":"acme-archive-r2"}],"requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`header` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`header` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"header":{"$ref":"#/components/schemas/JwetGeneralJsonHeaderDecrypt"},"file":{"type":"string","format":"binary","description":"Binary envelope + ciphertext + tag."}},"required":["file","header"]},"encoding":{"header":{"contentType":"application/jose+json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `stream.decryptStream` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for DECRYPT on the addressed key (for a JWE addressed to several keys, the recipient `recipientKid` names; the body names no key), or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/stream/decrypt","timestamp":1745120400}}}}}},"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/decrypt","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/stream/decrypt"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict, one of two durable conditions told apart by the problem type. <code>key-protection-backend-not-configured</code>: the tenant's key-protection backend was never declared, or is declared but not yet bound - complete the setup rather than retrying. <code>invalid-key-state</code>: the composite key's material version records no usable key-derivation function, so no combiner can be selected for it; the material must be rotated. Both are answered before any payload byte, and no Retry-After is sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/decrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Composite material records no usable KDF":{"description":"Composite material records no usable KDF","value":{"type":"https://docs.ankatech.co/errors/invalid-key-state","title":"Invalid key state","status":409,"detail":"Composite key 'my-hybrid-key' material version 2 cannot be used because it records no key-derivation function from [HKDF-SHA256, HKDF-SHA384, HKDF-SHA512, CatKDF, CasKDF]; rotate the material","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403,"extensions":{"code":"INVALID_KEY_STATE"}}}}}}},"422":{"description":"Unprocessable Entity, answered with zero payload bytes. <code>unprocessable-entity</code> with the JWET-header detail: the <code>header</code> part is not a well-formed JWET header. A <code>crit</code> is part of the same 422, and its detail names the defect and where it sits (never a value from the header): a <code>crit</code> in <code>protected</code> naming any extension (this route understands none), or a <code>crit</code> in any <code>recipients[i].header</code> - <code>crit</code> MUST be integrity protected (RFC 7516 section 4.1.13). A recipient without <code>encrypted_key</code> is the empty key of <code>alg</code> <code>dir</code> (RFC 7516 section 7.2.1) and is accepted; a member that is present but not a string is this 422. <code>composite-kdf-mismatch</code>: the protected <code>anka_kdf</code> of a composite JWET is absent, is not a key-derivation function this platform defines, or is not the one the key material records; the material is the only source of the combiner (NIST SP 800-227 section 4.6.3 downgrade) and the header is a cross-check, so the refusal is raised before any decapsulation. <code>composite-wire-shape-mismatch</code> and <code>composite-component-algorithm-mismatch</code>: the recipient count or a per-position <code>alg</code> does not match the key's components. An AES-GCM tag failure is NOT reported here: it is only detectable at the end of the stream, after plaintext may already have been written, so it is the INVALID verdict of the trailing part. This contract covers <code>/stream/decrypt</code> and <code>/stream/reencrypt</code>; <code>/stream/decrypt-verify</code> reports every outer decrypt failure in its verdict. A JWE addressed to several keys is refused with this status BEFORE any authorization or lifecycle check when its shape is one no key accounts for, each with a key-neutral <code>extensions.errorCode</code>: <code>JWE_RECIPIENT_KID_MISSING</code> (a recipient names no key), <code>JOSE_ACTOR_KID_REPEATED</code> (two recipients name one key), <code>JOSE_SYMMETRIC_ACTOR</code> (a symmetric or direct <code>alg</code>), <code>JOSE_ACTOR_LIMIT_EXCEEDED</code> (more recipients than the crypto plane admits), and <code>JWE_SHARED_KID_NOT_COMPOSITE</code> (a <code>kid</code> in <code>protected</code> that does not resolve, in the caller's tenant, to a composite key whose components the recipients match). <code>JWE_COMPOSITE_RECIPIENT</code>: the recipient <code>recipientKid</code> names is a composite key, refused after its grant and lifecycle decisions and before any key is used. A single-recipient JWE whose <code>alg</code> and <code>kid</code> sit only in its unprotected recipient header (the General layout of RFC 7516 section 7.2.1, which a PKCS#7 streaming conversion emits for <code>serialization</code> <code>JSON</code>) is read after its <code>kid</code> is authorised and resolved in the caller's tenant: its <code>alg</code> must be exactly the key-management algorithm the platform uses for that key, so the key decides how the content-encryption key is unwrapped and the header only repeats it. Any other <code>alg</code> is refused with this status and <code>extensions.errorCode</code> <code>JWE_ALG_KEY_MISMATCH</code> before any body byte; for a symmetric or composite key that layout is refused with <code>JWE_ALG_NOT_PROTECTED</code>. For a single-recipient JWE, a header parameter name present both in <code>protected</code> and in its recipient header is refused with this status as a malformed header, whatever the two values (RFC 7516 section 7.2.1: the header locations are disjoint); for a composite JWE or one addressed to several keys the same overlap is a 400, refused by the classification of its recipients. A single-recipient JWE whose <code>alg</code> and <code>kid</code> are in <code>protected</code> is read from there, as on the buffered routes: its recipient carries its <code>encrypted_key</code> alone (nothing at all for <code>alg</code> <code>dir</code>). A member the header does not define is refused with this status before any key is looked up, on every shape (one key, a composite key, several keys): <code>JWE_ROOT_MEMBER_FORBIDDEN</code> for a root member besides <code>protected</code>, <code>recipients</code>, <code>iv</code>, <code>ciphertext</code> (absent or null: it travels in the body, and a value is this 422 as a malformed header) and <code>tag</code> (absent or null when it is the body's last 16 octets, or the Base64URL tag when the body is the ciphertext alone) and, on a composite key's header, <code>anka_composite_mode</code> - a JSON <code>aad</code> member and a shared <code>unprotected</code> header included - and <code>JWE_RECIPIENT_MEMBER_FORBIDDEN</code> for a recipient member besides <code>header</code> and <code>encrypted_key</code>.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"A JSON aad member at the JWE root":{"description":"A JSON aad member at the JWE root","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The JWE carries a member at its root besides 'protected', 'recipients', 'iv', 'ciphertext' and 'tag': this platform reads no JSON 'aad' and no shared 'unprotected' header (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ROOT_MEMBER_FORBIDDEN"}}},"General-layout recipient whose alg is not the key's own":{"description":"General-layout recipient whose alg is not the key's own","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This JWE carries its key-management algorithm only in the unprotected per-recipient header, and that algorithm is not the one the platform uses for the key its kid names. An algorithm outside the integrity-protected header is accepted only when it is exactly that key's own key-management algorithm.","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_KEY_MISMATCH"}}},"General-layout recipient addressed to a symmetric or composite key":{"description":"General-layout recipient addressed to a symmetric or composite key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This operation reads the key-management algorithm of a JWE addressed to a symmetric or composite key from its integrity-protected header, and this JWE carries its alg only in the unprotected per-recipient header (the General JSON layout, RFC 7516 section 7.2.1). Only a JWE addressed to one asymmetric key may carry its alg there.","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_NOT_PROTECTED"}}},"A recipient of a JWE addressed to several keys names no key":{"description":"A recipient of a JWE addressed to several keys names no key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"A recipient of the JWE carries no 'kid' in its header: a JWE addressed to several keys names the key of every recipient (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_RECIPIENT_KID_MISSING"}}},"The named recipient is a composite key":{"description":"The named recipient is a composite key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"A recipient of the JWE is a composite key: a composite key is read from and written to its own composite JWE, never as one recipient among several","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_COMPOSITE_RECIPIENT"}}},"Composite KDF mismatch":{"description":"Composite KDF mismatch","value":{"type":"https://docs.ankatech.co/errors/composite-kdf-mismatch","title":"Composite KDF Mismatch","status":422,"detail":"Composite JWE for key 'my-hybrid-key' declares anka_kdf 'HKDF-SHA256' but the key material records 'CatKDF'","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}},"Composite recipient count mismatch":{"description":"Composite recipient count mismatch","value":{"type":"https://docs.ankatech.co/errors/composite-wire-shape-mismatch","title":"Composite Wire Shape Mismatch","status":422,"detail":"Composite material has 2 components but the wire has 1 recipients","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}},"Malformed JWET header":{"description":"Malformed JWET header","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"JWET is not valid.","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"multipart/mixed: PART 1 decrypted plaintext, trailing PART 2 the end-of-stream StreamVerdict (VALID/INVALID after the AES-GCM tag check).","headers":{"Crypto-Policy-Info":{"description":"Base64URL-encoded JSON payload (snake_case) describing key\nselection and policy warnings for the current request.\nDecode and deserialise into the `CryptoPolicyInfo` schema.\n","style":"simple","schema":{"$ref":"#/components/schemas/CryptoPolicyInfo"}}},"content":{"multipart/mixed":{"examples":{"Plaintext + trailing verdict":{"description":"Plaintext + trailing verdict","value":"--boundary\nContent-Type: application/octet-stream\n\n<decrypted plaintext bytes>\n--boundary\nContent-Type: application/json\n\n{\"verdict\":\"VALID\",\"operation\":\"decrypt\",\"correlationId\":\"a1b2c3\",\"timestamp\":1736500000000}\n--boundary--\n"}}}}},"400":{"description":"Malformed multipart, invalid JWE header, or key does not support the requested operation. For a JWE addressed to several keys: `recipientKid` absent, naming no recipient of the JWE, given more than once, or not 1 to 255 characters of letters, digits and `. _ -` - the problem names the parameter and no key, and no grant or lifecycle decision is taken; or a member name present both in `protected` and in a recipient header (RFC 7516 section 7.2.1); or a recipient `kid` that is not 1 to 255 characters of letters, digits and `. _ -` (the kid rule every JOSE header kid is read under; the detail does not echo it). For a single-recipient or composite JWE, a `recipientKid` naming another key than the JWE's. PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Malformed-Header":{"description":"Malformed-Header","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid JWE header","status":400,"detail":"The JWE header is syntactically incorrect."}},"recipientKid absent for a JWE addressed to several keys":{"description":"recipientKid absent for a JWE addressed to several keys","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"recipientKid is required: the token does not name exactly one key, so the request names the one this operation uses","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}},"Key-does-not-support-operation":{"description":"Key-does-not-support-operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'encrypt-only-key-001' does not support operation 'decrypt'"}},"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"kid not found or unsuitable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Unexpected error during streaming decryption.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/stream/decrypt-verify":{"post":{"tags":[" Secure Streaming"],"summary":"Decrypt-then-verify data (streaming, two verdicts).","description":"Consumes the nested ciphertext as <code>multipart/mixed</code> (outer JWE header +\nciphertext), decrypts it and verifies the recovered inner JWS. The response is\n<code>multipart/mixed</code>: PART&nbsp;1 is the decrypted plaintext, PART&nbsp;2 a\nJSON verdict carrying <code>gcmTagVerdict</code>, <code>signatureVerdict</code> and\n<code>signatureResults</code> (one entry per inner signature: its <code>kid</code>,\n<code>algorithm</code>, <code>materialVersion</code> and <code>verified</code>)\n— both verdicts MUST be VALID for the overall <code>verdict</code> to be VALID.<br><br>\n<b>Several keys.</b> An outer JWE addressed to several keys is decrypted with\nthe ONE recipient the <code>recipientKid</code> query parameter names: its DECRYPT grant\nand lifecycle are decided before the body starts, and a refusal there is a problem\nanswered with no body byte. The inner JWS may carry one signature (Compact) or several\n(the General JSON JWS a PKCS#7 conversion writes, <code>payload</code> first): every\nsigner passes its VERIFY grant and lifecycle, for the caller of this request, before its\nsignature is checked, and the <code>signatureVerdict</code> is VALID only when every\nsignature verifies. Because the inner signers are readable only after the 200 is\ncommitted, three refusals are reported as <code>signatureVerdict</code> INVALID rather\nthan as a status: a signer the caller may not verify with (403 on\n<code>/decrypt-verify</code>), a signature that does not verify (422 there), and a\ndecrypting recipient that is also an inner signer (400 there). While the signatures of a\nGeneral inner JWS are unread its payload segment is held in a bounded temporary file,\nencrypted under a key of its own and deleted when the verdict is known.<br><br>\nThe inner JWS may be signed by any signing key this platform issues - symmetric,\nasymmetric or composite - and the outer JWE addressed to any encryption key, so\nevery output of <code>/stream/sign-encrypt</code> is accepted here. A composite\ninner signature is verified as one value (both components must verify) and its\nprotected <code>iat</code>/<code>exp</code> are enforced as for a SIMPLE key; its\noutcome is the <code>signatureVerdict</code>, VALID or INVALID. As for every inner\nkey, the plaintext of PART&nbsp;1 may be streamed before the verdict is known: a\ncaller MUST NOT use it unless the overall <code>verdict</code> is VALID.\n","operationId":"decryptVerifyStream","parameters":[{"name":"recipientKid","in":"query","description":"The recipient of the outer JWE this operation decrypts with, named by its key identifier. Required when the JWE is addressed to several keys (no `kid` in `protected`, a `kid` in every recipient header): exactly one grant and one lifecycle decision are taken on it, and only its `encrypted_key` is unwrapped. Optional for a single-recipient or composite JWE; when present it must name the one key the JWE names. It must be none of the inner JWS's signers. Given at most once; absent where required, or naming no recipient, the request is refused with 400 naming it.","schema":{"type":"string","maxLength":255,"pattern":"^[A-Za-z0-9._-]+$"},"example":"acme-legal-rsa"}],"requestBody":{"description":"Sequential multipart body — the parts are read in the order they arrive and the server never buffers the payload.\n\n**`header` MUST be sent before `file`.** A `file`-first body is rejected with `400 application/problem+json`, type `.../errors/multipart-part-order`. This is a property of streaming, not a policy: the server cannot look ahead past a 512 MiB payload to find the part that tells it what to do with it.\n\n`header` is bounded at 2 MiB; `file` has no size limit.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"header":{"$ref":"#/components/schemas/JwetGeneralJsonHeaderDecrypt"},"file":{"type":"string","format":"binary","description":"Binary envelope + ciphertext + tag."}},"required":["file","header"]},"encoding":{"header":{"contentType":"application/jose+json"},"file":{"contentType":"application/octet-stream"}}}},"required":true},"responses":{"408":{"description":"Request Timeout: the request body was not fully received within the deployment's configured total-duration budget for one streaming upload (`crypto.stream.max-read-duration-ms`). The `detail` names that budget in milliseconds. Returned while the response is still uncommitted, that is while less than one response buffer of output has been written. Once the response has committed, a read-budget refusal aborts it instead: no status, no verdict and no problem document: the stream fails closed. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming read budget exhausted":{"description":"Streaming read budget exhausted","value":{"type":"https://docs.ankatech.co/errors/stream-read-budget-exceeded","title":"Request Body Read Timeout","status":408,"detail":"The request body was not fully received within the 900000 ms this deployment allows for one streaming upload. Resend it over a faster connection, or ask an operator to raise the configured streaming read budget.","instance":"/api/v3/crypto/stream/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"429":{"description":"Too Many Requests: the streaming concurrency bound is full, either on this instance or for this tenant across the deployment. `Retry-After` carries the suggested delay in seconds. This bound applies to every streaming endpoint.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: the small part (`metadata` or `header`) exceeded its 2 MiB cap. `file` has NO size limit — this response never refers to the payload. This refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the store that enforces the per-tenant streaming concurrency bound across the deployment cannot be reached: the request is refused before any payload byte is read, never admitted. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. On a streaming response whose first bytes were already sent, no status can be returned any more: the response then ends without its closing boundary and must be discarded. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/stream/decrypt-verify","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/stream/decrypt-verify"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/stream/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"409":{"description":"Conflict: the tenant's key-protection backend is not configured - never declared, or declared but not yet bound. Durable: complete the setup rather than retrying. No Retry-After is sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/stream/decrypt-verify","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"200":{"description":"Plaintext + two-verdict trailing part (stream); the verdict part reports every inner signature in signatureResults.","content":{"multipart/mixed":{"examples":{"Recipient 1 of 2 named, two inner signers, the second not verified":{"description":"Recipient 1 of 2 named, two inner signers, the second not verified","value":{"verdict":"INVALID","operation":"decrypt-verify","gcmTagVerdict":"VALID","signatureVerdict":"INVALID","signatureResults":[{"kid":"acme-treasury-signer","algorithm":"ML-DSA-65","materialVersion":2,"verified":true},{"kid":"acme-legal-signer","algorithm":"RSA-3072","materialVersion":1,"verified":false}],"reason":"signature verification failed","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304000}}}}}},"400":{"description":"Malformed multipart or invalid JWE header. For an outer JWE addressed to several keys: `recipientKid` absent, naming no recipient of the JWE, given more than once, or not 1 to 255 characters of letters, digits and `. _ -` - the problem names the member and no key, and no grant or lifecycle decision is taken; a member name present both in `protected` and in a recipient header (RFC 7516 section 7.2.1); or a recipient `kid` that is not 1 to 255 characters of letters, digits and `. _ -` (the kid rule every JOSE header kid is read under; the detail does not echo it). For a single-recipient or composite JWE, a `recipientKid` naming another key than the JWE's. PQC session transport is also refused here: an `X-PQC-Transport: true` or `X-PQC-Session` header on this route yields `.../errors/pqc-transport-not-available-for-streaming`, refused before any body byte is read. A request with more multipart parts than the service admits is refused with `.../errors/multipart-part-count-exceeded`. That part-count refusal is the response: the rest of the request body is not processed. On the service's own connection it stops accepting the body once at most a bounded remainder has been discarded (HTTP/1.1, `Connection: close`) or resets the request stream (HTTP/2); behind the edge proxy the caller's connection to the edge is unaffected.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"PQC transport on a streaming route":{"description":"PQC transport on a streaming route","value":{"type":"https://docs.ankatech.co/errors/pqc-transport-not-available-for-streaming","title":"PQC Transport Not Available","status":400,"detail":"PQC session transport cannot be combined with a streaming multipart request. Resend this request without the X-PQC-Transport and X-PQC-Session headers; the connection is already protected by TLS.","instance":"/api/v3/crypto/stream/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","extensions":{"code":"pqc.transport.streaming.unsupported"}}}}}}},"401":{"description":"Unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"Unprocessable Entity, answered with zero payload bytes, before any key is resolved: the <code>header</code> part is not a well-formed JWET header. This includes a <code>crit</code> in <code>protected</code> naming any extension (this route understands none) and a <code>crit</code> in any <code>recipients[i].header</code> - <code>crit</code> MUST be integrity protected (RFC 7516 section 4.1.13); the detail names the defect and where it sits, never a value from the header. A recipient without <code>encrypted_key</code> is the empty key of <code>alg</code> <code>dir</code> (RFC 7516 section 7.2.1) and is accepted. Every failure detected after the header is admitted is reported in the verdict part. A JWE addressed to several keys is refused with this status BEFORE any authorization or lifecycle check when its shape is refused (<code>JWE_RECIPIENT_KID_MISSING</code>, <code>JOSE_ACTOR_KID_REPEATED</code>, <code>JOSE_SYMMETRIC_ACTOR</code>, <code>JOSE_ACTOR_LIMIT_EXCEEDED</code>), and after its gates when the named recipient is a composite key (<code>JWE_COMPOSITE_RECIPIENT</code>). A single-recipient JWE whose <code>alg</code> and <code>kid</code> sit only in its unprotected recipient header (the General layout of RFC 7516 section 7.2.1, which a PKCS#7 streaming conversion emits for <code>serialization</code> <code>JSON</code>) is read after its <code>kid</code> is authorised and resolved in the caller's tenant: its <code>alg</code> must be exactly the key-management algorithm the platform uses for that key, so the key decides how the content-encryption key is unwrapped and the header only repeats it. Any other <code>alg</code> is refused with this status and <code>extensions.errorCode</code> <code>JWE_ALG_KEY_MISMATCH</code> before any body byte; for a symmetric or composite key that layout is refused with <code>JWE_ALG_NOT_PROTECTED</code>. For a single-recipient JWE, a header parameter name present both in <code>protected</code> and in its recipient header is refused with this status as a malformed header, whatever the two values (RFC 7516 section 7.2.1: the header locations are disjoint); for a composite JWE or one addressed to several keys the same overlap is a 400, refused by the classification of its recipients. A single-recipient JWE whose <code>alg</code> and <code>kid</code> are in <code>protected</code> is read from there, as on the buffered routes: its recipient carries its <code>encrypted_key</code> alone (nothing at all for <code>alg</code> <code>dir</code>). A member the header does not define is refused with this status before any key is looked up, on every shape (one key, a composite key, several keys): <code>JWE_ROOT_MEMBER_FORBIDDEN</code> for a root member besides <code>protected</code>, <code>recipients</code>, <code>iv</code>, <code>ciphertext</code> (absent or null: it travels in the body, and a value is this 422 as a malformed header) and <code>tag</code> (absent or null when it is the body's last 16 octets, or the Base64URL tag when the body is the ciphertext alone) and, on a composite key's header, <code>anka_composite_mode</code> - a JSON <code>aad</code> member and a shared <code>unprotected</code> header included - and <code>JWE_RECIPIENT_MEMBER_FORBIDDEN</code> for a recipient member besides <code>header</code> and <code>encrypted_key</code>.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"A JSON aad member at the JWE root":{"description":"A JSON aad member at the JWE root","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The JWE carries a member at its root besides 'protected', 'recipients', 'iv', 'ciphertext' and 'tag': this platform reads no JSON 'aad' and no shared 'unprotected' header (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/stream/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ROOT_MEMBER_FORBIDDEN"}}},"General-layout recipient whose alg is not the key's own":{"description":"General-layout recipient whose alg is not the key's own","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This JWE carries its key-management algorithm only in the unprotected per-recipient header, and that algorithm is not the one the platform uses for the key its kid names. An algorithm outside the integrity-protected header is accepted only when it is exactly that key's own key-management algorithm.","instance":"/api/v3/crypto/stream/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_KEY_MISMATCH"}}},"General-layout recipient addressed to a symmetric or composite key":{"description":"General-layout recipient addressed to a symmetric or composite key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This operation reads the key-management algorithm of a JWE addressed to a symmetric or composite key from its integrity-protected header, and this JWE carries its alg only in the unprotected per-recipient header (the General JSON layout, RFC 7516 section 7.2.1). Only a JWE addressed to one asymmetric key may carry its alg there.","instance":"/api/v3/crypto/stream/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_NOT_PROTECTED"}}}}}}},"403":{"description":"Missing scope, cross-tenant key reference, or the recipient recipientKid names is refused by its DECRYPT grant or lifecycle (the detail names no key). An inner signer's refusal is reported in the verdict part, never as this status.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Decryption kid not found / unsuitable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Unexpected failure during streaming decrypt-verify.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the streaming role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Streaming route received by a buffered-role instance":{"summary":"A streaming operation addressed to an instance of the buffered role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/stream/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/sign":{"post":{"tags":[" Secure"],"summary":"Sign data (JWS).","description":"Signs Base64-encoded `data` with the private key associated with `kid` and returns the produced JWS inside a signing-metadata envelope. Supported algorithms: ML-DSA, Falcon, RSA-PSS (PS256), ECDSA (ES256/384/512 on the P-curves, ESB256/384/512 on the brainpool curves), and COMPOSITE hybrid keys.\n\n**Serialization selection.** The optional `serialization` field expresses the client's chosen JWS representation and is orthogonal to the backend-authoritative RFC 3161 time-stamping policy. Two honest compliance tiers exist: **RFC 7515 JWS** (`COMPACT` = §7.1 three-segment compact, or `JSON` = §7.2 JWS JSON Serialization) and **JAdES B-T** (JWS JSON Serialization only, carrying an RFC 3161 qualified timestamp `sigTst` in the JWS Unprotected Header per ETSI TS 119 182-1 — a `COMPACT` token structurally cannot carry it).\n\n**Behavior truth table** (`serialization` × effective stamping → output form):\n\n| `serialization` | stamping | output | 400? |\n|---|---|---|---|\n| omitted | OFF | compact JWS | no |\n| omitted | REQUIRED | JWS-JSON + `sigTst` (auto-upgrade, JAdES B-T) | no |\n| `COMPACT` | OFF | compact JWS | no |\n| `COMPACT` | REQUIRED | — | **400** `serialization-incompatible-with-timestamp` |\n| `JSON` | OFF | JWS-JSON, no `sigTst` | no |\n| `JSON` | REQUIRED | JWS-JSON + `sigTst` (JAdES B-T) | no |\n\nAn explicit `COMPACT` is never silently upgraded and a mandated stamp is never dropped — the compact+REQUIRED row is the only rejection. The `output` column names the logical serialization tier; see \"Output to parse\" for the exact wire shape.\n\n**Output to parse.** On the HTTP wire the produced JWS is ALWAYS carried in the typed `jwsToken` (RFC 7515 §7.2 JWS JSON Serialization) envelope. A COMPACT / unstamped result is a single-signature object with no `sigTst` (losslessly convertible to the 3-segment RFC 7515 §7.1 compact form, as the SDK renders it); a JAdES B-T result carries the RFC 3161 `sigTst` in the top-level unprotected `header` (`header.etsiU[].sigTst`, a member outside the RFC 7515 §7.2.1 General syntax). A COMPOSITE key's result is one signature too: one value, under one protected header `{alg, kid, ank_kv, iat[, exp]}` in that order. `iat` is stated whenever this deployment's JWS local clock issues it (`ankasecure.jws.local-clock.enabled` and `include-iat`, both on by default), and `exp` = `iat` + `validityDays` × 86400 when `validityDays` is sent. `iat` and `exp` in a JWS protected header are this platform's convention, not JWS Header Parameters registered with IANA, and neither is listed in `crit`: an external JOSE verifier that does not know them ignores them (RFC 7515 §4, §4.1.11), so it neither enforces the expiry nor refuses the signature for it. This platform's verifiers judge both while claim-time validation is on (`ankasecure.jws.local-clock.validation.enabled`, on by default), within `clock-skew-seconds`: an `iat` in the future is refused, and an `exp` in the past is refused while `validation.reject-expired` is on (the default). The surrounding metadata envelope (`keyRequested`, `materialVersion`, `algorithmUsed`, `warnings`) is ALWAYS present regardless of serialization.\n","operationId":"signData","requestBody":{"description":"JSON payload with the key identifier (<code>kid</code>) and the\nmessage (<code>data</code>) to sign, both Base64‑encoded.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignRequest"},"examples":{"Sign request":{"description":"Sign request","value":{"kid":"mySigningKeyKid","data":"SGVsbG8gQW5rYQ=="}},"COMPOSITE key, signature valid for 365 days":{"description":"COMPOSITE key, signature valid for 365 days","value":{"kid":"invoice-signer-2025","data":"SGVsbG8gQW5rYQ==","validityDays":365}}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.sign` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for SIGN on the addressed key, or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/sign","timestamp":1745120400}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/sign","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/sign"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"JWS generated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignJwsResponse"},"examples":{"SIMPLE key (RSA-3072, PS256)":{"description":"SIMPLE key (RSA-3072, PS256)","value":{"jwsToken":{"payload":"eyJkYXRhIjoiU0dWc2JHOGdRVzVyWVEiLCJpYXQiOjE3OTA2NTYyNDEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSJ9","signatures":[{"protected":"eyJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInR5cCI6IkpXVCIsImFsZyI6IlBTMjU2IiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwia2lkIjoibXktcnNhLTMwNzIta2V5In0","signature":"wdF8GP8DwiIwD06LJCicETh1trDvid__nvPN4pAJV90cf6NI8P8ujrNx4uPRXzumcf1e6rSUuOnyL2KQWLO8DQP0_1x2sK8SaeY6td93tV44hKm3x55nAon6yunxFTHNWAC3p-Dn_xXCDzKFoWu0WtYwXVRhKITB-fJ-jZEtgl4emB4yGxIzlFFkzCs_nxGTydOG9Z6mvnf0sQLHyIAS9O2PltJhKhBYxb6S80Nm6nIWVXigOB71QCszqDkGgedWxayHzb7NgIhyOPzTCy2lXGHQYdd0mlh8PdZAcMTgpXaeTpKx__hL9WV4LVFZpBQcIccGqDThBu7w7ZDn3ioEhov7mPxvIkx028xKRP51HeEk4z-cbcXsWHJmpqet16CIH1-3PPJTDeA5FZcXUu4xlTrvhmURmldpuIKMvvFa3T_ftlbSOMsZVPwIGZrGvOU6hyVzVrZ5dd_-DpO_M1LjO6RixUEWmOYIFO0lakXJt6V3EHOP7zvE4Zvb_khHc1lN"}]},"keyRequested":"my-rsa-3072-key","materialVersion":1,"algorithmUsed":"RSA-3072","warnings":[]}},"COMPOSITE key (Ed25519+ML-DSA-44, one composite signature)":{"description":"COMPOSITE key (Ed25519+ML-DSA-44, one composite signature)","value":{"jwsToken":{"payload":"SGVsbG8gQW5rYQ","signatures":[{"protected":"eyJhbGciOiJNTC1EU0EtNDQtRWQyNTUxOSIsImtpZCI6Imludm9pY2Utc2lnbmVyLTIwMjUiLCJhbmtfa3YiOjEsImlhdCI6MTc5MDY0NzEwNn0","signature":"DA3fv0AHKs1Zy4Z6gsTnoVfZU6qkGjTcUPP7RS7arzhtY_tbughY7Fq6dvNWXKioGsCbhsQrHyNvYPwPk9ja4yDYyimGKgApHesalAjjQGa1tlQOD6G2P2vLfOp1t6UBCQGeNb5MbQ-zynRqjmD6mIQf9xYfbDoT9kuMFVhdiAiO7J0sBND_VCG4gOkxAv-pIpyX8eYHEf_W5_2J5I3c9t3x_ej4-5LGAri6k81AQ8uXrRwTJeOn-DqTwOBv8Z91jwP5dTXfNnGzNaUdOP6rjDhw3YaptSWsgFGyMuBoZ13VLfihT0H5u1DEkJfCRy4Er8mxMYuGUhFvyXpsNPB10mJ9CK96u_EsL8RI291EsfijJNGU6Rxqsb6cTtd6kyGfEzYJofubxaPGGUBiLwNbISkgIRiL8sR_EK9M-1DUCj5lvO84UCJo6vpC5FNhCinXFuOst6_DGQfQYJ5etvkc0MTiyGFU01a-9n1Hs8FR46ch1cExq5SoTUeu22O2-CM2v8JBpodxW6kpIyIdKOLIL7-K2-ORR0EXR8LY_CdwRmE_8lpaFgfSyUeS8_V-CiHLb2jGc2BGlE5GO5WfwiZUp6TcV-9xdTcy8ID7qbvcONTxTOcVwrfGO-NZVoweXDHm6y5Aj-fpZQbbJboMY8fdT5LScHvjT87UbEz0q3RCGPuuyVmpwYf8wZdhcp-KCEYHYX6l0WAcVh9e4PLP0khLwY1Hex-uGXHUx9J2Hrq0SqV8jGNrz2iAwrEo8BU0gK7yISyPUFQ1z47gSBFzV0kXyi5Z79nRriwpCuANg_pCR5IdTC2QR2VRyNeaG-VT_IBLDkpsFiqISbHf83KTO7tuc7b95Qt2KSI6zEaJi27_sAZC-RPAdKHUogNqYsI9Px0f1l7o_r8w2TmZZPa8txEpg_BJQlIjCuh9jTmaiXoG835lb20VWs3qRWl3dKUI8CgMwa9c7pvrzR2hJHoxNF-RxVuKDt35ACSnJXLLjdcR4Ykp2sjKM3kuNntskTCS_FUDmMUQNa_Jqnt2484FphoTkF3xfgAIyPWi4Qzy99XlbaJ6IwCOwrrnpPd7HM3J6H5j3VhX0Fy5cCxgiDnCxtbx7qk0MY2ucy6oo2ojHI63_vkkSTZ-IvW3VdAEf1RcHPl5sxIBnzTsD3zRcPIASRytKKF_lGE1TVEk-3xepjNNnKzy7ivcf5hznV5YNuNVHL749tHgF50t0sZjj2JBTDQrt-KMZF3hSNyaljjmPJQXSInl35A-Fdy0ZbWTupl-OP1A4-PUR-6S1ztmYtmwocghkGPzz8qyfJHecG_4k1pSXJKeOIw501dkvk39ga5Fpza1fn_X-0CWSDUuJ-drVah-RvhhakilqUbFGUdB41kYmPMqRAllM4CY5wKLwqynSGf9Pa-UGPsPfZCZf8FR5hlKPbYOe8f_hY2Sm167g5nrq3xB4EX8fIR4xgTfS35CAZkRzYr264o7leR_i6TYv3VcFV0L2ySUOJ03N6gfdSgTkH-X9CgsQbVVxsXMbDT4f4jyuBT9DFwYYyGB6oym5287bVsV3JMJfqThABy4xw9-G9CAj4_93duRBTFxsofPN5XwlXtb4jlIWhCsV-_rK20ja5QE_Sy9etNSJaemWmokYV09pxOHP_phvkJO9NotnsFWxMkusQdYEgvp6Xg6ajAReMIardVkk_Lf_WGNy8kTtgfn2nbkxeeVciLXwDuq5SiEDAvXKrBEqQd5tcM5F9c9Br-7D_4HIk9k8MQgJFNA6eytDMstj4Z4GMXmHwkDy-IT_IdQc6WowIWZ7ADP-msRK3A7eapuHZ_WKgbDpYe_DDxkVxaXd6DHPBD74EmUS1kvrkOt-IxTcl1OOAN1DqjIsU-O33u4XARXuY20nvflfJhbD4PRgZMC_154riOWRN99I5MTsOgHNOZCmEwsEPhyQnQTC_ZQPF9xxcrKiwExGwStTV-HAX_M4HhIElpIhgkr0IgOH1I9GMC5yP1gtlIxCA82c1DQvT1l_tHA-nwmsJIXcGE3HyNNcdK8qKepyVTbzwuQtfAGVNWH8fEO494zsD8sRUtNSTIsX3snqNBzqliB8mXtPdA0SxLV0lkqkHQ1G5wGDfgGhkzIwkUnxShQn2uSKHpkC220z_p4RHcn2frkvWU8TgfXY0klhx0U1aAZ6yHYhW2NsHYFHiIGqMpScHkJ92_mPLc4OF-NaddHZJLNMNzN5BTB2wu_W_Yq5jmdb64IpNaN9-Md7_edU3htUGwHiuqcqIc5FMjzxZM3ehxPp0kqTLuNMcONhm4YHPWud1dN-ZmvCIVANNhmZC_tr4Pa5pS62BO1LuYn04eZ9MBggnS_j-CnqcN9vkh9oN-XadmNPY-MRHHEDp-e6Qx-Of4AjFNH8AzugG1ELcGM6n0_H3Xbq3b9eKELSPpdgP37FmHQCSLedsjDNeDWTd9-Mw0CrvXIH3008asmUGSD8abNZxLQQvcYk1LqVDvmzb6BUQDWNJY8zMGdetmKjL2T3rPoT00FC1cgDRxw4k2fX3q2FwkABlqiXmjK_Sw88kLV5YfZqSIPAEWLVfZC3fZjdb1QJDukpYM3K5bfLQyiAI2Y9fWBuMaTMzALYmTvGvlJztX5WzWKar_XGy4ZLYVjLY_MGarxuJChJiMBPmsFYmRbaw9XLjvxZVeoscU05JbVoDkQz7w31EYm7ZTtT1jmkf7DlLrvItMGNAr0TRuWtG4k2j7Q4JgzEAEea8SU7me9law2Uv0g5BdZdqJCLKsBxSUsw3A0w1XN2EdxE63CHu4Nj8bwaHVl1HO_1Y3ZxBzym7MQPb_o8Ep8XjTrPqhgwL3djIWACKTJsWU6am51QXNvxOGuUhjiYFX0FsrGZJeyOn6Yf4kpvIPMfP9uZ0lgMIFBAPSfeMy5PQXIIYERoXMG0CgIDzazVUr7Y5qfa1plxJLpxJQ9XsFcVyuTmKgkNs2mjhTnUguQ1N59Q4989WCaFNH0aJDBapfFoEuhvYIMLnppfIsLbJ58VO9vALiAjKoWQ3R_LS8cHuMuClCqkdlQ7yf6YC-qGqCLNDMqOAahLhblhjuVwC3J-pO_jahRo8Di3JSXvHrfCRrSDh7M40sQEilNW3uRoKSztLjJ0974BBghYWJjaHWEqLC0ur_L2-H2Ahc1OlteZnyCoLzIz9_l8fwFCUJZYmZndXqRkrnF2OPr-wAAAAAAAAAAAAAAABAiM0R2EnVgC05OvIWLpr22s7bSnheK2FoOmcWmTNt6wxb79PyRYeMFUzle-mUPh5-aF00dlow8WZXR7KsD9zYI6D8F"}]},"keyRequested":"invoice-signer-2025","materialVersion":1,"algorithmUsed":"Ed25519+ML-DSA-44","warnings":[]}},"COMPOSITE key with validityDays 365 (Ed25519+ML-DSA-44, iat and exp)":{"description":"The protected header decodes to {alg, kid, ank_kv, iat, exp}, with exp - iat = 31536000 (365 x 86400).","value":{"jwsToken":{"payload":"SGVsbG8gQW5rYQ","signatures":[{"protected":"eyJhbGciOiJNTC1EU0EtNDQtRWQyNTUxOSIsImtpZCI6Imludm9pY2Utc2lnbmVyLTIwMjUiLCJhbmtfa3YiOjEsImlhdCI6MTc5MDY0NzEwNiwiZXhwIjoxODIyMTgzMTA2fQ","signature":"sOYbkVIL5yP-Nw2kStQ-gGx8deKG121HuApPReGdyk967SOz_GKivrYQbmSXiq-s94geJQfxUkQnCIcsFHjCggXYvk28aF2-MAv7AWD5p01gGDnH287fqCo09Y6Ki4Ba3KN0xGI0tu-dK_b7nPR0sV5-vjL0TvgtAESJCMM5kZwdSHEivOVhTuHnmzF8Smzi86rMLW_ZpdXqyoS4uAoM06eV4bCG3H3QXbmYS4yV4aiodiNji0QRAhgrYQvtLx0U1kbLnWNKtRakAFydR7dth2udd2N0l6JugarHxqkaY4m2kQQf3F03JAe5m0fI3KHmbXK7WJDFonTHorYpDaxQmOSRoCSeoqQFt-Hp3XmpJ8ZOVCu9IvKfSZTTnxKC0o08h4f44O4y43kCIVIOO0fGcGKLkpwoMQpkCRtk9YcaaRjnPYfKoVuVOzu1LIfS683C6Dt7gD2nR-0y1ZBxfuDX5gs6uCIhmtxNshdRi8YsnUc91d4xYpFMvspdCgM4tMe9kxysMqBhhAgzfIe5MLYz1To00S5PtP8xjLYBMSF31VoZ3fjZmQYfShHupA-I7T3kJtAYyjgJ30Ld8RSFy2tdMmKm7EaBmzjrJIovvB7Nj7-6DOHzDjlFkddfJWAirJBtNrmJXTlsGvcf3PlcNWCYws2pkhP9RCi2Q5EQ84f_QdHbam4YivTSSFtvG3eLWv_uIyur_4IF0eA3ItOxTE1SLweHrIkb9V49S84-I915SKbxfy6WM0ESgu64j8EOyrkJEj4l3bzBpUVLkj0BWAims9CrTSl3PRW8jG92oZJ_C5pCBR_NW3noGq0TeNIP35u0WAdn2Yqc1Uge5TUbS0k0CrlEo6T6Gj2PddIB73WLGHNsjZSAQ-T0iX0vwP1zErfIua8HcDxH5KfoiHbrm_ygovPiFkhJC2836_EGn4e7WIgp-5a-fwdNe7uXd6oFQXuY9dWvE_ZC-qu3uoRDbCbRCCYZVT-d0uTC9oZhfj5DslaUgPhRIihogo8kIgeIeaUKVz36DlQibHKwXR3eNzezkc74zWZzfdtjMSR9VWxdmMo0ASDmiiuNDmVs7yfyGLDYB7KVdArxd7TbXHsGxHfHO-ILMXAhg88tVE3PDW4_upDm3MkXZ7DJzceOngUBtxl4FjPDNS2mKbnxPf6X0_4b982pNyDxqXxaIVqgDnqRWKiiKAhEyBggaC3wwbX73vzUlGH-8l8Wd09jzTy63Xhrd0ItszDISEo05X8Ic9UCG9A1nOGuLtNQiHsdEFeWFIA0OXp0FfhP-4-53FMR1GsjVjGIr1hryaehT3eP1x7wFD9ofkUEc7YkOQtg9oBrnFSSDALiBdKvsQAADFN5rI7CcBplMHk2Q43l4NHUH6zxAvk5cRIzzR5vMuwz03XBYdwCNB2ilfhDnxgc-6u3U6zXOQOlAIImyLJIrcs3Wx7aJl3K-kBo1UaTtOCJ9z27wfNCvAGKw6BbRLBpAJOKT0wqeMy_6vJimkmva_XOEnW6QYFfadq7-Y2AsiPtBr8UF_f5iScPwRARM-pnVKYuaCCm9Uj8fddsjRnDlUBjcKQVwV9wWtS0VuKJ5tpV6344ZuzexvZzklTpO0NrDCUgkjWlc8Y1__GIrB3nSja4NvNqBdBKCGgT_J3ajttRhkOzBIn7ZdK_fMMyN3wJrjzPLlxCgChWD_ZJVJiALnD8svkheMxqHs37XBpVjskXPGx1CmaAEM04KQz1C8hSR0fjlGN33kBNq0RXtVsQ91zyKjiCW3_5BvdNmWQR4G8F7lFg-ba6d6-LMbupE7WLgWC2PLUynVUgInYQkP6LgAydq3ixd-IvO1_KODy3WzZYMeaxkxXyI2gH0_UP6PUU-Oi8OOiPhee4gAiswBx7g4tWbGSSGG_fTv5XRt-FHQ0bjOsyKe5e8952QgOGwCDj35tn0mHF6U-GHGB_Z9GoGOuR8m7U4L3O623Yn8t725mRA26qPvKtWIJjY47OooH2TWCXhwPHilPcNTkNgG_AfS40x7qXxaGRlNw7Sk7sj7FsIx7IcioywrmosPwPdeU63GCH4j5tIM8W5ZA3qQ8GOt0NHC0tp8Lp1tU4CYVhTjJegcVjyDwoGrjQ2Vte351QYYyj-TXpVDAVhB29FUTiRYvZyRn6WzhzHREtVTgqMBswrC-loVg-jxRntrVwxeNFDfBbcD7mb79o4pXc4FZMvZpmdG1MUGnsB4RSUYXdBA9nb0uXl2v1QQWJN-PTHpXDCbRzPMzaKtuN1nMBRZcCMvZRoI6CzPCTfTaGahIZcKwRu64_pMICZQoX7gtzavEV1Xcw730pworL7q0AJNXtnzanPNMahrfo9b3QpLo-sA2eneA33lbKB1n3FO78vAL6aauWD-qVjH37gJGco5FdPDNdgRrg1gHuO6rXbsEfoQg-oADzE0WbS1nWcEaDMgdTR2binqBqnOc3Isiv61OxthE6E_jMKFuJrVNvTNeJJiTXRcCucoYSYaaAE83XxTrKCuH0GYZjXhmZfevKKDrnMgx6-FQjuSUJe4-IZM8zUihws3Fa7sPegLxKYwql2H1iKo7WoQqX8uOuYASjVt6BRGNeSbHXa8fXitqJOw59t8n37Nd-djI2rQDqvfpL4AVXG-LXqHT8-zCAH-k3nhgxjY9uXPt7kNRSj_l_PMvaM1pMUl9s5B-eMpp6qZRxHkj2WoT2SMMsnT2TVfzH1XVO3m6WPLyONdfhVQpuZotrUagQ8MMUG64ZAtCsQJKKCpBAhZGIW82A1vjpW-dozJ0lEiCU470QulFxQaPvq6jRbl3Rq9tY-3acA-sDoPJkfrTcdZ8F_7VMSrRTaTs9mt_SHhe-TxX3xXypUSLTkGImOhesN3L_tZonaUMKOja0iWsVRDi8_LIGZzVQLvqUY9zJ6ADqn42LfFgwlNZLqiRskREnqlmuBw-WBRf6PS3x51Rk0fzwcEpW4QLC2w-Deo4wzjliTG0PB2VzjiwYeV8aMfK1D9yJRfM-3CK7UgOW4hePtMyjuu5qaSNjLuxUAkVV8_wQcwtZpTYY181Hw4MkOiMu6-NH5PwXcbO1A8699dfi0glqK3_RJaZeriJ5qVsqrajaLJhKMIkMFiAoV3WMnaDB5OvuAyw7R2-vsvL1FRgzUmRpb3Z_gauwyNvv-QMXHiY5S3qxvcDM1dre7v0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0WJjZHq82EOkfF85jGKSFg95fq6WG1FaJKscD_0s0z5SrpZzLoZEIsdQbYNxtXpVjLUU1U4Ig3h6jawK-TyVyzEGgB"}]},"keyRequested":"invoice-signer-2025","materialVersion":1,"algorithmUsed":"Ed25519+ML-DSA-44","warnings":[]}}}}}},"400":{"description":"Bad Request:\n• Malformed Base64 or missing 'data' field, OR\n• Key does not support the requested operation (e.g., trying to sign with an encrypt-only key)\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid input":{"description":"Invalid input","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"'data' is not valid Base64 or missing.","instance":"/api/v3/crypto/sign","timestamp":1745120700}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'encrypt-only-key-001' does not support operation 'sign'","instance":"/api/v3/crypto/sign","timestamp":1745120700}},"Serialization incompatible with timestamp (the only serialization refusal)":{"description":"Serialization incompatible with timestamp (the only serialization refusal)","value":{"type":"https://docs.ankatech.co/errors/serialization-incompatible-with-timestamp","title":"Serialization Incompatible With Timestamp","status":400,"detail":"This deployment requires an RFC 3161 qualified timestamp on signatures, which can only be carried by the JWS JSON Serialization. Request JSON serialization or omit the serialization field to let the server choose the standards-correct representation.","instance":"/api/v3/crypto/sign","timestamp":1745120704,"extensions":{"errorCode":"SERIALIZATION_INCOMPATIBLE_WITH_TIMESTAMP"}}}}}}},"413":{"description":"Payload Too Large: decoded data exceeds the configured maximum size.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Payload too large":{"description":"Payload too large","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload too large","status":413,"detail":"Decoded data exceeds the maximum allowed size (5 000 000 bytes).","instance":"/api/v3/crypto/sign","timestamp":1745120701}}}}}},"404":{"description":"Not Found: key does not exist or lacks a private component.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key not found":{"description":"Key not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource not found","status":404,"detail":"Key 'mySigningKeyKid' does not exist or lacks a private key.","instance":"/api/v3/crypto/sign","timestamp":1745120702}}}}}},"409":{"description":"Conflict: key is in an invalid state (e.g., revoked or suspended). It is also answered as <code>key-material-expired</code> when the key's current material is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>); a material in both states is answered as expired. Nothing is produced and the key's state is unchanged: it still decrypts and verifies what it protected, and rotating it restores protection. Durable - do not retry. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/sign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/crypto/sign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/sign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Invalid key state":{"description":"Invalid key state","value":{"type":"https://docs.ankatech.co/errors/conflict","title":"Conflict","status":409,"detail":"Key 'mySigningKeyKid' is revoked and cannot be used for signing.","instance":"/api/v3/crypto/sign","timestamp":1745120703}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/sign","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Signer key at its usage limit":{"description":"Signer key at its usage limit","value":{"type":"https://docs.ankatech.co/errors/key-material-usage-exhausted","title":"Key Material Usage Exhausted","status":409,"detail":"Operation 'sign' is refused: the key's current material has reached its usage limit. Rotate the key to protect new data; what it already protected can still be decrypted and verified.","instance":"/api/v3/crypto/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"code":"KEY_MATERIAL_USAGE_EXHAUSTED"}}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Internal Server Error: unexpected error during signing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/sign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/sign-encrypt":{"post":{"tags":[" Secure"],"summary":"Sign then encrypt data (nested JWE(JWS))","description":"Performs sign-then-encrypt operation: signs data with signKid, then encrypts\nthe resulting JWS with encryptKid to produce a nested JWE(JWS) structure.\n\nThe two keys must be different.\n\n**Serialization selection.** The optional `serialization` field selects the representation of the INNER signed JWS (before it is encrypted into the outer JWE) and is orthogonal to the backend-authoritative RFC 3161 time-stamping policy. Two honest compliance tiers apply to the inner JWS: **RFC 7515 JWS** (`COMPACT` = §7.1 three-segment compact, or `JSON` = §7.2 JWS JSON Serialization) and **JAdES B-T** (JWS JSON Serialization only, carrying an RFC 3161 qualified timestamp `sigTst` in the Unprotected Header per ETSI TS 119 182-1 — a `COMPACT` inner token structurally cannot carry it). Truth table (`serialization` × effective stamping → inner form): omitted+OFF → compact; omitted+REQUIRED → JWS-JSON + `sigTst` (auto-upgrade, JAdES B-T); `COMPACT`+OFF → compact; `COMPACT`+REQUIRED → **400** `serialization-incompatible-with-timestamp` (rejected BEFORE any signing or encryption); `JSON`+OFF → JWS-JSON, no `sigTst`; `JSON`+REQUIRED → JWS-JSON + `sigTst`. It never adds or drops a timestamp and does not change the outer JWE. The `jweToken` field and the separated sign/encrypt metadata envelope are ALWAYS present regardless of serialization.\n\nUses JWE General JSON Serialization (RFC 7516 §7.2) and JWS General JSON Serialization (RFC 7515 §7.2) uniformly across SIMPLE and COMPOSITE keys.\nCOMPOSITE keys enable hybrid post-quantum cryptography with a composite signature (COMPOSITE_SIGNATURE)\nand combined key encapsulation (COMPOSITE_KEM_COMBINE).\n\n**Key pairs this buffered route serves: every one.** Each leg runs with the service of its own\nkey, exactly as on the streaming `/api/v3/crypto/stream/sign-encrypt`: a SIMPLE symmetric, SIMPLE\nasymmetric or COMPOSITE `signKid` with a SIMPLE symmetric, SIMPLE asymmetric or COMPOSITE\n`encryptKid` — for example HMAC-SHA256 + ML-KEM-768, ML-DSA-65 + AES-GCM-256, or\nCOMPOSITE_SIGNATURE + COMPOSITE_KEM_COMBINE. A key of the wrong kind for its leg (an encryption\nkey as `signKid`, a signature key as `encryptKid`) is refused **400** `invalid-input` before\nanything is signed.\n\nA COMPOSITE `signKid` signs with one composite signature under the protected header\n`{alg, kid, ank_kv, iat[, exp]}`; `validityDays` sets its `exp` exactly as for a SIMPLE key.\n\nThe JWE outer header includes cty=\"JWS\" to indicate nested content.\n","operationId":"signThenEncrypt","requestBody":{"description":"Data to sign and encrypt with two different keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignEncryptRequest"},"examples":{"SIMPLE keys (asymmetric PQC)":{"description":"SIMPLE keys (asymmetric PQC)","value":{"data":"SGVsbG8gQW5rYQ==","signKid":"myMLDSASignKey","encryptKid":"myMLKEMEncKey","validityDays":365}},"COMPOSITE keys (hybrid PQC)":{"description":"COMPOSITE keys (hybrid PQC)","value":{"data":"SGVsbG8gQW5rYQ==","signKid":"invoice-signer-2025","encryptKid":"myCompositeHybridKEMKey"}}}}},"required":true},"responses":{"401":{"description":"Unauthorized (`https://docs.ankatech.co/errors/unauthorized`): the request carries no bearer token, or the token is expired, not yet valid, badly signed, or issued for another issuer or audience. Answered by the authentication entry point before the operation runs, with a `WWW-Authenticate: Bearer` challenge.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Missing or expired access token":{"description":"Missing or expired access token","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to access this resource.","instance":"/api/v3/crypto/sign-encrypt","correlationId":"3f2b8c1e-6d4a-4b7f-9e2c-5a1d8f0b7c64","timestamp":1745120400,"extensions":{"code":"UNAUTHENTICATED"}}}}}}},"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.signAndEncrypt` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for SIGN on the signing key and ENCRYPT on the encryption key, or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/sign-encrypt","timestamp":1745120400}}}}}},"409":{"description":"Conflict, for one of two causes told apart by the problem type. <code>invalid-key-state</code>: the signing or the encryption key is not in a lifecycle state that permits the operation (for example, revoked). <code>key-protection-backend-not-configured</code>: the tenant's key-protection backend was never declared, or was declared but not yet bound; durable, so complete the setup rather than retrying, and no Retry-After is sent. It is also answered as <code>key-material-expired</code> when the key's current material is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>); a material in both states is answered as expired. Nothing is produced and the key's state is unchanged: it still decrypts and verifies what it protected, and rotating it restores protection. Durable - do not retry. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/sign-encrypt","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/crypto/sign-encrypt","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/sign-encrypt","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Key revoked":{"description":"Key revoked","value":{"type":"https://docs.ankatech.co/errors/invalid-key-state","title":"Invalid Key State","status":409,"detail":"The key with the specified kid is already revoked and cannot be used.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800000}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/sign-encrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Signer key at its usage limit":{"description":"Signer key at its usage limit","value":{"type":"https://docs.ankatech.co/errors/key-material-usage-exhausted","title":"Key Material Usage Exhausted","status":409,"detail":"Operation 'sign' is refused: the key's current material has reached its usage limit. Rotate the key to protect new data; what it already protected can still be decrypted and verified.","instance":"/api/v3/crypto/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"code":"KEY_MATERIAL_USAGE_EXHAUSTED"}}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/sign-encrypt"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"Sign-then-encrypt operation successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignEncryptResponse"},"examples":{"SignEncryptResponse":{"description":"SignEncryptResponse","value":{"jweToken":"eyJraWQiOiJlbmNLZXkiLCJhbGciOiJNTC1LRU0tMTAyNCIsImVuYyI6IkEyNTZHQ00iLCJjdHkiOiJKV1MifQ...","signKeyRequested":"mySignKey","signMaterialVersion":2,"signAlgorithmUsed":"ML-DSA-87","signWarnings":[],"encryptionKeyRequested":"myEncKey","encryptMaterialVersion":3,"encryptionAlgorithmUsed":"ML-KEM-1024","encryptionWarnings":[]}}}}}},"400":{"description":"Bad Request:\n• Invalid Base64, same kids, or key type mismatch, OR\n• Key does not support the requested operation (sign or encrypt)\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid Base64":{"description":"Invalid Base64","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Data is not valid Base64.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800000}},"Same kids (must be different)":{"description":"Same kids (must be different)","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"The signKid and encryptKid must be different keys.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800000}},"Key type mismatch (symmetric vs asymmetric)":{"description":"Key type mismatch (symmetric vs asymmetric)","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"The signKid and encryptKid must both be symmetric or both be asymmetric. Mixing key types is not permitted.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800000}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'encrypt-only-key-001' does not support operation 'sign'","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800001}},"Serialization incompatible with timestamp (the only serialization refusal)":{"description":"Serialization incompatible with timestamp (the only serialization refusal)","value":{"type":"https://docs.ankatech.co/errors/serialization-incompatible-with-timestamp","title":"Serialization Incompatible With Timestamp","status":400,"detail":"This deployment requires an RFC 3161 qualified timestamp on signatures, which can only be carried by the JWS JSON Serialization. Request JSON serialization or omit the serialization field to let the server choose the standards-correct representation.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800002,"extensions":{"errorCode":"SERIALIZATION_INCOMPATIBLE_WITH_TIMESTAMP"}}}}}}},"404":{"description":"Not Found: key does not exist","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key not found":{"description":"Key not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"Key not found with kid: myNonExistentKey","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800000}}}}}},"413":{"description":"Payload Too Large: exceeds the configured maximum size (see GET /api/v3/crypto/limits)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Data exceeds limit":{"description":"Data exceeds limit","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Data size exceeds the configured maximum limit.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800000}}}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Unexpected error":{"description":"Unexpected error","value":{"type":"https://docs.ankatech.co/errors/internal","title":"Internal Server Error","status":500,"detail":"Failed to perform sign-then-encrypt operation.","instance":"/api/v3/crypto/sign-encrypt","timestamp":1734800000}}}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/sign-encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/resign":{"post":{"tags":[" Secure"],"summary":"Re-sign a JWS (Compact or General JSON) with a new key","description":"Verifies the incoming JWS (Compact or General JSON Serialization) under its embedded `oldKid` (or the key `sourceKid` names when the header names none) and, if valid, produces a replacement signature with `newKid`. Useful for algorithm migrations without touching payload bytes.\n\n**A JWS signed by several keys** (a General JSON JWS with one signature per key, RFC 7515 §7.2.1 — what a PKCS#7 conversion emits for co-signed content). `sourceKid` is REQUIRED and names the ONE signer whose signature is replaced; `newKid` must be none of the kept signers (400 naming the member otherwise, before any key is gated). The caller holds `RESIGN` on `sourceKid` → `newKid` and `VERIFY` on every kept signer; every signer is judged on the VERIFY lifecycle row at its protected `ank_kv`, and `newKid` on the RESIGN row, all before any signature is checked. Then **every** signature is verified under its own key before anything is signed: a single signature that does not verify refuses the whole request with 400 `invalid-input` and nothing is signed, so a re-signature never launders an invalid co-signature. The new signature takes the replaced position under a protected header built fresh for `newKid` (no member of the replaced header is copied); the payload is the token's, so that header declares `typ` `JWT` exactly when a received signature declares it (RFC 7519 §5.1: the payload is a claims set). A SIMPLE `newKid` signs a claims set only: over a token that declares none (a raw-octet payload) it is 400 before any signature is checked, and a composite `newKid` re-signs either. Every other signature — `protected`, `header` and `signature` — and the `payload` are returned exactly as received. `signatureResults` reports every verified input signature. The output is always the General JSON JWS (`serialization: COMPACT` is 400). When the deployment requires an RFC 3161 timestamp on every new signature, the new signature carries its `sigTst` in its own entry's `header`, as a single-key re-sign carries it; every other signature keeps its `header`, and so its own timestamp, unchanged.\n\n**Serialization selection.** The optional `serialization` field selects the representation of the RE-SIGNED output (the incoming form does not govern it) and is orthogonal to the backend-authoritative RFC 3161 time-stamping policy. Two honest compliance tiers: **RFC 7515 JWS** (`COMPACT` = §7.1 three-segment compact, or `JSON` = §7.2 JWS JSON Serialization) and **JAdES B-T** (JWS JSON Serialization only, carrying an RFC 3161 qualified timestamp `sigTst` in the Unprotected Header per ETSI TS 119 182-1 — a `COMPACT` token structurally cannot carry it).\n\n**Behavior truth table** (`serialization` × effective stamping → output form):\n\n| `serialization` | stamping | output | 400? |\n|---|---|---|---|\n| omitted | OFF | compact JWS | no |\n| omitted | REQUIRED | JWS-JSON + `sigTst` (auto-upgrade, JAdES B-T) | no |\n| `COMPACT` | OFF | compact JWS | no |\n| `COMPACT` | REQUIRED | — | **400** `serialization-incompatible-with-timestamp` |\n| `JSON` | OFF | JWS-JSON, no `sigTst` | no |\n| `JSON` | REQUIRED | JWS-JSON + `sigTst` (JAdES B-T) | no |\n\nAn explicit `COMPACT` is never silently upgraded and a mandated stamp is never dropped; the `output` column names the logical serialization tier. On the HTTP wire the re-signed JWS is ALWAYS carried in the typed `jwsToken` (RFC 7515 §7.2 JWS JSON Serialization) envelope: a COMPACT / unstamped result is a single-signature object with no `sigTst` (losslessly convertible to the 3-segment compact form), a JAdES B-T result carries the RFC 3161 `sigTst` in the unprotected header. The re-sign metadata envelope (old/new key requested, per-leg material versions, algorithms, warnings) is ALWAYS present regardless of serialization.\n","operationId":"resignData","requestBody":{"description":"JSON object with:\n• `jwsToken` – typed JWS General JSON Serialization (RFC 7515 §7.2) to verify\n• `newKid` – kid to use for the new signature\n• `sourceKid` – the signer replaced: optional when the protected header names a   `kid`, where it must equal it; required when the header names none, where it names   the key that signed (not a composite key)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResignJwsRequest"},"examples":{"SIMPLE source (RSA-3072, PS256) re-signed by an ML-DSA-44 key":{"description":"SIMPLE source (RSA-3072, PS256) re-signed by an ML-DSA-44 key","value":{"jwsToken":{"payload":"eyJkYXRhIjoiU0dWc2JHOGdRVzVyWVEiLCJpYXQiOjE3OTA2NTYyNDEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSJ9","signatures":[{"protected":"eyJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInR5cCI6IkpXVCIsImFsZyI6IlBTMjU2IiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwia2lkIjoibXktcnNhLTMwNzIta2V5In0","signature":"wdF8GP8DwiIwD06LJCicETh1trDvid__nvPN4pAJV90cf6NI8P8ujrNx4uPRXzumcf1e6rSUuOnyL2KQWLO8DQP0_1x2sK8SaeY6td93tV44hKm3x55nAon6yunxFTHNWAC3p-Dn_xXCDzKFoWu0WtYwXVRhKITB-fJ-jZEtgl4emB4yGxIzlFFkzCs_nxGTydOG9Z6mvnf0sQLHyIAS9O2PltJhKhBYxb6S80Nm6nIWVXigOB71QCszqDkGgedWxayHzb7NgIhyOPzTCy2lXGHQYdd0mlh8PdZAcMTgpXaeTpKx__hL9WV4LVFZpBQcIccGqDThBu7w7ZDn3ioEhov7mPxvIkx028xKRP51HeEk4z-cbcXsWHJmpqet16CIH1-3PPJTDeA5FZcXUu4xlTrvhmURmldpuIKMvvFa3T_ftlbSOMsZVPwIGZrGvOU6hyVzVrZ5dd_-DpO_M1LjO6RixUEWmOYIFO0lakXJt6V3EHOP7zvE4Zvb_khHc1lN"}]},"newKid":"my-mldsa-44-key"}},"Three signers: the treasurer's P-256 signature replaced by an ML-DSA-65 key":{"description":"Three signers: the treasurer's P-256 signature replaced by an ML-DSA-65 key","value":{"jwsToken":{"payload":"UXVhcnRlcmx5IGJvYXJkIHJlc29sdXRpb24gMjAyNi1RMw","signatures":[{"protected":"eyJhbGciOiJQUzI1NiIsImtpZCI6ImJvYXJkLWNoYWlyLXJzYSIsInZlciI6IjIuMCIsImFua19rdiI6MiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwiaWF0IjoxNzkwNjU2MjQwfQ","signature":"Qm9hcmQgY2hhaXIgUlNBLVBTUyBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg"},{"protected":"eyJhbGciOiJFUzI1NiIsImtpZCI6InRyZWFzdXJlci1lYy1wMjU2IiwidmVyIjoiMi4wIiwiYW5rX2t2IjoxLCJ0aWQiOiI3ZDNhOWMxZS00YjJmLTRlOGEtOWM3My0yNzUwMDAwMDAwMTEiLCJpYXQiOjE3OTA2NTYyNDB9","signature":"VHJlYXN1cmVyIEVDRFNBIFAtMjU2IHNpZ25hdHVyZSBvdmVyIHRoZSByZXNvbHV0aW9uIGJ5IHRoZSB0cmVhc3VyZXI"},{"protected":"eyJhbGciOiJNTC1EU0EtNjUiLCJraWQiOiJzZWNyZXRhcnktbWxkc2EiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjMsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImlhdCI6MTc5MDY1NjI0MH0","signature":"U2VjcmV0YXJ5IE1MLURTQS02NSBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg"}]},"sourceKid":"treasurer-ec-p256","newKid":"treasurer-mldsa-65"}},"COMPOSITE source (Ed25519+ML-DSA-44) re-signed by a P-256+ML-DSA-44 key":{"description":"COMPOSITE source (Ed25519+ML-DSA-44) re-signed by a P-256+ML-DSA-44 key","value":{"jwsToken":{"payload":"SGVsbG8gQW5rYQ","signatures":[{"protected":"eyJhbGciOiJNTC1EU0EtNDQtRWQyNTUxOSIsImtpZCI6Imludm9pY2Utc2lnbmVyLTIwMjUiLCJhbmtfa3YiOjEsImlhdCI6MTc5MDY0NzEwNn0","signature":"DA3fv0AHKs1Zy4Z6gsTnoVfZU6qkGjTcUPP7RS7arzhtY_tbughY7Fq6dvNWXKioGsCbhsQrHyNvYPwPk9ja4yDYyimGKgApHesalAjjQGa1tlQOD6G2P2vLfOp1t6UBCQGeNb5MbQ-zynRqjmD6mIQf9xYfbDoT9kuMFVhdiAiO7J0sBND_VCG4gOkxAv-pIpyX8eYHEf_W5_2J5I3c9t3x_ej4-5LGAri6k81AQ8uXrRwTJeOn-DqTwOBv8Z91jwP5dTXfNnGzNaUdOP6rjDhw3YaptSWsgFGyMuBoZ13VLfihT0H5u1DEkJfCRy4Er8mxMYuGUhFvyXpsNPB10mJ9CK96u_EsL8RI291EsfijJNGU6Rxqsb6cTtd6kyGfEzYJofubxaPGGUBiLwNbISkgIRiL8sR_EK9M-1DUCj5lvO84UCJo6vpC5FNhCinXFuOst6_DGQfQYJ5etvkc0MTiyGFU01a-9n1Hs8FR46ch1cExq5SoTUeu22O2-CM2v8JBpodxW6kpIyIdKOLIL7-K2-ORR0EXR8LY_CdwRmE_8lpaFgfSyUeS8_V-CiHLb2jGc2BGlE5GO5WfwiZUp6TcV-9xdTcy8ID7qbvcONTxTOcVwrfGO-NZVoweXDHm6y5Aj-fpZQbbJboMY8fdT5LScHvjT87UbEz0q3RCGPuuyVmpwYf8wZdhcp-KCEYHYX6l0WAcVh9e4PLP0khLwY1Hex-uGXHUx9J2Hrq0SqV8jGNrz2iAwrEo8BU0gK7yISyPUFQ1z47gSBFzV0kXyi5Z79nRriwpCuANg_pCR5IdTC2QR2VRyNeaG-VT_IBLDkpsFiqISbHf83KTO7tuc7b95Qt2KSI6zEaJi27_sAZC-RPAdKHUogNqYsI9Px0f1l7o_r8w2TmZZPa8txEpg_BJQlIjCuh9jTmaiXoG835lb20VWs3qRWl3dKUI8CgMwa9c7pvrzR2hJHoxNF-RxVuKDt35ACSnJXLLjdcR4Ykp2sjKM3kuNntskTCS_FUDmMUQNa_Jqnt2484FphoTkF3xfgAIyPWi4Qzy99XlbaJ6IwCOwrrnpPd7HM3J6H5j3VhX0Fy5cCxgiDnCxtbx7qk0MY2ucy6oo2ojHI63_vkkSTZ-IvW3VdAEf1RcHPl5sxIBnzTsD3zRcPIASRytKKF_lGE1TVEk-3xepjNNnKzy7ivcf5hznV5YNuNVHL749tHgF50t0sZjj2JBTDQrt-KMZF3hSNyaljjmPJQXSInl35A-Fdy0ZbWTupl-OP1A4-PUR-6S1ztmYtmwocghkGPzz8qyfJHecG_4k1pSXJKeOIw501dkvk39ga5Fpza1fn_X-0CWSDUuJ-drVah-RvhhakilqUbFGUdB41kYmPMqRAllM4CY5wKLwqynSGf9Pa-UGPsPfZCZf8FR5hlKPbYOe8f_hY2Sm167g5nrq3xB4EX8fIR4xgTfS35CAZkRzYr264o7leR_i6TYv3VcFV0L2ySUOJ03N6gfdSgTkH-X9CgsQbVVxsXMbDT4f4jyuBT9DFwYYyGB6oym5287bVsV3JMJfqThABy4xw9-G9CAj4_93duRBTFxsofPN5XwlXtb4jlIWhCsV-_rK20ja5QE_Sy9etNSJaemWmokYV09pxOHP_phvkJO9NotnsFWxMkusQdYEgvp6Xg6ajAReMIardVkk_Lf_WGNy8kTtgfn2nbkxeeVciLXwDuq5SiEDAvXKrBEqQd5tcM5F9c9Br-7D_4HIk9k8MQgJFNA6eytDMstj4Z4GMXmHwkDy-IT_IdQc6WowIWZ7ADP-msRK3A7eapuHZ_WKgbDpYe_DDxkVxaXd6DHPBD74EmUS1kvrkOt-IxTcl1OOAN1DqjIsU-O33u4XARXuY20nvflfJhbD4PRgZMC_154riOWRN99I5MTsOgHNOZCmEwsEPhyQnQTC_ZQPF9xxcrKiwExGwStTV-HAX_M4HhIElpIhgkr0IgOH1I9GMC5yP1gtlIxCA82c1DQvT1l_tHA-nwmsJIXcGE3HyNNcdK8qKepyVTbzwuQtfAGVNWH8fEO494zsD8sRUtNSTIsX3snqNBzqliB8mXtPdA0SxLV0lkqkHQ1G5wGDfgGhkzIwkUnxShQn2uSKHpkC220z_p4RHcn2frkvWU8TgfXY0klhx0U1aAZ6yHYhW2NsHYFHiIGqMpScHkJ92_mPLc4OF-NaddHZJLNMNzN5BTB2wu_W_Yq5jmdb64IpNaN9-Md7_edU3htUGwHiuqcqIc5FMjzxZM3ehxPp0kqTLuNMcONhm4YHPWud1dN-ZmvCIVANNhmZC_tr4Pa5pS62BO1LuYn04eZ9MBggnS_j-CnqcN9vkh9oN-XadmNPY-MRHHEDp-e6Qx-Of4AjFNH8AzugG1ELcGM6n0_H3Xbq3b9eKELSPpdgP37FmHQCSLedsjDNeDWTd9-Mw0CrvXIH3008asmUGSD8abNZxLQQvcYk1LqVDvmzb6BUQDWNJY8zMGdetmKjL2T3rPoT00FC1cgDRxw4k2fX3q2FwkABlqiXmjK_Sw88kLV5YfZqSIPAEWLVfZC3fZjdb1QJDukpYM3K5bfLQyiAI2Y9fWBuMaTMzALYmTvGvlJztX5WzWKar_XGy4ZLYVjLY_MGarxuJChJiMBPmsFYmRbaw9XLjvxZVeoscU05JbVoDkQz7w31EYm7ZTtT1jmkf7DlLrvItMGNAr0TRuWtG4k2j7Q4JgzEAEea8SU7me9law2Uv0g5BdZdqJCLKsBxSUsw3A0w1XN2EdxE63CHu4Nj8bwaHVl1HO_1Y3ZxBzym7MQPb_o8Ep8XjTrPqhgwL3djIWACKTJsWU6am51QXNvxOGuUhjiYFX0FsrGZJeyOn6Yf4kpvIPMfP9uZ0lgMIFBAPSfeMy5PQXIIYERoXMG0CgIDzazVUr7Y5qfa1plxJLpxJQ9XsFcVyuTmKgkNs2mjhTnUguQ1N59Q4989WCaFNH0aJDBapfFoEuhvYIMLnppfIsLbJ58VO9vALiAjKoWQ3R_LS8cHuMuClCqkdlQ7yf6YC-qGqCLNDMqOAahLhblhjuVwC3J-pO_jahRo8Di3JSXvHrfCRrSDh7M40sQEilNW3uRoKSztLjJ0974BBghYWJjaHWEqLC0ur_L2-H2Ahc1OlteZnyCoLzIz9_l8fwFCUJZYmZndXqRkrnF2OPr-wAAAAAAAAAAAAAAABAiM0R2EnVgC05OvIWLpr22s7bSnheK2FoOmcWmTNt6wxb79PyRYeMFUzle-mUPh5-aF00dlow8WZXR7KsD9zYI6D8F"}]},"newKid":"invoice-signer-2026"}},"Header names no key: sourceKid supplies it":{"description":"Header names no key: sourceKid supplies it","value":{"jwsToken":{"payload":"eyJkYXRhIjoiU0dWc2JHOGdRVzVyWVEiLCJpYXQiOjE3OTA2NTYyNDEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSJ9","signatures":[{"protected":"eyJ0eXAiOiJKT1NFIiwiYWxnIjoiUFMyNTYifQ","signature":"UtlqZxpbEF2NBltNjFvCDAxHSgZNO7M759FXw_ZKBQKPqokTDBsaz1Bxpb0CsHFK-vgHUeZbqoIxWrqyFYTzwsZFQiqH2ZDO3IeYRCfdPJ5yBUfQ03h9Bd-tRDfqC9f38wNGrsp1DpJPOieDOtXx9Oa958w2x0NcxRRKaET5b4uNw1AGQXGxaqyIeuvXjWYJYh3vY4IcoOJWRYmIFHH_jz1jS45JNvKLXgHyOcjouc8Gs5PNz4e4MtDtfMjcQXv83f-xeTk_7zsIFnX4F9teoZfxRYlxAfcCsGfFMke9hWjmq0U5pWZYvQI2q6l1TBiU5kJdOiP2_URppjmiULF7Ts7rCkJi_JawIICKcU-nGyN-mQque36m42gYl4alVnMUgrj_icEmlQFF4nEb6iVFEnKQKvIKMq0lG0kYyvfJ5rBG3kzM5YNCLGhWB0JvPwLEzk_qG-r-pDmtB7b1X7Fa0fF548ybjNOG7uzhEPxUQ2Gzl3QHFbx3DE_jsVjM8ZLD"}]},"newKid":"my-mldsa-44-key","sourceKid":"legacy-sign-key-v1"}}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.resign` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for RESIGN on the source and target keys - or, for a JWS signed by several keys, for VERIFY on one of the kept signers - or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations, or a key's lifecycle does not admit its leg. Answered before any signature is checked; the body names no key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/resign","timestamp":1745120400}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code> when the database that records the next one-time signature of a stateful hash-based signature key cannot be reached, or does not complete that write within its bound: nothing is signed. When the connection is lost while that index is being committed, the index may be spent; it is never used for a signature. The key-protection backend that unwraps and re-wraps that key's state while its next one-time signature is reserved must answer each call within the deployment's configured bound; when it does not, the request, and any request for the same key that waited on it, is answered as <code>key-protection-backend-unavailable</code> with <code>Retry-After</code>: nothing is signed and no index is spent. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/resign","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/resign"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"Payload successfully re-signed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResignJwsResponse"},"examples":{"Three signers: signature 2 of 3 replaced, the other two returned as received":{"description":"Three signers: signature 2 of 3 replaced, the other two returned as received","value":{"jwsToken":{"payload":"UXVhcnRlcmx5IGJvYXJkIHJlc29sdXRpb24gMjAyNi1RMw","signatures":[{"protected":"eyJhbGciOiJQUzI1NiIsImtpZCI6ImJvYXJkLWNoYWlyLXJzYSIsInZlciI6IjIuMCIsImFua19rdiI6MiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwiaWF0IjoxNzkwNjU2MjQwfQ","signature":"Qm9hcmQgY2hhaXIgUlNBLVBTUyBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg"},{"protected":"eyJhbGciOiJNTC1EU0EtNjUiLCJraWQiOiJ0cmVhc3VyZXItbWxkc2EtNjUiLCJ0eXAiOiJKV1QiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImlhdCI6MTc5MDY1NjI0MX0","signature":"VHJlYXN1cmVyIE1MLURTQS02NSBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbiwgcmVwbGFjaW5nIHRoZSBQLTI1NiBvbmU"},{"protected":"eyJhbGciOiJNTC1EU0EtNjUiLCJraWQiOiJzZWNyZXRhcnktbWxkc2EiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjMsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImlhdCI6MTc5MDY1NjI0MH0","signature":"U2VjcmV0YXJ5IE1MLURTQS02NSBzaWduYXR1cmUgb3ZlciB0aGUgcmVzb2x1dGlvbg"}]},"oldKeyRequested":"treasurer-ec-p256","sourceMaterialVersion":1,"oldKeyAlgorithmUsed":"EC-P256","newKeyRequested":"treasurer-mldsa-65","targetMaterialVersion":1,"newKeyAlgorithmUsed":"ML-DSA-65","signatureResults":[{"kid":"board-chair-rsa","algorithm":"RSA-3072","materialVersion":2,"verified":true},{"kid":"treasurer-ec-p256","algorithm":"EC-P256","materialVersion":1,"verified":true},{"kid":"secretary-mldsa","algorithm":"ML-DSA-65","materialVersion":3,"verified":true}],"warnings":[]}},"SIMPLE target output (ML-DSA-44)":{"description":"SIMPLE target output (ML-DSA-44)","value":{"jwsToken":{"payload":"eyJkYXRhIjoiU0dWc2JHOGdRVzVyWVEiLCJpYXQiOjE3OTA2NTYyNDIsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSJ9","signatures":[{"protected":"eyJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInR5cCI6IkpXVCIsImFsZyI6Ik1MLURTQS00NCIsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSIsImtpZCI6Im15LW1sZHNhLTQ0LWtleSJ9","signature":"SwnwdscGMm9wzCD9ETl_9K7IJyKS9xSSAkEKImwZVwjglLiX01lvLJyPwB8Ou0reo9x-q2E7d53hM7s3RXsnoqYSMg5i-fM1d8MtqmQ11fX5oVhw4_0n7VocFGcIenpanUsqg92uk42ynWwFgDDBEtZPqnihu_ANzLh3_7DZQaPfkfrh_FTCWbO8q5ECodI-XIn9X8lhVLonmAesDTjWnrHpX2U7mEDK4-ESysJsCU56VCuezXUzN91hUsq-RMw-eZtZvX31k3cee0QjBfwAFsJbynAXLNezzVgFFGylh0uIuIFuqpXbaj1C81s8O3R4EpG0L7W-Wi0_oeLsh3M-DWeszM8Or5mFcYzDo4I1D1Pj42txtJlDXdrmH4hbWtWA0qIi2nc-_wPoBm0ufsxu-Vb_oNnwggk12k9GAnoEfDrky7FsKTZeIiH-D6iCN1jih_eGMdMqumTuuNmABwb4QslVx2_pSGxsGWXpevnxIn4QZ1MASgWZt2Tv1qvXt4i_2TwdRN9-DNPy0WTFoI-PB2R9Rwb3MYOTJMMO1t-mzSFhgLRIFoZYFsqd-AlbGRCbAa9AI0YGljezYN7wV7tGD-OTIVWYQOlStIVcy8BfNFjA5QQsVc-GhpbnlZ0GgKn3grpIvZPB_BViAbrDed8Xm4HEWzNxgM1r5ORKGxuvyCAsq7-WpPgfAgcrawdb_ApgW9n77Gp8U8FkT3WRD9drgmEFYO02Hwia11ZKgtD3D20pHE1kvC6dsUlrbiagnYb2gjqWuttwT3k2u-1HRTjYdsfY8cxJSmtaeDnLn3LdePmLYUYEbbdFkgav_Tu7lemStAtMiMoD0NpFqvtLJcr5ZQfSKHSbtzcYFRjTMvcyZEavMWzepfTfMUEJ5WKPGFBt1YdJOk_DVHiybpqbOUWDEsRyYDZ4q3wehDGzpOFCWPe2FZGnyFiTcK01MUQn5q4WyX_rpDNeIHDhTyAZJ3IL1n_BfGpKasFO7fseg2J_z6zzleujTxNKsi0fMSkr5mdBf2K3vnI-0_QV1IiH9AYj6SeTj9TlGesc1cK1a3fcdFmRbCiNsFVhoUyPDWt8IAI_plngpAzBGfVQOf0lwWyd1WuFVPwhKUMKO90k100R0bFv139HLSLJsm4Sp5LIQ12zRaGZbOP5DvXqqjHW0ST_whCYes-WLSRwOb2OAm-pTNdY5EK6X_VNlDEjgWYQbHKcTTvRTWFuLIhOkisU1jWVXhDyq-iuGKOtIj55TWSW_0Nz6A519FlNZ1o62bz5ySghAg6kRvUtIC_q650wCVM1f1vQ3domUWFalAwgdqY1JubevsJXG3VZs7iX5cWdjI5FAnWfuRAYcOY_VL5YeADh3xbyqVGrDJs-qq8BqGqoj8CugBmBpd5MgwTaf0CVaozi14hAfv-Ed-y_mzrnLTK7RZRZUYX2hd3nEvtheaMbsIVWti6Dx0saXKhXz9CRl9K26PVVHVdj1oXtHK8XovMIoCF4NXngda2NnbKvdhiCsrysPau-8thBpGc1yw7g-alQr5mc15CoGjrDakzENouWXbN-EoXtrCYqqIUy2708rEnlxcY-hKrCTTpZnVUIMd9pE2gMP13FwBxxVmpW00p8EhRS6cPx6d4exOElF4Z8YHVPBx_tfQiP6UJrsNdYu3kJBMTyUhdnoVk91bLqWnnaaAT7c2-oFzTRusC5c3EzK_7-0uteqONwAP2LgxGKY4ABrmPPp1gv89olUwbqXABPvhMfVknfTQ17cvUIN4mkBFyLaSymX5xQPwdj9gbluRziEBBY9UJJGtmXZkufRJNgiLHpanwrUk8GXuu4JD7LOeun40_L38-sMduABQ1q4aDilMkN8QA1peJb5Plxtgb8DFQJJ7Vi0HtRPvbMNMxKvquhY3jbI_q1eGlPnQdNML5o4EKE1z5pjzFfaPxnniSsRqFSTif7TEk5iiA6BEjyXaX86vP753-h52vP91VGzj6rBjQwSOtKpyBGsvrvIPugIoXwqW1DRMShlDbPbHGwX4i8FY5YoHqBuDukEMDMSeXZ-H4crUI1S7oSjz5vCYb9PqdSpAwrY4CWJHfgoTnJ6PkKyNJAd6ed-luasf60Jymk7hHaRHyjoOQ5G604XijkryvmB3rNIHBG82h4oICYrgWbVQqHoQc1pdgzXAL-kxJ6sn-dpnSvcifAc2trNiWrpe_Iygf86A5w_DmM-nSopoT8MA77E7YjEOO_nt3JLxrTuhKUR7uYjMwk0pf1Pu4tdMasv4jbjb0DqwbtJNGtDxWr_reA1-8CfOXTEhjykb-i21z14seusl_rMBOyw1UomjB4zINNwBRNKrwBDe0t5brXnlH0y0RluTAB9MxJZYbHi5Xi2piqRliSsTYcijd1CA1cgIzIJTFOe2mvbQVYVzS0DOKHufBMBfsct9FNWJzqNuORJGMLMx9lonWM7BVZg2UOwbGsLe5oRWGSoQjdiE6mEJWY8pLJnsYEnWAZ1UiC2lB5WDLAu0442cMKNjYZFEWVSV7fB_XIOAKjPnAGjnXS-ExVZek1CHp57kML7900Fj2B7G01M0wHPCmbLbGttgk3SP9JSKohwTeu9TSthOMuFA8BADRtVecO9nQCcbACyi2rq3aSvz-WAu15spp3TAb4YywubZ24m0ITUkvct7-cEKe8izE1yfn1VsKoPjb311D8ZUdAo9LMJwzcteZbn9FkxniMmgv_rCZnxdu6h8HtdQF53KsSGhfDsRZ0jVvVzVUmNvjn0XSOiO-fcXpEyyryt0aNwwXxuTEk7sg6Ahn1NntHNqDuUA8hbxpgqFIThBWzR4E9RybgxGY-ZlrFahlM3sVjFwzuSlwy3Rg2UdkEVvCjJ3-jlexXAf20nQ_FpjzbQKVXoAJIvqzxRblNpB_SZdWb7wmtYxijo34HPFrJKwCk3Pag7-0mKp06WVgmEQyGS4q2AAwY9apN5-jNZwkKr0s-BGJnGBM6TOnb-saMWEnptntgi-lamn0Y6tf6I204FE3JsFR9V7FniJwTbn9UgTm_9mp_4I11ZbZnSeBs_rvqmE7qahOWxp8YkDBjpeSsGg5DOrhMzjIykYwBns3jxx-So5X7RnBAr0YjOm4EKD9ASU5eao-Zubrl9vcHCxg2REVeg4Sjq9PU2NsGIic-P1pidHeAiJ20x9Lf9yc6PlJTX2CGj9Tb3fX-AAAAAAAAAAAAAAAAAAAAAAAAAA8eLz0"}]},"oldKeyRequested":"my-rsa-3072-key","sourceMaterialVersion":1,"oldKeyAlgorithmUsed":"RSA-3072","newKeyRequested":"my-mldsa-44-key","targetMaterialVersion":1,"newKeyAlgorithmUsed":"ML-DSA-44","warnings":[]}},"COMPOSITE target output (P-256+ML-DSA-44, one composite signature)":{"description":"COMPOSITE target output (P-256+ML-DSA-44, one composite signature)","value":{"jwsToken":{"payload":"SGVsbG8gQW5rYQ","signatures":[{"protected":"eyJhbGciOiJNTC1EU0EtNDQtRVMyNTYiLCJraWQiOiJpbnZvaWNlLXNpZ25lci0yMDI2IiwiYW5rX2t2IjoxLCJpYXQiOjE3OTA2NDcxMDZ9","signature":"l_OwQe_7XTDdpZ86ofbi1hO0mSv-ja95pdF9V6dcOaFFUAMvwAEQJbs8azjBMgWVr-Fa7PfnsK1kJZ42iNHpVcUYinRXGD3W1uDr9wJu-aAfx6mD4PWnaNQ1URYeB8bOPUg6584in2Ggi4wlnHANqPk4hepJJ33tiHf9EevyFOIJvlE-vWKKEGTRmPXoKa87sF_5R-3vOh_aIWhLN9OzmOHvw16QGwYYJlsqcltwJLq45NoSCIRKhfJzAFXS0m-k_TSwEbn_x3PlqRJFhdUQqFTulpU2TYIJkm9zV8tKEEYMrO8OA2x3sGCEA_DiqddVD2h2mVsPEb789FDsIATLTYWQsjL3gYntCbO-tBoAg_8RYoD7-bTLDTN4wavYrGjyd_oEsqeCr5g3yTLLhlOO1ac6Jrs6Hjj7xlDp2MwSD54wzN_82XyQqbuq-PVo9XhbAQ_d442k8Y8qS-OA3xhuBzYWwV4tzQrzUQrPnnborKESnh9GaYDH5Cfjzq5007piNHNSi5NUsDRmsL04VFUlE8Ouz3rFBs5_hG7Uo1BJSMeY-2ZX9LKBEDvqQP9vA89Vv85Qq894YCQu4T0C-_go0ieGrD_4c0byzpFMj2lGZY_nZYIMn2fYcioMo9IU9A4ky9zCnB593R4TgiVU5iE5ycMmF9NhfORgF2-_yX_jgf_Hm91ctShr1dp0AB_J4S9kWE3i935nUzC3lZ-EtzWLkZHzo-Wwmh0hERwc078WPKLsrkA0yja13QCTnMzkXP8lmddKJO4QVyHmVzweZI8wciLR5TAV3QwB6yWz4mSFx4HARZmnbkH5fROHtc1y5VVZmmsEyX4fZNDrWm7adIblzHBgoVrMFBOUM0HYszlrqnpH8dfv9J_c7X94LqSx6vhHMmcNzUPN-QEB2r3Ffu5lHjWfNvZnjbazR87Lv7hEFT7YvTwg0XkJNepIBl_XdcU9E1Rksh8al0qccZJkPPk8BAV3Le4Q6RxvKGY1IbxL5hv4Lp5UQsrDZ0Sw0KYcV7k7nZRH8d4mqBPvCt8dmyj2NHlsMtuKviqPsZxj4gXGGgNpJ-erdkgPkG9kdKIdWb98x7t_ybmGNOCbdxeaURMynIVNB8IrvkiV2mdGBPlaIYMZrOZKcw7x5OybB7nNrXW7XGlq9xbqmKTpOs2lSgYfYk549Eq7FRJYDsxXQzaCXPfzt-0V1Xrkbr2iNpFPoLC3sv_i9j_E9jxr9L0tOg6nOp1a1hfcZzA7Dkw3tIah8g4Ce6mcdSs3V-t8dhgtM7NN1ZYNbg2aIrUkz6QjNEycU9xScSt_lpNUuLfdZYlXOKhI0ZhGkqiJOwutO0pXpij2AHReS_0ckMMjuVG7-3ozqGCZ7S4SOiJsm4rX22kJCvYF1nveLHi7rThPy0b2z5bzaJ7Uxx0jd0c4F_NIGg6Y02Q70xZLeo2y6rJ9DRQoqfSPxOlpSI69V-J6I2WUXh-ox98T7o8Lc8zQTYI57sC1uzRkox4KDQgA3ZDXpnYMhwvm9pZEd0mMTS8Q_0kdTBpIwKqf6YZ-DxtapUuaDPoR_gOlTQg5IZDAL4sbGi6LuRrYUUQXEJphhCM1LzdFsIAz-Ouhs8jP_f7HpcF4LEiiQTObntTRQ8vf2-oj2OfpIHgRWZLfjEnBMNvIcG_NTElYaubcZFpejDYpm7BuHPDnplYL8Uum7M6t9IcPCJdHNPvnd4aRmUI3H4NxRiBilVUPWL42CO2ctf1fRVcu-2gxyWdrrDjhRuzdjOSOOdCgGNZwnLSO_ecO85aYoQkm7WkLbi67VKgNaG7w4qHpahbUHSqr0XAkQbh4FKBau8TbdSb0TngWAvAmfSxqoaefksofrpQLvFCqpF4ImCnckLU7B1a26Yr4gF4owSrCjk1ErlU1osoy8kaMXAymdmT516MRafLE1HFK0mXnxjFRJ4zfIhDZVTHWVh_NohGIjzkMDdFJoK-aOQj42cZVf1uVQtw6DXa32JbntiE-xZzD7ZIje5EKSOx-uEBqTkO26Da1beamIe1VTl6DMVj96P5OPo9etKIJNqYZCrX_Qy50B3DiU_7lQcKf0LY6FxgNQChxnqFnKkbs_LYTj5nKgDxKjB9ahdgdgM2E-xJd5lCLMsHa4ZB1lgfpsXHzgG0VU11CQ8D2MEJJ00iQtFrrR4MjMibyz6GWQJqZ4UPRma2KedHZsL9HhFhSu69CVAJKhsDfJDXkKeV7j9vMrGka8RhXbXRQ8cKZwLdkVnpgfASjricWHTWvRRHVITap57KCrvjcbFEacQ8kSvHbwEuE6SRMcf3kSKHjJbO2RKT1B07JZ_wAyq-vE3Jo7zpTS9IZIbrcPGqYiaMQ2eVsP1iZ3QSe_OMz8A4MURbikJQZZVnjoGTTiBkj48w4TlH8LqWQj8OwnJTpfG7XwaMogoa7xBQlchPvfT8YaEywBb1YTCvK7ftR1W7eHRnG55WEe0anY-dSbi7WShhF7FuoktdC-bBqUDVBQR1ivmoQRgy0QA6JRpAOMcDhH9HkJB4JnMJHFfmfAIct-ZVh_DodkY9dyzSY4jLlrPNSD4TsR4ZsJN9PRRuhqUuAPssdEBjf3vr3X0iui8XNkb1CX2hWDVv6PGyUXte0eNB8cVNprIvmbnLmxZpV-C7CPNcHHmcPPBYDvKE2gDISbdq_OukDnRlnbGKFcrXghpeSf_XhJWWE6DcVK9BBzE7Eq5F_AwkcACoz3QjCB_YA_pDp5Du18-I6v6ASYOHDA_7dO4lxijS01gEYFUs5ah1u-zylv7lUpUSJbubaHSOoCqUcm73xcf9SJnv3DpPdrUA5l6B0YSH4jqLQ6_CqTjwSx5TNnzXWJbc-OCRLCTy_1hVQn7JO-a8jLt-TpYItRfcfgSGLU_UK6QvlWYHJ5WpXwvLBbn65JFf4c4k4YRcRrYbkAo1OGt8JAT8OaBz_I_X7W7KGH43ZhlsVmdzFWxKdR8oqLdu8e-HXhw-OTU4edJ9VqPKZ8Je196njW4waXcAgSu2XHfkB5Kg1fP-GmgKBkIokeEGJnHrKvkcOM8viniDsxiyl8I7ZzKrhciioD4hp8Q7_wIfAYJSQWnHj1YFnqkUnIJcFtoP2eOy92q4HCgssQoGRlJ6qu8LEzgQiZmp9fomSlaK1xtLc9vwFJCc5bXqDhZGgpqq1w8Td4_YUGTlCSlJjaoKtztbe4uTs7_r7AAAAAAAAAAAAAAAAAA4eMEMwRAIgalzbNyTvPBqO6zlckYkC4WCre48JVpZZr-r4XSkS8xYCIAGtRMp8uldmGF44E2v8j-FM8gMn6fVtyAutqCDOysmj"}]},"oldKeyRequested":"invoice-signer-2025","sourceMaterialVersion":1,"oldKeyAlgorithmUsed":"Ed25519+ML-DSA-44","newKeyRequested":"invoice-signer-2026","targetMaterialVersion":1,"newKeyAlgorithmUsed":"P-256+ML-DSA-44","warnings":[]}}}}}},"400":{"description":"Bad Request:\n• `sourceKid` is absent while the JWS header names no `kid`, or differs from the   header's `kid`, or is given twice; the detail names the member and no key, OR\n• The JWS header is refused for any reason other than an ABSENT `kid` - a `crit` naming any extension in the protected header (this route understands none), a `crit` in the signature's `header` or in the shared `header` (`crit` MUST be integrity protected, RFC 7515 section 4.1.11), a parameter such as `kid` present in both the protected and an unprotected header (RFC 7515 section 7.2.1), or a `kid` outside the key-identifier alphabet. `sourceKid` supplies the source key only when the header carries no `kid`; it never turns such a refusal into a request, OR\n• Key does not support the requested operation (verify or sign), OR\n• The source and target keys are of different classes - one symmetric (HMAC), the other   asymmetric (a composite key is asymmetric): 400 `key-operation-incompatible`, refused   after the grant, lifecycle and capability checks and before any key is used; the problem   names no key, OR\n• A signature of the input does not verify under the key its header names - on every key   flavour, SIMPLE, symmetric or composite. For a JWS signed by several keys EVERY signature   is verified first, and one that does not verify refuses the request: nothing is signed, OR\n• For a JWS signed by several keys: `sourceKid` is absent or names no signer, `newKid` names   a signer the operation keeps, or `serialization` is `COMPACT` (it carries one signature).   The detail names the member and no key, OR\n• For a JWS signed by several keys: `newKid` is a SIMPLE key and no signature of the token   declares `typ` `JWT`, so its payload is raw octets, which a SIMPLE signature (a JWT) does   not sign. Refused before any signature is checked or any key is used.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Header names no key and sourceKid is absent":{"description":"Header names no key and sourceKid is absent","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"sourceKid is required: the token does not name exactly one key, so the request names the one this operation uses","instance":"/api/v3/crypto/resign","timestamp":1745120900}},"sourceKid names another key than the header":{"description":"sourceKid names another key than the header","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"sourceKid names no key of the token","instance":"/api/v3/crypto/resign","timestamp":1745120901}},"A signature of the input does not verify: nothing is signed":{"description":"A signature of the input does not verify: nothing is signed","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"The old signature is not valid for the provided data.","instance":"/api/v3/crypto/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120905}},"newKid names a signer the operation keeps":{"description":"newKid names a signer the operation keeps","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"newKid names a key the token already carries in an entry this operation keeps","instance":"/api/v3/crypto/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120906}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'verify-only-key-001' does not support operation 'sign'","instance":"/api/v3/crypto/resign","timestamp":1745120902}},"Serialization incompatible with timestamp (the only serialization refusal)":{"description":"Serialization incompatible with timestamp (the only serialization refusal)","value":{"type":"https://docs.ankatech.co/errors/serialization-incompatible-with-timestamp","title":"Serialization Incompatible With Timestamp","status":400,"detail":"This deployment requires an RFC 3161 qualified timestamp on signatures, which can only be carried by the JWS JSON Serialization. Request JSON serialization or omit the serialization field to let the server choose the standards-correct representation.","instance":"/api/v3/crypto/resign","timestamp":1745120905,"extensions":{"errorCode":"SERIALIZATION_INCOMPATIBLE_WITH_TIMESTAMP"}}},"HMAC source, Ed25519 target":{"description":"HMAC source, Ed25519 target","value":{"type":"https://docs.ankatech.co/errors/key-operation-incompatible","title":"Key Operation Incompatible","status":400,"detail":"Unsupported re-sign: symmetric <--> asymmetric keys are not permitted","instance":"/api/v3/crypto/resign","timestamp":1745120502}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Not Found: <code>oldKid</code> or <code>newKid</code> does not exist or lacks the required capability, or, for a JWS signed by several keys, a signature names a key that does not exist. The detail names no key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key not found":{"description":"Key not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource not found","status":404,"detail":"Key 'newKid' does not exist or cannot sign.","instance":"/api/v3/crypto/resign","timestamp":1745120901}}}}}},"409":{"description":"Conflict: <code>oldKid</code> or <code>newKid</code> is in a disallowed state (revoked, suspended). Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when the target key (<code>newKid</code>), or a recipient a re-encryption of a multi-recipient token keeps, is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>): nothing is re-encrypted or re-signed under such a key. The source key (<code>oldKid</code>) is not refused for either: what a key produced stays readable after it stops protecting. The refusal is decided before any output exists. A stateful hash-based signature key (XMSS, XMSS^MT, LMS/HSS) is answered as <code>stateful-key-exhausted</code> when it can produce no further one-time signature, or when it existed in a database that was later restored (rotate the key; the signatures it already produced still verify), and as <code>stateful-key-busy</code>, with <code>Retry-After</code>, when another request is reserving its next one-time signature (the request may be retried; the key use admission already counted is kept). Either refusal is decided before any output exists: on a streaming route the answer is the problem document, never a <code>200</code>. On a streaming route and on the PKCS#7 conversion the next one-time signature is reserved, and its index spent, before the request body is processed, so a request then refused for its body (for example a source signature that does not verify on <code>/stream/resign</code>) uses one index without producing a signature. <code>conflict</code>: the key was written between the request's admission and its use; retry the request.","headers":{"Retry-After":{"description":"Sent only with <code>stateful-key-busy</code>: the whole number of seconds after which the request may be retried. Absent on every other 409.","style":"simple","schema":{"type":"integer","format":"int32","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Stateful signer exhausted: every one-time signature used":{"description":"Stateful signer exhausted: every one-time signature used","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: every one-time signature it holds has been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/resign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472401,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Stateful signer busy: another request is reserving its next signature (Retry-After)":{"description":"Stateful signer busy: another request is reserving its next signature (Retry-After)","value":{"type":"https://docs.ankatech.co/errors/stateful-key-busy","title":"Stateful Key Busy","status":409,"detail":"Key 'my-lms-signing-key' is reserving a one-time signature for another request. The request may be retried.","instance":"/api/v3/crypto/resign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472402,"extensions":{"code":"STATEFUL_KEY_BUSY"}}},"Stateful signer predates a database restore":{"description":"Stateful signer predates a database restore","value":{"type":"https://docs.ankatech.co/errors/stateful-key-exhausted","title":"Stateful Key Exhausted","status":409,"detail":"Key 'my-lms-signing-key' cannot produce another signature: it existed in a database that was later restored, so its one-time signature state may already have been used. Rotate the key; the signatures it already produced still verify.","instance":"/api/v3/crypto/resign","correlationId":"3f2b8c1e-6d4a-4f0b-9a7e-2c5d8e1f4a90","timestamp":1768472403,"extensions":{"code":"STATEFUL_KEY_EXHAUSTED"}}},"Invalid key state":{"description":"Invalid key state","value":{"type":"https://docs.ankatech.co/errors/conflict","title":"Conflict","status":409,"detail":"Key 'oldSignKid' is revoked and cannot verify.","instance":"/api/v3/crypto/resign","timestamp":1745120902}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/resign","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Signer key at its usage limit":{"description":"Signer key at its usage limit","value":{"type":"https://docs.ankatech.co/errors/key-material-usage-exhausted","title":"Key Material Usage Exhausted","status":409,"detail":"Operation 'resign' is refused: the key's current material has reached its usage limit. Rotate the key to protect new data; what it already protected can still be decrypted and verified.","instance":"/api/v3/crypto/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1768472400,"extensions":{"code":"KEY_MATERIAL_USAGE_EXHAUSTED"}}}}}}},"413":{"description":"Payload Too Large: JWS envelope exceeds the configured maximum size.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Payload too large":{"description":"Payload too large","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload too large","status":413,"detail":"JWS payload exceeds 5 000 000 bytes.","instance":"/api/v3/crypto/resign","timestamp":1745120903}}}}}},"422":{"description":"Unprocessable Entity: incoming JWS is not valid (malformed Compact or General JSON). A JWS carrying several signatures is refused with a key-neutral <code>extensions.errorCode</code> decided from its headers alone, before any key is looked up: <code>JWS_ROOT_MEMBER_FORBIDDEN</code> (a member at the root besides <code>payload</code> and <code>signatures</code>; a root <code>protected</code> or <code>signature</code> is refused on any number of signatures), <code>JOSE_ACTOR_KID_REPEATED</code> (one key named by two signatures), <code>JOSE_SYMMETRIC_ACTOR</code> (an HMAC <code>alg</code>; a signer or <code>newKid</code> that resolves to a symmetric key is refused the same way, after its gate), <code>JOSE_ACTOR_LIMIT_EXCEEDED</code> (more signatures than the deployment admits in one token).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"One key named by two signatures":{"description":"One key named by two signatures","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The token names one key in more than one of its entries: every recipient or signature of a multi-key token names a distinct key","instance":"/api/v3/crypto/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JOSE_ACTOR_KID_REPEATED"}}},"Invalid JWS":{"description":"Invalid JWS","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Provided JWS token is not valid.","instance":"/api/v3/crypto/resign","timestamp":1745120904}}}}}},"500":{"description":"Internal Server Error: unexpected error during re-sign.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/resign","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/reencrypt":{"post":{"tags":[" Secure"],"summary":"Re-encrypt a JWE General JSON Serialization payload","description":"Decrypts the incoming JWE and re-encrypts the plaintext under a\n<strong>fresh</strong> content-encryption key for <code>newKid</code>.\nThe result is a fresh JWE General JSON Serialization (RFC 7516 §7.2)\npayload — single-recipient for SIMPLE target keys, multi-recipient\nfor COMPOSITE hybrid targets.\n\n**Which key is replaced — <code>sourceKid</code>.** For a JWE that\nnames one key (its <code>kid</code>), <code>sourceKid</code> is\noptional and, present, must equal it. For a JWE that names no key,\n<code>sourceKid</code> names the key it was produced for. For a JWE\naddressed to several keys (no <code>kid</code> in\n<code>protected</code>, a <code>kid</code> in every recipient header,\nthe recipient set recorded in the protected <code>ank_kvs</code>), it\nis required and names the ONE recipient replaced: only that\nrecipient's <code>encrypted_key</code> is unwrapped, and no recipient\nis ever tried in its place.\n\n**A JWE addressed to several keys.** The output keeps every other\nrecipient in its position and puts <code>newKid</code> at the replaced\none. Because one content-encryption key is shared by every recipient\n(RFC 7516 §7.2.1), a new one is always drawn: the replaced key cannot\nread the output. Every kept recipient is re-wrapped at its current\nPRIMARY, which the output's protected <code>ank_kvs</code> records and\n<code>recipients[]</code> of the response reports. Authorisation\nfollows the flow of the content, as AWS KMS <code>ReEncrypt</code> does\nwith <code>ReEncryptFrom</code>/<code>ReEncryptTo</code>: the caller\nneeds a Capability Grant for <code>REENCRYPT</code> from the replaced\nkey to <code>newKid</code> AND from the replaced key to every kept\nrecipient — and no <code>DECRYPT</code> grant on any key, so\nre-encryption grants no access to the plaintext. Every grant is decided\nin recipient order, then every key-lifecycle decision (the replaced key\non the DECRYPT row, <code>newKid</code> on the REENCRYPT row, each kept\nrecipient on the ENCRYPT row), all before any key material is used. A\nrecipient entry is not integrity-protected (only the protected header\nis): a JWE whose recipients are not exactly the set its protected\nheader records is refused, so no entry added to a token can receive its\ncontent.\n","operationId":"reencryptData","requestBody":{"description":"JSON object with:\n• <code>jweToken</code> – the source JWE General JSON Serialization\n  (RFC 7516 §7.2) payload to re-encrypt.\n• <code>newKid</code> – key ID for the target public key; for a JWE\n  addressed to several keys, it takes the replaced recipient's position\n  and must not be one of the recipients kept.\n• <code>sourceKid</code> – the recipient replaced: required for a JWE\n  addressed to several keys; for a single-key JWE equal to its\n  <code>kid</code> when present, or the key itself when the protected\n  header names none.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReencryptJweRequest"},"examples":{"SIMPLE → SIMPLE re-encryption":{"description":"SIMPLE → SIMPLE re-encryption","value":{"jweToken":{"protected":"eyJhbGciOiJSU0EtT0FFUC01MTIiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoib2xkLXJzYS1rZXkiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjF9","recipients":[{"encrypted_key":"OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9k"}],"iv":"48V1_ALb6US04U3b","ciphertext":"5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4","tag":"XFBoMYUZodetZdvTiFvSkQ"},"newKid":"new-mlkem-key"}},"COMPOSITE → COMPOSITE re-encryption (hybrid PQC rotation)":{"description":"COMPOSITE → COMPOSITE re-encryption (hybrid PQC rotation)","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwia2lkIjoib2xkLWh5YnJpZC1rZXkiLCJ0eXAiOiJKT1NFIiwiYW5rX2t2IjoxLCJhbmthX2tkZiI6IkhLREYtU0hBMjU2In0","recipients":[{"header":{"alg":"X25519"},"encrypted_key":"b30bG_ngCxu8BHsDepgrtjbnj_gXqb_BzluB3A9ebw0"},{"header":{"alg":"ML-KEM-768"},"encrypted_key":"LGP9JsZ-Cn83kxvIrTAlxqyRyEPcSyMywdAS5nQJfk2m_QiFgraznRN1JSU6TwxDlcPnoUc6mHABPS9PitfsqHBd00eYBaYjTo7gmjDWtaWXrt6Vqn7sSG5LeT3JdDIuyOuk6N4to3gLyMmfowsNnSqYXWFRysGHBXVQrbXZo0l9qf0yaMztmKUop-XpXiaDt2OxU5nB3FDWngARATHhMSWFjpPvr4F9HIVQMY8ED1q8lJdMTpj2qOOdJQEkeKvee_KE33gw81P7Vn916oI1KT8P6aTr3rtXxFU8GUDtQb-NuJX2Jo2ppsAdvD6crM8pgcTMU-7cY09_dpPM2qf_ieQoJ6NLMYelD-vtfbVzGKm_sL_FrrviiZNxalhQRFenDtlWbB1lOWco70nhe6CX_QMNuKq9Y19pj6gTow3OorpB9Uc0voU0h-P_e5icx2t-lt6a_x6cEB6481wOtPxKIS0zoC392d9Ln3hUsMl7EQx-yY8-n--loiBiKnOilfEUaDR1MifTysJo1LlofOj9qLWhtvSOpd5b7Or2VAkjfrAvUkcrP3rcmKtz823ayIfmiPjf-bd95n4_cgKGUHaYKEAg8a7AAQPbDH8SjhYOde1EiPoqnVekNEX3zigiloQ4mlla0780xJO4zmrjIwn1ahnACh5DmtSu9ccwg6jsVtVo_T6kwx-QuRwpTOz2bYGTg0eql3bihClVCfJqzFf07LXEQeakigHCGaHuEGtCnl3ACah5KlCsht9D3W8ie09cEhOjEHtkXPCL46gG0hyK7YWusrpwBGKGO5L4_BsDspyd1V_9QnP8FsFK9335bnOOX9ZmV3tNmsvgsvvtYC6AsH-4lkVfzxZIvvIWCiGNNpcx9GsVJggEUOucUeIzhw19QkrSBRu2XlQ1A4PoYluW_6BDUF7QTBso38WtU7NqS75A-xL2MbCvRrJLetn5AxpifgzN2YpR6z-R4M8OKn8YZ1ERiHF2cpeVNGqM_ASq-ZhgBRa9aBkwIHOwMfqjfCcNk_AF7o_9e61Cd4v9KxfUGuN2T6i9qFSDCsCmnhhyEgY3p7WPZ8t6XHQgQ6btSeDUbQxY5ETmvXMVY-d1vLfzXRJHNzGSeFpWJnOTdEEeuZqC65jV45plpyWL5DpjZmr916t68YsRmIRDTlG9um49kxAP_mlzmDcTvAZILwBUlf9pOu9bGxFoLTXY5OQPqAhaonoEPQJR1GcvbZadXib84GWnVnvP6G_FntB9qzj0Q6vemLxdvle6D0mrI0XZI2WRvfwxIx5bGE5j5hu6XIKxU6hKfJej-dTjC4GcorsaO9KK_Cji2NhKf-NDwe4jMW_sPBRWe_SdpcE0pPx9vr4oMnB9dRE5Wr2iS7untej5WRDOSf2YvqiaMWafcfaEFfDcq_6aT3qGvxUPV0uiCt0h-dO7OSNQtlQkI5Meh0vd1po"}],"iv":"aWQ1_9E0plxSVj_f","ciphertext":"wujOsY13uvBOcA","tag":"QH1mh28lTnKN1wXQAthkDg"},"newKid":"new-hybrid-key"}},"Header names no key: sourceKid supplies it":{"description":"Header names no key: sourceKid supplies it","value":{"jweToken":{"protected":"eyJhbGciOiJSU0EtT0FFUC01MTIiLCJlbmMiOiJBMjU2R0NNIn0","recipients":[{"header":{},"encrypted_key":"Q2tXdHNPR3VaUjNKZ1BKcE5mV0RoYlhVdVpxQkZpbHNCa0Rr"}],"iv":"Zm9vYmFyYmF6cXV4","ciphertext":"U2VjcmV0IGFyY2hpdmUgbWFuaWZlc3Q","tag":"d3JhcHBlZC10YWctMTY"},"newKid":"new-mlkem-key","sourceKid":"legacy-rsa-key"}},"Recipient 2 of 3 replaced":{"description":"Recipient 2 of 3 replaced","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwidmVyIjoiMi4wIiwiYW5rX2t2cyI6eyJhY21lLWFyY2hpdmUtcjEiOjEsImFjbWUtYXJjaGl2ZS1yMiI6NCwiYWNtZS1hcmNoaXZlLXIzIjoyfX0","recipients":[{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r1"},"encrypted_key":"QVpxc0ZnWmxDbjdXUE1qV3lVaFp2aEtUcEt3cWxHWkxUMnF6"},{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r2"},"encrypted_key":"dW5Qc1BTa3pVZ2RVZ0FjY2hRTVh6Q0dSb2tYbkpNZ2xTbHNE"},{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r3"},"encrypted_key":"a2pYc0lFVk5xQ3dtRmVhVnJ5YW9EV2l6cG5Ic0tQUmJ1VmJk"}],"iv":"yv66vvrO263eyviI","ciphertext":"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY","tag":"Mz-VPPyU4RlcuYv1IwIvzw"},"sourceKid":"acme-archive-r2","newKid":"acme-archive-r4"}}}}},"required":true},"responses":{"401":{"description":"Unauthorized (`https://docs.ankatech.co/errors/unauthorized`): the request carries no bearer token, or the token is expired, not yet valid, badly signed, or issued for another issuer or audience. Answered by the authentication entry point before the operation runs, with a `WWW-Authenticate: Bearer` challenge.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Missing or expired access token":{"description":"Missing or expired access token","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to access this resource.","instance":"/api/v3/crypto/reencrypt","correlationId":"3f2b8c1e-6d4a-4b7f-9e2c-5a1d8f0b7c64","timestamp":1745120400,"extensions":{"code":"UNAUTHENTICATED"}}}}}}},"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.reencrypt` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for REENCRYPT on the source and target keys (for a JWE addressed to several keys, also from the replaced key to every recipient kept), or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations, or a key's lifecycle refuses its row (the replaced key on DECRYPT, `newKid` on REENCRYPT, a kept recipient on ENCRYPT). Answered before any key material is used; the body names no key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/reencrypt","timestamp":1745120400}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/reencrypt","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/reencrypt"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"Re-encryption succeeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReencryptJweResponse"},"examples":{"SIMPLE key output (single-recipient General JSON)":{"description":"SIMPLE key output (single-recipient General JSON)","value":{"jweToken":{"protected":"eyJhbGciOiJNTC1LRU0tNzY4IiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6Im15LW5ldy1tbGtlbS1rZXkifQ","recipients":[{"header":{"kid":"my-new-mlkem-key"},"encrypted_key":"newEncryptedKey_BASE64URL"}],"iv":"NewIV_BASE64URL","ciphertext":"NewCiphertext_BASE64URL","tag":"NewTag_BASE64URL"},"oldKeyRequested":"old-key","sourceMaterialVersion":1,"oldKeyAlgorithmUsed":"RSA-3072","newKeyRequested":"my-new-mlkem-key","targetMaterialVersion":2,"newKeyAlgorithmUsed":"ML-KEM-768","recipients":[{"kid":"my-new-mlkem-key","materialVersion":2,"algorithm":"ML-KEM-768"}],"warnings":["Old key 'old-key' nearing sunset date","New key 'my-new-mlkem-key' usage at 90% capacity"]}},"COMPOSITE hybrid output (multi-recipient General JSON)":{"description":"COMPOSITE hybrid output (multi-recipient General JSON)","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwia2lkIjoibmV3LWh5YnJpZC1rZXkiLCJ0eXAiOiJKT1NFIiwiYW5rX2t2IjoxLCJhbmthX2tkZiI6IkhLREYtU0hBMjU2In0","recipients":[{"header":{"alg":"P-384"},"encrypted_key":"BF4Mr_rWOhcPb0515k0quCq9gu0IGRCLEXcsYoeXxEnECre8xyhPYi15Po-AYPspEy2HKRQVLurn_BZusUaLXwwrpNECsXqHbayBcJvjKgtlC9nrFxUhq34JeXuqLNhBmQ"},{"header":{"alg":"ML-KEM-1024"},"encrypted_key":"Gdxdz4SIf4uC4sbwqds0R3WbaSPLB2ic5E7TDVt96TTrbLmljZ_3iprK1z80fsxPmnwWoRVucC5gs10AZkKo3weAFpZDFdT1RqsrUjdLHFVXyuFY9QCWPRsGhbnadtyiKwqDUs49O1L-57XG8hsYTcf6TtZrtmpxbUmR-CWvvVC-HFrsCgmq9xD2-hrzCCmvvuQP5DFeDUwHi5zltSdTMELLZLMvyxGvaa4Lh3RJXQv6meYSEwzUNQFnL6HUUfWm3niuoEwYMtmpYrqWzXKsUbibs3AEljVGFBsSeHlPSj261db3Up8zSRhvdN0MNqztUAorvXsn1mafptrhGloZjfLLcknt2K459E96STLgWEfC5gSU9nWLInLAzhCoJWVql0s6klznn4acJBtfP5Vr-Eie-pTi7P--D5aPEcpNw8TOr6SKfwpHK59yhPwk_V2NGjRjJuJcULHtpyK9FscZfNb_IPw88xHgCoEBAaIovO1TiTNVvYLK7vDZV4HeDaYHRHKwL5UaTyGQpAVszYugA8zZRM9HvUwfp44L6-ypyYynvHM3MF8Sa3Mgb2TtS2apDuIAPZy0F5tjfBy1QdoOoULRs0WldB1ruMZkFiuvY90RkYF8jBf2caXz6u-NNuxcSiQYhuic74bTPPg4xKTCbbOrR8Km4XGdNEqE0sXoVZkTL3Qg1KmkwuJXBj8l5toset2VY9Ux3bznAlbSuybPu0aQhGtx6sUoTspHFXT5nEv_IijnAe8JcDxrG57qvAv0ftJHCnJy1GddBv6mvZ6BXiap4-0de4RP98iE4xOC8_xAI26bEzJJLakokQ4Y2FhitMikcCvQ0tAGGCcQ7wpNolkGl2D76Zv-CHMGyLxJSzQFsAGNzV2iydrmdL_YotXr_UjB3GdCBT-qcatrHEsRsQSpwHiEhaBy7RSU2N0FJTglYoXdv_J2OX9cbxiSwViA4WqcFLVZlensBA6WyfGsPdTaScRxohhSPdqqqLGuoNsDuDxwFUYV0pl19xEX_fniCsqUqkLEoXWkO73E2GV9sG1nqUgOY6mRP8VRZQRIEnByaap9CIOKVu57iXJh9EBJkTWlGGFOP5vHJxZSgRQ010bEUnluAk2IrTmD6hZDb6pZmz-n6Ipd9S0Eej_52s1e0yEaniV67XJ3tXmiwfqNCFaNQicAO2p--7Sk89xpg3TRt6KW_GXqYBJ3NFV-qs-PAq4ls9nT3IORwDnh6U92S_6vqZSd6wr2jSWmNmX3vP6trQCclDrr4g3uj5mVOvlJZ1Mc2_nvejt2hD3kzq5h-rCUE5MwnKNFJtQF2wAwX0t4HiedvOuUKEvT2niVnp2EPQjc58o2KOXZT55R5kdeUZ3b2BQDMO95TljJJf-_VHsG9jRPtl5mwolkV6uSMwJ80tpbCCvr2eJJTBtF4lxNfxAQnOvkIHcC8IfmwVinuzYPqdCODprYzdFyzgTtxq3Y6xYwEXbyWyF9yAaL0w_r1gjTbGR7xatzGf5q-Eo1DVYFm2NK8zkKomtXk3jSEu7IzrqJ8TlwqX3FuKDxd2hKA06IRjghiGOw3LoJQB-nxQ_x9LzVUVfJCnb3nkRtpm6DvQhgjcsCCoq_bFpGVy3dSK4fB352pxChMGFHifqjeXyTpfbVZVUmgJx_gGUUSG9-v6jM3t9ndmafPwpyhCwbYOmUzxVKw39rJtPWDIEPdofUYsyMpoIKc5eKOuda02cGtq4UtbFrIdWv8XCSYmzxIamvA0jq3a99FyghmAJ-kIsmht1GTkqJ5QU_1lSEquIaB6iYy2AeJHJh6a1m7Zpm4bL86dzLNfnXWvFYB7K-W5MOdKw7RSszq9-k467-VnnyGcqDEILpkEmlKFjNaPxaAGEt0Td5cihdqdLQoEPBO75BF4Qss_O813xe_uVCbkyDz6zz8HD7UbNwmpfyfR926A3AZ9-AkcZWfghIA88MAZ5SXXa39XNZ7Ac163KDcbeiJeqLTlOTt5TQjB_BcDwt34J7Z2kKkMEM32QOS7UwxmdFIuWiUXyxwVL5hcTjaHCErjNjG5wh-6RIfbSnYOpkLfTDcgbXQwUMgnAN722AHcY"}],"iv":"1IerJZ7aFw_vq2eU","ciphertext":"KWiA-iYvq6Z0eg","tag":"WirvAS1nZ0wm2O1xewb-0Q"},"oldKeyRequested":"old-hybrid-key","sourceMaterialVersion":1,"oldKeyAlgorithmUsed":"X25519+ML-KEM-768","newKeyRequested":"new-hybrid-key","targetMaterialVersion":1,"newKeyAlgorithmUsed":"P-384+ML-KEM-1024","recipients":[{"kid":"new-hybrid-key","materialVersion":1,"algorithm":"P-384+ML-KEM-1024"}],"warnings":[]}},"Recipient 2 of 3 replaced (fresh content key, kept recipients at PRIMARY)":{"description":"Recipient 2 of 3 replaced (fresh content key, kept recipients at PRIMARY)","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwidmVyIjoiMi4wIiwiYW5rX2t2cyI6eyJhY21lLWFyY2hpdmUtcjEiOjMsImFjbWUtYXJjaGl2ZS1yNCI6MSwiYWNtZS1hcmNoaXZlLXIzIjoyfX0","recipients":[{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r1"},"encrypted_key":"WnVYc0tmZ0JxbWxYVlJoVHpQZ0dVdGJvWmNGZ1dUZ2NuUlRr"},{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r4"},"encrypted_key":"cXJOY3ZGYXdKZ0h0Y0pZa0xtTnBTdXFXcnlYekJ2VmhQZ01r"},{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r3"},"encrypted_key":"SmJ0YlFvWXJXbkZpRWZzUWNkS3RMdmdoTnFYcFp6d2FVbUJi"}],"iv":"c2FsdHktaXYtMTIz","ciphertext":"Xk9PbVlzVGtOcFVHVnhPSEhTRmZWYjhjaUxqWVFfM3Q","tag":"eS1uZXctdGFnLTE2Yg"},"oldKeyRequested":"acme-archive-r2","sourceMaterialVersion":4,"oldKeyAlgorithmUsed":"RSA-3072","newKeyRequested":"acme-archive-r4","targetMaterialVersion":1,"newKeyAlgorithmUsed":"RSA-4096","recipients":[{"kid":"acme-archive-r1","materialVersion":3,"algorithm":"RSA-3072"},{"kid":"acme-archive-r4","materialVersion":1,"algorithm":"RSA-4096"},{"kid":"acme-archive-r3","materialVersion":2,"algorithm":"RSA-3072"}],"warnings":[]}}}}}},"400":{"description":"Bad Request:\n• `sourceKid` is absent where it is required (a JWE addressed to several keys, or a JWE   whose header names no key), names no recipient of the JWE, or differs from the one key   the JWE names; or `newKid` names a recipient the operation keeps; or either member is   given twice or is outside the key-identifier bounds. The detail names the member and no   key, OR\n• The JWE header is refused for any reason other than an ABSENT `kid` - a `crit` in `protected` (this route understands no extension), a `crit` in a `recipients[i].header` (`crit` MUST be integrity protected, RFC 7516 section 4.1.13), a protected and recipient `kid` that disagree, or a `kid` outside the key-identifier alphabet. `sourceKid` supplies the source key only when the header carries no `kid`; it never turns such a refusal into a request, OR\n• An explicit `\"encrypted_key\": null` (the member is absent for an empty key, RFC 7516 section 7.2.1, never null), OR\n• For a single-recipient JWE, a header parameter name present both in `protected` and in its recipient `header`, whatever the two values (RFC 7516 section 7.2.1), or an `alg` in neither; refused before any grant or lifecycle decision. An `alg` in the recipient header alone is read after the grant and lifecycle decisions, only when it is the source key's own (422 `JWE_ALG_KEY_MISMATCH` otherwise), as on the streaming route, OR\n• For a JWE addressed to several keys, a member name present both in `protected` and in a recipient header (RFC 7516 section 7.2.1), or a recipient `kid` that is not 1 to 255 characters of letters, digits and `. _ -` (the detail does not echo it), OR\n• Key does not support the requested operation (decrypt or encrypt), OR\n• The source and target keys are of different classes - one symmetric, the other asymmetric   (a composite key is asymmetric): 400 `key-operation-incompatible`, refused after the grant,   lifecycle and capability checks and before any key is used; the problem names no key. The   source key's class is the one of the material its `ank_kv` pins, OR\n• For a composite source key, a malformed JWE: `kid`, `ank_kv` or `anka_kdf` in a recipient `header` (they belong in `protected` only - RFC 7516 section 7.2.1 disjoint header locations); a missing, non-string or non-base64url `protected`, `recipients[i].encrypted_key`, `iv`, `ciphertext` or `tag`; a GCM tag that is not 16 octets; or a malformed encapsulation - a length other than the component's, a point that is not a SEC 1 uncompressed point on the key's curve, or an X25519/X448 low-order point. Every one is refused before any private key is used.\n\n`jweToken` carries `protected`, `recipients` (each with its `header` and optional `encrypted_key`), `iv`, `ciphertext` and `tag` and nothing else. Any other member - a JSON `aad` member or a shared `unprotected` header included - is refused with 422 `JWE_ROOT_MEMBER_FORBIDDEN` (`JWE_RECIPIENT_MEMBER_FORBIDDEN` inside a recipient), never ignored.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JWE addressed to several keys, sourceKid absent":{"description":"JWE addressed to several keys, sourceKid absent","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"sourceKid is required: the token does not name exactly one key, so the request names the one this operation uses","instance":"/api/v3/crypto/reencrypt","timestamp":1745120500}},"newKid is a recipient the operation keeps":{"description":"newKid is a recipient the operation keeps","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"newKid names a key the token already carries in an entry this operation keeps","instance":"/api/v3/crypto/reencrypt","timestamp":1745120501}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'encrypt-only-key-001' does not support operation 'decrypt'","instance":"/api/v3/crypto/reencrypt","timestamp":1745120502}},"Symmetric source, composite target":{"description":"Symmetric source, composite target","value":{"type":"https://docs.ankatech.co/errors/key-operation-incompatible","title":"Key Operation Incompatible","status":400,"detail":"Unsupported re-encryption: symmetric <--> asymmetric keys are not permitted","instance":"/api/v3/crypto/reencrypt","timestamp":1745120502}}}}}},"404":{"description":"Not Found: source or destination key missing or unsuitable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key not found":{"description":"Key not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource not found","status":404,"detail":"Key 'oldKey' does not exist or lacks a private key.","instance":"/api/v3/crypto/reencrypt","timestamp":1745120501}}}}}},"409":{"description":"Conflict: a key cannot serve its leg in its current state - revoked or suspended; a composite key whose material version records no usable key-derivation function (<code>invalid-key-state</code>: rotate the material); or a tenant key-protection backend that is not configured. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when the target key (<code>newKid</code>), or a recipient a re-encryption of a multi-recipient token keeps, is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>): nothing is re-encrypted or re-signed under such a key. The source key (<code>oldKid</code>) is not refused for either: what a key produced stays readable after it stops protecting. The refusal is decided before any output exists. <code>conflict</code>: the key was written between the request's admission and its use; retry the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid key state":{"description":"Invalid key state","value":{"type":"https://docs.ankatech.co/errors/conflict","title":"Conflict","status":409,"detail":"Key 'myNewMlkemKid' is suspended and cannot encrypt.","instance":"/api/v3/crypto/reencrypt","timestamp":1745120502}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/reencrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Composite material records no usable KDF":{"description":"Composite material records no usable KDF","value":{"type":"https://docs.ankatech.co/errors/invalid-key-state","title":"Invalid key state","status":409,"detail":"Composite key 'old-hybrid-key' material version 2 cannot be used because it records no key-derivation function from [HKDF-SHA256, HKDF-SHA384, HKDF-SHA512, CatKDF, CasKDF]; rotate the material","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403,"extensions":{"code":"INVALID_KEY_STATE"}}}}}}},"413":{"description":"Payload Too Large: envelope or re-encrypted output exceeds the configured maximum size","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Payload too large":{"description":"Payload too large","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload too large","status":413,"detail":"Encrypted data exceeds the maximum allowed size (5 000 000 bytes).","instance":"/api/v3/crypto/reencrypt","timestamp":1745120503}}}}}},"422":{"description":"Unprocessable Entity. The source JWE is not a valid serialization; or, for a composite source key: its protected <code>anka_kdf</code> is absent, not a key-derivation function this platform defines, or not the one the source key material records (<code>composite-kdf-mismatch</code>, refused before any decapsulation); the recipient count or a per-position <code>alg</code> does not match the key's components; or the source AES-GCM tag does not verify (<code>unprocessable-entity</code>, one fixed detail naming no key material). No target ciphertext is produced. A JWE addressed to several keys is refused with this status BEFORE any authorization or lifecycle check when its shape is one no key accounts for, each with a key-neutral <code>extensions.errorCode</code>: <code>JWE_RECIPIENT_KID_MISSING</code>, <code>JOSE_ACTOR_KID_REPEATED</code>, <code>JOSE_SYMMETRIC_ACTOR</code>, <code>JOSE_ACTOR_LIMIT_EXCEEDED</code>, <code>JWE_SHARED_KID_NOT_COMPOSITE</code>, and <code>JWE_RECIPIENT_SET_UNPROTECTED</code> when its recipients are not exactly the set its protected <code>ank_kvs</code> records (an entry outside that set is covered by no tag, so it is refused before any key is gated; a JWE whose protected header records no set is refused the same way). The set itself is authenticated by the source tag, which this route verifies before any output recipient is wrapped. After the grant and lifecycle decisions: <code>JWE_COMPOSITE_RECIPIENT</code> when the replaced recipient, <code>newKid</code> or a kept recipient is a composite key, <code>JOSE_SYMMETRIC_ACTOR</code> when <code>newKid</code> or a kept recipient is a symmetric key, and <code>JWE_ALG_KEY_MISMATCH</code> when the replaced recipient's unprotected <code>alg</code> is not the key-management algorithm the platform uses for it. An unwrap or tag failure of the replaced recipient is the same single integrity refusal whatever its cause. A member the JWE does not define is refused with this status where the body is bound, before any key is looked up and on every shape (one key, a composite key, several keys): <code>JWE_ROOT_MEMBER_FORBIDDEN</code> for a root member besides <code>protected</code>, <code>recipients</code>, <code>iv</code>, <code>ciphertext</code> and <code>tag</code> (a JSON <code>aad</code> member and a shared <code>unprotected</code> header included), and <code>JWE_RECIPIENT_MEMBER_FORBIDDEN</code> for a recipient member besides <code>header</code> and <code>encrypted_key</code>. A single-recipient JWE whose <code>alg</code> is in its recipient header alone (the General layout of RFC 7516 section 7.2.1, which a PKCS#7 JSON conversion writes) is refused with this status after the grant and lifecycle decisions and before any key is used: <code>JWE_ALG_KEY_MISMATCH</code> when that <code>alg</code> is not the key-management algorithm the platform uses for the key, and <code>JWE_ALG_NOT_PROTECTED</code> when the key is symmetric or composite (only a JWE addressed to one asymmetric key may carry its <code>alg</code> there).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"An alg outside the protected header, for a symmetric key":{"description":"An alg outside the protected header, for a symmetric key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This operation reads the key-management algorithm of a JWE addressed to a symmetric or composite key from its integrity-protected header, and this JWE carries its alg only in the unprotected per-recipient header (the General JSON layout, RFC 7516 section 7.2.1). Only a JWE addressed to one asymmetric key may carry its alg there.","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_NOT_PROTECTED"}}},"A JSON aad member at the JWE root":{"description":"A JSON aad member at the JWE root","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The JWE carries a member at its root besides 'protected', 'recipients', 'iv', 'ciphertext' and 'tag': this platform reads no JSON 'aad' and no shared 'unprotected' header (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ROOT_MEMBER_FORBIDDEN"}}},"A recipient entry outside the protected recipient set":{"description":"A recipient entry outside the protected recipient set","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The recipients of the JWE are not the recipient set its protected header records: a recipient entry the protected header does not name is not integrity-protected, so the JWE is refused before any key is gated","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_RECIPIENT_SET_UNPROTECTED"}}},"A kept recipient is a composite key":{"description":"A kept recipient is a composite key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"A recipient of the JWE is a composite key: a composite key is read from and written to its own composite JWE, never as one recipient among several","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_COMPOSITE_RECIPIENT"}}},"Invalid JWE":{"description":"Invalid JWE","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Provided 'jweToken' is not a valid JWE General JSON Serialization object.","instance":"/api/v3/crypto/reencrypt","timestamp":1745120504}},"Composite KDF mismatch":{"description":"Composite KDF mismatch","value":{"type":"https://docs.ankatech.co/errors/composite-kdf-mismatch","title":"Composite KDF Mismatch","status":422,"detail":"Composite JWE for key 'old-hybrid-key' declares anka_kdf 'HKDF-SHA256' but the key material records 'CatKDF'","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304}},"Composite ciphertext integrity failure":{"description":"Composite ciphertext integrity failure","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Unable to decrypt: key mismatch or ciphertext integrity failure.","instance":"/api/v3/crypto/reencrypt","timestamp":1745120305}}}}}},"500":{"description":"Internal Server Error: unexpected crypto failure","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/reencrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/encrypt":{"post":{"tags":[" Secure"],"summary":"Encrypt data (JWE).","description":"Encrypts Base64‑encoded data using the <em>public key</em> associated\nwith <code>kid</code>.\nSupported algorithms: ML‑KEM (KEM), RSA, ECC (ECIES).\n\nOutput: a typed JWE General JSON Serialization (RFC 7516 §7.2)\npayload — uniform shape across SIMPLE single-recipient keys and\nCOMPOSITE hybrid multi-recipient keys.\n","operationId":"encryptData","requestBody":{"description":"JSON payload containing the key identifier (<code>kid</code>) and\nthe plaintext (<code>data</code>) to encrypt, both Base64‑encoded.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncryptRequest"},"examples":{"Encrypt request":{"description":"Encrypt request","value":{"kid":"myKeyKid","data":"SGVsbG8gQW5rYQ=="}}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.encrypt` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for ENCRYPT on the addressed key, or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/encrypt","timestamp":1745120400}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when the database that counts the use of the key cannot be reached: the request is refused before any output exists. When the connection is lost while the use is being committed, whether that use was counted cannot be determined. It is answered as <code>usage-accounting-unavailable</code>, with <code>Retry-After</code>, when the use reaches the key's soft or maximum usage limit and the record of that crossing cannot be written: the use is not counted and no output is produced. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/encrypt","timestamp":1745120403}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"Encryption successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncryptJweResponse"},"examples":{"SIMPLE key (single-recipient General JSON)":{"description":"SIMPLE key (single-recipient General JSON)","value":{"jweToken":{"protected":"eyJhbGciOiJNTC1LRU0tMTAyNCIsImVuYyI6IkEyNTZHQ00iLCJraWQiOiJteS1tbGtlbS1rZXkiLCJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjF9","recipients":[{"encrypted_key":"T8h3K9XmZpQ2_aB4cD5eF6gH7iJ8kL9mN0oP1qR2sT3uV4wX5yZ6"}],"iv":"48V1_ALb6US04U3b","ciphertext":"5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4","tag":"XFBoMYUZodetZdvTiFvSkQ"},"keyRequested":"my-mlkem-key","materialVersion":2,"algorithmUsed":"ML-KEM-1024","warnings":["Usage at 90% limit, rotation recommended soon.","Key 'my-mlkem-key' is nearing sunset; consider rotating soon"]}},"COMPOSITE hybrid key (multi-recipient General JSON)":{"description":"COMPOSITE hybrid key (multi-recipient General JSON)","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwia2lkIjoibXktaHlicmlkLWtleSIsInR5cCI6IkpPU0UiLCJhbmtfa3YiOjEsImFua2Ffa2RmIjoiSEtERi1TSEEyNTYifQ","recipients":[{"header":{"alg":"X25519"},"encrypted_key":"nOOwTl-KlxS4C3zPRZE7AOBrq4phR7gy1pbmgWYZDQI"},{"header":{"alg":"ML-KEM-768"},"encrypted_key":"m20tVVdqBXnyjcdPW3w2E0sD9vxsF4Vqf9bt2guenEXUW_k12AsYrRAX3PudXN4YLi18MjfyJbq3CP0z7PwU8DcFabBuBLmjCT-Bv8EGIMRfkEPBuw9wUxjy8uwCn2u0Lwvcl2h8GSRq1edvHpSE71y1la3ObZT7w-PbAnbgyOQapHyoJpQS8O11hXQiKNbICH3WSYDIZztVYD-o5YsH9Ra6VbX5WZycF3r8rD1oSz5YV7K_qMWXUkiDF_g2jNdagK7cShPkn_Fx1TvVj9mX2RqVbB99xduyYRNiD1zq4DlJnI07r792AXONOvN-KmZaSqoM9qgfpEezA55H24j9-MIb4D8lBReWXnap3lbgcHQyLPsZ9N3dTGGDjIgGb6y8gBxPEK8wd1w2-GX-rF0NhNKr7Md1c7CST6cCkCG1TX9fhQv6yWx1hNIJVZ3irB3Qsdt2W6IZvacdrk0KTD_sD0tahNF6nWPd3P0lgoieHhGtcgZnoHPtw5ZVEbUpMpkLMMFb8W1JNXcosTWG8RfKxiVKI3H9vA3AWHNn7yuuQg02FSqfhoF6O_OdX4pk9NMoGN45w4sCVTWRi786la_g75oHKfdQUETHZVYApZiw9movE-sqhlRfwtma3mRU1nd6UTUYNPGbkzeqFR7fzy8R1UCjrd91zs4PeXnBzMSnYkRsvSnD9Ww6yY2ztqUdzDADIwpVOIbDku_1NKF5B2TF4_sZCojwLsXVSqirJqlw21k_qld7870MtzCzVFsYPo2c2W-z0xXLkY2hAluWfd_FtHZII0y3mgGCPc5Pomg-rupRd8zw_x173uPbCLshZdsjFRkW4dIUr9DKeTxkGpCKuC5MzT1Ww2KojPH442h3BbEO58XPM__hkl_Ii32x9mD97SbVc4U0s_caP5L3PMjFVbMi8waGBd6PumbSPiqOEC7e31v5PoRaA8_qQCv2AkbLMCRBWpWvYXe0DZqnqrV16wQhtKgafFwTF5qOiVy6RlgFneVuynzUG8m4xycyGGqplwjv4mqg89RmK6ER8Oey2WS9WtXIOVrX92RLLqR6Wt0gwqp5XduaRQy17VFn4V-iLmeBDnXp8RIFFC-OCG5UcT6gDwufKU5MyTqYe6cncjj1fg-F8HgpD62ZbUjOitqpHUKGaTGKbvqRVrja0xLx2vrXf6TWiY4Qlv1zKhEAkXvsNzSW7F-zZHxATfOvX6Wb_EdQFTCGuatw2KyJHnlaEOO-sBqnRGgST6pb7x3Xw8DskgJEgOpdXeK86DFQcLnJnaonmVnJkimtgpOob9459LI86GhhrFV4xCfK06Sn60MHKDypZKy6-WxDQWYepiYZaORhtC4QdC2ijtZVgHt-oXu0e1vsvhemO5-qMDxR3CzzzfqRfOjfLA4EPC64iHDk7Fspj2-6I-7Jq4HOuP4H-0gNvPiZFqJ_K1Tzxy_aUaQ"}],"iv":"e4j0J1JjR_sUyd38","ciphertext":"fwjaJQF0rRAwYw","tag":"xVbiloc3YU5aI3fl63-BRw"},"keyRequested":"my-hybrid-key","materialVersion":1,"algorithmUsed":"X25519+ML-KEM-768","warnings":[]}}}}}},"400":{"description":"Bad Request:\n• Input is not valid Base64, OR\n• Key does not support the requested operation (e.g., trying to encrypt with a sign-only key)\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid Base64":{"description":"Invalid Base64","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"'data' is not valid Base64.","instance":"/api/v3/crypto/encrypt","timestamp":1745120400}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'sign-only-key-001' does not support operation 'encrypt'","instance":"/api/v3/crypto/encrypt","timestamp":1745120400}}}}}},"413":{"description":"Payload Too Large: plaintext exceeds the configured maximum size (see GET /api/v3/crypto/limits)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Payload too large":{"description":"Payload too large","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload too large","status":413,"detail":"'data' exceeds the maximum allowed size (5 000 000 bytes).","instance":"/api/v3/crypto/encrypt","timestamp":1745120401}}}}}},"404":{"description":"Not Found: specified key does not exist or lacks a public component","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Unknown key":{"description":"Unknown key","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource not found","status":404,"detail":"Key 'myKeyKid' does not exist or has no public key.","instance":"/api/v3/crypto/encrypt","timestamp":1745120402}}}}}},"409":{"description":"Conflict: the key is in an invalid state (e.g. suspended or revoked), or the tenant key-protection backend is not configured. Both are durable - do not retry. Each protect use (a signature, an encryption per recipient key, the target of a re-operation, each recipient a re-encryption of a multi-recipient token keeps) is counted against the key's usage limit (<code>maxUsageLimit</code>) before any output exists; decrypting and verifying are never counted. The use that loses the race to the key's last unit is answered as <code>key-material-usage-exhausted</code>. A use is answered as <code>invalid-key-state</code> when the counter could not decide it because the key's material kept changing under it (a rotation or a limit change, retried once), including a use that would produce output with a material a rotation replaced after the request resolved it. It is also answered as <code>key-material-expired</code> when the key's current material is past its expiry (<code>expiresAt</code>) or has an expiry that cannot be read, and as <code>key-material-usage-exhausted</code> when it has reached its usage limit (<code>maxUsageLimit</code>); a material in both states is answered as expired. Nothing is produced and the key's state is unchanged: it still decrypts and verifies what it protected, and rotating it restores protection. Durable - do not retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid key state":{"description":"Invalid key state","value":{"type":"https://docs.ankatech.co/errors/conflict","title":"Conflict","status":409,"detail":"Key 'myKeyKid' is suspended and cannot encrypt.","instance":"/api/v3/crypto/encrypt","timestamp":1745120403}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/encrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Internal Server Error: unexpected error during encryption","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"402":{"description":"Payment Required. The status is the licence verdict; `type` says which verdict it is, so branch on `type`, never on `detail`.\n\n- `https://docs.ankatech.co/errors/deployment-license-expired` — no DEPLOYMENT licence is in effect (it has expired, or none was ever installed and no trial applies) and the installation is in cryptographic read-only mode, so operations that create new protection or new key material are refused; `detail` states which, for a human reader. Any authenticated caller can receive it, whatever issued its token (a user token from the authentication service or a service-to-service token from the administration API): the class belongs to the operation, not to the caller.\n- `https://docs.ankatech.co/errors/payment-required` — the per-CLIENT licence verdict refused this caller: its trial has expired or it is past its usage limit. Only a caller presenting a token this service validates as a licence client can receive it.\n\nThe licence gate runs after authentication and before scope authorization. On a deployment with no licence in effect an authenticated caller that lacks this operation's scope therefore receives 402, not 403. When the licence server cannot be reached the answer is 503 with `Retry-After`, never 402.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Deployment license expired (read-only mode)":{"summary":"The deployment licence has expired; this operation creates new protection or key material and is refused in read-only mode","value":{"type":"https://docs.ankatech.co/errors/deployment-license-expired","title":"Deployment License Expired","status":402,"detail":"The deployment license has expired. Contact your platform administrator to renew it.","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}},"Per-client license limit exceeded":{"summary":"The calling licence client is past its usage limit","value":{"type":"https://docs.ankatech.co/errors/payment-required","title":"Payment Required","status":402,"detail":"License limit exceeded for client: payments-client","instance":"/api/v3/{path-of-the-refused-operation}","timestamp":1745119200}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/encrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/decrypt":{"post":{"tags":[" Secure"],"summary":"Decrypt a JWE General JSON Serialization payload","description":"Parses and decrypts the given JWE using the private key it is\naddressed to. Returns the plaintext (Base64-encoded) plus metadata\nabout key selection, algorithm, and any warnings.\n\nUses JWE General JSON Serialization (RFC 7516 §7.2) uniformly —\nsingle-recipient for SIMPLE keys, one recipient per component for\nCOMPOSITE hybrid keys (the key named by the <code>kid</code> in\n<code>protected</code>), and one recipient per key for a JWE\naddressed to several keys (each recipient naming its key in its own\nheader, no <code>kid</code> in <code>protected</code>).\n\n<b>A JWE addressed to several keys is decrypted with the ONE recipient\nthe caller names</b> in <code>recipientKid</code> (RFC 7516 §5.2 leaves\nthat choice to the application). In this order, each step before the\nnext: the token is classified from its public headers alone (422, or\n400 for a member name in both <code>protected</code> and a recipient\nheader); <code>recipientKid</code> must name one of its recipients (400\nnaming the member and no key); ONE Capability Grant decision for\nDECRYPT and ONE key-lifecycle decision are taken on that key only, at\nthe material version the integrity-protected <code>ank_kvs</code>\nrecords for it (the PRIMARY when it records none) — a refusal is the\ngeneric 403, which names no key; a composite key as the named\nrecipient is refused (422); and only that recipient's\n<code>encrypted_key</code> is unwrapped. No other recipient is ever\ngated, resolved or tried, and there is no fallback after a refusal or\nan unwrap failure. <code>keyRequested</code> and\n<code>materialVersion</code> report the named recipient.\n","operationId":"decryptData","requestBody":{"description":"JSON object with:\n• <code>jweToken</code> – the typed JWE General JSON\n  Serialization (RFC 7516 §7.2) object to decrypt;\n• <code>recipientKid</code> – the recipient this decryption reads.\n  Required when the JWE is addressed to several keys; optional for a\n  single-recipient or composite JWE, where it must name the JWE's one\n  key when present.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecryptJweRequest"},"examples":{"SIMPLE key (single-recipient General JSON)":{"description":"SIMPLE key (single-recipient General JSON)","value":{"jweToken":{"protected":"eyJhbGciOiJSU0EtT0FFUC01MTIiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoibXktcnNhLTMwNzIta2V5IiwidmVyIjoiMi4wIiwiYW5rX2t2IjoxfQ","recipients":[{"encrypted_key":"OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9k"}],"iv":"48V1_ALb6US04U3b","ciphertext":"5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4","tag":"XFBoMYUZodetZdvTiFvSkQ"}}},"Recipient 2 of 3 named":{"description":"Recipient 2 of 3 named","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwidmVyIjoiMi4wIiwiYW5rX2t2cyI6eyJhY21lLWFyY2hpdmUtcjEiOjEsImFjbWUtYXJjaGl2ZS1yMiI6NCwiYWNtZS1hcmNoaXZlLXIzIjoyfX0","recipients":[{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r1"},"encrypted_key":"QVpxc0ZnWmxDbjdXUE1qV3lVaFp2aEtUcEt3cWxHWkxUMnF6"},{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r2"},"encrypted_key":"dW5Qc1BTa3pVZ2RVZ0FjY2hRTVh6Q0dSb2tYbkpNZ2xTbHNE"},{"header":{"alg":"RSA-OAEP-512","kid":"acme-archive-r3"},"encrypted_key":"a2pYc0lFVk5xQ3dtRmVhVnJ5YW9EV2l6cG5Ic0tQUmJ1VmJk"}],"iv":"yv66vvrO263eyviI","ciphertext":"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY","tag":"Mz-VPPyU4RlcuYv1IwIvzw"},"recipientKid":"acme-archive-r2"}},"COMPOSITE hybrid key (multi-recipient General JSON)":{"description":"COMPOSITE hybrid key (multi-recipient General JSON)","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwia2lkIjoibXktaHlicmlkLWtleSIsInR5cCI6IkpPU0UiLCJhbmtfa3YiOjEsImFua2Ffa2RmIjoiSEtERi1TSEEyNTYifQ","recipients":[{"header":{"alg":"X25519"},"encrypted_key":"nOOwTl-KlxS4C3zPRZE7AOBrq4phR7gy1pbmgWYZDQI"},{"header":{"alg":"ML-KEM-768"},"encrypted_key":"m20tVVdqBXnyjcdPW3w2E0sD9vxsF4Vqf9bt2guenEXUW_k12AsYrRAX3PudXN4YLi18MjfyJbq3CP0z7PwU8DcFabBuBLmjCT-Bv8EGIMRfkEPBuw9wUxjy8uwCn2u0Lwvcl2h8GSRq1edvHpSE71y1la3ObZT7w-PbAnbgyOQapHyoJpQS8O11hXQiKNbICH3WSYDIZztVYD-o5YsH9Ra6VbX5WZycF3r8rD1oSz5YV7K_qMWXUkiDF_g2jNdagK7cShPkn_Fx1TvVj9mX2RqVbB99xduyYRNiD1zq4DlJnI07r792AXONOvN-KmZaSqoM9qgfpEezA55H24j9-MIb4D8lBReWXnap3lbgcHQyLPsZ9N3dTGGDjIgGb6y8gBxPEK8wd1w2-GX-rF0NhNKr7Md1c7CST6cCkCG1TX9fhQv6yWx1hNIJVZ3irB3Qsdt2W6IZvacdrk0KTD_sD0tahNF6nWPd3P0lgoieHhGtcgZnoHPtw5ZVEbUpMpkLMMFb8W1JNXcosTWG8RfKxiVKI3H9vA3AWHNn7yuuQg02FSqfhoF6O_OdX4pk9NMoGN45w4sCVTWRi786la_g75oHKfdQUETHZVYApZiw9movE-sqhlRfwtma3mRU1nd6UTUYNPGbkzeqFR7fzy8R1UCjrd91zs4PeXnBzMSnYkRsvSnD9Ww6yY2ztqUdzDADIwpVOIbDku_1NKF5B2TF4_sZCojwLsXVSqirJqlw21k_qld7870MtzCzVFsYPo2c2W-z0xXLkY2hAluWfd_FtHZII0y3mgGCPc5Pomg-rupRd8zw_x173uPbCLshZdsjFRkW4dIUr9DKeTxkGpCKuC5MzT1Ww2KojPH442h3BbEO58XPM__hkl_Ii32x9mD97SbVc4U0s_caP5L3PMjFVbMi8waGBd6PumbSPiqOEC7e31v5PoRaA8_qQCv2AkbLMCRBWpWvYXe0DZqnqrV16wQhtKgafFwTF5qOiVy6RlgFneVuynzUG8m4xycyGGqplwjv4mqg89RmK6ER8Oey2WS9WtXIOVrX92RLLqR6Wt0gwqp5XduaRQy17VFn4V-iLmeBDnXp8RIFFC-OCG5UcT6gDwufKU5MyTqYe6cncjj1fg-F8HgpD62ZbUjOitqpHUKGaTGKbvqRVrja0xLx2vrXf6TWiY4Qlv1zKhEAkXvsNzSW7F-zZHxATfOvX6Wb_EdQFTCGuatw2KyJHnlaEOO-sBqnRGgST6pb7x3Xw8DskgJEgOpdXeK86DFQcLnJnaonmVnJkimtgpOob9459LI86GhhrFV4xCfK06Sn60MHKDypZKy6-WxDQWYepiYZaORhtC4QdC2ijtZVgHt-oXu0e1vsvhemO5-qMDxR3CzzzfqRfOjfLA4EPC64iHDk7Fspj2-6I-7Jq4HOuP4H-0gNvPiZFqJ_K1Tzxy_aUaQ"}],"iv":"e4j0J1JjR_sUyd38","ciphertext":"fwjaJQF0rRAwYw","tag":"xVbiloc3YU5aI3fl63-BRw"}}}}}},"required":true},"responses":{"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.decrypt` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for DECRYPT on the addressed key (for a JWE addressed to several keys, the recipient `recipientKid` names; the body names no key), or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations. Answered before any key material is used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/decrypt","timestamp":1745120400}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/decrypt","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/decrypt"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"Data decrypted successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecryptResponse"},"examples":{"DecryptResponse":{"description":"DecryptResponse","value":{"decryptedData":"SGVsbG8gQW5rYQ==","keyRequested":"myKeyKid","materialVersion":2,"algorithmUsed":"ML-KEM-768","warnings":[]}}}}}},"400":{"description":"Bad Request:\n• Malformed Base64 or missing/blank JWE header 'kid', OR\n• Key does not support the requested operation (e.g., trying to decrypt with an encrypt-only key), OR\n• For a composite key, a malformed JWE: 'kid', 'ank_kv' or 'anka_kdf' in a recipient 'header' (they belong in 'protected' only - RFC 7516 section 7.2.1 disjoint header locations); a missing, non-string or non-base64url 'protected', 'recipients[i].encrypted_key', 'iv', 'ciphertext' or 'tag'; a GCM tag that is not 16 octets; or a malformed encapsulation - a length other than the component's, a point that is not a SEC 1 uncompressed point on the key's curve, or an X25519/X448 low-order point. Every one is refused before any private key is used. OR\n• A 'crit' in 'protected' naming any extension (this route understands none), or a 'crit' member in any 'recipients[i].header' - 'crit' MUST be integrity protected (RFC 7515 section 4.1.11, RFC 7516 section 4.1.13), OR\n• An explicit \"encrypted_key\": null. The member is ABSENT when the JWE Encrypted Key is empty ('alg' 'dir', every symmetric key - RFC 7516 section 7.2.1); present, it must be a base64url string. OR\n• For a JWE addressed to several keys: 'recipientKid' absent, naming no recipient of the JWE, given twice, or not 1 to 255 characters of letters, digits and . _ - (the problem names the member and no key, and no grant or lifecycle decision is taken); or a member name present both in 'protected' and in a recipient header (RFC 7516 section 7.2.1); or a recipient 'kid' that is not 1 to 255 characters of letters, digits and . _ - (the kid rule every JOSE header kid is read under; the detail does not echo it). OR\n• For a single-recipient or composite JWE, a 'recipientKid' naming another key than the JWE's. OR\n• For a single-recipient JWE, a header parameter name present both in 'protected' and in its recipient 'header', whatever the two values (RFC 7516 section 7.2.1: the locations are disjoint), or an 'alg' in neither. Refused before any grant or lifecycle decision. An 'alg' in the recipient header alone (the General layout a PKCS#7 JSON conversion writes) is read after the grant and lifecycle decisions, only when it is the key's own (422 JWE_ALG_KEY_MISMATCH otherwise), as on the streaming route.\n\n'jweToken' carries 'protected', 'recipients' (each with its 'header' and optional 'encrypted_key'), 'iv', 'ciphertext' and 'tag' and nothing else. Any other member - a JSON 'aad' member or a shared 'unprotected' header included - is refused with 422 JWE_ROOT_MEMBER_FORBIDDEN (JWE_RECIPIENT_MEMBER_FORBIDDEN inside a recipient), never ignored.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid input":{"description":"Invalid input","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"'jweToken' is not valid Base64 or 'kid' header is missing.","instance":"/api/v3/crypto/decrypt","timestamp":1745120300}},"recipientKid names no recipient of the JWE":{"description":"recipientKid names no recipient of the JWE","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"recipientKid names no key of the token","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120300}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'encrypt-only-key-001' does not support operation 'decrypt'","instance":"/api/v3/crypto/decrypt","timestamp":1745120300}},"Composite X25519 encapsulation of the wrong length":{"description":"Composite X25519 encapsulation of the wrong length","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"X25519 encapsulation must be a 32-octet u-coordinate, got 31 octets","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120300}},"Composite kid in a recipient header":{"description":"Composite kid in a recipient header","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"Composite JWE header parameter 'kid' may appear only in 'protected', found in 'recipients[0].header'","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120300}}}}}},"413":{"description":"Payload Too Large: plaintext exceeds the configured maximum size after decryption.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Payload too large":{"description":"Payload too large","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload too large","status":413,"detail":"Decrypted plaintext exceeds the maximum allowed size (5 000 000 bytes).","instance":"/api/v3/crypto/decrypt","timestamp":1745120301}}}}}},"404":{"description":"Not Found: specified key does not exist or lacks a private component.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key not found":{"description":"Key not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource not found","status":404,"detail":"Key 'myKeyKid' does not exist or has no private key.","instance":"/api/v3/crypto/decrypt","timestamp":1745120302}}}}}},"409":{"description":"Conflict: the key cannot serve the operation in its current state - revoked or suspended; a composite key whose material version records no usable key-derivation function (<code>invalid-key-state</code>: rotate the material); or a tenant key-protection backend that is not configured.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid key state":{"description":"Invalid key state","value":{"type":"https://docs.ankatech.co/errors/conflict","title":"Conflict","status":409,"detail":"Key 'myKeyKid' is suspended and cannot decrypt.","instance":"/api/v3/crypto/decrypt","timestamp":1745120303}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/decrypt","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}},"Composite material records no usable KDF":{"description":"Composite material records no usable KDF","value":{"type":"https://docs.ankatech.co/errors/invalid-key-state","title":"Invalid key state","status":409,"detail":"Composite key 'my-hybrid-key' material version 2 cannot be used because it records no key-derivation function from [HKDF-SHA256, HKDF-SHA384, HKDF-SHA512, CatKDF, CasKDF]; rotate the material","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403,"extensions":{"code":"INVALID_KEY_STATE"}}}}}}},"422":{"description":"Unprocessable Entity. The JWE is not a valid serialization; or, for a composite key: the protected <code>anka_kdf</code> is absent, not a key-derivation function this platform defines, or not the one the key material records (<code>composite-kdf-mismatch</code> - the material is the only source of the combiner and the header a cross-check, so this is refused before any decapsulation); the recipient count or a per-position <code>alg</code> does not match the key's components; or the AES-GCM tag does not verify (<code>unprocessable-entity</code>, one fixed detail that names no key material and is identical for a substituted encapsulation, a tampered header and a tampered ciphertext). A JWE addressed to several keys is refused with this status BEFORE any authorization or lifecycle check when its shape is one no key accounts for, each with a key-neutral <code>extensions.errorCode</code>: <code>JWE_RECIPIENT_KID_MISSING</code> (a recipient names no key), <code>JOSE_ACTOR_KID_REPEATED</code> (two recipients name one key), <code>JOSE_SYMMETRIC_ACTOR</code> (a symmetric or direct <code>alg</code>), <code>JOSE_ACTOR_LIMIT_EXCEEDED</code> (more recipients than the crypto plane admits), and <code>JWE_SHARED_KID_NOT_COMPOSITE</code> (a <code>kid</code> in <code>protected</code> that does not resolve, in the caller's tenant, to a composite key whose components the recipients match). After the named recipient's grant and lifecycle decisions: <code>JWE_COMPOSITE_RECIPIENT</code> when it is a composite key, and <code>JWE_ALG_KEY_MISMATCH</code> when its unprotected <code>alg</code> is not the key-management algorithm the platform uses for it. An unwrap or tag failure of the named recipient is the same single integrity refusal whatever its cause. A member the JWE does not define is refused with this status where the body is bound, before any key is looked up and on every shape (one key, a composite key, several keys): <code>JWE_ROOT_MEMBER_FORBIDDEN</code> for a root member besides <code>protected</code>, <code>recipients</code>, <code>iv</code>, <code>ciphertext</code> and <code>tag</code> (a JSON <code>aad</code> member and a shared <code>unprotected</code> header included), and <code>JWE_RECIPIENT_MEMBER_FORBIDDEN</code> for a recipient member besides <code>header</code> and <code>encrypted_key</code>. A single-recipient JWE whose <code>alg</code> is in its recipient header alone (the General layout of RFC 7516 section 7.2.1, which a PKCS#7 JSON conversion writes) is refused with this status after the grant and lifecycle decisions and before any key is used: <code>JWE_ALG_KEY_MISMATCH</code> when that <code>alg</code> is not the key-management algorithm the platform uses for the key, and <code>JWE_ALG_NOT_PROTECTED</code> when the key is symmetric or composite (only a JWE addressed to one asymmetric key may carry its <code>alg</code> there).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"An alg outside the protected header, for a symmetric key":{"description":"An alg outside the protected header, for a symmetric key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This operation reads the key-management algorithm of a JWE addressed to a symmetric or composite key from its integrity-protected header, and this JWE carries its alg only in the unprotected per-recipient header (the General JSON layout, RFC 7516 section 7.2.1). Only a JWE addressed to one asymmetric key may carry its alg there.","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_NOT_PROTECTED"}}},"A JSON aad member at the JWE root":{"description":"A JSON aad member at the JWE root","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The JWE carries a member at its root besides 'protected', 'recipients', 'iv', 'ciphertext' and 'tag': this platform reads no JSON 'aad' and no shared 'unprotected' header (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ROOT_MEMBER_FORBIDDEN"}}},"Two recipients of a JWE name one key":{"description":"Two recipients of a JWE name one key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The token names one key in more than one of its entries: every recipient or signature of a multi-key token names a distinct key","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JOSE_ACTOR_KID_REPEATED"}}},"The named recipient is a composite key":{"description":"The named recipient is a composite key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"A recipient of the JWE is a composite key: a composite key is read from and written to its own composite JWE, never as one recipient among several","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_COMPOSITE_RECIPIENT"}}},"Malformed JWE":{"description":"Malformed JWE","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Unprocessable Entity","status":422,"detail":"'jweToken' is not a valid JWE General JSON Serialization object.","instance":"/api/v3/crypto/decrypt","timestamp":1745120304}},"Composite KDF mismatch":{"description":"Composite KDF mismatch","value":{"type":"https://docs.ankatech.co/errors/composite-kdf-mismatch","title":"Composite KDF Mismatch","status":422,"detail":"Composite JWE for key 'my-hybrid-key' declares anka_kdf 'HKDF-SHA256' but the key material records 'CatKDF'","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304}},"Composite ciphertext integrity failure":{"description":"Composite ciphertext integrity failure","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Unable to decrypt: key mismatch or ciphertext integrity failure.","instance":"/api/v3/crypto/decrypt","timestamp":1745120305}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Internal Server Error: unexpected error during decryption.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/decrypt","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/decrypt-verify":{"post":{"tags":[" Secure"],"summary":"Decrypt then verify nested JWE(JWS)","description":"Performs decrypt-then-verify operation: decrypts nested JWE to reveal inner JWS,\nthen verifies the signature and extracts original plaintext.\n\nThe decrypting key is the outer JWE's own key; for a JWE addressed to several keys it is the\nONE recipient `recipientKid` names, decided by one DECRYPT grant and one lifecycle decision\nbefore anything is unwrapped. Every signer of the inner JWS is read from its own\nprotected header (RFC 7515 §7.2.1), passes its VERIFY grant and lifecycle for the caller, and is\nverified under its own key; `signatureResults` reports each one. The plaintext is returned only\nwhen every inner signature verifies, and the decrypting key must be none of the inner signers.\n\nCOMPOSITE keys are supported via JWE/JWS General JSON Serialization format.\n\n**Key pairs this buffered route serves: every one.** Each leg runs with the service of its own\nkey, read at the material version its own header names, exactly as on the streaming\n`/api/v3/crypto/stream/decrypt-verify`: a SIMPLE symmetric, SIMPLE asymmetric or COMPOSITE\ndecryption key with a SIMPLE symmetric, SIMPLE asymmetric or COMPOSITE verification key — for\nexample ML-KEM-768 + HMAC-SHA256, AES-GCM-256 + ML-DSA-65, or COMPOSITE_KEM_COMBINE +\nCOMPOSITE_SIGNATURE. A key of the wrong kind for its leg is refused **400** `invalid-input`, and\nno plaintext is returned.\n\nA token addressed to, or signed by, several keys is verified one signature at a time, each\ndispatched by its own key; the key pairs below are those of a single recipient and a single\ninner signer.\n\nReturns 422 if signature verification fails or token format is invalid.\n","operationId":"decryptThenVerify","requestBody":{"description":"Nested JWE token containing signed JWS payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecryptVerifyRequest"},"examples":{"SIMPLE keys (single-recipient General JSON)":{"description":"SIMPLE keys (single-recipient General JSON)","value":{"jweToken":{"protected":"eyJhbGciOiJNTC1LRU0tMTAyNCIsImVuYyI6IkEyNTZHQ00iLCJjdHkiOiJKV1MiLCJraWQiOiJteS1kZWNyeXB0LWtleSJ9","recipients":[{"header":{"kid":"my-decrypt-key"},"encrypted_key":"EncryptedKey_BASE64URL"}],"iv":"IV_BASE64URL","ciphertext":"InnerJwsCiphertext_BASE64URL","tag":"Tag_BASE64URL"}}},"COMPOSITE hybrid keys (multi-recipient General JSON)":{"description":"COMPOSITE hybrid keys (multi-recipient General JSON)","value":{"jweToken":{"protected":"eyJlbmMiOiJBMjU2R0NNIiwia2lkIjoibXktaHlicmlkLWRlY3J5cHQta2V5IiwidHlwIjoiSk9TRSIsImFua19rdiI6MSwiYW5rYV9rZGYiOiJIS0RGLVNIQTI1NiJ9","recipients":[{"header":{"alg":"X25519"},"encrypted_key":"SyJuz7p1lAVPfQV420q07TZFOokvQ3zYQcrz0MYixF0"},{"header":{"alg":"ML-KEM-768"},"encrypted_key":"nDWQr4L03SsECvjZGtt0ZQu8seqeEHcJZk9Up7X1agoi8LWFZtGDOEU8w2LiDFcrvYoj4XYv1o1jHVAj11ezzEXQPned4qoxtF2o53P_5gnRjJ70Wmvizdiszv_zQwRj8lJiMbjV3SZucB1rxeXf4Py1Y0SdVXbZzSLf_xHcKDP7KyKUFwt5-YRKD2Dcx2enOal3GPhdw0_dhxPb1GXOJ8RXLBiCFF6dZOXM9mFn-VUn30rqazY1uz_i_8aCoaYSiHQ6k6nH9uMhfMORTD69HhTU9yrOXYJtgbd52msJOceGNmA5tzH3vCzqIVVhc5dfNHsgRp7Pen49oolc33kJUHhtOndKJyHI2v1EwBqeI8D7dffhFrZHm8clhTfnjkYiBDO8b2N4fCH4hGL1DlEMcHlUF5i8nWfPEogGsUnRTAvSJeRTyRbeURT58K4HDXj1Ov2ZFbpm1IEad_k-P3HAuib_zZknx9gS9RzcCaptIk1ETreLLTkg4dHUE1SJ2PfSuuxybO-06UbPZxboWYHhLaQ3ZsFhk9W9P9sj6xF6civPGCvgWQA5NF08pMMrGgjrmd2sJgA-u732BwxOWO_W_lsWNB-s5c-vAqiNNAT5wUELNX7CU_8dZVbeIMUnXJxxqR2MF4WW7ea729QrmwwObPISSYAPUR1iVVpwmP0b5PwpLKaEbgUGevW6yYK5WuvEoHpFWRupvsbibZkMO0fVdUcaUUh_12YjyLT2fJiPQVft2QZYxYbQXYqW-He1qZ7rl1clXEXaQGcJYL55sfY22J4yjYX5EeOgpyKr7M8_YM8F6R6Aku85ejlgt0MFkSoEw0x7at1Dde60W0YZK-lDE4DGhwv_e5Pp0Vfe04bwnyuAaBzWm90xrRXfplda6UbBibfw2VBbQx7rT2p0TpL2yqETTuYRj6nmfksUdv4HE_hdzlJMr172yWUHlE3IKkzU6hM-wsFp4Xc4QM5G9PusAQ474BxNeQu4g90aLiHp8iYC4EjBSABRrTZuNb67yvfnogG0iUayHL_b96QFuWx46teASs2Iae2gR18kS0tO2-aGE0GMZLSt1JKyn3acCpsWB01PLJq9Kojf056KywjzRtoxsKaEmnIWQjAqnR9j4GPGy-ipsRFnZNfWfkfSaCm6_cknl2CxDRXv0xh5di0opKk-6-cDa1cSpg6r9lOXBBt_JeN3Y1LMcEclrQgp4IDogOF3jku9hZnerk-S6r8FlrbdNzlF2VqiTgInUordo_v1Dm0chtfMn4Mj94HV504lP13zv_OfUjqbtlsIOARYias-EXYvGwDQzc3VDgMIDQkUHUXqWi9IYCSUEiNkSL4gKnDjjYEOXJ-r22QpPLnzOqNgitqLtlmN6LsrjdcLMf2dfBqlK2YixCN_gh9-S4rRF0gGAsFJQL2h4OzvSSQoEKmqQUSb68qX-ZKI2L1bvTE"}],"iv":"HkhZtfl5YrzWnNNC","ciphertext":"IQ_So8jdWxyak_HSMcVJVPsRCXuMMTTPZwCxh4ahyBEvqMN-1kNMuChHV-k1-96WwL8kXf7JbVtUbt_3IgrmW16fTC9ZZ9wOvP2fKPp9POlibIuR8TF7UNu1rwT38_YN5DSSjwnqu9YapwOkRjueQHKIVk-LQUpaT9ndEaQZfvn7D9cF3nnGg7_Kj79znzmzF-7HxXbgkOB0AGPKuo3d9Zg-jg9jIpTHdo6DFdRe54HQEvPDH1OiH0g1-pbSIBl93fpDo-021FKgP6Y5kipSYP0BmfXLgkfORhR1kvLF48hXq53COAUMjFFBMTkbqlvJSWFFkASXhaCm0Haj4DYaVUaOLdXFGW3Vk3x2Hvn2p-usUdAYa6QQSpN984bqnuEiSxBgM_wKVpiWgrWR4aYPV2eu3cAS8BDBQ9aKEWJMh1vNrlAwq8GyPAe-61auFzlyEMiPuKi6","tag":"dQpPYl3LKEmyuf3h-G60FA"}}}}}},"required":true},"responses":{"401":{"description":"Unauthorized (`https://docs.ankatech.co/errors/unauthorized`): the request carries no bearer token, or the token is expired, not yet valid, badly signed, or issued for another issuer or audience. Answered by the authentication entry point before the operation runs, with a `WWW-Authenticate: Bearer` challenge.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Missing or expired access token":{"description":"Missing or expired access token","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to access this resource.","instance":"/api/v3/crypto/decrypt-verify","correlationId":"3f2b8c1e-6d4a-4b7f-9e2c-5a1d8f0b7c64","timestamp":1745120400,"extensions":{"code":"UNAUTHENTICATED"}}}}}}},"403":{"description":"Forbidden (`https://docs.ankatech.co/errors/forbidden`): the access token does not carry the `secure.decryptAndVerify` scope; or the Capability Grant resolver refused the call - the token carries no Cryptographic Actor subject, or the calling Actor holds no active Capability Grant for DECRYPT on the decryption key and VERIFY on every signer of the inner JWS, or a grant constraint is violated, or its exchange context is suspended; or the tenant or the calling Actor is not in a state that permits cryptographic operations; or a key-lifecycle decision refuses one of those keys. The detail names no key. The decrypting key is decided before any key material is used; the inner signers once the outer JWE is opened, before any signature is checked and with no plaintext returned.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Calling Actor holds no Capability Grant for the key":{"description":"Calling Actor holds no Capability Grant for the key","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Operation denied by Capability Grant resolver: No Capability Grants projected for this actor","instance":"/api/v3/crypto/decrypt-verify","timestamp":1745120400}}}}}},"409":{"description":"Conflict, for one of two causes told apart by the problem type. <code>invalid-key-state</code>: the decryption key or an inner signer's key is not in a lifecycle state that permits the operation (for example, revoked). <code>key-protection-backend-not-configured</code>: the tenant's key-protection backend was never declared, or was declared but not yet bound; durable, so complete the setup rather than retrying, and no Retry-After is sent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key revoked":{"description":"Key revoked","value":{"type":"https://docs.ankatech.co/errors/invalid-key-state","title":"Invalid Key State","status":409,"detail":"The key with the specified kid is already revoked and cannot be used.","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}},"Key-protection backend not configured":{"description":"Key-protection backend not configured","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-not-configured","title":"Key protection backend not configured","status":409,"detail":"The key protection backend is not configured. Complete the setup before retrying.","instance":"/api/v3/crypto/decrypt-verify","setupState":"KEY_BACKEND_UNCONFIGURED","timestamp":1745120403}}}}}},"503":{"description":"Service Unavailable: the tenant's key-protection backend is bound but not currently usable. Two shapes, told apart by the problem type: <code>key-protection-backend-unavailable</code> is transient (a backend or store fault) and carries <code>Retry-After</code>; <code>key-protection-backend-misconfigured</code> is durable (a broken backend credential, or the platform's signed record of the tenant's key-encryption key is present but cannot be trusted) and carries no <code>Retry-After</code> - retrying re-presents the same fault. The body is oracle-safe and names no backend, token, slot, PIN or vendor. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed. It is also answered as <code>tenant-in-maintenance</code>, with <code>Retry-After</code>, while the caller's tenant is temporarily unavailable for cryptographic and key operations: nothing is executed, and the same request can be sent again once the <code>Retry-After</code> interval has passed. The body states that condition only; it names no reason and no end time.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key-protection backend transiently unavailable":{"description":"Key-protection backend transiently unavailable","value":{"type":"https://docs.ankatech.co/errors/key-protection-backend-unavailable","title":"Key protection backend unavailable","status":503,"detail":"The key protection backend is temporarily unavailable. Please retry.","instance":"/api/v3/crypto/decrypt-verify","timestamp":1745120403}},"Key-use counter database unreachable":{"description":"Key-use counter database unreachable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/crypto/decrypt-verify"}},"Tenant in maintenance":{"description":"Tenant in maintenance","value":{"type":"https://docs.ankatech.co/errors/tenant-in-maintenance","title":"Tenant in maintenance","status":503,"detail":"The tenant is temporarily unavailable for cryptographic operations. Retry later.","instance":"/api/v3/crypto/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120403}}}}}},"200":{"description":"Decrypt-then-verify operation successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecryptVerifyResponse"},"examples":{"SIMPLE keys response":{"description":"SIMPLE keys response","value":{"data":"SGVsbG8gQW5rYQ==","decryptKeyRequested":"myDecryptKey","decryptMaterialVersion":2,"decryptAlgorithmUsed":"ML-KEM-1024","decryptWarnings":[],"signatureResults":[{"kid":"myVerifyKey","algorithm":"ML-DSA-87","materialVersion":3,"verified":true}]}},"Recipient 1 of 2 named, two inner signers":{"description":"Recipient 1 of 2 named, two inner signers","value":{"data":"U2V0dGxlbWVudCBRMw==","decryptKeyRequested":"acme-legal-rsa","decryptMaterialVersion":1,"decryptAlgorithmUsed":"RSA-3072","decryptWarnings":[],"signatureResults":[{"kid":"acme-treasury-signer","algorithm":"ML-DSA-65","materialVersion":2,"verified":true},{"kid":"acme-controller-signer","algorithm":"EC-P256","materialVersion":4,"verified":true}]}},"COMPOSITE keys response":{"description":"COMPOSITE keys response","value":{"data":"SGVsbG8gQW5rYQ==","decryptKeyRequested":"composite-decrypt","decryptMaterialVersion":1,"decryptAlgorithmUsed":"X25519+ML-KEM-768","decryptWarnings":[],"signatureResults":[{"kid":"composite-verify","algorithm":"Ed25519+ML-DSA-65","materialVersion":1,"verified":true}]}}}}}},"400":{"description":"Bad Request:\n• Malformed token, or key type mismatch, OR\n• `recipientKid` absent for a JWE addressed to several keys, naming none of its recipients, given twice, or not 1 to 255 characters of letters, digits and `. _ -` (the detail names the member and no key, and no grant or lifecycle decision is taken); for a single-recipient or composite JWE, a `recipientKid` naming another key than the JWE's, OR\n• For a single-recipient JWE, a header parameter name present both in `protected` and in its recipient `header`, whatever the two values (RFC 7516 section 7.2.1), or no `alg` in either, OR\n• For a JWE addressed to several keys, a member name present both in `protected` and in a recipient header (RFC 7516 section 7.2.1), or a recipient `kid` that is not 1 to 255 characters of letters, digits and `. _ -` (the detail does not echo it), OR\n• The decrypting key is also a signer of the inner JWS, OR\n• In the outer JWE, a 'crit' in 'protected' or any 'crit' in a 'recipients[i].header', or an explicit \"encrypted_key\": null; in the inner JWS, a 'crit' in its protected header or any 'crit' in an unprotected header. This route understands no critical extension, and 'crit' MUST be integrity protected (RFC 7515 section 4.1.11, RFC 7516 section 4.1.13), OR\n• Key does not support the requested operation (decrypt or verify)\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JWE token missing":{"description":"JWE token missing","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"JWE token is missing.","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}},"Decrypting recipient is also an inner signer":{"description":"Decrypting recipient is also an inner signer","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"The key the outer JWE is decrypted with must be none of the keys that signed the inner JWS.","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}},"Key type mismatch (symmetric vs asymmetric)":{"description":"Key type mismatch (symmetric vs asymmetric)","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"The decryptKid and verifyKid must both be symmetric or both be asymmetric. Mixing key types is not permitted.","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}},"Key does not support operation":{"description":"Key does not support operation","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key 'encrypt-only-key-001' does not support operation 'decrypt'","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800101}}}}}},"404":{"description":"Not Found: key does not exist","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key not found":{"description":"Key not found","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"Key not found with kid: myMissingKey","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}}}}}},"413":{"description":"Payload Too Large: exceeds the configured maximum size (see GET /api/v3/crypto/limits)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Nested token too large":{"description":"Nested token too large","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Encrypted data size exceeds the configured maximum limit.","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}}}}}},"422":{"description":"Unprocessable Entity: missing kid in header, an inner signature that does not verify (no plaintext is released; every signature is judged under its own key), or a malformed token. A JWE addressed to several keys whose shape is refused (<code>JWE_RECIPIENT_KID_MISSING</code>, <code>JOSE_ACTOR_KID_REPEATED</code>, <code>JOSE_SYMMETRIC_ACTOR</code>, <code>JOSE_ACTOR_LIMIT_EXCEEDED</code>) is refused before any authorization or lifecycle check, and a composite key named as one recipient among several (<code>JWE_COMPOSITE_RECIPIENT</code>) after its gates; the inner JWS is classified the same way once it is revealed (<code>JWS_ROOT_MEMBER_FORBIDDEN</code> as well). A member the JWE does not define is refused with this status where the body is bound, before any key is looked up and on every shape (one key, a composite key, several keys): <code>JWE_ROOT_MEMBER_FORBIDDEN</code> for a root member besides <code>protected</code>, <code>recipients</code>, <code>iv</code>, <code>ciphertext</code> and <code>tag</code> (a JSON <code>aad</code> member and a shared <code>unprotected</code> header included), and <code>JWE_RECIPIENT_MEMBER_FORBIDDEN</code> for a recipient member besides <code>header</code> and <code>encrypted_key</code>. A single-recipient JWE whose <code>alg</code> is in its recipient header alone (the General layout of RFC 7516 section 7.2.1, which a PKCS#7 JSON conversion writes) is refused with this status after the grant and lifecycle decisions and before any key is used: <code>JWE_ALG_KEY_MISMATCH</code> when that <code>alg</code> is not the key-management algorithm the platform uses for the key, and <code>JWE_ALG_NOT_PROTECTED</code> when the key is symmetric or composite (only a JWE addressed to one asymmetric key may carry its <code>alg</code> there).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"An alg outside the protected header, for a symmetric key":{"description":"An alg outside the protected header, for a symmetric key","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"This operation reads the key-management algorithm of a JWE addressed to a symmetric or composite key from its integrity-protected header, and this JWE carries its alg only in the unprotected per-recipient header (the General JSON layout, RFC 7516 section 7.2.1). Only a JWE addressed to one asymmetric key may carry its alg there.","instance":"/api/v3/crypto/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ALG_NOT_PROTECTED"}}},"A JSON aad member at the JWE root":{"description":"A JSON aad member at the JWE root","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"The JWE carries a member at its root besides 'protected', 'recipients', 'iv', 'ciphertext' and 'tag': this platform reads no JSON 'aad' and no shared 'unprotected' header (RFC 7516 §7.2.1)","instance":"/api/v3/crypto/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745120304,"extensions":{"errorCode":"JWE_ROOT_MEMBER_FORBIDDEN"}}},"Missing JWE header kid":{"description":"Missing JWE header kid","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":422,"detail":"JWE header does not contain 'kid' claim","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}},"Missing JWS header kid":{"description":"Missing JWS header kid","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":422,"detail":"JWS header does not contain 'kid' claim","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}},"Signature verification failed":{"description":"Signature verification failed","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"Signature verification failed","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}}}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Unexpected error":{"description":"Unexpected error","value":{"type":"https://docs.ankatech.co/errors/internal","title":"Internal Server Error","status":500,"detail":"Failed to perform decrypt-then-verify operation.","instance":"/api/v3/crypto/decrypt-verify","timestamp":1734800100}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/decrypt-verify","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/license/metrics":{"get":{"tags":["License Metrics"],"summary":"Get license validation metrics","description":"Returns license validation counters, including the percentage of requests validated strictly vs allowed without validation. Useful for monitoring service availability vs enforcement tradeoff. The figures are those of the instance of this service that answered this request; they are not aggregated across instances, so two calls may return the counters of different instances. Only `unvalidatedRequestsAllowed` has a deployment-wide counterpart: the per-instance meter `ankasecure.license.grace{outcome=admitted}`, summed across instances. `strictlyValidatedRequests`, `validationRate`, `staleVerdictsServed` and `maxStalenessRefusals` are per instance, and no meter sums them.","operationId":"getMetrics","responses":{"200":{"description":"Metrics retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicenseValidationMetrics"}}}},"401":{"description":"Unauthorized - valid JWT required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LicenseValidationMetrics"}}}},"403":{"description":"Forbidden - the token does not carry the license.metrics scope","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LicenseValidationMetrics"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/license/metrics","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/key-management/supported-algorithms":{"get":{"tags":[" Algorithm Catalog"],"summary":"Get supported algorithms (crypto-agility)","description":"Returns the algorithm catalogue with full metadata for crypto-agility decisions.\nSupports optional server-side filtering via query parameters.\n\n## Query Parameters (all optional)\n\n| Parameter | Type | Logic | Description |\n|-----------|------|-------|-------------|\n| `category` | string | = | CLASSICAL, POST_QUANTUM, or HYBRID |\n| `status` | string | = | RECOMMENDED, EXPERIMENTAL, or LEGACY |\n| `minSecurityLevel` | int | >= | Minimum NIST level (1, 3, 5) |\n| `maxSecurityLevel` | int | <= | Maximum NIST level (1, 3, 5) |\n| `keyOps` | array | AND | Required operations (must support ALL) |\n| `standards` | array | AND | Required standards (must have ALL) |\n| `kty` | array | OR | Published key types (matches ANY, including COMPOSITE). An unpublished token is refused with 400 |\n| `alg` | array | OR | Algorithms (matches ANY) |\n\n## Response fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `kty` | string | Key type, one of the published tokens: ML-KEM, HQC, FRODO, CMCE, BIKE, SABER, NTRU, NTRUPRIME, SMAUG-T, ML-DSA, SLH-DSA, FALCON, XMSS, LMS, EC, RSA, oct, OKP, SM2, GOST-EC (simple), or COMPOSITE (every hybrid pairing) |\n| `compositeMode` | string | How a hybrid entry's components are combined. Present only on composite entries |\n| `alg` | string | Algorithm identifier: ML-KEM-768, RSA-4096 (simple) or X25519+ML-KEM-768 (composite) |\n| `keyOps` | array | Permitted operations (sign, verify, encrypt, decrypt), in RFC 7517 §4.3 order |\n| `status` | string | RECOMMENDED, EXPERIMENTAL, or LEGACY |\n| `sunsetDate` | date | Planned deprecation date (ISO 8601, optional) |\n| `advisory` | string | Migration or security guidance (optional) |\n| `securityLevel` | integer | NIST security level (1-5) |\n| `standards` | array | Endorsing bodies, in the order of the single platform vocabulary: NIST, ETSI, NSA, ISO, BSI, ANSSI, ENISA, IETF, KISA, CRYPTREC, GMT, MYSEAL, NCA_NCS, GOST |\n| `category` | string | CLASSICAL, POST_QUANTUM, or HYBRID |\n\n## Composite algorithm entries\n\nComposite algorithms appear as first-class entries with:\n- `category: \"HYBRID\"`\n- `kty`: the single coarse token `COMPOSITE`, for every hybrid pairing\n- `compositeMode`: how the components are combined — the construction, on its own axis\n- `alg`: Combined identifier (e.g., \"X25519+ML-KEM-768\", \"Ed25519+ML-DSA-65\")\n\nUse `category=HYBRID` to filter composite entries only.\n\n## Example queries\n\n```\n# Post-quantum algorithms only\nGET /api/v3/key-management/supported-algorithms?category=POST_QUANTUM\n\n# Hybrid/composite algorithms only\nGET /api/v3/key-management/supported-algorithms?category=HYBRID\n\n# High security, recommended algorithms\nGET /api/v3/key-management/supported-algorithms?minSecurityLevel=3&status=RECOMMENDED\n\n# Signature algorithms attributed to NIST\nGET /api/v3/key-management/supported-algorithms?keyOps=sign,verify&standards=NIST\n\n# Find specific composite algorithm\nGET /api/v3/key-management/supported-algorithms?alg=X25519+ML-KEM-768\n```\n","operationId":"getSupportedAlgorithms","parameters":[{"name":"category","in":"query","description":"Filter by category: CLASSICAL, POST_QUANTUM, or HYBRID","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by status: RECOMMENDED, EXPERIMENTAL, or LEGACY","required":false,"schema":{"type":"string"}},{"name":"minSecurityLevel","in":"query","description":"Minimum NIST security level (1, 3, or 5)","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"maxSecurityLevel","in":"query","description":"Maximum NIST security level (1, 3, or 5)","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"keyOps","in":"query","description":"Required operations (comma-separated). Must support ALL specified.","required":false,"schema":{"type":"array","items":{"type":"string"},"uniqueItems":true}},{"name":"standards","in":"query","description":"Required standards (comma-separated). Must have ALL specified.","required":false,"schema":{"type":"array","items":{"type":"string"},"uniqueItems":true}},{"name":"kty","in":"query","description":"Key types (comma-separated, OR logic). Matches ANY specified. Use COMPOSITE for hybrid algorithms — a composite entry's construction is selected with compositeMode, not here. An unpublished token is a 400 that names the accepted set, never a silently empty page.","required":false,"schema":{"type":"array","items":{"type":"string","description":"Published key-type vocabulary. A composite key publishes the coarse COMPOSITE token; its construction is carried by the compositeMode member and its exact pairing by alg.\n","enum":["ML-KEM","HQC","FRODO","CMCE","BIKE","SABER","NTRU","NTRUPRIME","SMAUG-T","ML-DSA","SLH-DSA","FALCON","XMSS","LMS","EC","RSA","oct","OKP","SM2","GOST-EC","COMPOSITE"]},"uniqueItems":true}},{"name":"alg","in":"query","description":"Algorithms (comma-separated, OR logic). Matches ANY specified.","required":false,"schema":{"type":"array","items":{"type":"string"},"uniqueItems":true}},{"name":"compositeMode","in":"query","description":"Composite construction modes (comma-separated, OR logic): COMPOSITE_KEM_COMBINE or COMPOSITE_SIGNATURE. Selects among hybrid algorithms, which all publish kty=COMPOSITE. Simple algorithms never match. An unpublished construction is a 400 that names the accepted set, never a silently empty page.","required":false,"schema":{"type":"array","items":{"type":"string","enum":["COMPOSITE_KEM_COMBINE","COMPOSITE_SIGNATURE"]},"uniqueItems":true}}],"responses":{"200":{"description":"Supported algorithms list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SupportedAlgorithm"}},"examples":{"Full algorithm catalogue (excerpt)":{"summary":"Shows simple and hybrid algorithms with their metadata","description":"Full algorithm catalogue (excerpt)","value":[{"kty":"COMPOSITE","compositeMode":"COMPOSITE_KEM_COMBINE","alg":"X25519+ML-KEM-768","keyOps":["encrypt","decrypt"],"status":"RECOMMENDED","securityLevel":1,"standards":["NIST","ETSI"],"category":"HYBRID"},{"kty":"COMPOSITE","compositeMode":"COMPOSITE_SIGNATURE","alg":"Ed25519+ML-DSA-65","keyOps":["sign","verify"],"status":"RECOMMENDED","securityLevel":1,"standards":["NIST","ETSI"],"category":"HYBRID"},{"kty":"ML-KEM","alg":"ML-KEM-768","keyOps":["encrypt","decrypt"],"status":"RECOMMENDED","securityLevel":3,"standards":["NIST","ETSI","BSI","ANSSI","ENISA","IETF","CRYPTREC"],"category":"POST_QUANTUM"},{"kty":"ML-DSA","alg":"ML-DSA-65","keyOps":["sign","verify"],"status":"RECOMMENDED","securityLevel":3,"standards":["NIST","ETSI","BSI","ANSSI","ENISA","IETF","CRYPTREC"],"category":"POST_QUANTUM"},{"kty":"OKP","alg":"Ed25519","keyOps":["sign","verify"],"status":"RECOMMENDED","securityLevel":1,"standards":["NIST","ETSI","ISO","IETF","CRYPTREC"],"category":"CLASSICAL"},{"kty":"oct","alg":"AES-GCM-256","keyOps":["encrypt","decrypt"],"status":"RECOMMENDED","securityLevel":5,"standards":["NIST","ETSI","NSA","ISO","BSI","ANSSI","ENISA","IETF","CRYPTREC","MYSEAL","NCA_NCS"],"category":"CLASSICAL"},{"kty":"RSA","alg":"RSA-2048","keyOps":["sign","verify","encrypt","decrypt"],"status":"RECOMMENDED","sunsetDate":"2030-12-31","advisory":"Consider migrating to RSA-3072.","securityLevel":1,"standards":["NIST","ISO","IETF","CRYPTREC","MYSEAL"],"category":"CLASSICAL"},{"kty":"CMCE","alg":"CMCE-460896","keyOps":["encrypt","decrypt"],"status":"LEGACY","advisory":"Standardized in ISO/IEC 18033-2:2006/Amd 2:2026. In 2026 BSI advised that Classic McEliece should no longer be used for new developments or in the planning of new cryptographic applications; results so far allow no practical attack on the parameter sets of BSI TR-02102-1, and existing use is not required to migrate. This parameter set may not fully meet the Category 3 security target.","securityLevel":3,"standards":["ISO"],"category":"POST_QUANTUM"},{"kty":"HQC","alg":"HQC-128","keyOps":["encrypt","decrypt"],"status":"RECOMMENDED","advisory":"NIST selected HQC for standardization in March 2025, but its FIPS (FIPS 207) is not yet published, neither final nor draft; the NIST attribution reflects that selection. ETSI standardization of HQC is IN PROGRESS (draft expected 2026, finalization targeted 2027); the ETSI attribution reflects that workstream, not a published deliverable.","securityLevel":1,"standards":["NIST","ETSI"],"category":"POST_QUANTUM"}]},"Hybrid composite algorithm":{"summary":"HYBRID algorithm combining classical and PQC components","description":"Hybrid composite algorithm","value":{"kty":"COMPOSITE","compositeMode":"COMPOSITE_KEM_COMBINE","alg":"P-384+ML-KEM-1024","keyOps":["encrypt","decrypt"],"status":"RECOMMENDED","securityLevel":3,"standards":["NIST","ETSI","ANSSI","ENISA"],"category":"HYBRID"}},"Simple PQC algorithm":{"summary":"Post-quantum ML-KEM-768 - simple (non-composite) entry","description":"Simple PQC algorithm","value":{"kty":"ML-KEM","alg":"ML-KEM-768","keyOps":["encrypt","decrypt"],"status":"RECOMMENDED","securityLevel":3,"standards":["NIST","ETSI","BSI","ANSSI","ENISA","IETF","CRYPTREC"],"category":"POST_QUANTUM"}}}}}},"400":{"description":"Invalid input: malformed request parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Bad query parameter":{"description":"Bad query parameter","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid input","status":400,"detail":"Parameter 'status' must be one of RECOMMENDED|EXPERIMENTAL|LEGACY.","instance":"/api/v3/key-management/supported-algorithms","timestamp":1745120400}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Forbidden: authenticated user lacks the required scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Internal Server Error: unexpected error while retrieving algorithms.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the answer is withheld. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed.","headers":{"Retry-After":{"description":"Integer seconds after which new requests are admitted again.","style":"simple","schema":{"type":"integer","example":30}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Audit record of the operation could not be confirmed":{"description":"Audit record of the operation could not be confirmed","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/key-management/supported-algorithms","correlationId":"0f6d2b1a-3c4e-4d5f-8a9b-1c2d3e4f5a6b"}}}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/key-management/supported-algorithms","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/key-management/keys":{"get":{"tags":[" Key Query"],"summary":"List keys (paginated, no secret data).","description":" Retrieves ONE PAGE of the tenant's keys. Secret fields (e.g. privateKey)\n are omitted. Filters (origin/restricted/kty/status) apply server-side\n BEFORE pagination: totalElements is the filtered count. size is capped\n at 200 (400 on violation, never clamped). A page beyond the last page\n yields an empty content array with the true totals (HTTP 200).\n\n **Population.** Without a status filter the page holds every key the\n tenant still holds, in whatever lifecycle state it is in: active,\n expired, suspended, revoked, archived and pending_destruction alike, each\n with its own status. A destroyed key is not among them. It is returned\n only when status=deleted_logical is requested, as a Deleted-view row\n carrying kid, uuid, status, createdAt and deletedAt, and no algorithm,\n key type, key operations, usage, origin or exportability.\n\n **Telling rows apart.** A kid can be taken again after its key is\n destroyed, so the Deleted view can return a row sharing its kid with a\n live key, or several rows sharing one kid. uuid is the key's own identity,\n never reused, and is the member that tells such rows apart.\n ","operationId":"listAllKeys","parameters":[{"name":"origin","in":"query","description":"Filter by key origin, using the SAME token a key's `origin` field carries in the response. Absent or empty means no filter. Any other value is rejected with 400.","required":false,"schema":{"type":"string","enum":["GENERATED","IMPORTED"]}},{"name":"restricted","in":"query","description":"Filter by restriction status","required":false,"schema":{"type":"boolean"}},{"name":"kty","in":"query","description":"Filter by key type, using the SAME token a key's `kty` field carries in the response: `ML-KEM`, `ML-DSA`, `SLH-DSA`, `FALCON`, `XMSS`, `LMS`, `HQC`, `FRODO`, `CMCE`, `BIKE`, `SABER`, `NTRU`, `NTRUPRIME`, `SMAUG-T`, `RSA`, `EC`, `OKP`, `oct`, `SM2`, `GOST-EC`, and `COMPOSITE` for every hybrid pairing. A composite key publishes one coarse token; its pairing is named by `alg` and its construction by `compositeMode`. Absent or empty means no filter. Any other value is rejected with 400.","required":false,"schema":{"type":"string","description":"Published key-type vocabulary. A composite key publishes the coarse COMPOSITE token; its construction is carried by the compositeMode member and its exact pairing by alg.\n","enum":["ML-KEM","HQC","FRODO","CMCE","BIKE","SABER","NTRU","NTRUPRIME","SMAUG-T","ML-DSA","SLH-DSA","FALCON","XMSS","LMS","EC","RSA","oct","OKP","SM2","GOST-EC","COMPOSITE"]}},{"name":"status","in":"query","description":"Filter by key lifecycle status, using the SAME token a key's `status` field carries in the response: `active`, `pending_rotation`, `rotated`, `expired`, `revoked`, `archived`, `deleted_logical`, `suspended`, `pending_destruction`. Absent or empty means no filter. Any other value is rejected with 400.","required":false,"schema":{"type":"string","enum":["active","pending_rotation","rotated","expired","revoked","archived","deleted_logical","suspended","pending_destruction"]}},{"name":"page","in":"query","description":"Zero-based page index.","required":false,"schema":{"type":"integer","format":"int32","default":0,"minimum":0},"example":0},{"name":"size","in":"query","description":"Page size (1..200). Values above 200 are REJECTED with 400, never clamped.","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":200,"minimum":1},"example":50}],"responses":{"200":{"description":"Page of keys listed successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyPageResponse"},"examples":{"Default view: an active and a revoked key":{"description":"Default view: an active and a revoked key","value":{"content":[{"kid":"payments-signing-key","uuid":"0b7e3c1a-5d2f-4e8a-9c61-3f2b7a9d0e14","type":"SIMPLE","kty":"ML-DSA","alg":"ML-DSA-65","keyOps":["sign","verify"],"exportable":true,"origin":"GENERATED","restricted":false,"status":"active","createdAt":"2026-03-02T09:15:00Z","expiresAt":"2028-03-02T09:15:00Z","usageCount":1243,"softUsageLimit":0,"maxUsageLimit":0},{"kid":"legacy-billing-key","uuid":"7d1f9a42-2c6b-4b0e-8f35-91a4c2e7b608","type":"SIMPLE","kty":"ML-KEM","alg":"ML-KEM-768","keyOps":["encrypt","decrypt"],"exportable":false,"origin":"IMPORTED","restricted":false,"status":"revoked","createdAt":"2025-11-18T08:00:00Z","expiresAt":"2027-11-18T08:00:00Z","usageCount":88,"softUsageLimit":0,"maxUsageLimit":0}],"totalElements":2,"totalPages":1,"number":0}},"Deleted view: two destroyed keys that held the same kid":{"description":"Deleted view: two destroyed keys that held the same kid","value":{"content":[{"kid":"legacy-billing-key","uuid":"e9b2d6f0-8a3c-4f71-b5d4-2c7e1a9f3b58","status":"deleted_logical","createdAt":"2024-02-10T10:00:00Z","deletedAt":"2025-08-14T15:22:09Z"},{"kid":"legacy-billing-key","uuid":"3c5a7e91-0d2b-4f68-a1c4-9e7b2d5f8a06","status":"deleted_logical","createdAt":"2025-09-01T12:00:00Z","deletedAt":"2025-11-17T16:40:00Z"}],"totalElements":2,"totalPages":1,"number":0}}}}}},"400":{"description":"Invalid input: a query parameter this endpoint cannot bind. The causes, in the order a caller meets them:\n<ul>\n  <li><code>status</code> is not one of the nine published key-status       tokens (the <code>status</code> field's own vocabulary). An       absent or empty value is NOT an error — it means \"no filter\";</li>\n  <li><code>origin</code> is not one of the published key-origin       tokens;</li>\n  <li><code>kty</code> is not one of the twenty-one published key-type       tokens (the <code>kty</code> field's own vocabulary). An absent       or empty value means \"no filter\";</li>\n  <li><code>page</code> is negative, or <code>size</code> is &lt; 1 or       &gt; 200 (rejected, never clamped), or either is non-numeric.</li>\n</ul>\nEvery one of these is decided BEFORE any upstream call, and the <code>detail</code> never echoes the value that was rejected. The one value a refusal may name is a token that is an EXACT member of a server-owned vocabulary — a composite construction sent where a key type belongs — because naming it repeats the platform's own constant rather than caller input, and tells the caller which member actually carries it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Unresolvable status filter":{"description":"Unresolvable status filter","value":{"type":"https://docs.ankatech.co/errors/validation","title":"Validation Error","status":400,"detail":"Parameter 'status' must be one of: active, pending_rotation, rotated, expired, revoked, archived, deleted_logical, suspended, pending_destruction.","instance":"/api/v3/key-management/keys","correlationId":"9f1c2b7e-4c6a-4a1d-9a3e-2b8f0d7c5a11","timestamp":1745119900}},"Composite mode sent as a key type":{"description":"Composite mode sent as a key type","value":{"type":"https://docs.ankatech.co/errors/validation","title":"Validation Error","status":400,"detail":"COMPOSITE_KEM_COMBINE is a composite mode, not a key type; a composite key publishes kty=COMPOSITE and carries its construction in the compositeMode member. Parameter 'kty' must be one of: ML-KEM, HQC, FRODO, CMCE, BIKE, SABER, NTRU, NTRUPRIME, SMAUG-T, ML-DSA, SLH-DSA, FALCON, XMSS, LMS, EC, RSA, oct, OKP, SM2, GOST-EC, COMPOSITE.","instance":"/api/v3/key-management/keys","correlationId":"9f1c2b7e-4c6a-4a1d-9a3e-2b8f0d7c5a11","timestamp":1745119900}},"Page size above the 200 cap":{"description":"Page size above the 200 cap","value":{"type":"https://docs.ankatech.co/errors/validation","title":"Validation Error","status":400,"detail":"size must be <= 200","instance":"/api/v3/key-management/keys","correlationId":"9f1c2b7e-4c6a-4a1d-9a3e-2b8f0d7c5a11","timestamp":1745119900}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Bearer token missing":{"description":"Bearer token missing","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication credentials were not provided.","instance":"/api/v3/key-management/keys","timestamp":1745119900}}}}}},"403":{"description":"Forbidden: authenticated user lacks the required scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Resource not found: keystore unavailable or missing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large: response would exceed server limits.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Internal Server Error: unexpected error while listing keys.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Service Unavailable: the answer is withheld. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed.","headers":{"Retry-After":{"description":"Integer seconds after which new requests are admitted again.","style":"simple","schema":{"type":"integer","example":30}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Audit record of the operation could not be confirmed":{"description":"Audit record of the operation could not be confirmed","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/key-management/keys","correlationId":"0f6d2b1a-3c4e-4d5f-8a9b-1c2d3e4f5a6b"}}}}}},"502":{"description":"Bad Gateway: the key administration service is unavailable\n(connection failure, upstream 5xx, or circuit breaker open), it\nrefused this service's own credential (an upstream 401 or 403 is\nnever presented as the caller's), or it answered with a key record\nthat breaks its contract (a record without a status, or a key other\nthan a destroyed one without origin or exportability); the problem\ndetail says which it was. One such record refuses the whole page.\n","headers":{"Retry-After":{"description":"Suggested wait in integer seconds; present ONLY while the circuit breaker toward the administration service is open.","style":"simple","schema":{"type":"integer","example":23}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"504":{"description":"Gateway Timeout: the key administration service did not respond\nwithin the per-operation read deadline.\n","headers":{"Retry-After":{"description":"Suggested wait in integer seconds; present ONLY while the circuit breaker toward the administration service is open.","style":"simple","schema":{"type":"integer","example":23}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/key-management/keys","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/key-management/keys/{kid}/metadata":{"get":{"tags":[" Key Query"],"summary":"Retrieve key metadata (no cryptographic material)","description":"Returns comprehensive metadata for a single key including:\n- Lifecycle information (status, created/expires timestamps)\n- Usage tracking (usage count, limits)\n- Algorithm details (kty, alg)\n- Capability flags (exportable, origin, restricted, keyOps)\n\nThis endpoint is suitable for administrative dashboards, key inventory\nsystems, and compliance audits where cryptographic material exposure\nis unnecessary.\n\n**Note**: To retrieve the public key material, use the administration API's export endpoint\nGET /api/v3/admin/tenants/{tenantId}/keys/{kid}/export which enforces exportable flag validation.\n","operationId":"getKeyMetadata","parameters":[{"name":"kid","in":"path","description":"Key identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Key metadata retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeySummaryResponse"},"examples":{"ML-KEM-1024 key (exportable)":{"description":"ML-KEM-1024 key (exportable)","value":{"type":"SIMPLE","kid":"myKemKey","kty":"ML-KEM","alg":"ML-KEM-1024","keyOps":["encrypt","decrypt"],"exportable":true,"origin":"GENERATED","status":"active","createdAt":"2025-01-15T10:20:00Z","expiresAt":"2030-01-15T10:20:00Z","softLimitExpiration":"2029-07-15T10:20:00Z","usageCount":1243,"softUsageLimit":900000,"maxUsageLimit":1000000}},"RSA-2048 key (non-exportable)":{"description":"RSA-2048 key (non-exportable)","value":{"type":"SIMPLE","kid":"myRsaKey","kty":"RSA","alg":"RSA-2048","keyOps":["sign","verify"],"exportable":false,"origin":"GENERATED","status":"active","createdAt":"2024-06-01T08:15:30Z","expiresAt":"2029-06-01T08:15:30Z","softLimitExpiration":"2028-12-01T08:15:30Z","usageCount":45032,"softUsageLimit":80000,"maxUsageLimit":100000}},"AES-GCM-256 symmetric key":{"description":"AES-GCM-256 symmetric key","value":{"type":"SIMPLE","kid":"myAesKey","kty":"oct","alg":"AES-GCM-256","keyOps":["encrypt","decrypt"],"exportable":false,"origin":"GENERATED","status":"active","createdAt":"2025-01-10T12:00:00Z","expiresAt":null,"softLimitExpiration":null,"usageCount":892,"maxUsageLimit":0}},"4. COMPOSITE (X25519+ML-KEM-768, kty=COMPOSITE)":{"description":"4. COMPOSITE (X25519+ML-KEM-768, kty=COMPOSITE)","value":{"type":"COMPOSITE","kid":"myHybridKemKey","kty":"COMPOSITE","compositeMode":"COMPOSITE_KEM_COMBINE","alg":"X25519+ML-KEM-768","kdf":"HKDF-SHA256","keyOps":["encrypt","decrypt"],"exportable":false,"origin":"GENERATED","status":"active","createdAt":"2025-03-01T09:00:00Z","expiresAt":"2030-03-01T09:00:00Z","softLimitExpiration":"2029-09-01T09:00:00Z","usageCount":57,"softUsageLimit":90000,"maxUsageLimit":100000}}}}}},"400":{"description":"Invalid input: malformed kid parameter.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid kid format":{"description":"Invalid kid format","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Key identifier (kid) must not be empty or contain only whitespace.","instance":"/api/v3/key-management/keys/   /metadata","timestamp":1745120100}}}}}},"401":{"description":"Unauthorized: missing or invalid JWT token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Bearer token missing":{"description":"Bearer token missing","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication credentials were not provided.","instance":"/api/v3/key-management/keys/myKey/metadata","timestamp":1745120200}}}}}},"403":{"description":"Forbidden: authenticated user lacks required scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Insufficient permissions":{"description":"Insufficient permissions","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"Insufficient permissions. Required scope: key_management.viewKeyMetadata","instance":"/api/v3/key-management/keys/myKey/metadata","timestamp":1745120300}}}}}},"404":{"description":"Key not found with the specified identifier.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Key does not exist":{"description":"Key does not exist","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"Key not found for kid=nonExistentKey","instance":"/api/v3/key-management/keys/nonExistentKey/metadata","timestamp":1745120400}}}}}},"500":{"description":"Internal server error during key retrieval.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Keystore access failure":{"description":"Keystore access failure","value":{"type":"https://docs.ankatech.co/errors/internal","title":"Internal Server Error","status":500,"detail":"Unexpected error occurred while retrieving key metadata from keystore.","instance":"/api/v3/key-management/keys/myKey/metadata","timestamp":1745120500}}}}}},"503":{"description":"Service Unavailable: the answer is withheld. It is also answered as <code>dependency-not-ready</code>, with <code>Retry-After</code>, when a required dependency is temporarily unavailable and the audit record of the operation cannot be confirmed: the result is withheld, and new requests are refused until the <code>Retry-After</code> interval has passed.","headers":{"Retry-After":{"description":"Integer seconds after which new requests are admitted again.","style":"simple","schema":{"type":"integer","example":30}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Audit record of the operation could not be confirmed":{"description":"Audit record of the operation could not be confirmed","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/key-management/keys/payments-signing-key/metadata","correlationId":"0f6d2b1a-3c4e-4d5f-8a9b-1c2d3e4f5a6b"}}}}}},"502":{"description":"Bad Gateway: the key administration service is unavailable\n(connection failure, upstream 5xx, or circuit breaker open), it refused\nthis service's own credential (an upstream 401 or 403 is never presented\nas the caller's), or it answered with a key record that breaks its\ncontract (a record without a status, or a key other than a destroyed one\nwithout origin or exportability); the problem detail says which it was.\n","headers":{"Retry-After":{"description":"Suggested wait in integer seconds; present ONLY while the circuit breaker toward the administration service is open.","style":"simple","schema":{"type":"integer","example":23}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"504":{"description":"Gateway Timeout: the key administration service did not respond within\nthe per-operation read deadline.\n","headers":{"Retry-After":{"description":"Suggested wait in integer seconds; present ONLY while the circuit breaker toward the administration service is open.","style":"simple","schema":{"type":"integer","example":23}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/key-management/keys/my-mlkem-key/metadata","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}},"/api/v3/crypto/limits":{"get":{"tags":["Crypto Limits"],"summary":"Discover the crypto plaintext limit","description":"Returns the maximum plaintext byte length the producer crypto endpoints (encrypt / sign / sign-encrypt) accept for the calling tenant. Resolved from the same single source of truth, for the same tenant, that the encrypt gate enforces, so the advertised value never drifts from the enforced one. The operator sets it on the deployment plane and it applies without a restart.","operationId":"getCryptoLimits","responses":{"200":{"description":"The effective crypto plaintext limit for the calling tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoLimitsResponse"},"examples":{"Default 4 MB limit":{"description":"Default 4 MB limit","value":{"maxPlaintextBytes":4000000}}}}}},"401":{"description":"Missing, invalid, or expired authentication.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"421":{"description":"Misdirected Request. This service runs in two deployment roles, and this operation is served by the buffered role; the instance that received the request runs the other one. The platform edge routes every operation to the role that serves it, so a caller using the public API never receives this status: only a caller that addresses an instance of this service directly, on the internal network, can.\n\nThe refusal happens after authentication and before anything else: no request body is read, no usage is counted, no audit event is written and no scope is evaluated, so an unauthenticated request is answered 401 first and a request lacking this operation's scope still receives 421, not 403. Repeating the request on the same instance returns the same refusal and has no effect; send it through the edge. No `Retry-After` is sent, and the body names no host or service.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Buffered route received by a streaming-role instance":{"summary":"A buffered operation addressed to an instance of the streaming role","value":{"type":"https://docs.ankatech.co/errors/misdirected-request","title":"Misdirected Request","status":421,"detail":"This route is not served by the deployment role that received the request.","instance":"/api/v3/crypto/limits","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1745119200}}}}}}}}}},"components":{"schemas":{"AlgorithmPolicy":{"type":"object","description":"Algorithm transformation policy","properties":{"allowLegacyHash":{"type":"boolean","default":false,"description":"Allow legacy hash algorithms (SHA-1, MD5).\n- false (default): Reject PKCS#7 files using SHA-1/MD5 (security policy)\n- true: Allow legacy hashes (for compatibility with very old files)\n","example":false}}},"Pkcs7ConversionStreamRequest":{"type":"object","description":"Metadata for streaming PKCS#7 to JOSE conversion. The outer JOSE container is detached; SignedAndEnvelopedData produces a JWE whose plaintext is an ATTACHED JWS.","properties":{"decryptionKid":{"type":["string","null"],"description":"Optional. The kid of the tenant key that decrypts, for an encrypted structure (EnvelopedData or SignedAndEnvelopedData): the kid under which one recipient's private key and certificate were imported. When present it must name one of the recipients (400 invalid-input otherwise; 404 for a kid this tenant does not hold). When absent, the first recipient in encoded order whose tenant key admits decryption decrypts, with no fallback to another recipient. The /analyze-pkcs7 response names recipients by issuerDN and serialNumber (recommendations.requiredKeys[]), not by kid.","example":"legacy-recipient-key","maxLength":255,"minLength":0,"pattern":"^\\s*$|^[A-Za-z0-9._-]+$"},"serialization":{"type":"string","default":"AUTO","description":"Serialization of the detached output. Optional; an absent member is AUTO. Exact case; any other value is refused with 400 invalid-input naming `serialization`, before the upload is read and before any response byte.\n- AUTO (default): the single-actor form when the PKCS#7 carries at most one signer and at most one recipient (a flattened detached JWS for SignedData; the multipart JWE for enveloped structures); General otherwise.\n- COMPACT: the single-actor form. More than one signer or more than one recipient is refused with 422 INCOMPATIBLE_SERIALIZATION and zero body bytes. Detached streaming output is JSON-serialized, so this form is not RFC 7515 §7.1 Compact.\n- JSON: General serialization (a `signatures` array, or a `recipients` array), also for a single actor. The General JWE header is disjoint (RFC 7516 §7.2.1): `protected` carries what every recipient shares and each recipient's `header` its own `alg` and `kid`, also for one recipient. /api/v3/crypto/stream/decrypt, /reencrypt and /decrypt-verify read that one-recipient form: its unprotected `alg` is admitted only when it is exactly the key-management algorithm of the key its `kid` resolves to (422 JWE_ALG_KEY_MISMATCH otherwise).\n","enum":["AUTO","COMPACT","JSON"],"maxLength":64,"minLength":0},"targetFormat":{"type":"string","default":"AUTO","description":"Optional; an absent member is AUTO. Target JOSE format. AUTO auto-detects the format from the parsed PKCS#7/CMS structure type (SignedData->JWS, EnvelopedData->JWE, SignedAndEnvelopedData->JWE_JWS_NESTED). An explicit non-AUTO value that is incompatible with the parsed structure (e.g. JWS for EnvelopedData) is rejected with 422 INCOMPATIBLE_TARGET_FORMAT before conversion.","enum":["AUTO","JWS","JWE","JWE_JWS_NESTED"]},"algorithmPolicy":{"$ref":"#/components/schemas/AlgorithmPolicy","description":"Algorithm policy: whether a signer on a legacy hash (SHA-1, MD5) is converted"}}},"ProblemDetails":{"type":"object","description":"Standard RFC 7807 problem envelope.","properties":{"type":{"type":"string","description":"URI reference to the problem type.","example":"https://docs.ankatech.co/errors/invalid-input"},"title":{"type":"string","description":"Short, human‑readable summary.","example":"Invalid input"},"status":{"type":"integer","format":"int32","description":"HTTP status code.","example":400},"detail":{"type":"string","description":"Human‑readable explanation of the error.","example":"The 'kid' field must not be blank."},"instance":{"type":"string","description":"URI reference to the specific occurrence.","example":"/api/v3/key-management/keys"},"correlationId":{"type":"string","description":"Correlation ID for log tracing.","example":"550e8400-e29b-41d4-a716-446655440000"},"timestamp":{"type":"integer","format":"int64","description":"Epoch‑second timestamp of the error.","example":1745119200},"extensions":{"type":"object","additionalProperties":{},"description":"Optional extension bag."}}},"Pkcs7AnalysisStreamRequest":{"type":"object","description":"Metadata part of the streaming PKCS#7 analysis: a JSON object that defines no member (send {}). Analysis needs no key and no option; any member is refused with 400 naming it.","example":{}},"AnalysisLimits":{"type":"object","description":"Bounds a PKCS#7 to JOSE conversion enforces","properties":{"maxActors":{"type":"integer","format":"int32","description":"The most signers and recipients, counted together, one conversion accepts: the crypto plane's one actor bound (`ankasecure.crypto.jose.max-actors`), the same bound a multi-key JOSE token is held to. An input above it is reported unsupported with `ACTOR_LIMIT_EXCEEDED`, and the conversion refuses it with 422 `ACTOR_LIMIT_EXCEEDED` before any key is used.","example":16,"minimum":1}},"required":["maxActors"]},"ConversionRecommendations":{"type":"object","description":"Recommendations for migration to JOSE","properties":{"migrationPath":{"type":"string","description":"The JOSE form the conversion produces under serialization AUTO, per structure: _COMPACT when the structure carries at most one signer and at most one recipient, _JSON (General JSON serialization, RFC 7515 section 7.2.1 / RFC 7516 section 7.2.1) otherwise. JWS for SignedData, JWE for EnvelopedData, JWE_JWS (a JWE whose plaintext is a JWS) for SignedAndEnvelopedData.","enum":["CONVERT_TO_JWS_COMPACT","CONVERT_TO_JWE_COMPACT","CONVERT_TO_JWE_JWS_COMPACT","CONVERT_TO_JWS_JSON","CONVERT_TO_JWE_JSON","CONVERT_TO_JWE_JWS_JSON"],"example":"CONVERT_TO_JWS_JSON"},"requiredKeys":{"type":"array","description":"One entry per actor the conversion needs a tenant key for: every signer (in encoded order, purpose signing), then every recipient (in encoded order, purpose encryption). Each entry names the actor by the issuer and serial number the tenant key's certificate must carry. Import the keys through POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore.","items":{"$ref":"#/components/schemas/RequiredKey"}},"decryptionRequirement":{"type":"string","description":"Present for an encrypted structure: the conversion additionally needs the PRIVATE key of exactly one recipient to decrypt the content (the recipient named by decryptionKid, or else the first recipient in encoded order whose tenant key admits decryption). Every other recipient needs a tenant key admissible for encryption: a key imported with its private part, or the encryption projection of a DUAL import. A certificate imported alone is a verify-only trust anchor and is refused as a recipient.","enum":["ONE_RECIPIENT_PRIVATE_KEY"],"example":"ONE_RECIPIENT_PRIVATE_KEY"},"estimatedComplexity":{"type":"string","description":"Estimated complexity level","enum":["LOW","MEDIUM","HIGH"],"example":"MEDIUM"}}},"DetectedFormat":{"type":"object","description":"Detected PKCS#7/CMS format and structure type","properties":{"standard":{"type":"string","description":"Standard detected. PKCS#7 (RFC 2315) and CMS (RFC 5652) identify each content type with the same OID, so every structure the analyzer recognises is reported PKCS#7.","example":"PKCS#7"},"structure":{"type":"string","description":"Structure type detected","enum":["SignedData","EnvelopedData","SignedAndEnvelopedData"],"example":"SignedAndEnvelopedData"},"contentTypeOID":{"type":"string","description":"Content-type OID from PKCS#7 structure","example":"1.2.840.113549.1.7.1"},"encoding":{"type":"string","description":"Transfer encoding, DERIVED from the outer ContentInfo header.\n\n- `BER` — the structure uses the indefinite-length form, which DER forbids, so it\n  is provably not DER. This is the default output of most CMS producers, including\n  BouncyCastle, and the only form an incremental producer can emit when the content\n  length is not known in advance.\n- `DER` — the structure uses a definite-length form. Note this is not a proof of\n  canonical DER: BER permits definite lengths too, and distinguishing the two would\n  require re-encoding and comparing the whole structure, which this endpoint does\n  not do because it never materialises the payload.\n","enum":["BER","DER"],"example":"BER"},"payloadEmbedded":{"type":"boolean","description":"Whether payload is embedded (true) or detached (false)","example":true}}},"EncryptionMetadata":{"type":"object","description":"Encryption algorithm and parameters","properties":{"contentEncryptionAlgorithmOID":{"type":"string","description":"Content encryption algorithm OID","example":"2.16.840.1.101.3.4.1.42"},"contentEncryptionAlgorithm":{"type":"string","description":"Content encryption algorithm name","example":"AES-256-CBC"},"ivLength":{"type":"integer","format":"int32","description":"Initialization Vector length in bytes","example":16}}},"Pkcs7AnalysisResponse":{"type":"object","description":"Detailed analysis of PKCS#7/CMS file structure with conversion recommendations.","properties":{"detectedFormat":{"$ref":"#/components/schemas/DetectedFormat","description":"Detected PKCS#7/CMS format information"},"signers":{"type":"array","description":"List of signers found in the PKCS#7 structure","items":{"$ref":"#/components/schemas/SignerMetadata"}},"recipients":{"type":"array","description":"List of recipients found in the PKCS#7 structure","items":{"$ref":"#/components/schemas/RecipientMetadata"}},"encryption":{"$ref":"#/components/schemas/EncryptionMetadata","description":"Encryption metadata (present only for EnvelopedData)"},"supportStatus":{"$ref":"#/components/schemas/SupportStatus","description":"Conversion support status and reasons"},"recommendations":{"$ref":"#/components/schemas/ConversionRecommendations","description":"Recommendations for successful conversion to JOSE"},"limits":{"$ref":"#/components/schemas/AnalysisLimits","description":"The bounds a conversion of this input is held to, stated before the conversion is attempted"}},"required":["detectedFormat","limits","supportStatus"]},"ReasonCode":{"type":"object","description":"Reason code explaining support or limitation","properties":{"code":{"type":"string","description":"Machine-readable code. An ERROR is a refusal the conversion raises from the structure alone, with the conversion's own errorCode: ACTOR_LIMIT_EXCEEDED, SIGNED_AND_ENVELOPED_WITHOUT_SIGNER, UNSUPPORTED_SIGNED_AND_ENVELOPED_SIGNER_KEY, SIGNED_AND_ENVELOPED_DIGEST_NOT_ENCRYPTED, UNSUPPORTED_CONTENT_ENCRYPTION, or INVALID_PKCS7 for a refusal the conversion answers 400 invalid-pkcs7 (a SignedData with no signer or no encapsulated content). LEGACY_ALGORITHM_REJECTED (WARNING): a SHA-1/MD5 signer, which the conversion refuses unless algorithmPolicy.allowLegacyHash is true. TIMESTAMP_DETECTED (INFO): a signer carries an RFC 3161 timestamp, which is reported and not validated.","example":"TIMESTAMP_DETECTED"},"severity":{"type":"string","description":"Severity level","enum":["INFO","WARNING","ERROR"],"example":"INFO"},"message":{"type":"string","description":"Human-readable message","example":"RFC 3161 timestamp detected. The timestamp is reported, not validated."}}},"RecipientMetadata":{"type":"object","description":"Metadata for a recipient in the PKCS#7 structure","properties":{"index":{"type":"integer","format":"int32","description":"Recipient index (0-based)","example":0},"issuerDN":{"type":"string","description":"Issuer Distinguished Name from recipient certificate","example":"CN=Example User, O=Example Corp, C=US"},"serialNumber":{"type":"string","description":"Certificate serial number: 0x plus the upper-case hexadecimal of the serial DER INTEGER content octets, as the certificate carries them (a serial whose first octet is 80-FF keeps its leading 00; a non-conforming negative serial renders in two's complement)","example":"0x009F8E7D6C5B4A3B2C"},"keyEncryptionAlgorithmOID":{"type":"string","description":"Key encryption algorithm OID","example":"1.2.840.113549.1.1.1"},"keyEncryptionAlgorithm":{"type":"string","description":"Key encryption algorithm name","example":"RSA"},"certificatePresent":{"type":"boolean","description":"Whether recipient certificate is present in PKCS#7 structure","example":true},"certificatePEM":{"type":"string","description":"Recipient certificate in PEM format (if present)"}}},"RequiredKey":{"type":"object","description":"A tenant key the conversion needs for one signer or one recipient","properties":{"purpose":{"type":"string","description":"The minimum the conversion needs from this key. signing: a signer's private key, which re-signs the content. encryption: a recipient's encryption-capable key, to which the content is re-encrypted: a key imported with its private part, or the encryption projection of a DUAL import (a certificate imported alone only verifies and is refused); the one recipient whose private key decrypts needs that key to decrypt as well (see decryptionRequirement).","enum":["signing","encryption"],"example":"signing"},"issuerDN":{"type":"string","description":"Issuer DN that must match the imported key","example":"CN=Example User, O=Example Corp, C=US"},"serialNumber":{"type":"string","description":"Serial number that must match the imported key: 0x plus the upper-case hexadecimal of the serial DER INTEGER content octets, as the certificate carries them (a serial whose first octet is 80-FF keeps its leading 00; a non-conforming negative serial renders in two's complement)","example":"0x009F8E7D6C5B4A3B2C"},"algorithm":{"type":"string","description":"Algorithm of this actor as the PKCS#7 declares it: the signature algorithm of a signer, the key-encryption algorithm of a recipient","example":"SHA256withRSA"},"importHint":{"type":"string","description":"Hint for importing the key","example":"Use POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore to import the private key and certificate of this signer"}}},"SignerMetadata":{"type":"object","description":"Metadata for a signer in the PKCS#7 structure","properties":{"index":{"type":"integer","format":"int32","description":"Signer index (0-based)","example":0},"issuerDN":{"type":"string","description":"Issuer Distinguished Name from signer certificate","example":"CN=Example CA Authority, O=Example Organization, C=US"},"serialNumber":{"type":"string","description":"Certificate serial number: 0x plus the upper-case hexadecimal of the serial DER INTEGER content octets, as the certificate carries them (a serial whose first octet is 80-FF keeps its leading 00; a non-conforming negative serial renders in two's complement)","example":"0x4A3F2E1D5B6C7A8B"},"signatureAlgorithmOID":{"type":"string","description":"Signature algorithm OID","example":"1.2.840.113549.1.1.11"},"signatureAlgorithm":{"type":"string","description":"Signature algorithm name","example":"SHA256withRSA"},"digestAlgorithmOID":{"type":"string","description":"Digest algorithm OID","example":"2.16.840.1.101.3.4.2.1"},"digestAlgorithm":{"type":"string","description":"Digest algorithm name","example":"SHA-256"},"certificatePresent":{"type":"boolean","description":"Whether signer certificate is present in PKCS#7 structure","example":true},"certificatePEM":{"type":"string","description":"Signer certificate in PEM format (if present)"},"certificateChainLength":{"type":"integer","format":"int32","description":"Length of certificate chain","example":3},"keyUsage":{"type":"array","description":"KeyUsage bits from certificate","example":["digitalSignature","nonRepudiation"],"items":{"type":"string"}},"timestampPresent":{"type":"boolean","description":"Whether RFC 3161 timestamp is present","example":true},"timestampTSA":{"type":"string","description":"Timestamp Authority Distinguished Name (if timestamp present)","example":"CN=TSA Example Authority"},"timestampGenTime":{"type":"string","description":"Timestamp generation time in ISO-8601 format (if timestamp present)","example":"2025-12-21T15:30:00Z"}}},"SupportStatus":{"type":"object","description":"Whether the analyzed PKCS#7 can be converted to JOSE, and why","properties":{"supported":{"type":"boolean","description":"Whether POST /api/v3/migration/convert-pkcs7-to-jose (or its streaming twin) can convert this structure. It does not depend on how many signers or recipients the structure carries: every one of them is converted. It is false only when a reason of severity ERROR is present.","example":true},"reasons":{"type":"array","description":"Reason codes qualifying the support status; empty when there is nothing to report","items":{"$ref":"#/components/schemas/ReasonCode"}}}},"Pkcs7ConversionRequest":{"type":"object","description":"Request for converting PKCS#7/CMS to JOSE (JWE/JWS).","properties":{"pkcs7Data":{"type":"string","description":"PKCS#7/CMS data encoded as Base64 (DER format)","example":"MIIGHgYJKoZIhvcNAQcCoIIGDzCCBgsCAQAxggE...","minLength":1},"decryptionKid":{"type":["string","null"],"description":"Optional. The kid of the tenant key that decrypts, for an encrypted structure (EnvelopedData or SignedAndEnvelopedData). When present it must name one of the recipients (matched by the issuer and serial number of the key's certificate): a kid that names none of them is refused with 400 invalid-input, and a kid this tenant does not hold with 404. When absent, the first recipient in encoded order whose tenant key admits decryption decrypts; there is no fallback to another recipient after a refusal or a decryption failure. Ignored for SignedData.\n","example":"legacy-recipient-key","maxLength":255,"minLength":0,"pattern":"^\\s*$|^[A-Za-z0-9._-]+$"},"serialization":{"type":"string","default":"AUTO","description":"JOSE serialization of the output. Exact case; any other value is refused with 400 invalid-input naming `serialization`, before the PKCS#7 is decoded.\n- AUTO (default): Compact when the PKCS#7 carries at most one signer and at most one recipient; General JSON otherwise (both layers of a nested result).\n- COMPACT: Compact serialization. A PKCS#7 with more than one signer or more than one recipient is refused with 422 INCOMPATIBLE_SERIALIZATION before any key is resolved.\n- JSON: General JSON serialization (RFC 7515 §7.2.1 / RFC 7516 §7.2.1), also for a single actor.\n","enum":["AUTO","COMPACT","JSON"],"example":"AUTO","maxLength":64,"minLength":0},"targetFormat":{"type":"string","default":"AUTO","description":"Target JOSE format.\n- AUTO (default): Detect from PKCS#7 structure\n  - SignedData only → JWS\n  - EnvelopedData only → JWE\n  - Both → JWE_JWS_NESTED\n- JWS: Force JWS output (error if PKCS#7 has encryption)\n- JWE: Force JWE output (error if PKCS#7 has signature)\n- JWE_JWS_NESTED: Force nested output (error if PKCS#7 lacks signature or encryption)\n","enum":["AUTO","JWS","JWE","JWE_JWS_NESTED"],"example":"AUTO"},"algorithmPolicy":{"$ref":"#/components/schemas/AlgorithmPolicy","description":"Algorithm policy: whether a signer on a legacy hash (SHA-1, MD5) is converted. The content is always encrypted again under the JWE content encryption the conversion emits (A256GCM), whatever cipher the PKCS#7 used.\n"}},"required":["pkcs7Data"]},"AlgorithmTransformation":{"type":"object","description":"Algorithm transformation applied during conversion","properties":{"original":{"type":"string","description":"Original PKCS#7 algorithm with OID","example":"SHA256withRSA (OID: 1.2.840.113549.1.1.11)"},"transformed":{"type":"string","description":"The JOSE algorithm that replaces it, read from the emitted token: for a signature, the JWS alg in the emitted protected header; for the content cipher, the JWE enc the emitted JWE carries","example":"PS256"},"reason":{"type":"string","description":"Reason for transformation","example":"Re-signed with the matched key under the JWS alg that key signs with"}}},"DecisionMetadata":{"type":"object","description":"Metadata about conversion decisions","properties":{"signerCount":{"type":"integer","format":"int32","description":"Number of signers detected in PKCS#7","example":1},"recipientCount":{"type":"integer","format":"int32","description":"Number of recipients detected in PKCS#7","example":0},"chosenFormat":{"type":"string","description":"Target format chosen (AUTO mode decision)","example":"JWS"},"chosenSerialization":{"type":"string","description":"Serialization chosen (AUTO mode decision)","example":"COMPACT"},"reason":{"type":"string","description":"Explanation of why this format was chosen","example":"3 signer(s) and 0 recipient(s) detected; JWS in General JSON serialization, one entry per distinct key"}}},"Pkcs7ConversionResponse":{"type":"object","description":"Response containing converted JOSE token with conversion metadata.","properties":{"joseToken":{"type":"string","description":"Converted JOSE token, as a string. With `serialization` COMPACT it is the Compact serialization (RFC 7515 §7.1 / RFC 7516 §7.1); with JSON it is the General JSON serialization document (RFC 7515 §7.2.1 / RFC 7516 §7.2.1), carrying one signature per signer key and one recipient per recipient key of the PKCS#7 input: two actors of one tenant key give one entry.\n","example":"eyJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInR5cCI6IkpXVCIsImFsZyI6IlBTMjU2IiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwia2lkIjoibGVnYWN5U2lnbmVyS2V5In0.eyJkYXRhIjoiU0dWc2JHOGdWMjl5YkdRIiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIn0.signature..."},"serialization":{"type":"string","description":"Serialization mode used","enum":["COMPACT","JSON"],"example":"COMPACT"},"structure":{"type":"string","description":"JOSE structure type","enum":["JWS","JWE","JWE_JWS_NESTED"],"example":"JWS"},"decision":{"$ref":"#/components/schemas/DecisionMetadata","description":"Decision metadata explaining auto-selection logic"},"signers":{"type":"array","description":"Signer conversion results","items":{"$ref":"#/components/schemas/SignerConversionResult"}},"recipients":{"type":"array","description":"Recipient conversion results","items":{"$ref":"#/components/schemas/RecipientConversionResult"}},"algorithmTransformations":{"type":"array","description":"Algorithm transformations applied during conversion","items":{"$ref":"#/components/schemas/AlgorithmTransformation"}},"warnings":{"type":"array","description":"Warnings about conversion process","items":{"type":"string"}}},"required":["joseToken","serialization","structure"]},"RecipientConversionResult":{"type":"object","description":"Recipient conversion result","properties":{"originalIssuerDN":{"type":"string","description":"Original issuer DN from PKCS#7 RecipientInfo","example":"CN=Example Recipient, O=Example Organization, C=US"},"originalSerialNumber":{"type":"string","description":"Original serial number from PKCS#7 RecipientInfo: 0x plus the upper-case hexadecimal of the serial DER INTEGER content octets, as the certificate carries them (a serial whose first octet is 80-FF keeps its leading 00; a non-conforming negative serial renders in two's complement)","example":"0x009F8E7D6C5B4A3B2C"},"assignedKid":{"type":"string","description":"KID assigned to this recipient (user-specified or matched)","example":"legacy-recipient-key"},"algorithm":{"type":"string","description":"The key-management alg of this recipient in the emitted JWE: a new content-encryption key is wrapped to the matched key with the alg that key wraps with, read back from the token, never mapped from the PKCS#7 key transport","example":"RSA-OAEP-512"},"warnings":{"type":"array","description":"Warnings specific to this recipient","items":{"type":"string"}}},"required":["assignedKid"]},"SignerConversionResult":{"type":"object","description":"Signer conversion result","properties":{"originalIssuerDN":{"type":"string","description":"Original issuer DN from PKCS#7 SignerInfo","example":"CN=Example Signer, O=Example Organization, C=US"},"originalSerialNumber":{"type":"string","description":"Original serial number from PKCS#7 SignerInfo: 0x plus the upper-case hexadecimal of the serial DER INTEGER content octets, as the certificate carries them (a serial whose first octet is 80-FF keeps its leading 00; a non-conforming negative serial renders in two's complement)","example":"0x4A3F2E1D5B6C7A8B"},"assignedKid":{"type":"string","description":"KID assigned to this signer (matched from imported keys)","example":"legacySignerKey"},"algorithm":{"type":"string","description":"JWS alg of the new signature: the alg the matched key signs with, the one in the emitted JWS protected header (PS256 for an RSA key; for an EC key by its curve, ES256/ES384/ES512 on P-256/P-384/P-521 and ESB256/ESB384/ESB512 on brainpoolP256r1/P384r1/P512r1). The content is re-signed, so this is not derived from the PKCS#7 signature algorithm.","example":"PS256"},"certificateMatched":{"type":"boolean","description":"Whether original PKCS#7 certificate was already imported in keystore","example":true},"warnings":{"type":"array","description":"Warnings specific to this signer","items":{"type":"string"}}},"required":["assignedKid"]},"Pkcs7AnalysisRequest":{"type":"object","description":"Request for analyzing PKCS#7/CMS file structure and extracting metadata.","properties":{"pkcs7Data":{"type":"string","description":"PKCS#7/CMS data encoded as Base64 (DER format)","example":"MIIGHgYJKoZIhvcNAQcDoIIGDzCCBgsCAQAxggE...","minLength":1}},"required":["pkcs7Data"]},"SignatureUtilityApi":{"type":"object","description":"Model for verifying digital signatures, including kty/alg/publicKey/signatureBase64.","properties":{"kty":{"type":"string","description":"Key type (kty) for the signature, e.g. 'RSA', 'EC', 'ML-DSA'.\nMust match the actual algorithm and publicKey used.\n","example":"ML-DSA","minLength":1},"alg":{"type":"string","description":"Algorithm name or parameter set, e.g. 'RSA-2048', 'ML-DSA-87'.\nMust be consistent with the kty if the publicKey is provided.\n","example":"ML-DSA-87","minLength":1},"publicKey":{"type":"string","description":"Base64-encoded public key, required for signature verification of asymmetric keys.","example":"BASE64-ENCODED-PUBLIC-KEY"},"signatureBase64":{"type":"string","description":"Base64-encoded digital signature to be verified under the specified kty/alg/publicKey.","example":"BASE64-SIGNATURE-DATA"}},"required":["alg","kty"]},"QualifiedTimestamp":{"type":"object","description":"Embedded RFC 3161 qualified timestamp (JAdES sigTst) reported on verify. Informational only — never affects the signature validity verdict.","properties":{"present":{"type":"boolean","description":"Whether an embedded qualified timestamp was present in the JWS.","example":true},"trustStatus":{"type":"string","description":"Typed TSA trust outcome, independent of the signature verdict. The checks run in this order and the first one that fails decides the value:\n1. UNTRUSTED: the token does not parse, or carries no certificate for its signer.\n2. SIGNATURE_INVALID: the token's own signature does not verify, or a certificate identifier it carries does not name the certificate that verified it.\n3. UNTRUSTED: the token's message imprint declares a hash other than SHA-256, SHA-384 or SHA-512, algorithm parameters other than absent or NULL, or a digest whose length is not that hash's.\n4. IMPRINT_MISMATCH: the token's message imprint is not the digest of the presented JWS signature value; the token time-stamps other bytes.\n5. UNTRUSTED: the signer's certificate does not carry id-kp-timeStamping as its sole, critical purpose.\n6. EXPIRED: the signer's certificate was not valid at genTime: expired, or not yet valid, when the token was made.\n7. UNTRUSTED: no trust anchor is configured.\n8. The certificate path is validated at genTime: EXPIRED when a certificate in it, a CA's included, was not valid at genTime; UNTRUSTED for every other path failure: it does not end at a configured trust anchor, a certificate signature does not verify, or a name or basic constraint is violated. A signer that is itself a configured self-signed anchor is VALID at this step, and step 9 does not run for it.\n9. Only when the deployment checks revocation, the default — REVOKED: a certificate in the path is revoked in the sense of RFC 3161 section 4: its revocation took effect at or before genTime, or it was revoked with no reason code, for keyCompromise, or for any reason other than unspecified, affiliationChanged, superseded or cessationOfOperation. A certificate retired after genTime for one of those four reasons leaves the stamp VALID. UNTRUSTED when the revocation status of a certificate in the path could not be determined (no current, usable CRL covers it) while the deployment's revocation posture is hard-fail, the default, or when the revocation evaluation itself failed.\n10. VALID: every check passed.\nFrom step 5 on, a certificate check that cannot be completed at all, for a reason not listed above (a certificate that cannot be read, for example), is UNTRUSTED.\nA certificate that expired after genTime does not make a stamp EXPIRED: the certificate path is judged as it stood at genTime, and only whether a CRL is current is judged at the time of verification.\nThe token's age is not judged: genTime is compared with the clock only when the time-stamping authority's reply is received at signing, so a stamp verifies VALID however long ago it was issued. genTime, serialNumber and policyOid are reported only for a value decided by the TSA-certificate checks, steps 5 to 10; for steps 1 to 4 they are absent.","enum":["VALID","SIGNATURE_INVALID","IMPRINT_MISMATCH","UNTRUSTED","REVOKED","EXPIRED"],"example":"VALID"},"genTime":{"type":"string","description":"TSA-asserted generation time (ISO-8601). Absent unless the token is authenticated and covers the presented signature: absent for an unparseable token, a token without its signer's certificate, SIGNATURE_INVALID, a malformed message imprint, and IMPRINT_MISMATCH.","example":"2026-07-05T10:20:00Z"},"serialNumber":{"type":"string","description":"Timestamp token serial number (decimal). Absent under the same rule as genTime.","example":"84215045"},"policyOid":{"type":"string","description":"TSA policy OID (dotted-decimal). Absent under the same rule as genTime.","example":"1.3.6.1.4.1.99999.1.1"}},"required":["present","trustStatus"]},"SignatureResult":{"type":"object","description":"The verification detail of one signature of the verified JWS: one entry per signature, in token order (RFC 7515 §7.2.1).","properties":{"kid":{"type":"string","description":"Key identifier the signature's protected header names. Absent on the interoperability plane, whose verification key the caller supplies.","example":"invoice-signer-2025"},"algorithm":{"type":"string","description":"Catalogue algorithm of the verification key; a COMPOSITE key reports its pairing.","example":"ML-DSA-65"},"materialVersion":{"type":"integer","format":"int32","description":"Key-material version the signature's key resolved to: the protected ank_kv, or the PRIMARY when the header carries none. When no key material resolved, the ank_kv the signature's protected header names; absent when there is neither (the interoperability plane, whose key the caller supplies).","example":2},"verified":{"type":"boolean","description":"True if this signature verified (for a COMPOSITE key: both components).","example":true}},"required":["algorithm","verified"]},"VerifySignatureResponse":{"type":"object","properties":{"valid":{"type":"boolean","description":"True only when every signature of the JWS verified; a JWS with any failing signature is invalid, never a partial success.","example":true},"warnings":{"type":"array","description":"Non-critical warnings (usage limits, expiry, …).","items":{"type":"string"}},"signatureResults":{"type":"array","description":"One entry per signature of the JWS, in token order: its key, the key's algorithm and material version, and whether that signature verified. For a COMPOSITE key the entry is its one composite signature, verified only when both of its components verify.","items":{"$ref":"#/components/schemas/SignatureResult"}},"qualifiedTimestamp":{"$ref":"#/components/schemas/QualifiedTimestamp","description":"Embedded RFC 3161 qualified timestamp, when present. Informational — never affects 'valid'."}},"required":["signatureResults","valid"]},"CryptoUtilityApi":{"type":"object","description":"Unified model for cryptographic key data in API operations.","properties":{"kty":{"type":"string","description":"Key type, e.g. 'oct', 'RSA', 'EC', 'ML-KEM', 'ML-DSA', 'FALCON', 'SLH-DSA'.\nFor a dynamic list of currently supported kty values, see:\nGET /api/v3/key-management/supported-algorithms.\n","example":"ML-KEM","minLength":1},"alg":{"type":"string","description":"Algorithm name, e.g. 'A256GCM', 'RSA-2048', 'ML-KEM-768'.\nFor a dynamic list of currently supported algorithms, see:\nGET /api/v3/key-management/supported-algorithms.\n","example":"ML-KEM-1024","minLength":1},"publicKey":{"type":"string","description":"Base64-encoded public key bytes, for asymmetric keys.","example":"BASE64-ENCODED-PUBLIC-KEY"}},"required":["alg","kty"]},"GeneralJsonJws":{"type":"object","description":"RFC 7515 §7.2 General JSON Serialization JWS — the single ANKASecure wire format for JWS.","properties":{"payload":{"type":"string","description":"Base64url-encoded JWS payload.","example":"eyJkYXRhIjoiU0dWc2JHOGdRVzVyWVEiLCJpYXQiOjE3OTA2NTYyNDEsInRpZCI6IjdkM2E5YzFlLTRiMmYtNGU4YS05YzczLTI3NTAwMDAwMDAxMSJ9","minLength":1},"signatures":{"type":"array","description":"Signature array, one entry per signing key in token order: length 1 for a JWS signed by one key, SIMPLE or COMPOSITE (a composite signature is one value whose protected header names the key and the composite algorithm), and one entry per key for a JWS signed by several keys (RFC 7515 §7.2.1).","items":{"$ref":"#/components/schemas/JwsSignature"},"minItems":1},"header":{"type":"object","additionalProperties":{},"description":"Not a member of the RFC 7515 §7.2.1 General syntax, which places an unprotected header inside each signature object: this platform carries the flattened form's unprotected header here, and that is where a JAdES B-T signature's \"etsiU\" timestamp arrives. It is not integrity protected, so it never identifies the key: \"kid\", \"ank_kv\" and \"crit\" are read from the protected header only, and no member name may repeat a protected-header name. Absent on an unstamped signature."}},"required":["payload","signatures"]},"JwsSignature":{"type":"object","description":"Per-signature header + signature value per RFC 7515 §7.2.1.","properties":{"protected":{"type":"string","description":"Base64url-encoded JOSE protected header for this signature.","example":"eyJ2ZXIiOiIyLjAiLCJhbmtfa3YiOjEsInR5cCI6IkpXVCIsImFsZyI6IlBTMjU2IiwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwia2lkIjoibXktcnNhLTMwNzIta2V5In0"},"header":{"type":"object","additionalProperties":{},"description":"Optional unprotected header.","example":{"kid":"my-rsa-3072-key"}},"signature":{"type":"string","description":"Base64url-encoded signature octets.","example":"wdF8GP8DwiIwD06LJCicETh1trDvid__nvPN4pAJV90cf6NI8P8ujrNx4uPRXzumcf1e6rSUuOnyL2KQWLO8DQP0_1x2sK8SaeY6td93tV44hKm3x55nAon6yunxFTHNWAC3p-Dn_xXCDzKFoWu0WtYwXVRhKITB-fJ-jZEtgl4emB4yGxIzlFFkzCs_nxGTydOG9Z6mvnf0sQLHyIAS9O2PltJhKhBYxb6S80Nm6nIWVXigOB71QCszqDkGgedWxayHzb7NgIhyOPzTCy2lXGHQYdd0mlh8PdZAcMTgpXaeTpKx__hL9WV4LVFZpBQcIccGqDThBu7w7ZDn3ioEhov7mPxvIkx028xKRP51HeEk4z-cbcXsWHJmpqet16CIH1-3PPJTDeA5FZcXUu4xlTrvhmURmldpuIKMvvFa3T_ftlbSOMsZVPwIGZrGvOU6hyVzVrZ5dd_-DpO_M1LjO6RixUEWmOYIFO0lakXJt6V3EHOP7zvE4Zvb_khHc1lN","minLength":1}},"required":["signature"]},"VerifyJwsRequest":{"type":"object","description":"Request object for verifying a JWS (General JSON Serialization).","properties":{"jwsToken":{"$ref":"#/components/schemas/GeneralJsonJws","description":"Typed JWS token to verify (General JSON Serialization)."}},"required":["jwsToken"]},"VerifyDetachedJwsStreamRequest":{"type":"object","description":"JSON wrapper that carries the detached-JWS produced by\n<code>/stream/sign</code> or <code>/stream/resign</code>.\n","properties":{"detachedJws":{"type":"string","description":"Detached JWS as the flattened JWS JSON Serialization (RFC 7515 §7.2.2) with an unencoded, detached payload (RFC 7797 §3): exactly the members <code>protected</code> and <code>signature</code>, with no <code>payload</code> and no <code>signatures</code> array. The protected header names the key (<code>kid</code>) and the algorithm (<code>alg</code>) and carries <code>\"b64\":false</code> with <code>\"crit\":[\"b64\"]</code>. Send the document <code>/stream/sign</code> or <code>/stream/resign</code> returned, unchanged, as a JSON string. A General JSON document (a <code>signatures</code> array) is refused with 422 <code>composite-wire-shape-mismatch</code>.","example":{"protected":"eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6Im15LXJzYS0zMDcyLWtleSIsInZlciI6IjIuMCIsImFua19rdiI6MSwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwiaWF0IjoxNzkwNjU2MjQwfQ","signature":"DOcxA9VrbL_UUZeJ7UNGaT4tvXaPYJVeGYJMqVnzrsBrumtnB7RN-YeMRIDlijN9YP1jBadLybT0AwR5wLxOZ-g4nQ2gWQDBlouE4WyIB5H_MWl2cajjGXcNpcCM5r51kX1YLp5cvGgAO3DqQ81SDinIkthR_tGEH3TmElGCrpxuQrE0lm-BJRj_xypnxv3d88eYJxneymMP0dOokvWUCUfuajXHyLLwh2nNLPYLudRLDTxKhXwk_vsELvq9bEcf0u0ACV1HpQGdjWf6ggzt4ziHPuXEzzzQFAl4ALVSD9wNETT70uaFra0mwXptsLn4cfv5aq7WRv6m_xbWeCVyZhNnZOMqbIwCNzwhyv_Tp1-HpVxsOIehN2bGrBSp07X000qyNREDbAa0CxLl-5AnvF8g_oN-nuAt1dStwMb769wo27SECtg1REot-Mk1Xi7eZ5bnQhkcmJeULtzxLdT-6hk-Obe2HTTF1J9S6GSmDZ1GQc0e5hL3eHfDHyqmBbGZ"},"minLength":1}},"required":["detachedJws"]},"SignStreamRequest":{"type":"object","description":"JSON metadata sent with multipart sign requests.","properties":{"kid":{"type":"string","description":"Private-key identifier (kid) that signs the payload.","example":"myPrivateKid","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"}},"required":["kid"]},"CryptoPolicyInfo":{"type":"object","description":"Decoded payload of the **Crypto-Policy-Info** header.","properties":{"current_key_requested":{"type":"string","description":"kid as present in the incoming request header"},"current_key_algorithm":{"type":"string","description":"Algorithm used with the current key"},"current_key_exp_warn_days":{"type":"integer","format":"int32","description":"Days until hard expiry of the current key (0 = none)"},"current_key_usage_warn":{"type":"integer","format":"int32","description":"Invocations remaining until the current key’s hard usage limit (0 = none)"},"new_key_requested":{"type":"string","description":"kid supplied via the newKid parameter"},"new_key_algorithm":{"type":"string","description":"Algorithm used with the new key"},"new_key_exp_warn_days":{"type":"integer","format":"int32","description":"Days until hard expiry of the new key (0 = none)"},"new_key_usage_warn":{"type":"integer","format":"int32","description":"Invocations remaining until the new key’s hard usage limit (0 = none)"},"migration_mode":{"type":"boolean","description":"true when the token named no key and sourceKid supplied it"}}},"SignEncryptStreamRequest":{"type":"object","description":"JSON metadata sent with multipart streaming sign-encrypt requests.","properties":{"signKid":{"type":"string","description":"Identifier (kid) of the key that signs the payload (inner JWS): a symmetric, asymmetric or composite signature key.","example":"my-mldsa-signing-key","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"encryptKid":{"type":"string","description":"Identifier (kid) of the recipient key that encrypts the JWS (outer JWE): a symmetric, asymmetric or composite KEM key.","example":"my-mlkem-recipient-key","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"validityDays":{"type":"integer","format":"int64","description":"Optional signature validity in days of the `signKid`, SIMPLE or COMPOSITE alike (exp member of the inner JWS protected header, exp = iat + validityDays × 86400; null = no expiration). From 1 to 3650 days, and at most this deployment's configured maximum signature validity (`ankasecure.jws.local-clock.max-validity-days`, 730 by default): a value above that maximum is refused with 400 invalid-input naming `validityDays`.","example":365,"maximum":3650,"minimum":1},"flavor":{"type":"string","description":"Optional advisory key-flavor hint; the effective flavor is derived from the keys.","enum":["SYMMETRIC","ASYMMETRIC","COMPOSITE"],"example":"ASYMMETRIC","pattern":"^(SYMMETRIC|ASYMMETRIC|COMPOSITE)$"}},"required":["encryptKid","signKid"]},"ResignDetachedJwsStreamRequest":{"type":"object","description":"JSON metadata sent in the <code>metadata</code> field when streaming a\nre-sign request. Carries the existing detached JWS, the new signing key,\nthe signer it replaces, and an optional validity period.\n","properties":{"oldJws":{"type":"string","description":"Detached JWS produced earlier by <code>/stream/sign</code> or <code>/stream/resign</code>, sent unchanged as a JSON string: the flattened JWS JSON Serialization (RFC 7515 §7.2.2) of an unencoded, detached payload (RFC 7797 §3), with exactly the members <code>protected</code> and <code>signature</code> and a protected header carrying <code>\"b64\":false</code> with <code>\"crit\":[\"b64\"]</code>. A General JSON document (a <code>signatures</code> array of such entries) is the detached JWS signed by several keys: every entry is verified and the one <code>sourceKid</code> names is replaced. Every member of <code>metadata</code> is given at most once.","example":{"protected":"eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6Im15LXJzYS0zMDcyLWtleSIsInZlciI6IjIuMCIsImFua19rdiI6MSwidGlkIjoiN2QzYTljMWUtNGIyZi00ZThhLTljNzMtMjc1MDAwMDAwMDExIiwiaWF0IjoxNzkwNjU2MjQwfQ","signature":"DOcxA9VrbL_UUZeJ7UNGaT4tvXaPYJVeGYJMqVnzrsBrumtnB7RN-YeMRIDlijN9YP1jBadLybT0AwR5wLxOZ-g4nQ2gWQDBlouE4WyIB5H_MWl2cajjGXcNpcCM5r51kX1YLp5cvGgAO3DqQ81SDinIkthR_tGEH3TmElGCrpxuQrE0lm-BJRj_xypnxv3d88eYJxneymMP0dOokvWUCUfuajXHyLLwh2nNLPYLudRLDTxKhXwk_vsELvq9bEcf0u0ACV1HpQGdjWf6ggzt4ziHPuXEzzzQFAl4ALVSD9wNETT70uaFra0mwXptsLn4cfv5aq7WRv6m_xbWeCVyZhNnZOMqbIwCNzwhyv_Tp1-HpVxsOIehN2bGrBSp07X000qyNREDbAa0CxLl-5AnvF8g_oN-nuAt1dStwMb769wo27SECtg1REot-Mk1Xi7eZ5bnQhkcmJeULtzxLdT-6hk-Obe2HTTF1J9S6GSmDZ1GQc0e5hL3eHfDHyqmBbGZ"},"minLength":1},"newKid":{"type":"string","description":"Public-key identifier (kid) that will sign the new JWS. For a JWS signed by several keys it must be none of the signers the operation keeps (400 naming this member otherwise).","example":"myNewSignKid","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"sourceKid":{"type":["string","null"],"description":"The signer of the JWS this operation replaces, named by its key identifier. When the JWS protected header names a <code>kid</code> it is optional and, present, must equal it; when the header names none it is required and names the key that signed (not a COMPOSITE key). For a JWS signed by several keys it is required and names the ONE signer whose signature is replaced. Otherwise the request is refused with 400 naming this member and no key. Absence is expressed by OMITTING the member or sending <code>null</code>; an empty string is not a key identifier and is rejected with 400.","example":"legacySignKeyV1","maxLength":255,"minLength":0,"pattern":"^[A-Za-z0-9._-]+$"},"validityDays":{"type":"integer","format":"int64","description":"Optional validity period in days for the new signature of the `newKid`, SIMPLE or COMPOSITE alike (exp member of its protected header), whatever the source's kind. From 1 to 3650 days, and at most this deployment's configured maximum signature validity (`ankasecure.jws.local-clock.max-validity-days`, 730 by default): a value above that maximum is refused with 400 invalid-input naming `validityDays`.","example":365,"maximum":3650,"minimum":1}},"required":["newKid","oldJws"]},"JwetGeneralJsonHeaderReencrypt":{"type":"object","description":"Detached General-JSON JWET header used by the streaming\nre-encryption endpoint.<br>\nEach header parameter is in one location (RFC 7516 §7.2.1). The <code>kid</code>, in\n<code>protected</code> or the one recipient's <code>header</code>, is optional: when\nit is absent, the <code>sourceKid</code> query parameter names the source key.","properties":{"protected":{"type":"string","description":"Base64url-encoded protected header."},"iv":{"type":"string","description":"Base64url-encoded IV of the AEAD algorithm."},"ciphertext":{"type":["string","null"],"description":"Detached ciphertext: absent or null (it travels in the body); a value is refused."},"tag":{"type":["string","null"],"description":"Detached authentication tag: absent or null when it is the last 16 octets of the body, or the Base64URL tag when the body is the ciphertext alone."},"recipients":{"type":"array","description":"The recipients; a single-key header carries one (its kid optional, see sourceKid).","items":{"$ref":"#/components/schemas/RecipientLegacy"}}},"required":["iv","protected","recipients"]},"RecipientHeaderLegacy":{"type":"object","properties":{"kid":{"type":["string","null"],"description":"Key identifier (kid), when `protected` names none."},"alg":{"type":"string","description":"Key-encapsulation / wrap algorithm (e.g. RSA-OAEP-512). Present only when `protected` names none."}}},"RecipientLegacy":{"type":"object","properties":{"header":{"$ref":"#/components/schemas/RecipientHeaderLegacy","description":"Per-recipient header. Absent when every member is in `protected` (the integrity-protected form every single-recipient JWE this platform writes); otherwise it carries the `alg` and `kid` `protected` does not. A name in both is refused (RFC 7516 §7.2.1)."},"encrypted_key":{"type":"string","description":"Base64url-encoded JWE Encrypted Key for this recipient. Present only when the key is non-empty (RFC 7516 §7.2.1): a recipient whose `alg` is `dir` (every symmetric key) carries no `encrypted_key` member."}}},"EncryptStreamRequest":{"type":"object","description":"Metadata for the streaming encryption endpoint.","properties":{"kid":{"type":"string","description":"Public-key identifier (kid) that will receive the CEK.","example":"myPublicKid","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"}},"required":["kid"]},"JwetGeneralJsonHeaderDecrypt":{"type":"object","description":"Detached General-JSON JWET header used by the streaming\ndecryption endpoint – the <code>kid</code> is\n<strong>mandatory</strong>, read from <code>protected</code> first and otherwise from the\none recipient's header.","properties":{"protected":{"type":"string","description":"Base64url-encoded protected header."},"iv":{"type":"string","description":"Base64url-encoded IV of the AEAD algorithm."},"ciphertext":{"type":["string","null"],"description":"Detached ciphertext: absent or null (it travels in the body); a value is refused."},"tag":{"type":["string","null"],"description":"Detached authentication tag: absent or null when it is the last 16 octets of the body, or the Base64URL tag when the body is the ciphertext alone."},"recipients":{"type":"array","description":"Exactly one recipient describing the wrapped CEK.","items":{"$ref":"#/components/schemas/RecipientManaged"}}},"required":["iv","protected","recipients"]},"RecipientHeaderManaged":{"type":"object","properties":{"kid":{"type":"string","description":"Key identifier (kid), when `protected` names none; refused when it does."},"alg":{"type":"string","description":"Key-encapsulation / wrap algorithm (e.g. RSA-OAEP-256), when `protected` names none; refused when it does."}}},"RecipientManaged":{"type":"object","properties":{"header":{"$ref":"#/components/schemas/RecipientHeaderManaged","description":"Per-recipient header. Absent when every member is in `protected` (the integrity-protected form); otherwise it carries the `alg` and `kid` `protected` does not. A name in both is refused (RFC 7516 §7.2.1)."},"encrypted_key":{"type":"string","description":"Base64url-encoded JWE Encrypted Key for this recipient. Present only when the key is non-empty (RFC 7516 §7.2.1): a recipient whose `alg` is `dir` (every symmetric key) carries no `encrypted_key` member."}}},"SignRequest":{"type":"object","description":"Request object for signing Base64-encoded data with optional timestamp and expiration.","properties":{"kid":{"type":"string","description":"The kid of the private key used to sign the data.","example":"mySigningKeyKid","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"data":{"type":"string","description":"The raw data in Base64 to be signed.","example":"U2lnblRoaXNEYXRh","minLength":1},"validityDays":{"type":["integer","null"],"format":"int64","description":"Optional validity period in days for the signature, for a SIMPLE and a COMPOSITE signature key alike: it sets `exp` = `iat` + validityDays × 86400. A SIMPLE key states it as the JWS `exp` claim; a COMPOSITE key states it as the `exp` member of its signed protected header, after `iat`. If not specified, the signature does not expire. From 1 to 3650 days, and at most this deployment's configured maximum signature validity (`ankasecure.jws.local-clock.max-validity-days`, 730 by default): a value above that maximum is refused with 400 invalid-input naming `validityDays`.","example":365,"maximum":3650,"minimum":1},"serialization":{"type":["string","null"],"description":"Optional JWS serialization representation to produce. COMPACT = RFC 7515 §7.1 compact (3 dot-separated segments); JSON = RFC 7515 §7.2 JWS JSON Serialization (a JSON object able to carry a JAdES qualified timestamp). Omit to let the server choose the standards-correct representation: compact when no timestamp is mandated, JWS-JSON when the deployment/tenant policy requires an RFC 3161 signature timestamp (JAdES B-T). This choice is orthogonal to the backend-authoritative timestamp policy and never adds or drops a timestamp.","enum":["COMPACT","JSON"]}},"required":["data","kid"]},"SignJwsResponse":{"type":"object","description":"Response object returned by the /sign endpoint.\n\n• <b>jwsToken</b> is a typed JWS General JSON Serialization\n  (RFC 7515 §7.2) object carrying exactly one signature for\n  every key, SIMPLE or COMPOSITE.\n• <b>keyRequested</b> echoes the <code>kid</code> originally\n  supplied by the caller (the Stable KID — fixed across rotation).\n• <b>materialVersion</b> is the key-material version (JOSE\n  <code>ank_kv</code>) that produced the signature; absent when\n  the version is unknown.\n• <b>algorithmUsed</b> is the catalogue algorithm of the signing key\n  (e.g. <code>RSA-3072</code>, <code>ML-DSA-87</code>); for a COMPOSITE\n  key it is the catalogue pairing name (e.g. <code>Ed25519+ML-DSA-65</code>).\n• <b>warnings</b> lists non-fatal advisories (sunset notices,\n  usage counter limits, rotation recommended).\n","properties":{"jwsToken":{"$ref":"#/components/schemas/GeneralJsonJws","description":"The produced JWS, always carried as a typed JWS General JSON Serialization (RFC 7515 §7.2) object. The client's optional `serialization` request field and the backend-authoritative timestamp policy determine the JWS representation WITHIN this field — a COMPACT/unstamped signature is a single-signature object with no JAdES `sigTst` (losslessly convertible to the 3-segment compact form), while a JAdES B-T signature carries the RFC 3161 `sigTst` in the unprotected header. The surrounding metadata envelope below is ALWAYS present regardless of serialization."},"keyRequested":{"type":"string","description":"Key identifier requested by the client.","example":"mySigningKeyKid"},"materialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) that produced the signature. Absent when the version is unknown.","example":2},"algorithmUsed":{"type":"string","description":"Catalogue algorithm of the signing key (e.g. RSA-3072, ML-DSA-87; for a COMPOSITE key its pairing name, e.g. Ed25519+ML-DSA-65).","example":"ML-DSA-87"},"warnings":{"type":"array","description":"Optional warnings (usage counters, sunset notices, …).","items":{"type":"string"}}},"required":["jwsToken"]},"SignEncryptRequest":{"type":"object","description":"Request for sign-then-encrypt operation producing nested JWE(JWS).","properties":{"data":{"type":"string","description":"Base64-encoded data to sign and encrypt","example":"SGVsbG8gQW5rYQ==","minLength":1},"signKid":{"type":"string","description":"Key identifier for signing operation","example":"mySigningKey","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"encryptKid":{"type":"string","description":"Key identifier for encryption operation","example":"myEncryptionKey","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"validityDays":{"type":["integer","null"],"format":"int64","description":"Optional validity period in days for the inner signature of the `signKid`, SIMPLE or COMPOSITE alike: it sets the inner JWS `exp` (the claim for a SIMPLE key, the signed protected header member for a COMPOSITE key). From 1 to 3650 days, and at most this deployment's configured maximum signature validity (`ankasecure.jws.local-clock.max-validity-days`, 730 by default): a value above that maximum is refused with 400 invalid-input naming `validityDays`.","example":365,"maximum":3650,"minimum":1},"serialization":{"type":["string","null"],"description":"Optional JWS serialization representation to produce for the INNER signed JWS before it is encrypted into the outer JWE. COMPACT = RFC 7515 §7.1 compact; JSON = RFC 7515 §7.2 JWS JSON Serialization (able to carry a JAdES qualified timestamp). Omit to let the server choose the standards-correct representation: compact when no timestamp is mandated, JWS-JSON when the deployment/tenant policy requires an RFC 3161 signature timestamp (JAdES B-T). Governs only the inner JWS representation; it never adds or drops a timestamp and does not change the outer JWE. Requesting COMPACT while the backend-authoritative policy mandates a timestamp is the single incompatible combination and is rejected with HTTP 400 before any signing or encryption is performed.","enum":["COMPACT","JSON"]}},"required":["data","encryptKid","signKid"]},"GeneralJsonJwe":{"type":"object","description":"RFC 7516 §7.2 General JSON Serialization JWE — the single ANKASecure wire format for JWE.","properties":{"protected":{"type":"string","description":"Base64url-encoded JOSE protected header shared by all recipients.","example":"eyJlbmMiOiJBMjU2R0NNIn0","minLength":1},"recipients":{"type":"array","description":"Per-recipient header + wrapped CEK array: length 1 for a JWE addressed to one SIMPLE key, one entry per component for a COMPOSITE key's JWE, and one entry per key, each naming its \"kid\", for a JWE addressed to several keys (RFC 7516 §7.2.1).","items":{"$ref":"#/components/schemas/Recipient"},"minItems":1},"iv":{"type":"string","description":"Base64url-encoded initialization vector.","example":"48V1_ALb6US04U3b","minLength":1},"ciphertext":{"type":"string","description":"Base64url-encoded AEAD ciphertext.","example":"5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4","minLength":1},"tag":{"type":"string","description":"Base64url-encoded AEAD authentication tag.","example":"XFBoMYUZodetZdvTiFvSkQ","minLength":1}},"required":["ciphertext","iv","protected","recipients","tag"]},"Recipient":{"type":"object","description":"Per-recipient header + wrapped CEK per RFC 7516 §7.2.1.","properties":{"header":{"type":"object","additionalProperties":{},"description":"Per-recipient header (alg, kid, …). OPTIONAL per RFC 7516 §7.2.1 — MUST be absent for single-recipient JWEs that carry all JOSE parameters in the JWE-level `protected` header (typical SIMPLE-key case). REQUIRED only for multi-recipient JWEs where each recipient has its own algorithm/kid (COMPOSITE).","example":{"alg":"RSA-OAEP-512","kid":"my-rsa-3072-key"}},"encrypted_key":{"type":"string","description":"Base64url-encoded JWE Encrypted Key (the wrapped Content Encryption Key, or a composite component's KEM encapsulation). Per RFC 7516 §7.2.1 it is present when the JWE Encrypted Key is non-empty and ABSENT otherwise: a recipient whose protected `alg` is `dir` (every symmetric key) carries no `encrypted_key` member. On input an absent member and an empty string are both read as the empty key; an explicit null is not a string and is refused.","example":"OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQ"}}},"SignEncryptResponse":{"type":"object","description":"Response containing nested JWE(JWS) payload with separated sign and encrypt metadata.","properties":{"jweToken":{"$ref":"#/components/schemas/GeneralJsonJwe","description":"Outer JWE General JSON Serialization (RFC 7516 §7.2) that encrypts\nthe inner JWS produced by the signing leg. The client's optional\n`serialization` request field governs the representation of\nthat INNER JWS (compact vs JWS-JSON, and whether it carries a JAdES\n`sigTst` when the timestamp policy applies) before it is encrypted;\nit does not change this outer JWE. The separated sign/encrypt metadata\nenvelope below is ALWAYS present regardless of serialization.\n"},"signKeyRequested":{"type":"string","description":"Signing key identifier requested by client","example":"mySignKey"},"signMaterialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) of the SIGNING key — its current PRIMARY. Absent when the version is unknown.","example":2},"signAlgorithmUsed":{"type":"string","description":"Signing algorithm used","example":"ML-DSA-87"},"signWarnings":{"type":"array","description":"Warnings from signing operation","items":{"type":"string"}},"encryptionKeyRequested":{"type":"string","description":"Encryption key identifier requested by client","example":"myEncKey"},"encryptMaterialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) of the ENCRYPTION key — its current PRIMARY. Absent when the version is unknown.","example":3},"encryptionAlgorithmUsed":{"type":"string","description":"Catalogue algorithm of the encryption key (a COMPOSITE key reports its pairing name)","example":"ML-KEM-1024"},"encryptionWarnings":{"type":"array","description":"Warnings from encryption operation","items":{"type":"string"}}},"required":["jweToken"]},"ResignJwsRequest":{"type":"object","description":"Request to verify an existing JWS under <code>oldKid</code>\n(embedded in the protected header) and sign the <em>same payload</em>\nwith <code>newKid</code>. <code>sourceKid</code> names the signer this operation\nreplaces: when the protected header names a <code>kid</code> it is optional and must equal\nit; when the header names none it is required and names the key. Supplying the key that\nway, it may not name a COMPOSITE key, whose signature names its key in the protected header.\nFor a JWS signed by several keys, <code>sourceKid</code> is required and names the ONE\nsigner whose signature is replaced; every signature is verified first, and every other one\nis returned exactly as received.\n\nUses JWS General JSON Serialization (RFC 7515 §7.2) uniformly — exactly one\nsignature for every key, SIMPLE or COMPOSITE; a JWS signed by several keys carries one per key.\n","properties":{"jwsToken":{"$ref":"#/components/schemas/GeneralJsonJws","description":"Source JWS General JSON Serialization (RFC 7515 §7.2) signed with the old key."},"newKid":{"type":"string","description":"kid of the private key that must produce the new signature. For a JWS signed by several keys it must be none of the signers the operation keeps (400 naming this member otherwise). Given at most once.","example":"myNewSigningKid","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"sourceKid":{"type":"string","description":"The signer of the JWS this operation replaces, named by its key identifier. When the\nprotected header names a <code>kid</code> this member is optional and, present, must\nequal it. When the header names none it is required and names the key that signed. A\nvalue naming no signer, or absent where required, is refused with 400 naming this member\nand no key. Supplying the key for a header that names none, it may not name a COMPOSITE\nkey. For a JWS signed by several keys it is required and names the ONE signer whose\nsignature is replaced. Given at most once.\n","example":"legacySignKeyV1","maxLength":255,"minLength":0,"pattern":"^[A-Za-z0-9._-]+$"},"serialization":{"type":["string","null"],"description":"Optional JWS serialization representation to produce for the re-signed token. COMPACT = RFC 7515 §7.1 compact (3 dot-separated segments); JSON = RFC 7515 §7.2 JWS JSON Serialization (able to carry a JAdES qualified timestamp). Omit to let the server choose the standards-correct representation: compact when no timestamp is mandated, JWS-JSON when the deployment/tenant policy requires an RFC 3161 signature timestamp (JAdES B-T). Governs the re-sign output only; it never adds or drops a timestamp. Requesting COMPACT while the backend-authoritative policy mandates a timestamp is the single incompatible combination and is rejected with HTTP 400.","enum":["COMPACT","JSON"]}},"required":["jwsToken","newKid"]},"ResignJwsResponse":{"type":"object","description":"Response body returned after JWS re-signing.","properties":{"jwsToken":{"$ref":"#/components/schemas/GeneralJsonJws","description":"The re-signed JWS, always carried as a typed JWS General JSON Serialization (RFC 7515 §7.2) object. The client's optional `serialization` request field and the backend-authoritative timestamp policy determine the JWS representation WITHIN this field — a COMPACT/unstamped result is a single-signature object with no JAdES `sigTst` (losslessly convertible to the 3-segment compact form), while a JAdES B-T result carries the RFC 3161 `sigTst` in the unprotected header. The re-sign metadata envelope below is ALWAYS present regardless of serialization."},"oldKeyRequested":{"type":"string","description":"kid that was requested for verification (the source Stable KID)."},"sourceMaterialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) of the SOURCE key that signed the incoming data. Absent when the version is unknown.","example":1},"oldKeyAlgorithmUsed":{"type":"string","description":"Algorithm used for verification."},"newKeyRequested":{"type":"string","description":"kid that was requested for signing (the target Stable KID)."},"targetMaterialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) of the TARGET key — its current PRIMARY — that produced the new signature. Absent when the version is unknown.","example":2},"newKeyAlgorithmUsed":{"type":"string","description":"Algorithm used for the new signature."},"signatureResults":{"type":"array","description":"Every signature of the input JWS, in token order, each verified under its own key before the new signature was computed: its key, algorithm and material version. One entry for a JWS signed by one key; one per signature for a JWS signed by several, including the one replaced. Every entry reports verified = true: an input with any signature that does not verify is refused with 400 and nothing is signed.","items":{"$ref":"#/components/schemas/SignatureResult"}},"warnings":{"type":"array","description":"Non-fatal warnings (usage limits, expiry…).","items":{"type":"string"}},"migrationMode":{"type":"boolean","description":"true when the incoming JWS header named no key and <code>sourceKid</code>\nnamed it. Absent otherwise.\n","example":true}},"required":["jwsToken","signatureResults"]},"ReencryptJweRequest":{"type":"object","description":"Request body for JWE re-encryption. Carries the source RFC 7516 §7.2\nGeneral JSON Serialization payload plus the target key identifier\n(<code>newKid</code>). <code>sourceKid</code> names the recipient\nthis operation replaces: required when the JWE is addressed to\nseveral keys, equal to the JWE's own key when it names one, and the\nkey itself when the JWE names none.\n","properties":{"jweToken":{"$ref":"#/components/schemas/GeneralJsonJwe","description":"Source JWE General JSON Serialization payload to re-encrypt."},"newKid":{"type":"string","description":"Target key identifier that will protect the new JWE.","example":"my-new-mlkem-kid","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"sourceKid":{"type":"string","description":"The recipient of the JWE this operation replaces, named by its key identifier: its\n<code>encrypted_key</code> is the one unwrapped, and <code>newKid</code> takes its\nposition. <b>Required</b> when the JWE is addressed to several keys (several recipients,\neach naming its key in its own header, and no <code>kid</code> in <code>protected</code>),\nwhere it must name exactly one of them. When the JWE names one key it is optional and,\npresent, must equal that key. When the JWE names no key it is required and names the key\nthe JWE was produced for. Otherwise the request is refused with 400 naming this member and\nno key. Given at most once.\n","example":"acme-archive-r2","maxLength":255,"minLength":0,"pattern":"^[A-Za-z0-9._-]+$"}},"required":["jweToken","newKid"]},"ReencryptJweResponse":{"type":"object","description":"Response body returned after JWE re-encryption.","properties":{"jweToken":{"$ref":"#/components/schemas/GeneralJsonJwe","description":"Re-encrypted JWE General JSON Serialization payload (RFC 7516 §7.2)."},"oldKeyRequested":{"type":"string","description":"kid extracted from the *incoming* JWE header (the source Stable KID).","example":"myOldRsaKid"},"sourceMaterialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) of the SOURCE key that encrypted the incoming data. Absent when the version is unknown.","example":1},"oldKeyAlgorithmUsed":{"type":"string","description":"Algorithm of the key used for decryption.","example":"RSA-2048"},"newKeyRequested":{"type":"string","description":"kid requested by the caller for encryption (the target Stable KID).","example":"myNewMlkemKid"},"targetMaterialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) of the TARGET key — its current PRIMARY — that re-encrypted the data. Absent when the version is unknown.","example":2},"newKeyAlgorithmUsed":{"type":"string","description":"Algorithm of the key used for encryption.","example":"ML-KEM-1024"},"warnings":{"type":"array","description":"Aggregated warning messages (usage limit, expiry…).","items":{"type":"string"}},"errorMessage":{"type":"string","description":"Set only when the operation failed partially."},"migrationMode":{"type":"boolean","description":"true when the incoming JWE named no key and <code>sourceKid</code>\nnamed it. Absent otherwise.\n","example":true},"recipients":{"type":"array","description":"One entry per KEY the produced JWE is addressed to, in its order: the key, the version of\nthe material its content-encryption key was wrapped to, and that key's algorithm. For a JWE\naddressed to several keys there is one entry per recipient: <code>newKid</code> sits at the\nposition of the replaced recipient and every other recipient is re-wrapped at the PRIMARY\nits write gate judged; the versions are the ones the JWE's protected header records for each\nrecipient. A JWE addressed to one key lists that key once, including a composite key's JWE,\nwhose several <code>recipients</code> entries (one per component) all belong to that one key.\n","items":{"$ref":"#/components/schemas/ReencryptedRecipient"}}},"required":["jweToken","recipients"]},"ReencryptedRecipient":{"type":"object","description":"One key the re-encrypted JWE is addressed to, in the JWE's order: one entry per recipient of a JWE addressed to several keys, and one entry for a JWE addressed to one key, a composite key's included.","properties":{"kid":{"type":"string","description":"Key identifier the recipient names.","example":"acme-archive-r1"},"materialVersion":{"type":["integer","null"],"format":"int32","description":"Version of the key material the recipient's content-encryption key was wrapped to: the PRIMARY the key's write gate judged when the re-encryption ran.","example":3},"algorithm":{"type":"string","description":"Algorithm of the recipient's key.","example":"RSA-3072"}},"required":["algorithm","kid"]},"EncryptRequest":{"type":"object","description":"Request object for encrypting Base64-encoded data.","properties":{"kid":{"type":"string","description":"The kid of the key used to encrypt the data.","example":"myKeyKid","maxLength":255,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$"},"data":{"type":"string","description":"The raw data in Base64 format to be encrypted.","example":"SGVsbG8gQW5rYQ==","minLength":1}},"required":["data","kid"]},"EncryptJweResponse":{"type":"object","description":"Response object returned by the /encrypt endpoint.\n\n• <b>jweToken</b> is a typed JWE General JSON Serialization\n  (RFC 7516 §7.2) object — uniform shape across SIMPLE and\n  COMPOSITE keys (recipients[] length 1 vs N).\n• <b>keyRequested</b> echoes the <code>kid</code> originally\n  supplied by the caller (the Stable KID — fixed across rotation).\n• <b>materialVersion</b> is the key-material version (the JOSE\n  <code>ank_kv</code> protected-header claim) that performed the\n  operation; absent when the version is unknown.\n• <b>algorithmUsed</b> is the catalogue algorithm of the key that\n  encrypted (e.g. <code>RSA-3072</code>, <code>ML-KEM-1024</code>); for\n  a COMPOSITE key it is the catalogue pairing name (e.g.\n  <code>X25519+ML-KEM-768</code>).\n• <b>warnings</b> lists non-fatal advisories (soft usage limit,\n  impending expiry, rotation recommended, etc.).\n","properties":{"jweToken":{"$ref":"#/components/schemas/GeneralJsonJwe","description":"JWE General JSON Serialization payload (RFC 7516 §7.2)."},"keyRequested":{"type":"string","description":"Key identifier that the client requested.","example":"my_original_kid"},"materialVersion":{"type":"integer","format":"int32","description":"Key-material version that performed the encryption (the JOSE\n\"ank_kv\" protected-header claim). The kid never changes across\nrotation; the material version is what advances. Absent when the\nversion is unknown (e.g. utility mode).\n","example":2},"algorithmUsed":{"type":"string","description":"Catalogue algorithm of the key that encrypted (e.g. \"ML-KEM-1024\",\n\"RSA-3072\"); for a COMPOSITE key, its pairing name (e.g. \"X25519+ML-KEM-768\").\n","example":"ML-KEM-1024"},"warnings":{"type":"array","description":"Optional list of warnings (soft expiry, usage quota, …).","items":{"type":"string"}}},"required":["algorithmUsed","jweToken","keyRequested","warnings"]},"DecryptJweRequest":{"type":"object","description":"Request body for decrypting a JWE General JSON Serialization\n(RFC 7516 §7.2) payload. The token contains the key-encryption\nalgorithm (<code>alg</code>), content-encryption algorithm\n(<code>enc</code>) and all cryptographic parameters (IV, wrapped\nCEK, ciphertext, authentication tag). When the token is addressed\nto several keys, <code>recipientKid</code> names the recipient this\ndecryption reads.\n","properties":{"jweToken":{"$ref":"#/components/schemas/GeneralJsonJwe","description":"RFC 7516 §7.2 General JSON Serialization JWE to be decrypted."},"recipientKid":{"type":"string","description":"The recipient of the JWE this decryption reads, named by its key identifier.             <b>Required</b> when the JWE is addressed to several keys (several recipients, each naming             its key in its own header, and no <code>kid</code> in <code>protected</code>): exactly one             <code>authorise(DECRYPT)</code> and one key-lifecycle decision are taken, on this key only,             and only its <code>encrypted_key</code> is unwrapped - no other recipient is ever tried.             Optional for a single-recipient JWE and for a composite key's JWE; when present it must             equal the one key the JWE names. Absent where required, or naming no recipient of the             JWE, the request is refused with 400 naming this member and no key.","example":"acme-archive-r2","maxLength":255,"minLength":0,"pattern":"^[A-Za-z0-9._-]+$"}},"required":["jweToken"]},"DecryptResponse":{"type":"object","description":"Response containing decrypted data or an error message.","properties":{"decryptedData":{"type":"string","description":"The decrypted data in Base64 format."},"errorMessage":{"type":"string","description":"An error message if the decryption failed."},"keyRequested":{"type":"string","description":"The key identifier requested by the client."},"materialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) that encrypted the data. Absent when the version is unknown.","example":2},"algorithmUsed":{"type":"string","description":"The algorithm actually used for decryption (e.g., ML-KEM-768)."},"warnings":{"type":"array","description":"A list of warning messages (e.g., usage limit, key expired).","items":{"type":"string"}}}},"DecryptVerifyRequest":{"type":"object","description":"Request body for the decrypt-then-verify operation: unwraps a nested JWE-of-JWS payload.","properties":{"jweToken":{"$ref":"#/components/schemas/GeneralJsonJwe","description":"Outer JWE General JSON Serialization (RFC 7516 §7.2) containing\nan inner JWS payload. The decrypting key is the JWE's own key, or\nthe recipient recipientKid names when the JWE is addressed to\nseveral keys; every inner signer is read from its own JWS\nprotected header (RFC 7515 §7.2.1).\n"},"recipientKid":{"type":"string","description":"The recipient of the outer JWE this operation decrypts with, named by its key identifier. <b>Required</b> when the outer JWE is addressed to several keys (several recipients, each naming its key in its own header, and no <code>kid</code> in <code>protected</code>): exactly one <code>authorise(DECRYPT)</code> and one key-lifecycle decision are taken on this key, and only its <code>encrypted_key</code> is unwrapped - no other recipient is ever tried. Optional for a single-recipient JWE and for a composite key's JWE; when present it must equal the one key the JWE names. It must be none of the inner JWS's signers. Absent where required, or naming no recipient of the JWE, the request is refused with 400 naming this member and no key.","example":"acme-legal-rsa","maxLength":255,"minLength":0,"pattern":"^[A-Za-z0-9._-]+$"}},"required":["jweToken"]},"DecryptVerifyResponse":{"type":"object","description":"Response from decrypt-then-verify: the plaintext, the key the outer JWE was decrypted with, and one entry per signature of the inner JWS.","properties":{"data":{"type":"string","description":"Base64-encoded decrypted and verified plaintext data","example":"SGVsbG8gQW5rYQ=="},"decryptKeyRequested":{"type":"string","description":"Decryption key identifier requested by client","example":"myDecryptKey"},"decryptMaterialVersion":{"type":"integer","format":"int32","description":"Key-material version (JOSE ank_kv) that encrypted the outer JWE. Absent when the version is unknown.","example":2},"decryptAlgorithmUsed":{"type":"string","description":"Catalogue algorithm of the decryption key (a COMPOSITE key reports its pairing name)","example":"ML-KEM-1024"},"decryptWarnings":{"type":"array","description":"Warnings from the decryption and from the verification of every inner signature","items":{"type":"string"}},"signatureResults":{"type":"array","description":"One entry per signature of the inner JWS, in token order (RFC 7515 §7.2.1): its key, that key's catalogue algorithm, the material version it resolved to, and whether it verified. One entry for an inner JWS signed by one key, N for one signed by N keys. A response is returned only when every signature verified.","items":{"$ref":"#/components/schemas/SignatureResult"}},"qualifiedTimestamp":{"$ref":"#/components/schemas/QualifiedTimestamp","description":"Embedded RFC 3161 qualified timestamp from the inner JWS, when present. Informational — never affects the verification result."}},"required":["data","signatureResults"]},"LicenseValidationMetrics":{"type":"object","properties":{"unvalidatedRequestsAllowed":{"type":"integer","format":"int32"},"strictlyValidatedRequests":{"type":"integer","format":"int32"},"staleVerdictsServed":{"type":"integer","format":"int32"},"maxStalenessRefusals":{"type":"integer","format":"int32"},"validationRate":{"type":"number","format":"double"}}},"SupportedAlgorithm":{"type":"object","description":"Algorithm profile supported by the platform, including cryptographic capabilities, security classification, and lifecycle status.\n\nCore fields: kty (key type), alg (algorithm), keyOps (permitted operations)\n\nLifecycle: status (RECOMMENDED/LEGACY), sunsetDate, advisory\n\nSecurity: securityLevel (NIST 1-5), standards (endorsing bodies), category (CLASSICAL/POST_QUANTUM/HYBRID)\n\nComposite: compositeMode, present only for a hybrid entry — absent means the algorithm is not composite\n","properties":{"kty":{"type":"string","description":"JWK key type identifier (e.g., ML-KEM, ML-DSA, RSA, EC, oct, COMPOSITE). A hybrid algorithm publishes the coarse COMPOSITE token; how it is constructed is carried by compositeMode and its exact pairing by alg.","example":"ML-KEM"},"alg":{"type":"string","description":"Algorithm identifier specifying the exact variant (e.g., ML-KEM-768, RSA-4096, AES-256, X25519+ML-KEM-768)","example":"ML-KEM-768"},"keyOps":{"type":"array","description":"Permitted JWK operations for this algorithm","example":["encrypt","decrypt"],"items":{"type":"string"}},"status":{"type":"string","description":"Lifecycle status: RECOMMENDED for active use, EXPERIMENTAL for testing, LEGACY for deprecated algorithms","enum":["RECOMMENDED","EXPERIMENTAL","LEGACY"],"example":"RECOMMENDED"},"sunsetDate":{"type":"string","format":"date","description":"Planned deprecation date (ISO 8601). Present only for LEGACY or soon-to-be-deprecated algorithms.","example":"2030-12-31"},"advisory":{"type":"string","description":"Migration guidance or security advisory message.","example":"Consider migrating to post-quantum alternatives."},"securityLevel":{"type":"integer","format":"int32","description":"NIST security level (1-5). Level 1 = 128-bit, Level 3 = 192-bit, Level 5 = 256-bit equivalent.","example":3,"maximum":5,"minimum":1},"standards":{"type":"array","description":"Cryptographic standards and regulatory bodies that endorse this algorithm, in CryptoStandard declaration order.","example":["NIST","ETSI","BSI","ANSSI","ENISA","IETF","CRYPTREC"],"items":{"type":"string"}},"category":{"type":"string","description":"Algorithm category: CLASSICAL for traditional cryptography, POST_QUANTUM for quantum-resistant algorithms, HYBRID for composite algorithms combining both.","enum":["CLASSICAL","POST_QUANTUM","HYBRID"],"example":"POST_QUANTUM"},"compositeMode":{"type":"string","description":"Composite construction mode. Present only for a hybrid algorithm; its absence means the algorithm is not composite.","enum":["COMPOSITE_KEM_COMBINE","COMPOSITE_SIGNATURE"],"example":"COMPOSITE_KEM_COMBINE"}},"required":["alg","category","keyOps","kty","securityLevel","standards","status"]},"KeyPageResponse":{"type":"object","description":"  Paginated envelope for cryptographic key listings. Filters apply server-side\n  BEFORE pagination: totalElements is the filtered, tenant-scoped count.\n  Each key is summarized in a KeySummaryResponse, omitting private/sensitive data.\n","example":{"content":[{"kid":"cli-ml-kem-768","kty":"ML-KEM","alg":"ML-KEM-768","status":"active","createdAt":"2026-06-12T20:36:30.028Z","expiresAt":"2028-06-12T20:36:30.028Z","usageCount":0,"exportable":true},{"kid":"cli-rsa-2048","kty":"RSA","alg":"RSA-2048","status":"active","createdAt":"2026-06-12T20:36:27.923Z","expiresAt":"2028-06-12T20:36:27.923Z","usageCount":4,"exportable":true}],"totalElements":41,"totalPages":21,"number":0},"properties":{"content":{"type":"array","description":"The keys on this page, each described by a KeySummaryResponse model.","items":{"$ref":"#/components/schemas/KeySummaryResponse"}},"totalElements":{"type":"integer","format":"int64","description":"Total number of keys matching the filters across ALL pages (filtered, tenant-scoped count).","example":41},"totalPages":{"type":"integer","format":"int32","description":"Total number of pages at the requested page size.","example":21},"number":{"type":"integer","format":"int32","description":"Zero-based index of this page.","example":0}}},"KeySummaryResponse":{"type":"object","description":"Key metadata response. The 'kty' field indicates the key type: simple algorithm keys (oct, RSA, EC, ML-KEM, etc.) or the single coarse token COMPOSITE. A composite key's construction is reported by the separate 'compositeMode' member. Optional fields are omitted when null.\n","properties":{"kid":{"type":"string","description":"Unique key identifier (kid).","example":"myRsaKey"},"uuid":{"type":"string","description":"Stable identity of the key: assigned when the key is created, kept across every rotation of its kid, and never reused, so a kid taken again after its key is destroyed belongs to a key with a new identity. It is the only member that tells apart rows sharing a kid: a live key and the Deleted-view row of a destroyed key that once held the same kid, or two Deleted-view rows of a kid reused twice. Present on every row of the key list and on the key-metadata read; not stated on the responses of key generation and rotation.","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479"},"type":{"type":"string","description":"Key structure type.\n- SIMPLE: Single-algorithm key (kty indicates algorithm family)\n- COMPOSITE: Hybrid key combining classical and PQC algorithms\nAbsent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.\n","enum":["SIMPLE","COMPOSITE"],"example":"SIMPLE"},"kty":{"type":"string","description":"Key type (kty) - indicates the key family or composite mode.\n\n**Simple keys:**\n- 'oct': Symmetric keys (AES, Camellia, SM4, SEED, ARIA, ChaCha20)\n- 'RSA': RSA asymmetric keys\n- 'EC': Elliptic curve keys (P-256, P-384, P-521, brainpool)\n- 'OKP': Edwards/Montgomery curves (Ed25519, Ed448, X25519, X448)\n- 'ML-KEM': Post-quantum KEM (ML-KEM-512, ML-KEM-768, ML-KEM-1024)\n- 'ML-DSA': Post-quantum signatures (ML-DSA-44, ML-DSA-65, ML-DSA-87)\n- 'FALCON': Compact PQC signatures (FALCON-512, FALCON-1024)\n- 'SLH-DSA': Stateless hash-based signatures\n- 'SM2': China GM/T signatures\n- 'HQC': Code-based PQC KEM\n\n**Composite keys:**\n- 'COMPOSITE': a hybrid key pairing a classical and a post-quantum component.\n  One token for every pairing; the pairing is named by 'alg' and the\n  construction by 'compositeMode'.\n\nAbsent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.\n","example":"ML-KEM"},"compositeMode":{"type":"string","description":"How a composite key's components are combined. Present only for composite keys; absent for simple keys and on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.\n- COMPOSITE_KEM_COMBINE: the component secrets are combined into one via the   KDF named by 'kdf'\n- COMPOSITE_SIGNATURE: the components sign as one composite signature, which   verifies only when both of them verify\n","enum":["COMPOSITE_KEM_COMBINE","COMPOSITE_SIGNATURE"],"example":"COMPOSITE_KEM_COMBINE"},"alg":{"type":"string","description":"Algorithm identifier. For simple keys: single algorithm. For composite keys: combined (e.g., X25519+ML-KEM-768). Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":"ML-KEM-768"},"kdf":{"type":"string","description":"Key Derivation Function the composite KEM key combines its component secrets with: one of HKDF-SHA256, HKDF-SHA384, HKDF-SHA512, CatKDF, CasKDF, exactly as stored. Absent for simple keys, for composite signatures, and on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":"HKDF-SHA256"},"keyOps":{"type":"array","description":"Permitted key operations. Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":["encrypt","decrypt"],"items":{"type":"string"}},"exportable":{"type":"boolean","description":"Whether the key's public material can be exported. Present on every key except a Deleted-view row (status=deleted_logical): a logically deleted key has no exportability to report, so the member is omitted rather than stated.","example":true},"origin":{"type":"string","description":"How the key was created or acquired (GENERATED, IMPORTED). Present on every key except a Deleted-view row (status=deleted_logical), which is projected from the key identity alone and states no provenance.","enum":["GENERATED","IMPORTED"],"example":"GENERATED"},"restricted":{"type":"boolean","description":"Whether the key has operational restrictions (imported with an expired certificate). Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":false},"status":{"type":"string","description":"Lifecycle state, as the lower-case wire alias of the key-status vocabulary. The accepted values are derived from the key-status enum itself, so this list cannot drift from the tokens the service emits. The LIST endpoint's <code>status</code> filter takes this same vocabulary, so a value returned here selects that key when passed straight back as <code>?status=</code>. Present on every row.","enum":["active","pending_rotation","rotated","expired","revoked","archived","deleted_logical","suspended","pending_destruction"],"example":"active"},"createdAt":{"type":"string","description":"ISO-8601 UTC creation time.","example":"2025-01-15T10:20:00Z"},"expiresAt":{"type":"string","description":"Hard expiration time (UTC), absent when the key has none. Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":"2030-01-15T10:20:00Z"},"softLimitExpiration":{"type":"string","description":"Soft-limit expiration time (UTC), absent when the key has none. Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":"2029-07-15T10:20:00Z"},"deletedAt":{"type":"string","description":"UTC time at which the key was destroyed. Present only on a Deleted-view row (status=deleted_logical), which the key list returns only when that status is requested; absent on every key the tenant still holds.","example":"2026-08-14T15:22:09Z"},"usageCount":{"type":"integer","format":"int32","description":"Current usage counter. Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":1243},"softUsageLimit":{"type":"integer","format":"int32","description":"Soft usage limit, absent when the key has none. Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone.","example":900000},"maxUsageLimit":{"type":"integer","format":"int32","description":"Hard usage limit (0 or absent = unlimited). Absent on a Deleted-view row (status=deleted_logical), which is projected from the key identity alone and states no usage limit.","example":1000000},"primaryMaterialVersion":{"type":"integer","format":"int32","description":"PRIMARY material version of the Stable KID. Populated on the rotation response to surface the ADVANCED version the rotation produced (the kid itself never changes). Omitted when the version is not resolvable, and absent on every key-list row, a Deleted-view row (status=deleted_logical) included: a destroyed key has no PRIMARY material.\n","example":2}},"required":["status"]},"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"CryptoLimitsResponse":{"type":"object","description":"Discovery of the crypto producer plaintext byte limit enforced by this deployment.","properties":{"maxPlaintextBytes":{"type":"integer","format":"int64","description":"Maximum permitted plaintext byte length accepted by the producer crypto endpoints (encrypt / sign / sign-encrypt), resolved for the calling tenant.","example":4000000}},"required":["maxPlaintextBytes"]}},"securitySchemes":{"bearerAuth":{"type":"http","description":"Supply a valid Bearer JWT issued by the Auth API.","name":"bearerAuth","scheme":"bearer","bearerFormat":"JWT"}}}}