Use the official Node SDK when your agent runtime already runs in Node.js or TypeScript.
npm install @r4-sdk/nodeRequires Node.js 18 or newer.
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)| Option | Required | Description |
|---|---|---|
apiKey | Yes | AGENT API key in {accessKey}.{secret} format |
auth | No | Explicit low-level auth provider, either API key or access token |
profile | No | Named CLI profile from ~/.r4 |
unlockPassword | No | Unlock password for account profiles created by r4 login |
privateKey | No | PEM-encoded RSA private key kept locally by the runtime |
privateKeyPath | No | Path to the PEM-encoded RSA private key |
projectId | No | Optional project filter |
licenseInstanceId | No | Scope the env map to one managed license instance instead of project/vault discovery |
trustStorePath | No | Optional path to the local trust-store JSON |
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(). 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.
R4.create() DoesR4.create() is the canonical runtime entry point.
It:
projectIdGET /api/v1/machine/vault/:vaultId/environment-fieldsFor 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.
const r4 = await R4.create({
apiKey: process.env.R4_API_KEY!,
privateKeyPath: './agent-private-key.pem',
})
const dbPassword = r4.env.PRIMARY_DATABASE_PASSWORDScope 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_IDThe plaintext JSON is produced locally by the SDK or CLI. The machine API never returns already-decrypted environment variables.
Use refresh() when the runtime is long-lived and needs the latest shared values:
await r4.refresh()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.
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:
AssetContentCheckpoint against the trusted signer directoryChoose the SDK when the runtime:
Choose the CLI when shell commands and scripts are the main integration surface.
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.
The SDK now also supports budget-aware token-provider helpers without giving R4 the plaintext vendor secret.
const provider = await r4.getTokenProviderFields('TOKEN_PROVIDER_ID', {
unsafeExport: true,
})
console.log(provider.providerType)
console.log(provider.fields['API Key'])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: