{"openapi":"3.1.0","info":{"title":"ANKASecure PQC Handshake API","description":"API for PQC Handshake","contact":{"name":"ANKATech Support","url":"https://ankatech.co","email":"info@ankatech.co"},"version":"3.3.0"},"servers":[{"url":"https://{host}:{port}","description":"Parametrized ANKASecure PQC Handshake Server (https://host:port)","variables":{"host":{"description":"Hostname or IP","default":"demo.ankatech.co"},"port":{"description":"Listening port","default":"443"}}}],"tags":[{"name":"PQC Handshake","description":"Initialize a post-quantum key exchange: clients submit a Base64\npublic key and requested KEM algorithm; server returns a new\nAES-256 key encapsulated under that algorithm, plus a sessionId.\n"}],"paths":{"/api/v3/pqc-handshake/init":{"post":{"tags":["PQC Handshake"],"summary":"Initialize PQC handshake","description":"Receives the client's Base64-encoded PQC public key and the\ndesired KEM algorithm name.  The server will:\n1) Reserve a new sessionId.\n2) Generate a random AES-256 key.\n3) Encapsulate it using the client's public key with the specified algorithm.\n4) Store the AES key under the sessionId.\n5) Return ciphertextBase64 and sessionId in the response.\n\n<b>Session lifetime.</b> A new session is pending: it expires 5 minutes after the\nhandshake unless a request that proves an identity uses it first (an authenticated API\ncall, or the token grant that issues a token). That first use extends it to 4 hours from\nthat moment. A request that proves no identity (a preflight, an unauthenticated route)\ndoes not extend it.\n\nThe handshake is acknowledged only once the platform has durably recorded it. The server\nanswers <code>503</code> with a <code>Retry-After</code> header in two cases, and in both\nno session exists afterwards, so the client retries the whole handshake after the\nindicated interval:\n<ul>\n<li><code>pqc-session-capacity</code> — the platform holds its maximum number of pending\nsessions; <code>Retry-After</code> is the time until the oldest one expires, rounded up to a\nmultiple of 10 seconds. No key\nexchange ran.</li>\n<li><code>dependency-not-ready</code> — a required platform dependency is temporarily\nunavailable.</li>\n</ul>\nA 503 is a server-wide capacity or availability condition, not a rate limit on the\ncaller (RFC 9110 §15.6.4, <code>Retry-After</code> §10.2.3).\n","operationId":"initializeHandshake","requestBody":{"description":"JSON object containing clientPublicKeyBase64 and algorithm.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PqcHandshakeRequest"},"examples":{"PQC Handshake":{"description":"PQC Handshake","value":{"clientPublicKeyBase64":"BASE64_MLKEM_OR_HQC_PUBLIC_KEY","algorithm":"ML-KEM-768"}}}}},"required":true},"responses":{"200":{"description":"Handshake successful; returns ciphertext and sessionId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PqcHandshakeResponse"}}}},"400":{"description":"Bad Request: the body is missing or is not a valid JSON document (invalid-input), or the public key is missing, oversized or invalid, is not a public key of the requested algorithm and parameter set, or the algorithm is not one that GET /supported-algorithms lists.","content":{"application/problem+json":{}}},"406":{"description":"Not Acceptable: the Accept header excludes application/json. The response has no body."},"415":{"description":"Unsupported Media Type: the request Content-Type is not application/json (unsupported-media-type). The Accept response header names the supported type.","content":{"application/problem+json":{}}},"500":{"description":"Server error during handshake processing.","content":{"application/problem+json":{}}},"503":{"description":"Service Unavailable: the handshake was not established and no session exists. Either the platform holds its maximum number of pending PQC sessions (pqc-session-capacity; no key exchange ran) or a required platform dependency is temporarily unavailable (dependency-not-ready). Retry the whole handshake after the Retry-After interval (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying the handshake. At the session capacity: until the oldest pending session expires, rounded up to a multiple of 10 seconds (at most 300).","style":"simple","schema":{"type":"integer","example":30}}},"content":{"application/problem+json":{"examples":{"Pending PQC session capacity reached (no key exchange ran)":{"description":"Pending PQC session capacity reached (no key exchange ran)","value":{"type":"https://docs.ankatech.co/errors/pqc-session-capacity","title":"PQC Session Capacity Reached","status":503,"detail":"The platform holds its maximum number of pending PQC sessions. Retry the handshake after the indicated interval.","instance":"/api/v3/pqc-handshake/init","correlationId":"8d41f0b2-6c3e-4a57-9e21-0b7d5f3a9c64","timestamp":1760090400}},"Dependency not ready (handshake not established)":{"description":"Dependency not ready (handshake not established)","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/pqc-handshake/init","correlationId":"5b2e7c1a-93f4-4d6e-8a10-2f9c4e7b1d35"}}}}}},"413":{"description":"Payload Too Large. The JSON request body exceeds this service's limit of 262144 bytes. A request whose Content-Length is over the limit is refused before its body is read; a body sent without one is refused when the read crosses the limit. The body names only the limit, never the size that was sent.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JSON body over the service's limit":{"summary":"A request body larger than the limit, refused before it reached the operation","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Request body size exceeds the maximum allowed limit of 262144 bytes.","instance":"/api/v3/pqc-handshake/init","correlationId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","timestamp":1765057500}}}}}}}}},"/api/v3/pqc-handshake/supported-algorithms":{"get":{"tags":["PQC Handshake"],"summary":"List supported KEM algorithms","description":"Returns the KEM algorithms this deployment admits for the PQC handshake: exactly the algorithms POST /init accepts. A post-quantum catalogue KEM is listed when its status is RECOMMENDED; EXPERIMENTAL KEMs are added only when pqc.handshake.allow-experimental-kem is true. A LEGACY KEM is never listed.","operationId":"getSupportedAlgorithms","responses":{"200":{"description":"Algorithm list returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportedAlgorithmsResponse"}}}},"406":{"description":"Not Acceptable: the Accept header excludes application/json. The response has no body."}}}}},"components":{"schemas":{"PqcHandshakeRequest":{"type":"object","description":"Request object to initiate a post-quantum handshake.  Includes the\nclient's Base64-encoded public key and the KEM algorithm name\nthat the server should use for AES-256 key encapsulation.\n","properties":{"clientPublicKeyBase64":{"type":"string","description":"Base64-encoded client public key (X.509 SubjectPublicKeyInfo DER) for the post-quantum KEM. At most 29,000 characters, which admits the largest key of any admitted KEM (FrodoKEM-1344).","example":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...","maxLength":29000,"minLength":0},"algorithm":{"type":"string","description":"Identifier of the KEM algorithm (must match server support).","example":"ML-KEM-512"}}},"PqcHandshakeResponse":{"type":"object","description":"Response returned after initializing the PQC handshake. Contains the\nciphertext in Base64 (to be decapsulated by the client using its private key)\nand a sessionId used to identify the corresponding AES key on the server.\n","properties":{"ciphertextBase64":{"type":"string","description":"The ciphertext generated by encapsulating the AES-256 key with the client's public key under the requested KEM algorithm, in Base64.","example":"Q3lwaGVyZWRUZXN0Q2lwaGVydGV4dA=="},"sessionId":{"type":"string","description":"A unique session identifier referencing the stored AES-256 key on the server.","example":"6bca2d82-66bb-4f82-aab7-d206f21adcdf"}}},"SupportedAlgorithmsResponse":{"type":"object","properties":{"algorithms":{"type":"array","items":{"type":"string"}},"experimentalEnabled":{"type":"boolean"},"total":{"type":"integer","format":"int32"}}},"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 'clientPublicKeyBase64' field must not be blank."},"instance":{"type":"string","description":"URI reference to the specific occurrence.","example":"/api/v3/pqc-handshake/init"},"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."}}}}}}