REST API
You can call the Qpher API directly over HTTP from any language. This page shows cURL examples for every operation. All request and response bodies use JSON. Binary fields (plaintext, ciphertext, signatures, public keys) are base64-encoded strings.
Qpher supports hybrid PQC + classical algorithms. KEM endpoints accept
"algorithm": "X-Wing" and signature endpoints accept "algorithm": "Composite-ML-DSA".
Requests without the algorithm field use PQC-only algorithms (backward-compatible).
Note: Composite ML-DSA signatures use Qpher's internal wire format. They must be
verified using Qpher's /api/v1/signature/verify endpoint and are not interoperable
with external Composite ML-DSA implementations.
See API Overview — Supported Algorithms for details.
Base URL
https://api.qpher.ai
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your Qpher API key (starts with qph_) |
Content-Type | Yes (for POST) | Always application/json |
X-Request-ID | No | UUID for distributed tracing. If omitted, the API generates one. |
Every response includes these headers:
| Header | Description |
|---|---|
X-Request-ID | UUID for the request (same as your input, or auto-generated) |
Content-Type | application/json |
Response Format
All responses follow a consistent envelope:
{
"data": { },
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-02-15T10:30:00.000Z"
}
Error responses use the same envelope with an error field instead of data:
{
"error": {
"code": "ERR_AUTH_001",
"message": "Invalid or missing API key"
},
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-02-15T10:30:00.000Z"
}
KEM Operations
Encrypt
Encrypt data using Kyber768 (ML-KEM-768). The plaintext field must be a base64-encoded string.
Request:
curl -X POST https://api.qpher.ai/api/v1/kem/encrypt \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-H "X-Request-ID: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"plaintext": "c2Vuc2l0aXZlIGRhdGEgdG8gcHJvdGVjdA==",
"key_version": 1
}'
Response (200):
{
"data": {
"ciphertext": "YWJjZGVmZzEyMzQ1Njc4OTAxMjM0NTY3ODkw...",
"key_version": 1
},
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-02-15T10:30:00.123Z"
}
| Field | Type | Description |
|---|---|---|
plaintext | string (base64) | The data to encrypt |
key_version | integer | Key version to use (must be active) |
mode | string | Optional. "standard" (default) or "deterministic" |
salt | string (base64) | Required when mode is "deterministic" |
Hybrid encryption (Pro/Enterprise): Add
"algorithm": "X-Wing"for hybrid X25519 + ML-KEM-768: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": "c2Vuc2l0aXZlIGRhdGEgdG8gcHJvdGVjdA==",
"key_version": 1,
"algorithm": "X-Wing"
}'
Decrypt
Decrypt data that was previously encrypted with Kyber768.
Request:
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": "YWJjZGVmZzEyMzQ1Njc4OTAxMjM0NTY3ODkw...",
"key_version": 1
}'
Response (200):
{
"data": {
"plaintext": "c2Vuc2l0aXZlIGRhdGEgdG8gcHJvdGVjdA=="
},
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-02-15T10:30:01.456Z"
}
| Field | Type | Description |
|---|---|---|
ciphertext | string (base64) | The encrypted payload |
key_version | integer | Key version used during encryption (active or retired) |
Qpher does not support implicit "latest" key selection. You must specify the key_version for every operation.
Signature Operations
Sign
Sign a message using Dilithium3 (ML-DSA-65).
Request:
curl -X POST https://api.qpher.ai/api/v1/signature/sign \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"message": "SW52b2ljZSAjMTIzNDogJDUsMDAwLjAwIGR1ZSAyMDI2LTAzLTAx",
"key_version": 1
}'
Response (200):
{
"data": {
"signature": "TUVVQ0lRQ2dBd0lCQU...",
"key_version": 1
},
"request_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"timestamp": "2026-02-15T10:30:02.789Z"
}
| Field | Type | Description |
|---|---|---|
message | string (base64) | The message to sign |
key_version | integer | Signing key version (must be active) |
Hybrid signatures (Pro/Enterprise): Add
"algorithm": "Composite-ML-DSA"for hybrid ECDSA + ML-DSA-65:curl -X POST https://api.qpher.ai/api/v1/signature/sign \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"message": "SW52b2ljZSAjMTIzNDogJDUsMDAwLjAwIGR1ZSAyMDI2LTAzLTAx",
"key_version": 1,
"algorithm": "Composite-ML-DSA"
}'
Verify
Verify a Dilithium3 signature against the original message.
Request:
curl -X POST https://api.qpher.ai/api/v1/signature/verify \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"message": "SW52b2ljZSAjMTIzNDogJDUsMDAwLjAwIGR1ZSAyMDI2LTAzLTAx",
"signature": "TUVVQ0lRQ2dBd0lCQU...",
"key_version": 1
}'
Response (200):
{
"data": {
"valid": true
},
"request_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"timestamp": "2026-02-15T10:30:03.012Z"
}
| Field | Type | Description |
|---|---|---|
message | string (base64) | The original message |
signature | string (base64) | The signature to verify |
key_version | integer | Key version used during signing (active or retired) |
Key Management Operations
Generate Key
Generate a new PQC key pair. The private key is stored securely inside Qpher's enclave and is never returned.
Request:
curl -X POST https://api.qpher.ai/api/v1/kms/keys/generate \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"algorithm": "Kyber768"
}'
Response (201):
{
"data": {
"key_version": 1,
"algorithm": "Kyber768",
"status": "active",
"public_key": "MIIBIjANBgkqhkiG9w0BAQEF...",
"created_at": "2026-02-15T10:30:04.000Z"
},
"request_id": "d4e5f6a7-b8c9-0123-def0-234567890123",
"timestamp": "2026-02-15T10:30:04.000Z"
}
| Field | Type | Description |
|---|---|---|
algorithm | string | "Kyber768" or "Dilithium3" |
List Keys
List all PQC keys for a given algorithm.
Request:
curl -X GET "https://api.qpher.ai/api/v1/kms/keys?algorithm=Kyber768" \
-H "X-API-Key: qph_your_key_here"
Response (200):
{
"data": {
"keys": [
{
"key_version": 1,
"algorithm": "Kyber768",
"status": "retired",
"public_key": "MIIBIjANBgkqhkiG9w0BAQEF...",
"created_at": "2026-02-01T08:00:00.000Z"
},
{
"key_version": 2,
"algorithm": "Kyber768",
"status": "active",
"public_key": "MIIBIjANBgkqhkiG9w0BAQEF...",
"created_at": "2026-02-15T10:30:04.000Z"
}
]
},
"request_id": "e5f6a7b8-c9d0-1234-ef01-345678901234",
"timestamp": "2026-02-15T10:30:05.000Z"
}
Get Active Key
Retrieve the currently active key for a given algorithm.
Request:
curl -X GET "https://api.qpher.ai/api/v1/kms/keys/active?algorithm=Kyber768" \
-H "X-API-Key: qph_your_key_here"
Response (200):
{
"data": {
"key_version": 2,
"algorithm": "Kyber768",
"status": "active",
"public_key": "MIIBIjANBgkqhkiG9w0BAQEF...",
"created_at": "2026-02-15T10:30:04.000Z"
},
"request_id": "f6a7b8c9-d0e1-2345-f012-456789012345",
"timestamp": "2026-02-15T10:30:06.000Z"
}
Get Key by Version
Retrieve a specific key version by algorithm and version number.
Request:
curl -X GET "https://api.qpher.ai/api/v1/kms/keys/Kyber768/1" \
-H "X-API-Key: qph_your_key_here"
Response (200):
{
"data": {
"key_version": 1,
"algorithm": "Kyber768",
"status": "retired",
"public_key": "MIIBIjANBgkqhkiG9w0BAQEF...",
"created_at": "2026-02-01T08:00:00.000Z"
},
"request_id": "06a7b8c9-d0e1-2345-f012-456789012346",
"timestamp": "2026-02-15T10:30:06.500Z"
}
Rotate Key
Generate a new key version and set it as active. The previous active key is automatically retired.
Request:
curl -X POST https://api.qpher.ai/api/v1/kms/keys/rotate \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"algorithm": "Kyber768"
}'
Response (200):
{
"data": {
"key_version": 3,
"algorithm": "Kyber768",
"status": "active",
"public_key": "MIIBIjANBgkqhkiG9w0BAQEF...",
"created_at": "2026-02-15T10:30:07.000Z"
},
"request_id": "a7b8c9d0-e1f2-3456-0123-567890123456",
"timestamp": "2026-02-15T10:30:07.000Z"
}
Retire Key
Retire a specific key version. Retired keys can still be used for decryption and verification, but not for encryption or signing.
Request:
curl -X POST https://api.qpher.ai/api/v1/kms/keys/retire \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"algorithm": "Kyber768",
"key_version": 2
}'
Response (200):
{
"data": {
"key_version": 2,
"algorithm": "Kyber768",
"status": "retired"
},
"request_id": "b8c9d0e1-f2a3-4567-1234-678901234567",
"timestamp": "2026-02-15T10:30:08.000Z"
}
Once a key is retired, it cannot encrypt or sign new data. It can still decrypt or verify data that was encrypted or signed with that key version. Archiving a key disables all operations.
Error Responses
All errors follow the same envelope. The HTTP status code indicates the error category.
401 Unauthorized
curl -X POST https://api.qpher.ai/api/v1/kem/encrypt \
-H "X-API-Key: invalid_key" \
-H "Content-Type: application/json" \
-d '{"plaintext": "dGVzdA==", "key_version": 1}'
{
"error": {
"code": "ERR_AUTH_001",
"message": "Invalid or missing API key"
},
"request_id": "c9d0e1f2-a3b4-5678-2345-789012345678",
"timestamp": "2026-02-15T10:30:09.000Z"
}
400 Bad Request
{
"error": {
"code": "ERR_INVALID_001",
"message": "Validation failed: key_version is required"
},
"request_id": "d0e1f2a3-b4c5-6789-3456-890123456789",
"timestamp": "2026-02-15T10:30:10.000Z"
}