Node SDK

Use the official Node SDK when your agent runtime already runs in Node.js or TypeScript.

Install

npm install @r4-sdk/node

Requires Node.js 18 or newer.

Quick Start

import R4 from '@r4-sdk/node'
 
const r4 = await R4.create({
  apiKey: process.env.R4_API_KEY!,
  privateKeyPath: './agent-private-key.pem',
})
 
console.log(r4.env.GITHUB_PRODUCTION_TOKEN)

Required Configuration

OptionRequiredDescription
apiKeyYesAGENT API key in {accessKey}.{secret} format
authNoExplicit low-level auth provider, either API key or access token
profileNoNamed CLI profile from ~/.r4
unlockPasswordNoUnlock password for account profiles created by r4 login
privateKeyNoPEM-encoded RSA private key kept locally by the runtime
privateKeyPathNoPath to the PEM-encoded RSA private key
projectIdNoOptional project filter
licenseInstanceIdNoScope the env map to one managed license instance instead of project/vault discovery
trustStorePathNoOptional path to the local trust-store JSON
baseUrlNoAPI base URL, defaults to https://r4.dev
devNoUse https://dev.r4.dev when baseUrl is not set

Provide either privateKey or privateKeyPath for R4.create(). High-level local decryption currently requires an AGENT API key or agent profile. Account profiles are supported by the low-level R4Client for machine API calls such as /me.

Create the agent in the Platform wizard first, then download the one-time runtime JSON. The wizard registers the runtime public key and applies the selected security groups; the SDK only needs the AGENT API key plus the matching local private key for normal environment-variable reads.

What R4.create() Does

R4.create() is the canonical runtime entry point.

It:

  1. authenticates with the AGENT API key
  2. lists accessible vaults, optionally filtered by projectId
  3. fetches each vault's wrapped DEK and signer directory
  4. fetches only fields marked for environment-variable export from GET /api/v1/machine/vault/:vaultId/environment-fields
  5. verifies signer directories, transparency proofs, and wrapped-DEK signatures
  6. unwraps vault DEKs locally
  7. decrypts field ciphertext locally into a flat environment map

For older servers that do not yet expose GET /vault/:vaultId/environment-fields, the SDK falls back to GET /vault/:vaultId/items plus per-item detail reads and still filters locally to fields marked isEnvironmentVariable.

If R4.create() fails because no wrapped key exists for a vault, check the agent setup and sharing path first: the Platform wizard should have registered the public key before the operator granted security-group, project, or direct vault access.

The SDK trust store pins signer continuity by concrete signer key ID: user:<orgUserId>:<userKeyPairId> for user key pairs and agent:<agentId>:<encryptionKeyId> for agent keys. That lets one user or agent have multiple historical keys while still detecting unexpected changes to a previously trusted key.

Access the Decrypted Environment Map

const r4 = await R4.create({
  apiKey: process.env.R4_API_KEY!,
  privateKeyPath: './agent-private-key.pem',
})
 
const dbPassword = r4.env.PRIMARY_DATABASE_PASSWORD

Scope the env map to a project by passing projectId:

const r4 = await R4.create({
  profile: 'openclaw-agent',
  projectId: 'PROJECT_ID',
})
 
console.log(JSON.stringify(r4.env))

This is the SDK equivalent of:

r4 project env PROJECT_ID

The plaintext JSON is produced locally by the SDK or CLI. The machine API never returns already-decrypted environment variables.

Refresh Secrets

Use refresh() when the runtime is long-lived and needs the latest shared values:

await r4.refresh()

License Workflows

Scope the env map to a single managed license instead of a project:

const r4 = await R4.create({
  profile: 'openclaw-agent',
  licenseInstanceId: 'LICENSE_INSTANCE_ID',
})
 
console.log(JSON.stringify(r4.env))

This is the SDK equivalent of r4 licenses env LICENSE_INSTANCE_ID. It calls GET /api/v1/machine/licenses/:licenseInstanceId/environment and decrypts the license credentials locally.

Purchase a license and poll the Pincer job:

const purchase = await r4.purchaseLicense({
  licenseId: 'LICENSE_SLUG',
  vault: { mode: 'new', name: 'New Vault Name' },
})
 
const status = await r4.getLicensePurchaseStatus(purchase.purchaseId)

R4Client exposes the same flows without booting the zero-trust decrypt path, plus the raw bundle reads: getLicenseEnvironment(licenseInstanceId) and getProjectEnvironment(projectIdOrSlug) return wrapped keys, signer directories, and encrypted fields for callers that do their own decryption.

Download Vault Attachments

Use downloadVaultAttachment() when a vault item includes file attachments and you want the runtime to verify and decrypt the blob locally:

const result = await r4.downloadVaultAttachment({
  vaultId: 'VAULT_ID',
  assetId: 'ASSET_ID',
  outputPath: './attachment.bin',
})
 
console.log(result.outputPath)
console.log(result.bytes.length)

That helper:

  1. fetches the machine download ticket
  2. verifies the signed AssetContentCheckpoint against the trusted signer directory
  3. verifies ciphertext size/hash
  4. decrypts the blob locally with the vault DEK
  5. verifies plaintext size/hash before returning or writing the file

When to Use the SDK

Choose the SDK when the runtime:

  • already runs in Node.js
  • wants direct in-process secret access
  • needs programmatic refresh logic
  • wants the zero-trust workflow without shelling out to the CLI

Choose the CLI when shell commands and scripts are the main integration surface.

Low-Level Machine Routes

If you need a machine endpoint that does not have a dedicated SDK helper yet, use the low-level bridge:

const result = await r4.requestMachine({
  method: 'GET',
  path: '/webhook',
})

You can also use new R4Client(apiKey, baseUrl).requestMachine(...) when you want authenticated machine access without booting the full zero-trust decrypt flow.

The client also accepts explicit auth providers:

const accountClient = new R4Client(
  {
    type: 'accessToken',
    accessToken: process.env.R4_ACCESS_TOKEN!,
    deviceInstallationId: process.env.R4_DEVICE_INSTALLATION_ID,
  },
  'https://r4.dev',
)

Or load the same profile the CLI uses:

const client = R4Client.fromProfile({
  profile: 'personal',
  unlockPassword: process.env.R4_CLI_PASSWORD,
})
 
const identity = await client.getMachineIdentity()

For an agent profile imported with r4 configure agent --config <path>, R4.create({ profile: 'openclaw-agent' }) loads the API key, private key path, and trust store path from ~/.r4.

If the SDK is missing a capability you need and the current MCP server or raw machine API also does not cover it, submit product-gap feedback through POST /api/v1/machine/feedback with an AGENT API key. Do not include secrets or private user data in that payload.

Managed Token Providers

The SDK now also supports budget-aware token-provider helpers without giving R4 the plaintext vendor secret.

Read decrypted provider fields locally

const provider = await r4.getTokenProviderFields('TOKEN_PROVIDER_ID', {
  unsafeExport: true,
})
 
console.log(provider.providerType)
console.log(provider.fields['API Key'])

Call Anthropic through a managed token provider

const result = await r4.callAnthropicWithTokenProvider({
  tokenProviderId: 'TOKEN_PROVIDER_ID',
  body: {
    model: 'claude-sonnet-4-6',
    max_tokens: 1024,
    messages: [{ role: 'user', content: 'Summarize the latest runbook changes.' }],
  },
})
 
console.log(result.response)
console.log(result.usage.actualCostUsd)

That path:

  1. fetches the provider-scoped runtime bundle from R4
  2. decrypts the provider secret locally
  3. checks the estimated worst-case hard-block budget state with R4
  4. sends the Anthropic request directly from the runtime
  5. reports finalized usage back to R4
sdk - R4 Docs