---
title: TypeScript SDK
description: The complete @veefore/sdk surface — protect, the client, typed errors and the assertion helpers.
label: TypeScript SDK
---

```bash
npm install @veefore/sdk
```

ESM and CommonJS builds with type declarations and no runtime dependencies.
Works on Node 20.10+ and in any runtime with `fetch` and WebCrypto.

Running `veefore` in your project installs this package, writes its
configuration and wraps your tools for you. See
[Automatic setup](/build/automatic-setup).

:::security A convenience layer, never a security boundary
Every protected action is decided server-side by the gateway on every call.
Nothing in this package can grant an agent authority it does not already have,
and nothing here caches a permission.
:::

## `guard(toolKey, fn, options?)`

The way most agents integrate. Wrap the function that performs a tool, and
every call is decided by the gateway before your function runs.

```ts
import { guard } from '@veefore/sdk';

const refundCustomer = tool({
  description: 'Refund a customer order',
  parameters: z.object({ orderId: z.string(), amount: z.number() }),
  execute: guard('refund_customer', async ({ orderId, amount }) =>
    payments.refunds.create({ orderId, amount }),
  ),
});
```

| Decision | What `guard()` does |
| --- | --- |
| Permitted, `CLIENT` tool | Runs your function and returns its result |
| Permitted, brokered `HTTP` tool | Returns the gateway's result **without** running your function |
| Refused | Throws `VeeforeBlockedError` |
| Needs a human | Throws `VeeforeApprovalRequiredError`, or waits with `waitForApproval` |

The tool's first argument is sent as the policy `parameters`. The wrapped
function always returns a Promise, so wrap `async` functions.

:::params

:::param capability | string | optional
Defaults to `tool.<toolKey>`, which `veefore init` registers.
:::

:::param waitForApproval | boolean or { timeoutMs } | optional
Wait for a human instead of throwing. Suits background jobs; in a chat turn,
throwing lets the model tell the user.
:::

:::param describe | (...args) => Partial<ActionRequest> | optional
Adds policy inputs derived from the call, such as `amount`, `currency`,
`destination` or `purpose`.
:::

:::param client | VeeforeClient | optional
Use this client instead of the shared one built from the environment.
:::

:::

```ts
execute: guard('refund_customer', refund, {
  describe: ({ amount }) => ({ amount, currency: 'USD', purpose: 'Customer refund' }),
}),
```

Configuration comes from the `VEEFORE_*` environment variables. If they are not
set, `guard()` reads the block `veefore init` writes to `.env.local`; values
already in the environment always win.

## `protect(agent, options)`

For code that calls Veefore directly rather than through a tool function.

```ts
import { protect } from '@veefore/sdk';

const agent = protect(myAgent, { name: 'finance-agent' });
```

Returns a `Proxy` over your object. Own properties and methods pass through
untouched; `veefore`, `act`, `evaluate`, `simulate` and `handshake` are layered
on top, and only where your object does not already define them.

:::params

:::param name | string | optional
Display metadata. Never used for authorization — identity is `agentId`.
:::

:::param gatewayUrl | string | required
From `VEEFORE_GATEWAY_URL` if not passed.
:::

:::param agentId | string | required
From `VEEFORE_AGENT_ID`.
:::

:::param privateKey | string | required
Base64 PKCS#8 Ed25519. From `VEEFORE_AGENT_PRIVATE_KEY`. Never logged, never
sent anywhere, never written to disk by the SDK.
:::

:::param keyId | string | required
From `VEEFORE_AGENT_KEY_ID`. Identifies which key, which is what makes rotation
work.
:::

:::param audience | string | optional
Bound into assertions. Must match the gateway's published audience **exactly**.
Defaults to `gatewayUrl`.
:::

:::param declaredTools | string[] | optional
Tools you intend to use, reported at handshake so anything unauthorized is a
startup warning rather than a mid-task failure.
:::

:::param eagerHandshake | boolean | optional
Opens the session at construction so misconfiguration surfaces immediately.
Defaults to `false` to keep `protect()` synchronous.
:::

:::param requestTimeoutMs | number | optional
Per-request timeout.
:::

:::param maxRetries | number | optional
Retries for **transport errors only**. Decisions are never retried.
:::

:::param fetch | typeof fetch | optional
Inject your own, for instrumentation or a custom agent pool.
:::

:::param onLog | (entry: SdkLogEntry) => void | optional
Structured diagnostics. Never receives secrets. The SDK does not write to
stdout on its own.
:::

:::

Missing required configuration throws `VeeforeConfigurationError` at
construction, naming exactly what is absent.

## `createClient(options)` and `configFromEnv(env?)`

```ts
import { createClient, configFromEnv } from '@veefore/sdk';

const client = createClient({ name: 'worker' });   // env + overrides
const partial = configFromEnv();                   // just read the env
```

Use `createClient` when you have no agent object to wrap — a worker, a cron job,
a service.

## `VeeforeClient`

### `handshake()`

```ts
const info = await agent.handshake();
```

Returns `HandshakeResult`: `agentId`, `mode`, `environment`, `organization`,
`authorizedTools`, `capabilities`, `passportVersion`, `expiresAt`.

Advisory. Useful for failing fast and for logging; not a permission grant, and
not trusted on the next call.

### `authenticate()` and `info`

```ts
await agent.veefore.authenticate();   // open a session without a full handshake
agent.veefore.info;                   // last HandshakeResult, or null
```

### `act(request)`

Performs the action. **Throws** when refused.

### `evaluate(request)`

Returns the `ActionDecision` as data. Throws only on transport and
authentication failures, so a `BLOCK` comes back as a value.

### `simulate(request)`

`evaluate` with `dryRun: true`. Evaluates without executing.

### `actWithApproval(request, { timeoutMs? })`

Evaluates; if an approval is required, waits for it honouring `pollAfterMs`,
then resubmits the identical request with the `approvalId`. The gateway
re-validates everything.

### `approvalStatus(approvalId)` and `waitForApproval(approvalId, options)`

```ts
const status = await agent.veefore.approvalStatus('apr_01ABC');
await agent.veefore.waitForApproval('apr_01ABC', {
  timeoutMs: 15 * 60 * 1000,
  pollIntervalMs: 5000,
});
```

### `mcpCall(request)`

```ts
await agent.veefore.mcpCall({
  server: 'internal-tools',
  tool: 'search_tickets',
  arguments: { query: 'refund failed' },
});
```

### `a2aInvoke(request)`

```ts
await agent.veefore.a2aInvoke({
  connection: 'a2a_partner_logistics',
  capability: 'shipment.query',
  action: 'query',
  payload: { trackingId: 'TRK-8812' },
});
```

## `ActionRequest`

```ts
interface ActionRequest {
  tool: string;                  // registered tool key — required
  action?: string;
  capability?: string;
  parameters?: JsonObject;
  resource?: { key?: string; type?: string; attributes?: JsonObject };
  amount?: number | null;        // minor units; a policy input only
  currency?: string | null;
  destination?: string | null;
  purpose?: string | null;       // shown on the approval card — fill this in
  idempotencyKey?: string;
  approvalId?: string;
  dryRun?: boolean;
  metadata?: JsonObject;
}
```

## `ActionDecision`

```ts
interface ActionDecision {
  actionId: string;
  requestId: string;
  traceId: string;
  mode: GatewayMode;
  decision: PolicyDecision;      // ALLOW BLOCK REQUIRE_APPROVAL STEP_UP_AUTH LIMIT MASK
  outcome: ActionOutcome;
  allowed: boolean;              // branch on this
  result: JsonObject | null;
  reason: string;
  explanation: JsonObject;
  approval: { required: boolean; approvalId: string | null; status: string | null;
              expiresAt: string | null; pollAfterMs: number | null } | null;
  risk: { level: string; score: number } | null;
  shadow: { decision: PolicyDecision; reason: string } | null;
  limits: { appliedAmount: number | null; maskedFields: string[] } | null;
  latencyMs: number;
  stages: StageResult[];
}
```

:::warning `decision` is not always the enforcement decision
In `OBSERVE`, `decision` is the **effective** outcome (`ALLOW`) and
`shadow.decision` is what enforcement would have done. `allowed` already
accounts for mode, which is why it is the field to branch on.
:::

## Errors

| Class | Meaning | Retry |
| --- | --- | --- |
| `VeeforeConfigurationError` | Required configuration missing | no — fix config |
| `VeeforeAuthenticationError` | Identity refused: rotated, revoked, replayed, bad audience | no |
| `VeeforeBlockedError` | Policy refused. Carries the decision | **never** |
| `VeeforeApprovalRequiredError` | Human needed. Carries `approvalId` | not a retry — wait |
| `VeeforeApprovalDeniedError` | A human said no | no |
| `VeeforeRateLimitError` | `429` | yes, with backoff |
| `VeeforeConnectionError` | Transport failure or timeout | yes, with backoff |
| `VeeforeError` | Base class | depends |

```ts
import { VeeforeBlockedError } from '@veefore/sdk';

try {
  await agent.act(request);
} catch (error) {
  if (error instanceof VeeforeBlockedError) {
    error.decision;    // the full ActionDecision
    error.message;     // the operator-authored reason from the policy rule
  }
}
```

`decisionToError(decision)` converts a decision into the matching error class,
which is useful if you used `evaluate()` and later decide you want to throw.

## Assertion helpers

Exported so you can build the wire protocol yourself:

```ts
import {
  createAssertion,
  generateNonce,
  canonicalJson,
  ASSERTION_TTL_SECONDS,
} from '@veefore/sdk';
```

`canonicalJson` is the canonicalisation the server uses — sorted keys, no
insignificant whitespace — so two implementations produce identical bytes and
identical signatures. See
[Sign your own assertion](/build/sign-your-own-assertion).

## Retries, precisely

The SDK retries **transport errors** — connection failures, timeouts, `5xx` —
with exponential backoff, up to `maxRetries`.

It never retries a decision. A `403` is the server's answer, and the answer will
not change. Retrying a refusal produces the same refusal and burns your rate
limit.

## Related

:::cards

:::card Protect an agent | /build/protect-an-agent
The full integration walkthrough.
:::

:::card Errors and retries | /build/errors-and-retries
Every error code mapped to an action.
:::

:::card Python SDK | /build/python-sdk
The same surface, in Python.
:::

:::card Sign your own assertion | /build/sign-your-own-assertion
The protocol with no SDK at all.
:::

:::
