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

# OpenAI Integration

> Use Boundary with OpenAI structured outputs

OpenAI structured outputs help keep the response JSON-shaped. Boundary applies your schema and domain rules before that JSON reaches application state.

## Install

```bash theme={null}
npm install @withboundary/contract zod openai
```

## Define the contract

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

const openai = new OpenAI();

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

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}; lower the tier or raise score to at least 70`,
    },
    {
      name: "reason_required",
      description: "Every scoring decision must have a non-empty reason",
      fields: ["reason"],
      check: (lead) =>
        lead.reason.trim().length > 0 || "reason must explain the score",
    },
  ],
});
```

## Call OpenAI inside `accept()`

Pass `attempt.instructions` to the model and append `attempt.repairs`. On the first attempt, repairs is empty. On retries, it contains the schema and rule failures from the previous attempt.

```typescript theme={null}
const result = await leadContract.accept(async (attempt) => {
  const response = await openai.responses.create({
    model: process.env.OPENAI_MODEL ?? "gpt-4.1",
    input: [
      {
        role: "user",
        content: [
          "Score this lead as JSON.",
          attempt.instructions,
          `Lead: ${leadSummary}`,
        ].join("\n\n"),
      },
      ...attempt.repairs,
    ],
    text: {
      format: {
        type: "json_schema",
        name: "lead_score",
        schema: {
          type: "object",
          additionalProperties: false,
          properties: {
            tier: { type: "string", enum: ["hot", "warm", "cold"] },
            score: { type: "number", minimum: 0, maximum: 100 },
            reason: { type: "string" },
          },
          required: ["tier", "score", "reason"],
        },
      },
    },
  });

  return response.output_text;
});
```

Return the raw response text. Boundary handles parsing, validation, repair, and the final accept/reject decision.

## Gate side effects

```typescript theme={null}
if (result.ok) {
  await crm.updateLead(leadId, result.data);
  return { status: "accepted", lead: result.data };
}

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

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

Do not fall back to the last parsed attempt when `ok` is false. The rejected output failed the contract.

## Chat Completions

If you are using Chat Completions, the same pattern applies. Put `attempt.instructions` in the system message and map repair messages into the provider's message shape.

```typescript theme={null}
const result = await leadContract.accept(async (attempt) => {
  const response = await openai.chat.completions.create({
    model: process.env.OPENAI_MODEL ?? "gpt-4.1",
    messages: [
      { role: "system", content: attempt.instructions },
      { role: "user", content: `Score this lead: ${leadSummary}` },
      ...attempt.repairs.map((repair) => ({
        role: repair.role as "user" | "assistant",
        content: repair.content,
      })),
    ],
    response_format: { type: "json_object" },
  });

  return response.choices[0]?.message?.content ?? null;
});
```

## What Boundary adds

Use OpenAI structured output support for response shape. Use Boundary for the deterministic decision your app can trust:

* cross-field rules such as "hot tier requires score >= 70"
* repair messages built from actual schema and rule failures
* a typed `ContractResult` that separates accepted data from rejection history
* local logging during development and hosted traces when you add the SDK

Next: [Add Boundary to an LLM feature](/guides/add-boundary-to-an-llm-feature) for the provider-agnostic workflow, or [Production observability](/guides/production-observability) when you want dashboard traces.
