Node SDK Reference
Complete API reference for the Sigil Auth Node.js/TypeScript SDK
Node SDK Reference
The @sigilauth/sdk package provides a TypeScript-first client for integrating Sigil Auth into Node.js applications.
[!NOTE] Pre-release: The SDK is not yet published to npm. For local development, install from the monorepo:
npm install file:../server/sdk-node
Installation
Once published:
npm install @sigilauth/sdk
Requires Node 18 or later.
Quick Example
import { SigilAuth } from '@sigilauth/sdk';
const sigil = new SigilAuth({
serviceUrl: 'https://sigil.example.com'
});
const challenge = await sigil.auth.createChallenge({
fingerprint: 'a1b2c3...',
device_public_key: 'Ag8xYz...',
action: {
type: 'step_up',
description: 'Transfer $5000 to external account'
}
});
const result = await sigil.auth.awaitResult(challenge.challenge_id);
Configuration
new SigilAuth(config)
Creates a new Sigil Auth client.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
serviceUrl |
string |
Yes | HTTPS URL of your Sigil service |
apiKey |
string |
No | Must not be passed. Load from SIGIL_API_KEY env var |
rejectUnauthorized |
boolean |
No | Must not be false. TLS verification is mandatory |
certFingerprints |
string[] |
No | Optional certificate pinning (SHA-256 fingerprints) |
maxRetries |
number |
No | Maximum retry attempts (default: 3, max: 10) |
retryDelays |
number[] |
No | Delay in ms between retries (default: [100, 200, 400]) |
timeout |
number |
No | Request timeout in ms (default: 10000, min: 1000) |
Example:
const sigil = new SigilAuth({
serviceUrl: 'https://sigil.example.com',
maxRetries: 5,
timeout: 15000
});
Security:
The SDK enforces security rules at config time:
- API key from environment only. Pass nothing for
apiKey. The SDK readsprocess.env.SIGIL_API_KEY. If you try to pass a hardcoded key, the constructor throws. - HTTPS mandatory.
http://URLs are rejected. - TLS verification mandatory. You cannot disable certificate validation.
// ❌ This throws an error
const sigil = new SigilAuth({
serviceUrl: 'https://sigil.example.com',
apiKey: 'sgk_test_abc123' // ERROR: hardcoded secrets rejected
});
// ✅ This works
process.env.SIGIL_API_KEY = 'sgk_test_abc123';
const sigil = new SigilAuth({
serviceUrl: 'https://sigil.example.com'
});
Expected API key format:
sgk_test_<64 hex characters>
sgk_live_<64 hex characters>
Authentication API
sigil.auth.createChallenge(request)
Creates a new authentication challenge and sends a push notification to the device.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
fingerprint |
string |
Yes | SHA-256 hash of device public key (64 hex chars) |
device_public_key |
string |
Yes | Device’s P-256 public key (base64, 33 bytes compressed) |
action |
object |
No | Context shown to user on approval screen |
action.type |
string |
No | Action type (e.g., "step_up", "delete_account") |
action.description |
string |
No | Human-readable description shown on device |
action.params |
object |
No | Additional structured context (JSON) |
Returns: Promise<ChallengeCreated>
| Field | Type | Description |
|---|---|---|
challenge_id |
string |
UUID for this challenge |
fingerprint |
string |
Device fingerprint (echoed back) |
pictogram |
string[] |
Five emoji identifying this challenge |
pictogram_speakable |
string |
Space-separated words (e.g., "apple banana plane car dog") |
expires_at |
string |
ISO 8601 timestamp when challenge expires (5 minutes) |
Errors:
400— Invalid fingerprint format, or fingerprint doesn’t match public key401— Invalid or missing API key404— Device not registered with relay (no push token)429— Rate limit exceeded
Example:
const challenge = await sigil.auth.createChallenge({
fingerprint: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2',
device_public_key: 'Ag8xYzI3ZWRkNDUzYmNlYzVmMTJjNmI5MzA4OGY0ZjIxYWI5NzM4NTJhOWU2ZGUzMg==',
action: {
type: 'step_up',
description: 'Transfer $5000 to external account',
params: {
recipient: 'sarah@example.com',
amount_usd: 5000
}
}
});
console.log(challenge.challenge_id);
// "550e8400-e29b-41d4-a716-446655440000"
console.log(challenge.pictogram);
// ["🍎", "🍌", "✈️", "🚗", "🐕"]
console.log(challenge.pictogram_speakable);
// "apple banana plane car dog"
sigil.auth.getStatus(challengeId)
Checks the current status of a challenge.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
challengeId |
string |
Yes | UUID from createChallenge |
Returns: Promise<ChallengeStatus>
| Field | Type | Description |
|---|---|---|
status |
string |
One of: "pending", "verified", "rejected", "expired" |
challenge_id |
string |
UUID |
fingerprint |
string |
Device fingerprint |
created_at |
string |
ISO 8601 timestamp |
expires_at |
string |
ISO 8601 timestamp |
responded_at |
string? |
When device responded (null if pending) |
Example:
const status = await sigil.auth.getStatus('550e8400-e29b-41d4-a716-446655440000');
if (status.status === 'verified') {
console.log('User approved at', status.responded_at);
}
sigil.auth.awaitResult(challengeId, options?)
Polls for challenge completion. Blocks until the user approves, denies, or the challenge expires.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
challengeId |
string |
Yes | — | UUID from createChallenge |
options.pollInterval |
number |
No | 2000 |
Milliseconds between status checks |
options.timeout |
number |
No | 60000 |
Total timeout in milliseconds (60 seconds) |
Returns: Promise<ChallengeStatus>
Resolves when status becomes verified, rejected, or expired.
Rejects with Error if polling times out before reaching a terminal state.
Example:
try {
const result = await sigil.auth.awaitResult(challenge.challenge_id, {
pollInterval: 2000, // check every 2 seconds
timeout: 120000 // give up after 2 minutes
});
if (result.status === 'verified') {
console.log('Approved!');
} else if (result.status === 'rejected') {
console.log('User denied the request.');
} else {
console.log('Challenge expired before user responded.');
}
} catch (err) {
console.error('Polling timeout:', err.message);
}
Multi-Party Approval (MPA)
sigil.mpa.request(request)
Creates a multi-party approval request. Challenges are sent to multiple devices. The request is approved when M out of N groups approve.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
required |
number |
Yes | Number of groups that must approve (M) |
groups |
object[] |
Yes | Array of approval groups (N total) |
groups[].name |
string |
Yes | Group name (e.g., "Engineering Leads") |
groups[].approvers |
object[] |
Yes | Array of device fingerprints in this group |
groups[].approvers[].fingerprint |
string |
Yes | Device fingerprint |
groups[].approvers[].device_public_key |
string |
Yes | Device public key |
action |
object |
Yes | Action context shown on all devices |
action.type |
string |
Yes | Action type |
action.description |
string |
Yes | Human-readable description |
action.params |
object |
No | Structured parameters (JSON) |
expires_in_seconds |
number |
No | Time to approve (default: 300, min: 60, max: 900) |
Returns: Promise<MPACreated>
| Field | Type | Description |
|---|---|---|
request_id |
string |
UUID for this MPA request |
groups_required |
number |
How many groups must approve |
groups_total |
number |
Total number of groups |
expires_at |
string |
ISO 8601 expiry timestamp |
Example:
const mpaRequest = await sigil.mpa.request({
required: 2, // Need 2 out of 3 groups to approve
groups: [
{
name: 'Engineering Leads',
approvers: [
{
fingerprint: 'abc123...',
device_public_key: 'Ag8xYz...'
}
]
},
{
name: 'Security Team',
approvers: [
{
fingerprint: 'def456...',
device_public_key: 'AhMxNj...'
}
]
},
{
name: 'Operations',
approvers: [
{
fingerprint: 'ghi789...',
device_public_key: 'AjQyOT...'
},
{
fingerprint: 'jkl012...',
device_public_key: 'AkU3MT...'
}
]
}
],
action: {
type: 'cold_boot',
description: 'Cold boot engine ENG-001',
params: {
engine_id: 'ENG-001',
reason: 'Scheduled maintenance'
}
},
expires_in_seconds: 600 // 10 minutes
});
console.log(mpaRequest.request_id);
// "7c3e5f9a-2b4d-4a1e-8f6c-9d2a1b3c4e5f"
sigil.mpa.getStatus(requestId)
Checks the current status of an MPA request.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
requestId |
string |
Yes | UUID from mpa.request() |
Returns: Promise<MPAStatus>
| Field | Type | Description |
|---|---|---|
status |
string |
"pending", "approved", "rejected", "timeout" |
request_id |
string |
UUID |
groups_required |
number |
M |
groups_satisfied |
string[]? |
Names of groups that have approved |
created_at |
string |
ISO 8601 timestamp |
expires_at |
string |
ISO 8601 timestamp |
completed_at |
string? |
When request reached terminal state |
Example:
const status = await sigil.mpa.getStatus('7c3e5f9a-2b4d-4a1e-8f6c-9d2a1b3c4e5f');
console.log(`${status.groups_satisfied?.length ?? 0} of ${status.groups_required} groups approved`);
sigil.mpa.awaitResult(requestId, options?)
Polls for MPA completion.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
requestId |
string |
Yes | — | UUID from mpa.request() |
options.pollInterval |
number |
No | 3000 |
Milliseconds between checks (MPA is slower) |
options.timeout |
number |
No | 180000 |
Total timeout (3 minutes) |
Returns: Promise<MPAStatus>
Example:
const result = await sigil.mpa.awaitResult(mpaRequest.request_id);
if (result.status === 'approved') {
console.log('MPA approved by', result.groups_satisfied);
// Proceed with the action
} else if (result.status === 'rejected') {
console.log('At least one group denied.');
} else {
console.log('Request timed out before reaching quorum.');
}
Webhooks
sigil.webhooks.verify(headers, body, secret)
Verifies the HMAC signature on a webhook delivery from Sigil.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
headers |
object |
Yes | HTTP headers from the webhook request |
body |
string or Buffer |
Yes | Raw request body (not parsed JSON) |
secret |
string |
Yes | Webhook secret from Sigil config |
Returns: boolean
true if signature is valid, false otherwise.
Example:
import express from 'express';
const app = express();
app.post('/webhooks/sigil', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = sigil.webhooks.verify(
req.headers,
req.body, // raw Buffer
process.env.SIGIL_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString());
console.log('Webhook event:', event.type);
res.sendStatus(200);
});
Webhook payload structure:
{
"type": "challenge.verified",
"challenge_id": "550e8400-e29b-41d4-a716-446655440000",
"fingerprint": "a1b2c3d4...",
"status": "verified",
"timestamp": "2026-04-24T10:32:15Z"
}
Event types:
challenge.verifiedchallenge.rejectedchallenge.expiredmpa.approvedmpa.rejectedmpa.timeout
Error Handling
All SDK methods throw standard JavaScript errors. Network errors, HTTP errors, and validation errors are all surfaced as Error instances.
Common error patterns:
try {
const challenge = await sigil.auth.createChallenge({ ... });
} catch (err) {
if (err.message.includes('401')) {
console.error('Invalid API key');
} else if (err.message.includes('404')) {
console.error('Device not registered');
} else if (err.message.includes('429')) {
console.error('Rate limited — slow down');
} else {
console.error('Unexpected error:', err.message);
}
}
The SDK retries on 429 and 5xx responses automatically using exponential backoff.
TypeScript Types
The SDK is written in TypeScript. All request and response types are exported:
import type {
SigilAuthConfig,
ChallengeRequest,
ChallengeCreated,
ChallengeStatus,
MPARequest,
MPACreated,
MPAStatus
} from '@sigilauth/sdk';
Types are generated from the OpenAPI spec at api/openapi.yaml, so they stay in sync with the server.
Next Steps
- Go SDK Reference — Go equivalent of this SDK
- Integrator Quickstart — Quick path to your first auth
- MPA Setup Guide — How to configure multi-party approval policies
- Self-Hosting Guide — Deploy Sigil Auth with Docker