Skip to main content

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/sdk

Requirements: 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
});
ParameterTypeDefaultDescription
apiKeystringrequiredYour Qpher API key (starts with qph_)
baseUrlstringhttps://api.qpher.aiAPI base URL
timeoutnumber30000Request timeout in milliseconds
maxRetriesnumber3Retries for transient errors (429, 502, 503, 504)
Protect your API key

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
ParameterTypeRequiredDescription
plaintextBufferYesThe data to encrypt
keyVersionnumberYesKey version to use (must be active)
modestringNo"standard" (default) or "deterministic"
saltBufferNoRequired 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
ParameterTypeRequiredDescription
ciphertextBufferYesThe encrypted payload to decrypt
keyVersionnumberYesKey version used during encryption (active or retired)

Returns: Promise<DecryptResult> with fields plaintext (Buffer), keyVersion (number), algorithm (string), and requestId (string).

Key version is always required

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
ParameterTypeRequiredDescription
messageBufferYesThe message to sign
keyVersionnumberYesSigning 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
ParameterTypeRequiredDescription
messageBufferYesThe original message
signatureBufferYesThe signature to verify
keyVersionnumberYesKey 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
ParameterTypeRequiredDescription
algorithmstringYes"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
}
ParameterTypeRequiredDescription
algorithmstringNoFilter by "Kyber768" or "Dilithium3"
statusstringNoFilter 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).

Key lifecycle

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 CodeStatusMeaning
ERR_AUTH_001401Invalid or missing API key
ERR_RATE_001429Rate limit exceeded (auto-retried)
ERR_KEM_005404Key version not found
ERR_INVALID_001400Invalid request parameters
ERR_FORBIDDEN_001403Operation not allowed by policy
ERR_SERVICE_001503Service 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:

VariablePurpose
QPHER_API_KEYAPI key (used if apiKey option is omitted)
QPHER_BASE_URLBase URL (used if baseUrl option is omitted)
Security

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