intermediate sdknodetypescriptapi-reference

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 reads process.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 key
  • 401 — Invalid or missing API key
  • 404 — 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.verified
  • challenge.rejected
  • challenge.expired
  • mpa.approved
  • mpa.rejected
  • mpa.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