Skip to main content
Boundary is built so you can deploy it in regulated, customer-data-handling environments without rewriting your trust boundaries.

Boundary wraps your code, not your network. Your LLM provider relationship is unchanged.

Two products, two trust models

Boundary ships as two separate npm packages with different network behavior. You choose how much of the stack you adopt.
PackageWhat it doesNetwork behavior
@withboundary/contractValidates, retries, and repairs structured LLM output using your schema and rules.None. Pure local validation. No fetch, no telemetry, no analytics. The only runtime peer is zod.
@withboundary/sdkOptional observability — emits structured events about each contract run.Opt-in. Only sends data if you construct a logger with an apiKey or a custom write sink. Ships nothing automatically.
If you only install @withboundary/contract, no data leaves your process.

Data flow

The dashed path only exists when you construct a logger. The solid path runs entirely in-process.

What Boundary cloud can see

When you wire the SDK with an apiKey, every contract run produces a BoundaryLogEvent. Three optional buckets control whether sensitive content rides along — defaults are conservative:
BucketContainsDefault
repairsRepair messages generated to nudge the model on retryon
inputsThe raw prompt you sent to the modeloff
outputsThe raw model responseoff
Everything else is structural and always sent: contract name, environment, run id, attempt count, duration, ok flag, model name, the rule names that failed, the failure category, and SDK attribution. Without those, Boundary can’t show you the run at all. Raw prompts and completions are the most sensitive payload in an LLM pipeline — they stay off until you explicitly opt in. See Capture policy for the exact field-to-bucket mapping.

Why you might still want them on

Turning inputs or outputs on is a debugging convenience for your team, not a feature Boundary needs. When a contract fails, the dashboard’s failure detail page can show you the exact prompt that was sent and the exact response the model returned — usually the fastest way to figure out why a rule rejected an output. Without these buckets you still get the failure category, which rule failed, and the repair messages — enough to track acceptance rates and spot patterns, just not enough to read the original payload. Boundary does not analyze, train on, or repurpose captured payloads. They are stored so you can see them in the dashboard. If your data-handling policy precludes sending raw prompts, leave both off and rely on the always-on metadata + rule failures.
import { createBoundaryLogger } from "@withboundary/sdk";

const logger = createBoundaryLogger({
  apiKey: process.env.BOUNDARY_API_KEY,
  environment: "production",
  capture: {
    inputs: false,   // default
    outputs: false,  // default
    repairs: true,   // default
  },
});

Three transport modes

You decide where events go — or whether they go anywhere at all.
ModeHowWhat leaves your process
No loggerDon’t construct a logger, or construct one without apiKey and without write. The factory returns null.Nothing. The contract still runs; it just has no observability hook. Safe default for local dev.
Custom transportPass a write(events) function.Only what you forward. Events go to your Datadog / Honeycomb / collector. No Boundary egress.
Boundary cloudPass an apiKey.Events POST to https://api.withboundary.com/v1/ingest.
// Custom sink — events stay inside your infrastructure
createBoundaryLogger({
  environment: "production",
  write: (events) => myCollector.emit(events),
});
When the Boundary cloud transport is active, the API key travels in the Authorization: Bearer … header. It is never placed in URLs, query strings, or request bodies. The transport retries with exponential backoff and a circuit breaker so a temporary outage doesn’t turn into a retry storm.

Redaction

Even when you opt into capturing inputs or outputs, you can scrub data before it leaves the process. Three composable layers:
createBoundaryLogger({
  redact: {
    fields:   ["ssn", "email", "apiKey"],     // 1. exact key names, deep
    patterns: [/\b\d{3}-\d{2}-\d{4}\b/],      // 2. regex over string leaves
    custom:   (value, path) => transform(value, path),  // 3. last-chance per leaf
  },
});
Order of operations: Capture runs first — anything it strips can’t be resurrected later. Redaction walks every leaf of the event, so PII in nested objects, repair messages, and any input / output you’ve opted into is covered. The walker is cycle-safe. See Redaction for path semantics, dropping leaves with undefined, and full examples.

API key handling

  • Source: options.apiKey argument or the BOUNDARY_API_KEY environment variable.
  • Transport: sent only as Authorization: Bearer ${apiKey} on requests to the ingest endpoint. Never in URL, query, or body.
  • Logging: never written to console, never echoed in error messages.
  • Absent key: if neither apiKey nor write is configured, createBoundaryLogger returns null. You can leave the call in place across environments without guarding it — local dev runs with no observability and no errors.

No hidden behavior

Boundary does not:
Modify your prompts silentlyThe repair instructions appended on retry are generated from your schema and rules — visible in attempt.instructions, deterministic, and emitted via the logger when repairs capture is on.
Call additional models or servicesOnly your RunFn makes LLM calls. @withboundary/contract never makes network requests.
Persist state between runsEach .accept() call is independent. No global cache, no cross-run state.
Auto-enable telemetryYou explicitly construct a logger and pass it. No default key, no default sink.
Every attempt, every failure, every repair instruction is also available to your code via the ContractResult:
if (!result.ok) {
  for (const attempt of result.error.attempts) {
    console.log(attempt.raw);       // raw model output
    console.log(attempt.issues);    // validation failures
    console.log(attempt.category);  // failure category
  }
}
Nothing is hidden behind the SDK. Anything Boundary cloud sees, your code can also see locally.

Control stays with you

You define:
  • The schema — what structure the output must have.
  • The rules — what “correct” means for your domain.
  • The retry policy — how many attempts, what backoff.
  • The capture policy — which buckets of data leave the process.
  • The redaction rules — what is stripped before transmission.
  • The transport — Boundary cloud, your sink, or no sink at all.
Boundary only enforces what you specify. Nothing more.

See also

Capture policy

Field-to-bucket mapping and opt-in recipes

Redaction

Fields, patterns, and per-leaf scrubbing

beforeSend

Last-chance per-event transform or drop

BoundaryLogEvent

Full wire format reference