Skip to main content

KEM API

The KEM (Key Encapsulation Mechanism) API provides post-quantum encryption and decryption using the Kyber768 algorithm. Qpher uses a hybrid KEM-DEM scheme: Kyber768 encapsulates a shared secret, which is then used with HKDF-SHA256 and AES-256-GCM to encrypt your data.

Private keys never leave Qpher

All decryption operations happen inside the Qpher secure enclave. Your private keys are never exposed or exported.

X-Wing hybrid KEM (Starter+)

Pass "algorithm": "X-Wing" in encrypt/decrypt requests to use hybrid KEM (X25519 + ML-KEM-768). If omitted, the default is "Kyber768" (PQC-only, backward-compatible).

X-Wing provides defense-in-depth by combining the post-quantum security of ML-KEM-768 with the battle-tested classical security of X25519. This construction follows the IETF CFRG X-Wing draft (draft-connolly-cfrg-xwing-kem).

See Security Architecture for details.


Encrypt​

POST/api/v1/kem/encryptEncrypt plaintext using Kyber768 KEM-DEM hybrid encryption.

Encrypts the provided plaintext using the KEM-DEM scheme with the specified key version. The key must be in active status.

Request body​

FieldTypeRequiredDescription
plaintextstring (base64)YesBase64-encoded data to encrypt. Maximum size: 1 MB.
key_versionintegerYesThe version of the Kyber768 key to use. Must reference an active key.
algorithmstringNo"Kyber768" (default) or "X-Wing" (hybrid, Starter+). If omitted, defaults to Kyber768.
modestringNoEncryption mode: "standard" (default) or "deterministic".
saltstring (base64)ConditionalBase64-encoded salt. Required when mode is "deterministic".
Deterministic mode

Deterministic encryption (mode: "deterministic") produces identical ciphertext for the same plaintext and salt. Use it only when you need equality checks on encrypted data. Standard mode is recommended for all other use cases.

Response (200 OK)​

FieldTypeDescription
data.ciphertextstring (base64)The encrypted data, base64-encoded.
data.key_versionintegerThe key version used for encryption.
data.algorithmstring"Kyber768" or "X-Wing", matching the algorithm used for the operation.

Example​

RequestPOST/api/v1/kem/encrypt
X-API-Key: qph_your_key_here
Content-Type: application/json
{
  "plaintext": "SGVsbG8sIFdvcmxkIQ==",
  "key_version": 1
}
Response200
{
  "data": {
    "ciphertext": "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5...",
    "key_version": 1,
    "algorithm": "Kyber768"
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2026-01-15T10:30:00.000Z"
}
Encrypt example
curl -X POST https://api.qpher.ai/api/v1/kem/encrypt \
  -H "X-API-Key: qph_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "plaintext": "SGVsbG8sIFdvcmxkIQ==",
    "key_version": 1
  }'

Errors​

HTTP StatusError CodeDescription
400ERR_KEM_001Invalid encryption request — missing plaintext, invalid key_version, or bad encoding.
401ERR_AUTH_001Missing or invalid API key.
404ERR_NOT_FOUND_001Key version not found or key is not in active status.
429ERR_RATE_LIMIT_001Rate limit exceeded for your plan tier.

Decrypt​

POST/api/v1/kem/decryptDecrypt ciphertext using Kyber768 KEM-DEM hybrid decryption.

Decrypts ciphertext that was previously encrypted with the KEM encrypt endpoint. The referenced key must be in active or retired status.

Request body​

FieldTypeRequiredDescription
ciphertextstring (base64)YesBase64-encoded ciphertext returned by the encrypt endpoint.
key_versionintegerYesThe key version that was used to encrypt this data.
algorithmstringNo"Kyber768" (default) or "X-Wing" (hybrid, Starter+). If omitted, defaults to Kyber768.

Response (200 OK)​

FieldTypeDescription
data.plaintextstring (base64)The decrypted data, base64-encoded.
data.key_versionintegerThe key version used for decryption.
data.algorithmstring"Kyber768" or "X-Wing", matching the algorithm used for the operation.

Example​

RequestPOST/api/v1/kem/decrypt
X-API-Key: qph_your_key_here
Content-Type: application/json
{
  "ciphertext": "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5...",
  "key_version": 1
}
Response200
{
  "data": {
    "plaintext": "SGVsbG8sIFdvcmxkIQ==",
    "key_version": 1,
    "algorithm": "Kyber768"
  },
  "request_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "timestamp": "2026-01-15T10:30:01.000Z"
}
Decrypt example
curl -X POST https://api.qpher.ai/api/v1/kem/decrypt \
  -H "X-API-Key: qph_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "ciphertext": "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5...",
    "key_version": 1
  }'

Errors​

HTTP StatusError CodeDescription
400ERR_KEM_002Invalid decryption request — corrupt ciphertext or wrong key_version.
401ERR_AUTH_001Missing or invalid API key.
404ERR_NOT_FOUND_001Key version not found or key is in archived status.
429ERR_RATE_LIMIT_001Rate limit exceeded for your plan tier.

Encapsulate​

POST/api/v1/kem/encapsulateEncapsulate a shared secret using Kyber768 KEM — for client-side (zero-trust) encryption.

Returns a KEM ciphertext and a 32-byte shared secret. The plaintext never leaves your environment. Use the shared secret as an AES-256-GCM key to encrypt data locally, then store the KEM ciphertext for later decapsulation.

Zero-trust encryption

With encapsulate/decapsulate, Qpher never sees your plaintext. You perform AES-256-GCM encryption locally using the shared secret. This is the most privacy-preserving mode.

Request body​

FieldTypeRequiredDescription
key_versionintegerYesThe version of the KEM key to use. Must reference an active key.
algorithmstringNo"Kyber768" (default) or "X-Wing" (hybrid, Starter+).

Response (200 OK)​

FieldTypeDescription
data.kem_ciphertextstring (base64)The KEM ciphertext. Store this — you need it for decapsulate.
data.shared_secretstring (base64)32-byte shared secret (base64). Use as your AES-256-GCM key. Ephemeral — Qpher does not store this.
data.key_versionintegerThe key version used.
data.algorithmstring"Kyber768" or "X-Wing".

Example​

Encapsulate example
curl -X POST https://api.qpher.ai/api/v1/kem/encapsulate \
  -H "X-API-Key: qph_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "key_version": 1
  }'

Errors​

HTTP StatusError CodeDescription
400ERR_KEM_001Invalid encapsulate request — invalid key_version.
401ERR_AUTH_001Missing or invalid API key.
404ERR_NOT_FOUND_001Key version not found or key is not in active status.
429ERR_RATE_LIMIT_001Rate limit exceeded for your plan tier.

Decapsulate​

POST/api/v1/kem/decapsulateDecapsulate a shared secret using Kyber768 KEM — for client-side (zero-trust) decryption.

Returns the same 32-byte shared secret that was produced during encapsulation. Use it to decrypt data that was encrypted client-side. The referenced key must be in active or retired status.

Request body​

FieldTypeRequiredDescription
kem_ciphertextstring (base64)YesThe KEM ciphertext from the encapsulate response.
key_versionintegerYesThe key version used during encapsulation.
algorithmstringNo"Kyber768" (default) or "X-Wing" (hybrid, Starter+).

Response (200 OK)​

FieldTypeDescription
data.shared_secretstring (base64)The same 32-byte shared secret from the original encapsulate call.
data.key_versionintegerThe key version used.
data.algorithmstring"Kyber768" or "X-Wing".

Example​

Decapsulate example
curl -X POST https://api.qpher.ai/api/v1/kem/decapsulate \
  -H "X-API-Key: qph_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "kem_ciphertext": "a2VtX2NpcGhlcnRleHRfZnJvbV9lbmNhcHN1bGF0ZS4uLg==",
    "key_version": 1
  }'

Errors​

HTTP StatusError CodeDescription
400ERR_KEM_002Invalid decapsulate request — invalid kem_ciphertext size or key_version.
401ERR_AUTH_001Missing or invalid API key.
404ERR_NOT_FOUND_001Key version not found or key is in archived status.
429ERR_RATE_LIMIT_001Rate limit exceeded for your plan tier.

Key Wrap​

POST/api/v1/kem/key/wrapWrap a symmetric key using Kyber768 KEM-DEM hybrid encryption.

Wraps (encrypts) a symmetric key using the KEM-DEM scheme with the specified key version. Use this to protect existing AES keys, HMAC secrets, or other symmetric material with quantum-safe encryption. The key must be in active status.

Request body​

FieldTypeRequiredDescription
symmetric_keystring (base64)YesBase64-encoded symmetric key to wrap. Supported sizes: 16, 24, 32, 48, or 64 bytes.
key_versionintegerYesThe version of the Kyber768 key to use. Must reference an active key.
algorithmstringNo"Kyber768" (default) or "X-Wing" (hybrid, Starter+). If omitted, defaults to Kyber768.

Response (200 OK)​

FieldTypeDescription
data.wrapped_keystring (base64)The wrapped (encrypted) symmetric key, base64-encoded.
data.key_versionintegerThe key version used for wrapping.
data.algorithmstring"Kyber768" or "X-Wing", matching the algorithm used for the operation.
data.wrapping_methodstringThe wrapping method used, e.g. "KEM-DEM".

Example​

Key Wrap example
curl -X POST https://api.qpher.ai/api/v1/kem/key/wrap \
  -H "X-API-Key: qph_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "symmetric_key": "c3ltbWV0cmljX2tleV8zMl9ieXRlcw==",
    "key_version": 1
  }'

Errors​

HTTP StatusError CodeDescription
400ERR_KEM_010Invalid wrap request — missing symmetric_key, invalid key_version, bad encoding, or unsupported key size. Supported sizes: 16, 24, 32, 48, or 64 bytes.
401ERR_AUTH_001Missing or invalid API key.
404ERR_NOT_FOUND_001Key version not found or key is not in active status.
429ERR_RATE_LIMIT_001Rate limit exceeded for your plan tier.

Key Unwrap​

POST/api/v1/kem/key/unwrapUnwrap a symmetric key using Kyber768 KEM-DEM hybrid decryption.

Unwraps (decrypts) a symmetric key that was previously wrapped with the Key Wrap endpoint. The referenced key must be in active or retired status.

Request body​

FieldTypeRequiredDescription
wrapped_keystring (base64)YesBase64-encoded wrapped key returned by the wrap endpoint.
key_versionintegerYesThe key version that was used to wrap this key.
algorithmstringNo"Kyber768" (default) or "X-Wing" (hybrid, Starter+). If omitted, defaults to Kyber768.

Response (200 OK)​

FieldTypeDescription
data.symmetric_keystring (base64)The unwrapped symmetric key, base64-encoded.
data.key_versionintegerThe key version used for unwrapping.
data.algorithmstring"Kyber768" or "X-Wing", matching the algorithm used for the operation.

Example​

Key Unwrap example
curl -X POST https://api.qpher.ai/api/v1/kem/key/unwrap \
  -H "X-API-Key: qph_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "wrapped_key": "d3JhcHBlZF9rZXlfZGF0YV9oZXJl...",
    "key_version": 1
  }'

Errors​

HTTP StatusError CodeDescription
400ERR_KEM_011Invalid unwrap request — missing wrapped_key, bad base64 encoding, or data too short.
400ERR_KEM_012Wrapped key integrity check failed — corrupt data or wrong key_version.
401ERR_AUTH_001Missing or invalid API key.
404ERR_NOT_FOUND_001Key version not found or key is in archived status.
429ERR_RATE_LIMIT_001Rate limit exceeded for your plan tier.

Key Version Requirements​

The key_version field is mandatory on all KEM operations. There is no implicit "use latest key" behavior.

OperationAllowed Key Statuses
Encryptactive only
Decryptactive or retired
Encapsulateactive only
Decapsulateactive or retired
Key Wrapactive only
Key Unwrapactive or retired

Use the Key Management API to list your keys and find the current active version.