@r4-sdk/node
v1.1.0
Published
Official R4 SDK for Node.js — programmatic access to R4 vault secrets
Maintainers
Readme
@r4-sdk/node
Official R4 SDK for Node.js. Use it from trusted server-side or agent runtimes to read R4 vault-backed secrets, download encrypted attachments, and call authenticated machine API routes.
The SDK is intentionally self-contained: it has no R4-internal runtime dependencies and ships only the compiled lib/ output.
Requirements
- Node.js 18 or newer
- An AGENT-scoped R4 API key and local PEM private key for zero-trust decryption
- Or a CLI account profile for low-level machine API calls
Installation
npm install @r4-sdk/nodeQuick Start
import R4 from '@r4-sdk/node'
const r4 = await R4.create({
apiKey: process.env.R4_API_KEY!,
privateKeyPath: './agent-private-key.pem',
})
const password = r4.env.PRODUCTION_DB_PASSWORDOnly fields explicitly marked as environment variables in R4 are exposed through r4.env.
Scope the environment map to one project:
const r4 = await R4.create({
profile: 'openclaw-agent',
projectId: 'production-infrastructure',
})
const password = r4.env.PRODUCTION_DB_PASSWORDThis uses the same project environment data as r4 project env production-infrastructure.
Values are decrypted locally; the machine API only returns encrypted vault data and wrapped keys.
Configuration
| Option | Required | Description |
| --- | --- | --- |
| apiKey | No | AGENT-scoped API key in {accessKey}.{secret} format. |
| auth | No | Explicit auth provider for low-level machine API access. |
| profile | No | Named CLI profile from ~/.r4. |
| unlockPassword | No | Unlock password for account profiles created by r4 login. |
| privateKey | No | Inline PEM private key. Use only when your runtime already manages key custody securely. |
| privateKeyPath | No | Filesystem path to the local PEM private key. |
| trustStorePath | No | JSON trust-store path for pinned signer keys. |
| projectId | No | Restrict vault discovery to one project by ID or slug. |
| baseUrl | No | API base URL. Defaults to https://r4.dev. |
| dev | No | Use https://dev.r4.dev when baseUrl is not set. |
Provide either privateKey or privateKeyPath for R4.create(). When both dev and baseUrl are provided, baseUrl wins.
High-level local decryption requires an AGENT API key or an agent profile imported with r4 configure agent --config <path>. Account profiles are supported by R4Client for authenticated machine API calls.
Core API
R4.create(config)
Creates an SDK instance, fetches accessible vault metadata, verifies signer trust, unwraps vault keys locally with the already-registered agent key, and populates r4.env.
r4.env
Flat Record<string, string> of decrypted environment variables. Keys are generated as VAULT_ITEM_FIELD in SCREAMING_SNAKE_CASE.
r4.refresh()
Re-fetches accessible vaults and rebuilds the local env map.
r4.requestMachine({ method, path, body })
Sends an authenticated request to a machine API route without bypassing SDK auth handling.
const identity = await r4.requestMachine({
method: 'GET',
path: '/me',
})new R4Client(auth, baseUrl)
Low-level machine API client for workflows that do not need local decryption.
import { R4Client } from '@r4-sdk/node'
const client = new R4Client(process.env.R4_API_KEY!, 'https://r4.dev')
const identity = await client.getMachineIdentity()The first argument can also be an explicit auth provider:
const client = new R4Client(
{
type: 'accessToken',
accessToken: process.env.R4_ACCESS_TOKEN!,
deviceInstallationId: process.env.R4_DEVICE_INSTALLATION_ID,
},
'https://r4.dev',
)Or load a CLI profile:
const client = R4Client.fromProfile({
profile: 'personal',
unlockPassword: process.env.R4_CLI_PASSWORD,
})Attachments
downloadVaultAttachment retrieves a signed download ticket, verifies signer trust and content checkpoints, downloads ciphertext, checks ciphertext and plaintext hashes/sizes, decrypts locally, and optionally writes the plaintext file.
const result = await r4.downloadVaultAttachment({
vaultId: 'VAULT_ID',
assetId: 'ASSET_ID',
outputPath: './artifact.bin',
})
console.log(result.outputPath)
console.log(result.bytes.length)Managed Token Providers
Use callAnthropicWithTokenProvider when you want R4 budget enforcement while keeping the vendor API key local to the runtime.
const result = await r4.callAnthropicWithTokenProvider({
tokenProviderId: 'TOKEN_PROVIDER_ID',
body: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Summarize the incident notes.' }],
},
})
console.log(result.response)
console.log(result.usage.actualCostUsd)If you need raw decrypted provider fields for a custom client, use the explicit escape hatch:
const provider = await r4.getTokenProviderFields('TOKEN_PROVIDER_ID', {
unsafeExport: true,
})Security Model
The SDK never asks R4 to decrypt vault values. The runtime:
- Loads the private key from the imported agent runtime config.
- Uses the public key registered by the Platform agent wizard.
- Fetches wrapped vault data encryption keys and signer directories.
- Verifies signer fingerprints and rotation continuity.
- Pins trusted signer keys in a local trust store.
- Verifies wrapped-key signatures before unwrapping vault keys locally.
- Decrypts only the requested vault fields or attachment bytes in process memory.
The trust store is written with owner-only permissions. If a trust store exists but cannot be parsed, the SDK fails closed instead of silently resetting pins.
Operational Notes
- Keep the private key outside source control and deployment logs.
- Use one agent key per runtime identity so access can be revoked precisely.
- Create agents in the R4 web UI, download the runtime JSON config, and import it with
r4 configure agent --config <path>when you want the CLI and SDK to share the same local agent profile. - The web UI registers the public key and applies selected security groups during agent creation.
- Rotate keys by re-registering the new public key with continuity proof and rewrapped vault keys.
- Prefer
privateKeyPathover inlineprivateKeyunless the surrounding runtime already has a dedicated secret manager.
Development
pnpm install
pnpm run typecheck
pnpm run test
pnpm run test:pack
pnpm run buildnpm pack --dry-run is covered by pnpm run test:pack; it verifies that source files and tests are excluded from the published tarball.
