Signature API
The Signature API provides post-quantum digital signatures using the ML-DSA-65 algorithm (FIPS 204; the API wire value is Dilithium3). Use it to sign messages and verify signatures with quantum-resistant security.
Signing operations happen inside the Qpher secure enclave. Your private signing keys are never exposed or exported.
Pass "algorithm": "Composite-ML-DSA" in sign/verify requests to use hybrid signatures
(ECDSA P-256 + ML-DSA-65). If omitted, the default is "Dilithium3" (PQC-only, backward-compatible).
Both ECDSA and ML-DSA components must verify for the signature to be valid, ensuring security if either algorithm is compromised.
Important: Composite ML-DSA signatures use Qpher's internal wire format and must be verified using this API. They are not interoperable with external Composite ML-DSA implementations.
See Security Architecture for details.
Pass "algorithm": "ML-DSA-87" to use the NIST Category 5 (highest-strength)
ML-DSA parameter set â the general-signature algorithm named in the NSA CNSA 2.0
suite. Most callers select it via the SDK compliance_profile="cnsa-2.0"
selector (which also sends the X-Compliance-Profile: cnsa-2.0 header) rather
than per call. This is algorithm alignment with CNSA 2.0, not a compliance
certification.
Pass "algorithm": "SLH-DSA-SHA2-128s", "âĻ-128f", "âĻ-192s", or "âĻ-256s"
to sign with SLH-DSA, the NIST hash-based signature standard (FIPS 205).
Its security rests on hash functions rather than lattices, giving cryptographic
diversification for long-term / archival use. Signatures are larger (~7.8â30 KB)
and signing is slower (~0.3â2 s) than ML-DSA-65; 128s is the smallest and
192s/256s suit long-lived documents. Qpher implements the FIPS 205
algorithms â this is not a FIPS validation or certification. See the
algorithm table for parameter
sets and sizes.
Signâ
Signs the provided message with the specified key version. The key must be in active status.
Request bodyâ
| Field | Type | Required | Description |
|---|---|---|---|
message | string (base64) | Yes | Base64-encoded message to sign. |
key_version | integer | Yes | The version of the ML-DSA-65 key to use. Must reference an active key. |
algorithm | string | No | "Dilithium3" (default, all plans), "Composite-ML-DSA", or "SLH-DSA-SHA2-128s/128f/192s/256s" (Composite and SLH-DSA require Starter+). If omitted, defaults to ML-DSA-65. |
Response (200 OK)â
| Field | Type | Description |
|---|---|---|
data.signature | string (base64) | The signature, base64-encoded. |
data.key_version | integer | The key version used for signing. |
data.algorithm | string | "Dilithium3", "Composite-ML-DSA", or "SLH-DSA-SHA2-*", matching the algorithm used for the operation. |
Exampleâ
/api/v1/signature/signX-API-Key: qph_your_key_here
Content-Type: application/json{
"message": "SW52b2ljZSAjMTIzNCAtIFRvdGFsOiAkOTkuMDA="
}{
"data": {
"signature": "c2lnbmF0dXJlX2J5dGVzX2hlcmUuLi4=",
"key_version": 1,
"algorithm": "Dilithium3"
},
"request_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"timestamp": "2026-01-15T10:30:02.000Z"
}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": "SW52b2ljZSAjMTIzNCAtIFRvdGFsOiAkOTkuMDA="
}'Errorsâ
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | ERR_SIG_001 | Invalid sign request â missing message, bad base64 encoding, or invalid key_version. |
| 400 | ERR_SIG_002 | Message exceeds maximum allowed size. |
| 401 | ERR_AUTH_001 | Missing or invalid API key. |
| 404 | ERR_NOT_FOUND_001 | Key version not found or key is not in active status. |
| 429 | ERR_RATE_LIMIT_001 | Rate limit exceeded â reduce request frequency or upgrade your plan. |
Verifyâ
Verifies that a signature is valid for the given message and key version. Verification can use keys in active or retired status.
Request bodyâ
| Field | Type | Required | Description |
|---|---|---|---|
message | string (base64) | Yes | Base64-encoded original message that was signed. |
signature | string (base64) | Yes | Base64-encoded signature to verify. |
key_version | integer | Yes | The key version that was used to create the signature. |
algorithm | string | No | "Dilithium3" (default, all plans), "Composite-ML-DSA", or "SLH-DSA-SHA2-128s/128f/192s/256s" (Composite and SLH-DSA require Starter+). If omitted, defaults to ML-DSA-65. |
Response (200 OK)â
| Field | Type | Description |
|---|---|---|
data.valid | boolean | true if the signature is valid for the given message and key, false otherwise. |
data.key_version | integer | The key version used for verification. |
data.algorithm | string | "Dilithium3", "Composite-ML-DSA", or "SLH-DSA-SHA2-*", matching the algorithm used for the operation. |
Exampleâ
/api/v1/signature/verifyX-API-Key: qph_your_key_here
Content-Type: application/json{
"message": "SW52b2ljZSAjMTIzNCAtIFRvdGFsOiAkOTkuMDA=",
"signature": "c2lnbmF0dXJlX2J5dGVzX2hlcmUuLi4=",
"key_version": 1
}{
"data": {
"valid": true,
"key_version": 1,
"algorithm": "Dilithium3"
},
"request_id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
"timestamp": "2026-01-15T10:30:03.000Z"
}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": "SW52b2ljZSAjMTIzNCAtIFRvdGFsOiAkOTkuMDA=",
"signature": "c2lnbmF0dXJlX2J5dGVzX2hlcmUuLi4=",
"key_version": 1
}'Errorsâ
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | ERR_SIG_001 | Invalid verify request â bad base64 encoding for message or signature. |
| 400 | ERR_SIG_002 | Message or signature exceeds maximum allowed size. |
| 401 | ERR_AUTH_001 | Missing or invalid API key. |
| 404 | ERR_NOT_FOUND_001 | Key version not found or key is in archived status. |
| 429 | ERR_RATE_LIMIT_001 | Rate limit exceeded â reduce request frequency or upgrade your plan. |
A signature that does not match returns {"valid": false} with HTTP 200 â not an error. Errors (4xx/5xx) indicate malformed requests or missing resources, not invalid signatures.
Sign Hashâ
Signs a pre-computed hash digest instead of the raw message. This is ideal for large files or artifacts where you compute the hash client-side and send only the digest. The key must be in active status.
Request bodyâ
| Field | Type | Required | Description |
|---|---|---|---|
hash | string (base64) | Yes | Base64-encoded hash digest to sign. |
hash_algorithm | string | Yes | Hash algorithm used to compute the digest: "SHA-256", "SHA-384", or "SHA-512". |
key_version | integer | Yes | The version of the ML-DSA-65 key to use. Must reference an active key. |
algorithm | string | No | "Dilithium3" (default, all plans), "Composite-ML-DSA", or "SLH-DSA-SHA2-128s/128f/192s/256s" (Composite and SLH-DSA require Starter+). If omitted, defaults to ML-DSA-65. |
Response (200 OK)â
| Field | Type | Description |
|---|---|---|
data.signature | string (base64) | The signature, base64-encoded. |
data.key_version | integer | The key version used for signing. |
data.algorithm | string | "Dilithium3", "Composite-ML-DSA", or "SLH-DSA-SHA2-*", matching the algorithm used for the operation. |
data.hash_algorithm | string | The hash algorithm that was specified in the request. |
data.signature_type | string | Always "detached" â indicates the signature covers a hash, not the raw message. |
Exampleâ
curl -X POST https://api.qpher.ai/api/v1/signature/sign-hash \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"hash": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
"hash_algorithm": "SHA-256",
"key_version": 1
}'Errorsâ
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | ERR_SIG_010 | Unsupported hash algorithm â use SHA-256, SHA-384, or SHA-512. |
| 400 | ERR_SIG_011 | Hash length does not match declared hash_algorithm (SHA-256=32, SHA-384=48, SHA-512=64 bytes). |
| 401 | ERR_AUTH_001 | Missing or invalid API key. |
| 404 | ERR_NOT_FOUND_001 | Key version not found or key is not in active status. |
| 429 | ERR_RATE_LIMIT_001 | Rate limit exceeded â reduce request frequency or upgrade your plan. |
Verify Hashâ
Verifies that a signature is valid for the given hash digest and key version. Verification can use keys in active or retired status.
Request bodyâ
| Field | Type | Required | Description |
|---|---|---|---|
hash | string (base64) | Yes | Base64-encoded hash digest that was signed. |
hash_algorithm | string | Yes | Hash algorithm used to compute the digest: "SHA-256", "SHA-384", or "SHA-512". |
signature | string (base64) | Yes | Base64-encoded signature to verify. |
key_version | integer | Yes | The key version that was used to create the signature. |
algorithm | string | No | "Dilithium3" (default, all plans), "Composite-ML-DSA", or "SLH-DSA-SHA2-128s/128f/192s/256s" (Composite and SLH-DSA require Starter+). If omitted, defaults to ML-DSA-65. |
Response (200 OK)â
| Field | Type | Description |
|---|---|---|
data.valid | boolean | true if the signature is valid for the given hash and key, false otherwise. |
data.key_version | integer | The key version used for verification. |
data.algorithm | string | "Dilithium3", "Composite-ML-DSA", or "SLH-DSA-SHA2-*", matching the algorithm used for the operation. |
data.hash_algorithm | string | The hash algorithm that was specified in the request. |
Exampleâ
curl -X POST https://api.qpher.ai/api/v1/signature/verify-hash \
-H "X-API-Key: qph_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"hash": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
"hash_algorithm": "SHA-256",
"signature": "c2lnbmF0dXJlX2J5dGVzX2hlcmUuLi4=",
"key_version": 1
}'Errorsâ
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | ERR_SIG_010 | Unsupported hash algorithm â use SHA-256, SHA-384, or SHA-512. |
| 400 | ERR_SIG_011 | Hash length does not match declared hash_algorithm (SHA-256=32, SHA-384=48, SHA-512=64 bytes). |
| 401 | ERR_AUTH_001 | Missing or invalid API key. |
| 404 | ERR_NOT_FOUND_001 | Key version not found or key is in archived status. |
| 429 | ERR_RATE_LIMIT_001 | Rate limit exceeded â reduce request frequency or upgrade your plan. |
A signature that does not match returns {"valid": false} with HTTP 200 â not an error. Errors (4xx/5xx) indicate malformed requests or missing resources, not invalid signatures.
Key Version Requirementsâ
The key_version field is mandatory on all signature operations. There is no implicit "use latest key" behavior.
| Operation | Allowed Key Statuses |
|---|---|
| Sign | active only |
| Verify | active or retired |
| Sign Hash | active only |
| Verify Hash | active or retired |
Use the Key Management API to list your keys and find the current active version.