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.

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_versionintegerKey version to use (must be active)
modestringOptional. "standard" (default) or "deterministic"
saltstring (base64)Required when mode is "deterministic"

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)
Key version is always required

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"
}
FieldTypeDescription
messagestring (base64)The message to sign
key_versionintegerSigning key version (must be active)

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

All API requests must use HTTPS. The API rejects plain HTTP connections. Your API key and all data are encrypted in transit via TLS 1.3.

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