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
- Explicit path — starts with
/or containsidentity.json - Vault ID — looks up
~/.sikkerkey/vaults/{vaultId}/identity.json SIKKERKEY_IDENTITYenv — path to identity file- 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
| Property | Type | Description |
|---|---|---|
machineId | string | Machine UUID |
machineName | string | Hostname from bootstrap |
vaultId | string | Vault this identity belongs to |
apiUrl | string | SikkerKey API URL |
All properties are read-only getters.
Method Reference
| Method | Returns | Description |
|---|---|---|
SikkerKey.create(vaultOrPath?) | SikkerKey | Create 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) | void | Watch a secret for changes |
unwatch(secretId) | void | Stop watching a secret |
setPollInterval(seconds) | void | Set poll interval (min 10s) |
close() | void | Stop 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
| Variable | Description |
|---|---|
SIKKERKEY_IDENTITY | Path to identity.json — overrides vault lookup |
SIKKERKEY_HOME | Base 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.