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

# Anthropic Integration

> Use Boundary with Claude

Claude can return text or tool results. Boundary validates the value that comes back, repairs fixable failures, and gives your app one decision: accepted or rejected.

## Install

```bash theme={null}
npm install @withboundary/contract zod @anthropic-ai/sdk
```

## Define the contract

```typescript theme={null}
import { z } from "zod";
import { defineContract } from "@withboundary/contract";
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic();

const invoiceSchema = z.object({
  subtotal: z.number(),
  tax: z.number(),
  total: z.number(),
  currency: z.string(),
});

const invoiceContract = defineContract({
  name: "invoice-extraction",
  schema: invoiceSchema,
  rules: [
    {
      name: "invoice_math_consistent",
      description: "Subtotal plus tax must equal total within a cent",
      fields: ["subtotal", "tax", "total"],
      check: (invoice) => {
        const expected = invoice.subtotal + invoice.tax;
        return Math.abs(expected - invoice.total) < 0.01
          || `subtotal ${invoice.subtotal} plus tax ${invoice.tax} is ${expected}, but total is ${invoice.total}`;
      },
    },
  ],
});
```

## Call Claude inside `accept()`

`attempt.repairs` are generic `{ role, content }` messages. Map them into Anthropic's message type before sending the retry.

```typescript theme={null}
const result = await invoiceContract.accept(async (attempt) => {
  const response = await anthropic.messages.create({
    model: process.env.ANTHROPIC_MODEL ?? "claude-sonnet-4-20250514",
    max_tokens: 1024,
    system: attempt.instructions,
    messages: [
      {
        role: "user",
        content: `Extract invoice totals from this text:\n\n${invoiceText}`,
      },
      ...attempt.repairs.map((repair) => ({
        role: repair.role as "user" | "assistant",
        content: repair.content,
      })),
    ],
  });

  const block = response.content[0];
  return block?.type === "text" ? block.text : null;
});
```

The `RunFn` must return the raw JSON string, or `null` if the model did not provide text. Boundary owns parsing and validation.

## Tool use

If you already use Claude tool use for structure, return the tool input as a JSON string and let Boundary enforce the rules.

```typescript theme={null}
const result = await invoiceContract.accept(async (attempt) => {
  const response = await anthropic.messages.create({
    model: process.env.ANTHROPIC_MODEL ?? "claude-sonnet-4-20250514",
    max_tokens: 1024,
    system: [
      "Extract invoice data. Use the provided tool.",
      attempt.instructions,
    ].join("\n\n"),
    messages: [
      { role: "user", content: invoiceText },
      ...attempt.repairs.map((repair) => ({
        role: repair.role as "user" | "assistant",
        content: repair.content,
      })),
    ],
    tools: [
      {
        name: "extract_invoice",
        description: "Extract invoice totals",
        input_schema: {
          type: "object",
          additionalProperties: false,
          properties: {
            subtotal: { type: "number" },
            tax: { type: "number" },
            total: { type: "number" },
            currency: { type: "string" },
          },
          required: ["subtotal", "tax", "total", "currency"],
        },
      },
    ],
    tool_choice: { type: "tool", name: "extract_invoice" },
  });

  const toolBlock = response.content.find((block) => block.type === "tool_use");
  if (toolBlock?.type !== "tool_use") return null;

  return JSON.stringify(toolBlock.input);
});
```

## Gate side effects

```typescript theme={null}
if (result.ok) {
  await accounting.createEntry(result.data);
  return { status: "accepted", invoice: result.data };
}

await invoiceReviewQueue.add({
  sourceDocumentId,
  reason: result.error.message,
  attempts: result.error.attempts,
});

return { status: "needs_review" };
```

Use the model you already trust for the task. Boundary is not a model router; it is the deterministic check after the model returns structured data.

Next: [Invoice extraction](/guides/invoice-extraction) for a complete use case, or [Local development](/guides/local-development) for testing the contract without provider calls.
