Skip to main content

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.

Hybrid algorithms (Pro/Enterprise)

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​

HeaderRequiredDescription
X-API-KeyYesYour Qpher API key (starts with qph_)
Content-TypeYes (for POST)Always application/json
X-Request-IDNoUUID for distributed tracing. If omitted, the API generates one.

Every response includes these headers:

HeaderDescription
X-Request-IDUUID for the request (same as your input, or auto-generated)
Content-Typeapplication/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"
}
FieldTypeDescription
plaintextstring (base64)The data to encrypt
key_versionintegerOptional — omit to use your active key (the response returns the version used). If supplied, it must be active.
modestringOptional. "standard" (default) or "deterministic" (not yet in effect — see Deterministic Encryption)
saltstring (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"
}
FieldTypeDescription
ciphertextstring (base64)The encrypted payload
key_versionintegerKey version used during encryption (active or retired)
When to pass key_version

Encrypt and sign may omit key_version: Qpher uses the active key and returns its version. Decrypt, verify, encapsulate and key wrap always need it.


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"
}
FieldTypeDescription
messagestring (base64)The message to sign
key_versionintegerOptional — omit to use your active key (the response returns the version used). If supplied, it 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"
}
FieldTypeDescription
messagestring (base64)The original message
signaturestring (base64)The signature to verify
key_versionintegerKey version used during signing (active or retired)

Key Management Operations​

Generate Key​

Generate a new PQC key pair. The private key is generated and used only inside Qpher's isolated key service (KMS-Orchestrator) 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"
}
FieldTypeDescription
algorithmstring"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"
}
Key lifecycle

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"
}

404 Not Found​

{
"error": {
"code": "ERR_KEM_005",
"message": "Key version not found"
},
"request_id": "e1f2a3b4-c5d6-7890-4567-901234567890",
"timestamp": "2026-02-15T10:30:11.000Z"
}

429 Rate Limit Exceeded​

{
"error": {
"code": "ERR_RATE_001",
"message": "Rate limit exceeded. Retry after 2 seconds."
},
"request_id": "f2a3b4c5-d6e7-8901-5678-012345678901",
"timestamp": "2026-02-15T10:30:12.000Z"
}

403 Forbidden​

{
"error": {
"code": "ERR_FORBIDDEN_001",
"message": "Operation not allowed by policy"
},
"request_id": "a3b4c5d6-e7f8-9012-6789-123456789012",
"timestamp": "2026-02-15T10:30:13.000Z"
}

503 Service Unavailable​

{
"error": {
"code": "ERR_SERVICE_001",
"message": "Service temporarily unavailable"
},
"request_id": "b4c5d6e7-f8a9-0123-7890-234567890123",
"timestamp": "2026-02-15T10:30:14.000Z"
}

Error Code Reference​

CodeHTTP StatusDescription
ERR_AUTH_001401Invalid or missing API key
ERR_INVALID_001400Request validation failed
ERR_FORBIDDEN_001403Operation denied by policy
ERR_NOT_FOUND_001404Resource not found
ERR_KEM_005404Key version not found
ERR_RATE_001429Rate limit exceeded
ERR_CRYPTO_001500Internal cryptography error
ERR_INTERNAL_001500Internal server error
ERR_BAD_GATEWAY_001502Downstream service error
ERR_SERVICE_001503Service unavailable
ERR_TIMEOUT_001504Gateway timeout

Full cURL Workflow​

Here is a complete workflow using only cURL: generate a key, encrypt data, then decrypt it.

# Step 1: Generate a Kyber768 key
curl -s -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"}' | jq .

# Step 2: Encrypt data (base64-encode your plaintext first)
# "sensitive data to protect" -> "c2Vuc2l0aXZlIGRhdGEgdG8gcHJvdGVjdA=="
curl -s -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
}' | jq .

# Step 3: Decrypt (use the ciphertext from step 2)
curl -s -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": "<ciphertext_from_step_2>",
"key_version": 1
}' | jq .

# Step 4: Sign a message
curl -s -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": "SW52b2ljZSAjMTIzNA==",
"key_version": 1
}' | jq .

# Step 5: Verify the signature (use the signature from step 4)
curl -s -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": "SW52b2ljZSAjMTIzNA==",
"signature": "<signature_from_step_4>",
"key_version": 1
}' | jq .
Base64 encoding

Remember to base64-encode binary fields before sending them. On macOS and Linux, use echo -n "your data" | base64 to encode. The SDKs handle this automatically.

Always use HTTPS

Always call https://api.qpher.ai. A plain-HTTP request is redirected to HTTPS, but by then it has already crossed the network unencrypted, API key included. TLS 1.3 is negotiated when your client supports it; set your HTTP client's minimum to TLS 1.2.

Next Steps​

  • Python SDK — typed Python wrapper with automatic retries
  • Node.js SDK — TypeScript SDK with async/await
  • Go SDK — idiomatic Go SDK with context support
  • API Reference — full OpenAPI specification