> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withboundary.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Quickstart

> Install the hosted SDK and send contract events to the dashboard

This page starts after you already have a contract running locally. If you are still building your first contract, start with the [main quickstart](/quickstart) and [local development guide](/guides/local-development).

The SDK adds production visibility: accepted and rejected runs, attempt counts, failing rules, repair messages, and runtime attribution.

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @withboundary/contract @withboundary/sdk
  ```

  ```bash pnpm theme={null}
  pnpm add @withboundary/contract @withboundary/sdk
  ```

  ```bash yarn theme={null}
  yarn add @withboundary/contract @withboundary/sdk
  ```
</CodeGroup>

## Set the API key

Get an API key from the [Boundary dashboard](https://withboundary.com). The SDK reads `BOUNDARY_API_KEY` from the environment by default.

```bash theme={null}
export BOUNDARY_API_KEY=bnd_live_...
```

## Wire a logger to your contract

```typescript theme={null}
import { z } from "zod";
import { defineContract } from "@withboundary/contract";
import { createBoundaryLogger } from "@withboundary/sdk";

const logger = createBoundaryLogger({
  environment: "production",  // "production" | "staging" | "development"
});

const contract = defineContract({
  name: "lead-scoring",       // appears on every event
  schema: z.object({
    tier: z.enum(["hot", "warm", "cold"]),
    score: z.number().min(0).max(100),
  }),
  rules: [
    {
      name: "hot_requires_high_score",
      description: "Hot leads must have a score of at least 70",
      fields: ["tier", "score"],
      check: (lead) =>
        lead.tier !== "hot" || lead.score >= 70
          || `tier is "hot" but score is ${lead.score} (minimum 70 for hot)`,
    },
  ],
  logger,
});
```

## Run a contract

```typescript theme={null}
const result = await contract.accept(async (attempt) => {
  const response = await callYourLLM({
    messages: [
      {
        role: "user",
        content: [
          "Score this lead as JSON.",
          attempt.instructions,
          leadSummary,
        ].join("\n\n"),
      },
      ...attempt.repairs,
    ],
  });

  return response.text;
});

if (result.ok) {
  await crm.updateLead(leadId, result.data);
} else {
  await reviewQueue.add({
    leadId,
    reason: result.error.message,
    attempts: result.error.attempts,
  });
}
```

Every `accept()` produces a terminal event. Runs with failed intermediate attempts also produce non-terminal failure events with repair context. Events are queued, batched, and shipped to Boundary's ingest endpoint.

## What gets sent

The SDK's default capture policy is conservative. On a successful run you'll see roughly:

```json theme={null}
{
  "contractName": "lead-scoring",
  "environment": "production",
  "timestamp": "2026-04-20T09:45:12.104Z",
  "runId": "bnd_run_8t2k7n9p4qz3v1m6h5b8x",
  "attempt": 2,
  "maxAttempts": 3,
  "ok": true,
  "final": true,
  "durationMs": 1843,
  "rulesCount": 1,
  "sdk": { "name": "@withboundary/sdk", "runtime": "node/22.12.0" }
}
```

The repair message that nudged the model on the second attempt is on the prior `final: false` event for the same `runId` — the dashboard stitches them together for you.

Raw LLM `input` and `output` are **not** included unless you opt in — see [Capture policy](/sdk/capture-policy).

## Dev-safe by default

If `BOUNDARY_API_KEY` is not set and you don't pass a custom `write`, `createBoundaryLogger` returns `null`. Passing `null` to `defineContract` is a no-op — the contract runs normally, no events sent, no errors.

```typescript theme={null}
// Works in prod. No-op locally.
const logger = createBoundaryLogger();
```

## Next steps

<CardGroup cols={2}>
  <Card title="Capture policy" icon="filter" href="/sdk/capture-policy">
    Turn raw inputs/outputs on or off
  </Card>

  <Card title="Redaction" icon="eye-slash" href="/sdk/redaction">
    Scrub PII before events leave the process
  </Card>

  <Card title="Shutdown" icon="power-off" href="/sdk/shutdown">
    Drain the queue on serverless / edge / workers
  </Card>

  <Card title="createBoundaryLogger" icon="code" href="/api-reference/create-boundary-logger">
    Every option, with defaults
  </Card>
</CardGroup>
