> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buildbetter.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Chats SDK

> Connect your AI agent to BuildBetter with sessions, participant identity, product context, and message feedback.

Send your agent's conversations to BuildBetter to find user friction, misunderstood requests, and opportunities to improve responses. Send message feedback separately to distinguish explicit ratings from findings extracted through qualitative analysis.

<Warning>
  The Node SDK, `@buildbetter/ai`, is a preview and is not published to npm. The SDK examples below describe the preview interface, not an installable release. Use the [Agent Chats HTTP API](/pages/api/agent-chats#send-a-conversation) for an integration today.
</Warning>

## Set up your destination

1. Open **Library → Agent Chats → Set up Agent Chats** in the destination workspace. Ask an administrator if Agent Chats is unavailable.
2. Create an application, such as **Support assistant**, with a stable key, such as `support-agent`.
3. Set its access policy, content capture, redaction, retention, and allowed environments before sending customer data.
4. Create an ingest credential. Save the secret when it appears. BuildBetter shows it once.

An application represents the agent or product that sends conversations. It groups those conversations and sets their ingestion and access rules.

Keep the credential in your server's secret manager. It is an ingestion-only secret, not your organization API key. Never send it to a browser.

| Destination | API base URL |
| - | - |
| Production | `https://api.buildbetter.app` |
| Staging | `https://api-staging.buildbetter.app` |

Use an application key and credential created in the destination environment. The credential selects the workspace. `context.environment` describes your source environment and does not select the destination.

Enabling Agent Chats does not connect any agent automatically. Customers connect their own agents to their own workspaces. It does not grant access to another workspace's chats.

## Start with HTTP

The HTTP API works from any language. Send a server-side `POST` to `/v3/rest/agent-conversations/ingest` with:

* Header `X-BuildBetter-Agent-Key`: your ingest secret.
* Header `Content-Type`: `application/json`.
* Body `agentKey`: your application's stable key.
* Body `externalSessionId`: your conversation ID.
* Body `events`: messages or feedback with stable event IDs, sequences, and timestamps.

Follow the complete [conversation example](/pages/api/agent-chats#send-a-conversation), then send the [feedback example](/pages/api/agent-chats#send-message-feedback). Both use the same session. No SDK package is required.

## Node SDK preview

The preview requires Node.js 22.12 or later. The root package supports ESM and CommonJS. This example uses ESM.

The variables below represent server configuration. Set the secret outside your source code.

```ts theme={null}
import { BuildBetterAI } from "@buildbetter/ai";

const buildbetter = new BuildBetterAI({
  apiKey: process.env.BUILDBETTER_AGENT_SECRET!,
  agent: "support-agent",
  apiUrl: "https://api.buildbetter.app",
});

const session = {
  sessionId: "production:account_17:chat_123",
  startedAt: "2026-09-08T18:00:00.000Z",
  user: {
    id: "account_17:user_42",
    name: "Avery Chen",
    username: "avery",
    email: "avery@example.com",
    companyId: "account_17",
  },
  context: {
    productArea: "reporting",
    surface: "dashboard-assistant",
    routeTemplate: "/dashboards/:id",
    environment: "production",
  },
};

await buildbetter.capture({
  ...session,
  status: "open",
  messages: [
    {
      id: "user_1",
      role: "user",
      sequence: 1,
      createdAt: "2026-09-08T18:00:00.000Z",
      content: "Why is this report blank?",
    },
    {
      id: "assistant_1",
      role: "assistant",
      sequence: 3,
      createdAt: "2026-09-08T18:00:03.000Z",
      content: "I could not find the report.",
      final: true,
    },
  ],
});
```

Use your persisted IDs and timestamps in place of these sample values. Include the source environment and account in IDs when multiple accounts share one destination application.

The SDK uses positive odd message sequences and reserves the next even sequence for feedback. This example uses explicit sequences so later batches keep their positions. Without explicit sequences, send the complete visible history in its original order.

Names, usernames, and emails support participant identification. `user.id` maps to the HTTP field `externalId`, and `user.name` maps to `displayName`. External identities do not create BuildBetter users or Persons.

## Identity and properties

Send the identity from your authenticated server. Do not trust an ID supplied by an unauthenticated browser.

| SDK field | HTTP field | Meaning |
| - | - | - |
| `user.id` or `participants[].id` | `participants[].externalId` | Your stable customer or user ID, up to 255 characters. Scope it to your account when IDs can overlap. |
| `user.name` | `participants[].displayName` | Display name, up to 200 characters. |
| `user.username` | `participants[].username` | Username, up to 255 characters. |
| `user.email` | `participants[].email` | Email address, up to 320 characters. One unique, case-insensitive match can link an existing Person in this workspace. |
| `user.personId` | `participants[].personId` | An existing BuildBetter Person UUID in the destination workspace. An explicit UUID takes precedence over email matching. |
| `user.companyId` | `participants[].companyExternalId` | Your external company or account ID, up to 255 characters. This is not a BuildBetter Company UUID. |
| `user.boundary` | `participants[].boundary` | `external` for customers, or `internal` for team members. The SDK defaults to `external`. Raw HTTP participants must specify this field. |
| `user.properties` | `participants[].properties` | Participant attributes, such as role or plan. These do not update the canonical Person record. |
| `ownerUserId` | `ownerUserId` | An active BuildBetter user UUID in the destination workspace. Sets owner-only access. This is not your external customer ID. |
| `messages[].participantId` | `events[].participantExternalId` | The external ID of the participant who sent a message. Use this for conversations with multiple people. |
| `context.properties` | `context.properties` | Attributes for the conversation, such as account tier, experiment, or feature version. |
| `messages[].context.properties` | `events[].context.properties` | Attributes for a specific message. |

The same participant fields work inside `participants[]`. The `user` shortcut also attributes user messages to that participant unless a message supplies `participantId`.

Each properties object accepts up to 50 keys. Keys contain 1–80 characters. Values can be strings, finite numbers, booleans, null, arrays, or JSON objects.

Company IDs are retained with the conversation. They do **not** create companies, link CRM company records, or provide PostHog-style group profiles. Person matching does not create people. If an email matches multiple people, BuildBetter does not choose one. Configure existing CRM records separately when you need canonical associations.

```ts theme={null}
const identity = {
  id: "account_17:user_42",
  email: "avery@example.com",
  companyId: "account_17",
  boundary: "external" as const,
  properties: { role: "admin", plan: "growth" },
};

await buildbetter.capture({
  ...session,
  user: identity,
  context: {
    environment: "production",
    surface: "support-widget",
    properties: { accountId: "account_17", experiment: "answer-v2" },
  },
  messages: savedMessages,
});
```

Send only the personal data that your workspace needs. Redaction applies before storage and can prevent identity matching.

## SDK field reference

### Client options

| Option | Meaning |
| - | - |
| `apiKey` | Required ingestion secret. Keep it on the server. |
| `agent` | Required application key, 3–64 lowercase letters, digits, underscores, or hyphens. Start and end with a letter or digit. |
| `apiUrl` | Destination API origin. Defaults to `https://api.buildbetter.app`. Remote destinations require HTTPS. |
| `timeoutMs` | Timeout for each HTTP request. Defaults to 5,000 milliseconds. A timeout does not prove the server rejected the batch. |
| `fetch` | Optional Fetch API implementation, for example for transport instrumentation. Do not log the credential or transcript. |

### Conversation and message fields

| Field | Meaning |
| - | - |
| `sessionId`, `startedAt` | Required stable session identity and original start time. Dates accept a `Date`, an ISO string, or epoch milliseconds. |
| `status`, `endedAt` | Status defaults to `open`. Use `ended` or `abandoned` with the final batch and an end time when known. |
| `agentVersion` | Your agent version. Messages can supply their own version. |
| `user`, `participants`, `ownerUserId` | Identity and access fields described above. |
| `context` | Product context. See the complete [context reference](/pages/api/agent-chats#context-and-filters). |
| `messages` | Required messages with stable `id`, `role: "user"` or `"assistant"`, and visible text in `content` or text `parts`. |
| `messages[].createdAt` | Original message time. Without it, the SDK derives a stable time from the session start and position. Prefer persisted times. |
| `messages[].sequence` | Positive odd position. Supply it when sending only new messages. Otherwise, send complete visible history in its original order. |
| `messages[].revision`, `messages[].final` | Revision defaults to `1`. Increase it when content changes. Use `final: false` for an unfinished response. |
| `messages[].model` | Optional `provider` and `name`. |
| `messages[].usage` | Nonnegative integers: `inputTokens`, `outputTokens`, `cachedInputTokens`, `costUsdMicros`, and `latencyMs`. Cost uses millionths of a US dollar. |
| `messages[].trace` | Optional `traceId`, `spanId`, and `parentSpanId` for correlation. These do not upload a complete trace. |
| `source` | Advanced source metadata: `sdk`, optional `version`, `sequenceLayout`, and `batchComplete`. Keep SDK defaults for normal capture. |

An empty assistant message with an explicit odd sequence and `final: false` reserves a position without content. Reuse its ID and sequence when the response finishes. Increase its revision.

`capture()`, `feedback()`, and `ingest()` return the BuildBetter session UUID, accepted/deduplicated/revised event counts, and `processingStatus`. They also return `batchCount` and each transmitted event's ID and sequence. An accepted batch is not proof of completed analysis.

## Add thumbs-up, thumbs-down, or written feedback

Send feedback from your authenticated server after it verifies that the user can access the target message. For the assistant response above:

```ts theme={null}
await buildbetter.feedback({
  ...session,
  messageId: "assistant_1",
  messageSequence: 3,
  occurredAt: "2026-09-08T18:00:10.000Z",
  revision: 1,
  rating: "negative",
  labels: ["incorrect", "missing-context"],
  comment: "The report exists. You used the wrong dashboard.",
});
```

Use `positive` for a good response and `negative` for a bad response. Supply at least one rating, label, or comment. `messageSequence` is the target assistant's sequence, not the feedback sequence.

The preview SDK keeps one feedback event per target message. Increase `revision` when that feedback changes. Independent votes from multiple people require distinct feedback events through the [HTTP API](/pages/api/agent-chats#send-message-feedback).

Explicit feedback creates a Signal without model credits. Qualitative analysis of finalized assistant responses uses response credits. Use the Agent Chat source filters in Signals to separate explicit feedback from inferred findings.

## Vercel AI SDK preview

The optional wrapper supports Vercel AI SDK 7 (`ai@^7.0.0`) and uses the ESM subpath `@buildbetter/ai/vercel`. Your provider package must support that AI SDK version.

```ts theme={null}
import { generateText } from "ai";
import { withBuildBetter } from "@buildbetter/ai/vercel";

// providerModel is a model from your configured AI SDK provider.
const model = withBuildBetter(providerModel, {
  client: buildbetter,
  ...session,
  turnId: savedTurn.assistantMessageId,
  turnSequence: savedTurn.assistantSequence,
  turnStartedAt: savedTurn.startedAt,
  messageIds: savedChat.messageIds,
  messageSequences: savedChat.messageSequences,
  messageTimestamps: savedChat.messageTimestamps,
  messageRevisions: savedChat.messageRevisions,
  onError: () => console.warn("BuildBetter capture failed"),
});

await generateText({
  model,
  prompt: "Why is this report blank?",
});
```

The wrapper captures visible user and assistant text. It excludes system prompts, reasoning, files, and tool inputs and outputs. Capture errors do not fail the model request.

Persist `turnId`, a positive odd `turnSequence`, and `turnStartedAt` before generation. Reuse them on retries and tool steps. Supply the message maps by zero-based visible history position. Increase a message's revision when you edit it.

Await each call for a session before the next call. Consume or cancel a response stream before another call. The wrapper rejects overlapping calls for the same destination and session within a process. Coordinate sessions across processes in your application.

For the simplest integration, use `capture()` after saving finalized messages. Use the wrapper when you need model-level capture and can supply stable turn metadata. Do not combine wrapper capture and direct capture for the same session.

## Delivery and retries

* Persist message IDs, sequences, revisions, and timestamps before sending. Reuse them for retries.
* Increase `revision` when an existing event changes. Keep its sequence and type unchanged.
* With the SDK, send new messages with explicit odd sequences. Do not mix raw HTTP event numbering with SDK numbering in one session.
* Send `status: "ended"` or `"abandoned"` when the conversation ends. Include at least one event, such as a replay of the final message.
* Await delivery before the process exits. Use a durable server queue when delivery must survive request or process failures.
* Direct SDK methods throw on failure. They do not provide a durable queue or automatic retries. Retry transient failures with bounded backoff and stable event data.
* The SDK batches requests and truncates oversized visible text. The HTTP limits are 500 events, 2 MiB per request, and 256 KiB per event.

Fix authentication and payload errors before retrying. See the [response codes](/pages/api/agent-chats#troubleshooting).

## Confirm the integration

1. Send a synthetic conversation with a unique session ID and a known surface.
2. Confirm the session appears in **Library → Agent Chats** in the intended workspace.
3. Send negative feedback on its assistant response. Confirm the rating and message link.
4. Retry the unchanged payload. Confirm the API reports deduplicated events instead of a second conversation.
5. Check access with an account outside the application's allowed users.

An accepted response with `processingStatus: "pending"` confirms ingestion, not completed signal extraction. Check the conversation after processing finishes.

Before sending real data, review the [privacy settings](/pages/api/agent-chats#privacy-and-retention). When `ownerUserId` is omitted, the application's access policy controls visibility, with workspace access as the default. For owner-only chats, use an active BuildBetter user UUID from the destination workspace, not an external participant ID.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.