npm package: @r4-sdk/node
Description: Official R4 SDK for Node.js — programmatic access to R4 vault secrets
Latest version: 1.0.4

# @r4-sdk/node

Official R4 SDK for Node.js. Provides zero-trust programmatic access to R4 vault secrets for agent runtimes, plus a low-level bridge to arbitrary authenticated machine routes.

## Installation

```bash
npm install @r4-sdk/node
```

Requires Node.js >= 18.0.0 (uses native `fetch`).
The published SDK is self-contained and does not require any other R4 package at runtime.

For low-level machine calls such as identity/capability introspection or
arbitrary headless routes, the package also exports `R4Client`.

## Quick Start

```typescript
import R4 from '@r4-sdk/node'

const r4 = await R4.create({
  apiKey: 'agent_access_key.secret',
  privateKeyPath: './agent-private-key.pem',
})

const password = r4.env.PRODUCTION_DB_PASSWORD
```

## API

### `R4.create(config): Promise<R4>`

Static factory that creates and initializes an R4 instance. This is the recommended entry point.

### `r4.env: Record<string, string>`

Access project environment variables as a flat key-value map. Keys are `SCREAMING_SNAKE_CASE` (`VAULT_ITEM_NAME_FIELD_NAME`).

### `r4.refresh(): Promise<void>`

Re-fetches environment variables from the API. Useful for long-running processes that need fresh secrets.

### `r4.downloadVaultAttachment({ vaultId, assetId, outputPath? }): Promise<{ assetId, vaultId, checkpoint, outputPath, bytes }>`

Downloads a vault attachment through the machine attachment ticket route,
verifies the signed asset-content checkpoint against the trusted signer
directory, verifies ciphertext/plaintext hashes and sizes, decrypts the blob
locally with the vault DEK, and optionally writes the plaintext file to disk.

```typescript
const result = await r4.downloadVaultAttachment({
  vaultId: 'VAULT_ID',
  assetId: 'ASSET_ID',
  outputPath: './attachment.bin',
})

console.log(result.outputPath)
console.log(result.bytes.length)
```

### `r4.getTokenProviderFields(tokenProviderId, { unsafeExport: true }): Promise<{ id, name, providerType, fields }>`

Fetches the zero-trust token-provider runtime bundle, verifies its signer
directory and detail checkpoint, and decrypts the provider fields locally.
This is the explicit unsafe escape hatch for custom vendor clients. Prefer the
managed helpers when possible so budget preflight/reporting stays on the
default path.

### `r4.callAnthropicWithTokenProvider({ tokenProviderId, body, ... }): Promise<{ response, usage, authorization, requestId }>`

Managed Anthropic helper that:

1. decrypts the provider secret locally
2. preflights the token-provider budget gate with R4 using the estimated worst-case call cost
3. calls Anthropic directly from the runtime
4. reports finalized usage back to R4

R4 never proxies the Anthropic request or receives the plaintext API key.

### `new R4Client(apiKey, baseUrl).getMachineIdentity(): Promise<MachineIdentityResponse>`

Low-level helper for `GET /api/v1/machine/me`. Use this when a runtime needs to
inspect its current scope, org/tenant binding, resolved machine permissions, or
vault-write constraints without booting the full secret-decryption flow.

### `r4.requestMachine({ method, path, body }): Promise<unknown>`

Low-level helper that sends an authenticated request to any machine API route.
Use this when you need a newer headless endpoint before the SDK grows a
first-class wrapper for it.

### `new R4Client(apiKey, baseUrl).requestMachine({ method, path, body }): Promise<unknown>`

The same low-level machine-route bridge without booting the full zero-trust
runtime.

### `new R4Client(apiKey, baseUrl).getTokenProviderRuntime(...)`

Low-level access to the provider-scoped runtime bundle route.

### `new R4Client(apiKey, baseUrl).authorizeTokenProviderUsage(...)`

Low-level helper for the managed preflight budget/pricing route. The backend
uses the estimated call cost for a prospective hard-block decision.

### `new R4Client(apiKey, baseUrl).reportTokenProviderUsage(...)`

Low-level helper for finalized managed token-usage reporting.

## Configuration

| Option      | Required | Description                                       |
| ----------- | -------- | ------------------------------------------------- |
| `apiKey` | Yes | AGENT-scoped API key in format `{accessKey}.{secret}` |
| `privateKey` | No | PEM-encoded RSA private key kept locally by the agent |
| `privateKeyPath` | No | Path to the PEM-encoded RSA private key |
| `projectId` | No | Optional project ID filter for accessible vaults |
| `dev` | No | Use `https://dev.r4.dev` when `baseUrl` is not set |
| `trustStorePath` | No | Optional path for the signer trust-store JSON |
| `baseUrl` | No | API base URL (defaults to `https://r4.dev`) |

You must provide either `privateKey` or `privateKeyPath`.
When both `dev` and `baseUrl` are provided, `baseUrl` wins.

## How It Works

1. Authenticates with an AGENT-scoped API key
2. Registers the runtime's public key through `POST /api/v1/machine/vault/public-key`, sending a best-effort `X-R4-Agent-Hostname` claim so operators can see where the active key last initialized
3. Becomes eligible for vault-backed access grants after that first registration
4. Lists accessible vaults through the machine API
5. Fetches each vault's wrapped DEK and signed org user-key directory plus transparency proof
6. Verifies the signed org directory, checks the append-only transparency proof, and continuity-pins the signer in the local trust store
7. Verifies wrapped-DEK signatures against that authenticated signer set
8. Unwraps the DEK locally with the agent private key
9. Verifies signed vault summary/detail checkpoints, then decrypts field ciphertext locally into a flat `SCREAMING_SNAKE_CASE` env map

That same trust chain now also covers attachment downloads: the runtime verifies
the signed `AssetContentCheckpoint`, checks ciphertext and plaintext hashes /
sizes, decrypts the blob locally with the vault DEK, and pins `asset:<assetId>`
versions in the trust store to fail closed on rollback.

Operationally, the runtime should complete that first public-key registration
before operators assign security-group, project, or direct vault access to the
agent. Re-registering the same key is safe, and rotating to a different key is
supported when the caller submits the replacement `rewrappedVaultKeys` batch for
the active vault DEKs that key can reach. The backend also stores the observed
request IP and optional hostname claim on the agent for later platform/UI
visibility; this metadata is informational only and does not participate in
authorization.

## Managed Token Providers

Use the managed helper when you want R4 to enforce token-provider budgets while
still keeping the vendor secret local to the runtime:

```typescript
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 latest incident notes.' }],
  },
})

console.log(result.response)
console.log(result.usage.actualCostUsd)
```

If you need the raw decrypted fields for a custom client, call
`getTokenProviderFields(tokenProviderId, { unsafeExport: true })` instead.

## Trust Store

The SDK trust store persists:

- continuity pins for org-directory signer/user keys
- append-only transparency head pins for each org directory
- rollback pins for signed metadata versions

In production `r4.dev` environments, the first trusted org-directory head is
now anchored to the public witness at `https://transparency-prod.r4.dev` and
verified with the embedded witness root before the SDK accepts the API's first
signer set. Later directory updates must still present a valid append-only
proof from the pinned head, later signer rotations must present continuity
signatures, and every vault-specific signer subset must match the signed org
directory returned by the machine API.

The remaining gap is that the public witness still lives in the same AWS
account, so this is stronger than API-only TOFU but not yet a fully independent
external auditor or gossip network.
Development hosts such as `https://dev.r4.dev` now default to the dedicated
non-production witness at `https://transparency-dev.r4.dev`, while custom/local
hosts still fall back to explicit configuration or local trust pins.
By default the SDK stores this beside the private key as `<privateKeyPath>.trust.json`,
or in `./.r4-trust-store.json` when the private key is passed inline.

## Development

```bash
pnpm run test     # Run mocked zero-trust SDK tests from test/
pnpm run test:pack # Verify npm publish excludes src/ and test/
pnpm run build    # Build with tsup
pnpm run clean    # Remove lib/
```

The published SDK is controlled by the `files` allowlist in `package.json`, so
only `lib/` is shipped. Package-local tests live in `test/` and are validated
with `npm pack --dry-run` through `pnpm run test:pack`.