Skip to main content
This page starts after you already have a contract running locally. If you are still building your first contract, start with the main quickstart and local development guide. The SDK adds production visibility: accepted and rejected runs, attempt counts, failing rules, repair messages, and runtime attribution.

Install

npm install @withboundary/contract @withboundary/sdk

Set the API key

Get an API key from the Boundary dashboard. The SDK reads BOUNDARY_API_KEY from the environment by default.
export BOUNDARY_API_KEY=bnd_live_...

Wire a logger to your contract

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

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:
{
  "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.

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.
// Works in prod. No-op locally.
const logger = createBoundaryLogger();

Next steps

Capture policy

Turn raw inputs/outputs on or off

Redaction

Scrub PII before events leave the process

Shutdown

Drain the queue on serverless / edge / workers

createBoundaryLogger

Every option, with defaults