Node.js SDK
The Qpher Node.js SDK provides a fully typed TypeScript interface to the Qpher REST API. All cryptographic operations execute server-side. The SDK handles authentication, base64 encoding, retries, and error mapping.
Installationâ
npm install @qpher/sdkRequirements: Node.js 18 or later. Full TypeScript definitions are included.
Client Setupâ
import { Qpher, QpherError } from '@qpher/sdk';
const client = new Qpher({
apiKey: 'qph_your_key_here',
baseUrl: 'https://api.qpher.ai', // default
timeout: 30_000, // milliseconds, default: 30000
maxRetries: 3, // default: 3
});
| Parameter | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Your Qpher API key (starts with qph_) |
baseUrl | string | https://api.qpher.ai | API base URL |
timeout | number | 30000 | Request timeout in milliseconds |
maxRetries | number | 3 | Retries for transient errors (429, 502, 503, 504) |
Never hard-code API keys. Load them from environment variables:
new Qpher({ apiKey: process.env.QPHER_API_KEY! })
KEM Encryption and Decryptionâ
Qpher uses Kyber768 (ML-KEM-768) for key encapsulation. The SDK accepts Buffer for binary data and returns Buffer for ciphertext. Base64 encoding on the wire is transparent.
Encryptâ
const result = await client.kem.encrypt({
plaintext: Buffer.from('sensitive data to protect'),
keyVersion: 1,
});
console.log(result.ciphertext); // Buffer â the encrypted payload
console.log(result.keyVersion); // number â the key version used
console.log(result.requestId); // string â UUID for tracing
| Parameter | Type | Required | Description |
|---|---|---|---|
plaintext | Buffer | Yes | The data to encrypt |
keyVersion | number | Yes | Key version to use (must be active) |
mode | string | No | "standard" (default) or "deterministic" |
salt | Buffer | No | Required when mode is "deterministic" |
Returns: Promise<EncryptResult> with fields ciphertext (Buffer), keyVersion (number), algorithm (string), and requestId (string).
Decryptâ
const result = await client.kem.decrypt({
ciphertext: encryptedCiphertext,
keyVersion: 1,
});
console.log(result.plaintext); // Buffer â the original data
console.log(result.requestId); // string
| Parameter | Type | Required | Description |
|---|---|---|---|
ciphertext | Buffer | Yes | The encrypted payload to decrypt |
keyVersion | number | Yes | Key version used during encryption (active or retired) |
Returns: Promise<DecryptResult> with fields plaintext (Buffer), keyVersion (number), algorithm (string), and requestId (string).
Qpher does not support implicit "latest" key selection. You must specify the keyVersion for every operation to ensure auditability and prevent silent key-mismatch errors.
Digital Signaturesâ
Qpher uses Dilithium3 (ML-DSA-65) for post-quantum digital signatures.
Signâ
const result = await client.signatures.sign({
message: Buffer.from('document content to sign'),
keyVersion: 1,
});
console.log(result.signature); // Buffer â the Dilithium3 signature
console.log(result.keyVersion); // number
console.log(result.requestId); // string
| Parameter | Type | Required | Description |
|---|---|---|---|
message | Buffer | Yes | The message to sign |
keyVersion | number | Yes | Signing key version (must be active) |
Returns: Promise<SignResult> with fields signature (Buffer), keyVersion (number), and requestId (string).
Verifyâ
const result = await client.signatures.verify({
message: Buffer.from('document content to sign'),
signature: signatureBuffer,
keyVersion: 1,
});
console.log(result.valid); // boolean â true if signature is valid
console.log(result.keyVersion); // number
console.log(result.requestId); // string
| Parameter | Type | Required | Description |
|---|---|---|---|
message | Buffer | Yes | The original message |
signature | Buffer | Yes | The signature to verify |
keyVersion | number | Yes | Key version used during signing (active or retired) |
Returns: Promise<VerifyResult> with fields valid (boolean), keyVersion (number), algorithm (string), and requestId (string).
Key Managementâ
Manage PQC key lifecycle: generate, list, get, rotate, and retire keys.
Generate Keyâ
const result = await client.keys.generate({ algorithm: 'Kyber768' });
console.log(result.keyVersion); // number â the new key version
console.log(result.algorithm); // string â "Kyber768"
console.log(result.status); // string â "active"
console.log(result.publicKey); // Buffer â the public key (private key stays in Qpher)
console.log(result.createdAt); // string â ISO-8601 timestamp
console.log(result.requestId); // string â UUID for tracing
| Parameter | Type | Required | Description |
|---|---|---|---|
algorithm | string | Yes | "Kyber768" or "Dilithium3" |
Returns: Promise<GenerateResult> with fields keyVersion (number), algorithm (string), status (string), publicKey (Buffer), createdAt (string), and requestId (string).
List Keysâ
const result = await client.keys.list({ algorithm: 'Kyber768' });
console.log(`Total keys: ${result.total}`);
for (const key of result.keys) {
console.log(`Version ${key.keyVersion}: ${key.status}`);
// Version 1: retired
// Version 2: active
}
| Parameter | Type | Required | Description |
|---|---|---|---|
algorithm | string | No | Filter by "Kyber768" or "Dilithium3" |
status | string | No | Filter by "active", "retired", or "archived" |
Returns: Promise<KeyListResult> with fields keys (KeyInfo[]), total (number), and requestId (string). Each KeyInfo has keyVersion (number), algorithm (string), status (string), createdAt (string), and publicKey (Buffer).
Get Active Keyâ
const key = await client.keys.getActive({ algorithm: 'Kyber768' });
console.log(key.keyVersion); // number â the currently active version
console.log(key.algorithm); // string â "Kyber768"
console.log(key.publicKey); // Buffer â the public key
Get Key by Versionâ
const key = await client.keys.get({ algorithm: 'Kyber768', keyVersion: 1 });
console.log(key.keyVersion); // number â 1
console.log(key.status); // string â "active", "retired", or "archived"
console.log(key.publicKey); // Buffer â the public key
console.log(key.createdAt); // string â ISO-8601 timestamp
Rotate Keyâ
const newKey = await client.keys.rotate({ algorithm: 'Kyber768' });
console.log(newKey.keyVersion); // number â the new active version
console.log(newKey.oldKeyVersion); // number â the previous version (now retired)
console.log(newKey.publicKey); // Buffer â the new public key
console.log(newKey.requestId); // string â UUID for tracing
Returns: Promise<RotateResult> with fields keyVersion (number), algorithm (string), publicKey (Buffer), oldKeyVersion (number), and requestId (string).
Retire Keyâ
const result = await client.keys.retire({ algorithm: 'Kyber768', keyVersion: 1 });
console.log(result.status); // string â "retired"
// Key version 1 is now retired â decrypt/verify still work, encrypt/sign do not
Returns: Promise<RetireResult> with fields keyVersion (number), algorithm (string), status (string), publicKey (Buffer), createdAt (string), and requestId (string).
Retired keys can still decrypt and verify, but cannot encrypt or sign. Archived keys cannot perform any operations. Plan your rotation strategy accordingly.
Error Handlingâ
All API errors throw QpherError (or a subclass) with structured fields for programmatic handling.
import { Qpher, QpherError } from '@qpher/sdk';
const client = new Qpher({ apiKey: 'qph_your_key_here' });
try {
const result = await client.kem.encrypt({
plaintext: Buffer.from('data'),
keyVersion: 999,
});
} catch (err) {
if (err instanceof QpherError) {
console.log(err.errorCode); // string â e.g., "ERR_KEM_005"
console.log(err.message); // string â human-readable description
console.log(err.statusCode); // number â HTTP status code, e.g., 404
console.log(err.requestId); // string â UUID for support inquiries
} else {
console.log('Unexpected error:', err);
}
}
Error Subclassesâ
The SDK provides specific error subclasses for common error categories. All extend QpherError:
import {
QpherError, // Base class for all Qpher errors
AuthenticationError, // 401 â invalid or missing API key
ValidationError, // 400 â invalid request parameters
NotFoundError, // 404 â resource not found
ForbiddenError, // 403 â operation not allowed by policy
RateLimitError, // 429 â rate limit exceeded
ServerError, // 5xx â internal server error
TimeoutError, // 504 â request timed out
ConnectionError, // 503 â service unavailable
} from '@qpher/sdk';
You can catch specific error types using instanceof:
import { NotFoundError, RateLimitError, QpherError } from '@qpher/sdk';
try {
const result = await client.kem.encrypt({
plaintext: Buffer.from('data'),
keyVersion: 999,
});
} catch (err) {
if (err instanceof NotFoundError) {
console.log('Key version does not exist');
} else if (err instanceof RateLimitError) {
console.log('Rate limit exceeded â SDK already retried');
} else if (err instanceof QpherError) {
console.log(`Other API error: ${err.errorCode}`);
}
}
Common Error Codesâ
| Error Code | Status | Meaning |
|---|---|---|
ERR_AUTH_001 | 401 | Invalid or missing API key |
ERR_RATE_001 | 429 | Rate limit exceeded (auto-retried) |
ERR_KEM_005 | 404 | Key version not found |
ERR_INVALID_001 | 400 | Invalid request parameters |
ERR_FORBIDDEN_001 | 403 | Operation not allowed by policy |
ERR_SERVICE_001 | 503 | Service temporarily unavailable |
Retry Behaviorâ
The SDK automatically retries on transient errors (HTTP 429, 502, 503, 504) with exponential backoff (0.5s, 1s, 2s, capped at 10s). After maxRetries attempts, the final QpherError is thrown.
Full Round-Trip Exampleâ
This example demonstrates a complete encrypt-then-decrypt workflow in an async function.
import { Qpher, QpherError } from '@qpher/sdk';
async function main() {
const client = new Qpher({
apiKey: process.env.QPHER_API_KEY!,
timeout: 30_000,
maxRetries: 3,
});
try {
// 1. Get the active Kyber768 key
const activeKey = await client.keys.getActive({ algorithm: 'Kyber768' });
console.log(`Using key version: ${activeKey.keyVersion}`);
// 2. Encrypt sensitive data
const plaintext = Buffer.from('Patient record: John Doe, DOB 1990-01-15');
const encryptResult = await client.kem.encrypt({
plaintext,
keyVersion: activeKey.keyVersion,
});
console.log(
`Encrypted ${plaintext.length} bytes -> ${encryptResult.ciphertext.length} bytes`
);
console.log(`Request ID: ${encryptResult.requestId}`);
// 3. Decrypt to recover the original data
const decryptResult = await client.kem.decrypt({
ciphertext: encryptResult.ciphertext,
keyVersion: encryptResult.keyVersion,
});
// 4. Verify round-trip integrity
if (decryptResult.plaintext.equals(plaintext)) {
console.log('Round-trip successful: plaintext matches');
}
} catch (err) {
if (err instanceof QpherError) {
console.error(`Qpher API error: ${err.errorCode} - ${err.message}`);
console.error(`Status: ${err.statusCode}, Request ID: ${err.requestId}`);
} else {
console.error('Unexpected error:', err);
}
}
}
main();
Sign and Verify Exampleâ
import { Qpher, QpherError } from '@qpher/sdk';
async function signAndVerify() {
const client = new Qpher({ apiKey: process.env.QPHER_API_KEY! });
// Get the active Dilithium3 signing key
const signingKey = await client.keys.getActive({ algorithm: 'Dilithium3' });
// Sign a document
const document = Buffer.from('Invoice #1234: $5,000.00 due 2026-03-01');
const signResult = await client.signatures.sign({
message: document,
keyVersion: signingKey.keyVersion,
});
console.log(`Signature length: ${signResult.signature.length} bytes`);
// Verify the signature
const verifyResult = await client.signatures.verify({
message: document,
signature: signResult.signature,
keyVersion: signResult.keyVersion,
});
console.log(`Signature valid: ${verifyResult.valid}`); // true
// Verify with tampered message
const verifyTampered = await client.signatures.verify({
message: Buffer.from('Invoice #1234: $50,000.00 due 2026-03-01'),
signature: signResult.signature,
keyVersion: signResult.keyVersion,
});
console.log(`Tampered signature valid: ${verifyTampered.valid}`); // false
}
signAndVerify();
TypeScript Typesâ
The SDK exports all types for use in your application:
import type {
// Client options
QpherOptions,
// KEM types
EncryptInput,
EncryptResult,
DecryptInput,
DecryptResult,
// Signature types
SignInput,
SignResult,
VerifyInput,
VerifyResult,
// Key management types
GenerateInput,
GenerateResult,
RotateInput,
RotateResult,
RetireInput,
RetireResult,
GetActiveInput,
GetKeyInput,
ListKeysInput,
KeyInfo,
KeyListResult,
} from '@qpher/sdk';
Configuration Referenceâ
Environment Variablesâ
The SDK respects these environment variables as fallbacks:
| Variable | Purpose |
|---|---|
QPHER_API_KEY | API key (used if apiKey option is omitted) |
QPHER_BASE_URL | Base URL (used if baseUrl option is omitted) |
The SDK never logs your API key or response bodies containing ciphertext. Sensitive fields are redacted in debug output.
Next Stepsâ
- Python SDK â if your backend uses Python
- Go SDK â if your backend uses Go
- REST API â for raw HTTP access from any language
- API Reference â full OpenAPI specification