Node.js SDK

Read secrets from any Node.js or TypeScript application using Ed25519 machine authentication.

Installation

npm install @sikkerkey/sdk

Requires Node.js 18+. Zero runtime dependencies — uses only Node.js built-in modules.

Quick Start

import { SikkerKey } from '@sikkerkey/sdk'

const sk = SikkerKey.create('vault_abc123')
const secret = await sk.getSecret('sk_a1b2c3d4e5')

The SDK loads the machine identity from ~/.sikkerkey/vaults/{vaultId}/identity.json, signs every request with the machine's Ed25519 private key, and returns the decrypted value. All data methods are async and return Promises.

Client Creation

// Explicit vault ID
const sk = SikkerKey.create('vault_abc123')

// Direct path to identity file
const sk = SikkerKey.create('/etc/sikkerkey/vaults/vault_abc123/identity.json')

// Auto-detect from SIKKERKEY_IDENTITY env or single vault on disk
const sk = SikkerKey.create()

create() is synchronous — it reads identity and key files from disk. Throws ConfigurationError if the identity is missing, the key can't be loaded, or multiple vaults exist without a specified vault ID.

Reading Secrets

Single Value

const apiKey = await sk.getSecret('sk_stripe_prod')

Structured (Multiple Fields)

const db = await sk.getFields('sk_db_prod')
const host = db.host       // "db.example.com"
const user = db.username   // "admin"
const pass = db.password   // "hunter2"

Throws SecretStructureError if the secret value is not a JSON object.

Single Field

const password = await sk.getField('sk_db_prod', 'password')

Throws FieldNotFoundError if the field doesn't exist. The error message includes available field names.

Listing Secrets

// All secrets this machine can access
const secrets = await sk.listSecrets()
for (const s of secrets) {
    console.log(`${s.id}: ${s.name}`)
}

// Secrets in a specific project
const projectSecrets = await sk.listSecretsByProject('proj_abc123')

Returns SecretListItem[] with id, name, fieldNames (string | null), and projectId (string | null).

Export

Export all accessible secrets as a flat key-value map in a single round trip:

const env = await sk.export()
// { API_KEY: "sk-live-...", DB_CREDS_HOST: "db.example.com", DB_CREDS_PASSWORD: "s3cret" }

Structured secrets are flattened: SECRET_NAME_FIELD_NAME. Secret names are converted to uppercase env format.

Scope to a Project

const env = await sk.export('proj_abc123')

Inject into Environment

const env = await sk.export()
Object.assign(process.env, env)

Watching for Changes

Watch secrets for real-time updates. When a secret is rotated, updated, or deleted, the callback fires with the new value.

sk.watch('sk_db_credentials', (event) => {
  if (event.status === 'changed') {
    // event.value has the new value
    // event.fields has parsed key-value pairs for structured secrets
    db.reconfigure({
      username: event.fields!.username,
      password: event.fields!.password,
    })
  } else if (event.status === 'deleted') {
    console.log('Secret deleted')
  } else if (event.status === 'access_denied') {
    console.log('Access revoked')
  } else if (event.status === 'error') {
    console.error(`Error: ${event.error}`)
  }
})

Polling starts automatically on the first watch() call using setInterval. Default interval is 15 seconds (server enforces a 10-second minimum).

sk.setPollInterval(30) // seconds
sk.unwatch('sk_db_credentials') // stop watching one secret
sk.close() // stop all watches

Multi-Vault

const prod = SikkerKey.create('vault_a1b2c3d4e5f6g7h8')
const staging = SikkerKey.create('vault_x9y8z7w6v5u4t3s2')

const prodDb = await prod.getSecret('sk_db_prod')
const stagingDb = await staging.getSecret('sk_db_staging')

List Registered Vaults

const vaults = SikkerKey.listVaults()
// ["vault_a1b2c3d4e5f6g7h8", "vault_x9y8z7w6v5u4t3s2"]

Static method, synchronous (reads the filesystem).

Identity Resolution

  1. Explicit path — starts with / or contains identity.json
  2. Vault ID — looks up ~/.sikkerkey/vaults/{vaultId}/identity.json
  3. SIKKERKEY_IDENTITY env — path to identity file
  4. Auto-detect — single vault on disk

The vault_ prefix is added automatically if not present. Override the base directory with SIKKERKEY_HOME.

Error Handling

The SDK uses typed exceptions. All extend SikkerKeyError:

import { SikkerKey, NotFoundError, AccessDeniedError, AuthenticationError } from '@sikkerkey/sdk'

try {
    const secret = await sk.getSecret('sk_nonexistent')
} catch (e) {
    if (e instanceof NotFoundError) {
        // 404 — secret doesn't exist
    } else if (e instanceof AccessDeniedError) {
        // 403 — machine not approved or no grant
    } else if (e instanceof AuthenticationError) {
        // 401 — invalid signature or unknown machine
    }
}

Exception Hierarchy

SikkerKeyError (extends Error)
├── ConfigurationError      — identity/key issues
├── SecretStructureError    — secret is not a JSON object
├── FieldNotFoundError      — field not in structured secret
└── ApiError                — HTTP error (has httpStatus: number)
    ├── AuthenticationError — 401
    ├── AccessDeniedError   — 403
    ├── NotFoundError       — 404
    ├── ConflictError       — 409
    ├── RateLimitedError    — 429
    └── ServerSealedError   — 503

Properties

PropertyTypeDescription
machineIdstringMachine UUID
machineNamestringHostname from bootstrap
vaultIdstringVault this identity belongs to
apiUrlstringSikkerKey API URL

All properties are read-only getters.

Method Reference

MethodReturnsDescription
SikkerKey.create(vaultOrPath?)SikkerKeyCreate client (static, sync)
SikkerKey.listVaults()string[]List registered vault IDs (static, sync)
getSecret(secretId)Promise<string>Read a secret value
getFields(secretId)Promise<Record<string, string>>Read structured secret
getField(secretId, field)Promise<string>Read single field
listSecrets()Promise<SecretListItem[]>List all accessible secrets
listSecretsByProject(projectId)Promise<SecretListItem[]>List secrets in a project
export(projectId?)Promise<Record<string, string>>Export as env map
watch(secretId, callback)voidWatch a secret for changes
unwatch(secretId)voidStop watching a secret
setPollInterval(seconds)voidSet poll interval (min 10s)
close()voidStop all watches, shut down polling

Exported Types

interface SecretListItem {
    id: string
    name: string
    fieldNames: string | null
    projectId: string | null
}

Retry Behavior

429 and 503 responses are retried up to 3 times with exponential backoff (1s, 2s, 4s). Each retry uses a fresh timestamp and nonce. Network errors are also retried.

Environment Variables

VariableDescription
SIKKERKEY_IDENTITYPath to identity.json — overrides vault lookup
SIKKERKEY_HOMEBase config directory (default: ~/.sikkerkey)

Dependencies

None at runtime. Node.js built-ins only: crypto, fs, path, http, https.

TypeScript types (@types/node) are a dev dependency only. Requires Node.js 18+.

All HTTP requests have a 15-second timeout. HTTPS is enforced for all non-localhost connections.