> ## 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.

# Quickstart

> Install the local contract package and accept your first LLM output

This quickstart uses the free `@withboundary/contract` package. It does not require a Boundary account, API key, or hosted dashboard.

<Tip>
  Using an AI coding assistant? Install the Boundary skill first so it can find the LLM output path, add the contract at the trust boundary, and verify accepted, repaired, and rejected outcomes.
</Tip>

<Prompt description="Install the Boundary skill in your AI coding agent." actions={["copy", "cursor"]}>
  npx skills add [https://docs.withboundary.com](https://docs.withboundary.com)
</Prompt>

## Install

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

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

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

## Define what correct means

A contract is a schema plus rules. The schema checks the shape. Rules check the values your product cares about.

```typescript lead-contract.ts theme={null}
import { z } from "zod";
import { defineContract } from "@withboundary/contract";

const leadSchema = z.object({
  tier: z.enum(["hot", "warm", "cold"]),
  score: z.number().min(0).max(100),
  reason: z.string(),
});

export const leadContract = defineContract({
  name: "lead-scoring",
  schema: leadSchema,
  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}; set tier to warm/cold or raise score to at least 70`,
    },
    {
      name: "reason_required",
      description: "Every scoring decision must explain why",
      fields: ["reason"],
      check: (lead) =>
        lead.reason.trim().length > 0 || "reason must not be empty",
    },
  ],
  debug: true,
});
```

`debug: true` turns on the built-in console logger. It prints attempts, failures, repairs, and the final result locally. No data leaves your process.

## Wrap your model call

`contract.accept()` takes your existing LLM call as a function. Return the raw model output as a string. Boundary parses it, validates it, repairs failures when possible, and returns a typed result.

```typescript score-lead.ts theme={null}
import { leadContract } from "./lead-contract";

const result = await leadContract.accept(async (attempt) => {
  const response = await callYourLLM({
    messages: [
      {
        role: "user",
        content: [
          "Score this lead as JSON.",
          attempt.instructions,
          "Lead: signed up two days ago, visited pricing three times, opened one email.",
        ].join("\n\n"),
      },
      ...attempt.repairs,
    ],
  });

  return response.text;
});
```

On the first attempt, `attempt.repairs` is empty. If the output fails, Boundary builds repair messages from the schema and rule violations. Your next model call receives those messages.

## Handle the result

```typescript theme={null}
if (result.ok) {
  await crm.updateLead(leadId, result.data);
} else {
  const lastAttempt = result.error.attempts.at(-1);

  console.error("Lead scoring rejected:", {
    message: result.error.message,
    category: lastAttempt?.category,
    issues: lastAttempt?.issues,
  });

  await reviewQueue.add({
    leadId,
    reason: result.error.message,
    attempts: result.error.attempts,
  });
}
```

When `result.ok` is `true`, `result.data` is typed from your schema and every rule has passed. When `result.ok` is `false`, no data is returned. You decide whether to fail the request, retry with different context, or send it to a human review queue.

## What local debugging shows

With `debug: true`, a failed first attempt gives you the important parts:

```text theme={null}
[contract] lead-scoring attempt 1 failed RULE_ERROR
[contract] hot_requires_high_score: tier is "hot" but score is 25; set tier to warm/cold or raise score to at least 70
[contract] reason_required: reason must not be empty
[contract] retrying with repair context
[contract] lead-scoring accepted on attempt 2
```

That is enough to tune rules, prompts, and reject handling before you wire the hosted dashboard.

## Next steps

<CardGroup cols={2}>
  <Card title="Local development" icon="terminal" href="/guides/local-development">
    Use console logging, tests, and custom hooks without an API key
  </Card>

  <Card title="Add Boundary to an LLM feature" icon="code" href="/guides/add-boundary-to-an-llm-feature">
    Adapt this pattern to your existing provider code
  </Card>

  <Card title="OpenAI guide" icon="bolt" href="/guides/openai">
    Use Boundary with OpenAI structured outputs
  </Card>

  <Card title="Production observability" icon="chart-line" href="/guides/production-observability">
    Send contract events to the hosted dashboard
  </Card>
</CardGroup>
