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

> ## Agent Instructions
> The Relay API base URL is https://api.relayapp.im. Never use workers.dev origins.
> The contract is raw HTTPS and JSON at https://api.relayapp.im. The one optional published package is @relaymessenger/cli. Import nothing else.
> One send is one message. Mint a message_id (msg_ plus a lowercase Crockford ULID) before sending; it is the message's canonical id and the send's idempotency key, so a retry with the same id replays the stored message.
> Message content is immutable. There is no edit, unsend, or delete route, and no message versions or tombstones.
> A reply is a pointer: reply_to is { message_id, part_id? } and the client draws the quote from the target.
> Verify webhooks with the Standard Webhooks signature over the exact raw request body before parsing it.
> Webhooks and GET /v1/events read the same durable log and can run at once. The pull is plain: after is the last sequence you processed, and nothing is acknowledged.
> An agent in a group is an ordinary member. It receives every message from the sequence it joined at; there are no invocations and no invocation_id.

# Chat SDK

> Add Relay to a Vercel Chat SDK bot as one more adapter, so a single bot codebase answers Relay conversations.

Add Relay to a [Chat SDK](https://chat-sdk.dev) bot by putting one adapter in
its `adapters` map. `@relaymessenger/chat-sdk-adapter` receives signed
`message.received` webhooks, hands each one to your existing handlers as a Chat
SDK message, and commits replies through `POST /v1/messages`. A bot that
already answers on other platforms reaches Relay users without a second
codebase.

Prerequisites:

* **An agent and its Agent Token.** See [Create and connect an agent](/guides/your-agent).
* **A registered webhook with its signing secret.** See [Webhooks](/guides/webhooks).
* **A Chat SDK app.** `chat` is a peer dependency, version `^4.38.0`.

The adapter validates both secrets while the module is evaluated, so on
Cloudflare Workers a missing or malformed value fails the deploy rather than the
first request.

<Info>
  The adapter is published as
  [`@relaymessenger/chat-sdk-adapter`](https://www.npmjs.com/package/@relaymessenger/chat-sdk-adapter)
  and developed in the public
  [`relaymessenger/Relay-SDK`](https://github.com/relaymessenger/Relay-SDK)
  repository under `integrations/chat-sdk`.
</Info>

## Quickstart

```bash theme={null}
npm install @relaymessenger/chat-sdk-adapter chat @chat-adapter/state-memory
```

```ts app/api/relay/route.ts theme={null}
import { createMemoryState } from "@chat-adapter/state-memory";
import { createRelayAdapter } from "@relaymessenger/chat-sdk-adapter";
import { Chat } from "chat";

const chat = new Chat({
  userName: "My Agent",
  adapters: {
    relay: createRelayAdapter({
      token: process.env.RELAY_AGENT_TOKEN!,
      webhookSecret: process.env.RELAY_WEBHOOK_SECRET!,
    }),
  },
  state: createMemoryState(),
});

chat.onNewMention(async (thread, message) => {
  await thread.subscribe();
  await thread.post({ markdown: `You said: ${message.text}` });
});

export const POST = (request: Request) => chat.webhooks.relay(request);
```

Mount the handler as the POST route your webhook registration points at. The
adapter ships no runtime dependencies. It scopes each turn with
`AsyncLocalStorage`, so run it on Node or on a runtime with Node compatibility
enabled, such as Cloudflare Workers with `nodejs_compat`.

<Check>
  A Relay conversation arrives as a Chat SDK thread whose id is
  `relay:<chat_id>`. Handlers you already wrote for other platforms run
  unchanged.
</Check>

## What the adapter enforces

Each row is a contract from these docs that the adapter implements for you.

| Contract                                           | Adapter behavior                                                                                                                                                                          |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Signature verification](/guides/webhooks)         | Standard Webhooks HMAC over the exact raw body. An unsigned, tampered, or stale request gets `401` before its content is read                                                             |
| [At-least-once delivery](/guides/delivery-model)   | The `event_id` is claimed before your handler runs, so two redeliveries racing each other cannot both dispatch, and released if the handler throws so a failed turn is redelivered        |
| [Idempotent sends](/guides/sending-messages)       | Every send carries a `message_id` derived from the inbound `event_id` and the send's position in the turn, and from nothing else. A retry carrying the same id replays the stored message |
| [Group conversations](/guides/group-conversations) | The agent is an ordinary member, so every message in the group reaches your handlers and a reply is an ordinary send                                                                      |
| [Message size](/reference/limits)                  | A reply longer than one text part becomes more parts of the same message. A split consumes the whitespace it lands on                                                                     |
| [Event compatibility](/reference/events)           | Reaction, receipt, and conversation events acknowledge `200` without dispatch, so new event types never break your route                                                                  |

<Warning>
  The dedupe window is a bounded set in memory in one process. A restart, or a
  second instance behind the same webhook URL, has no claim to lose and dispatches
  the event again. The idempotency key is what makes that second dispatch
  harmless.
</Warning>

## Configuration

| Option             | Description                                    | Default                   |
| ------------------ | ---------------------------------------------- | ------------------------- |
| `token`            | Agent Token used for every send                | `RELAY_AGENT_TOKEN`       |
| `webhookSecret`    | Signing secret from webhook registration       | `RELAY_WEBHOOK_SECRET`    |
| `userName`         | Display name the Chat SDK shows for the bot    | `Relay Agent`             |
| `agentId`          | This agent's `agt_` id, when you know it       | unset                     |
| `baseUrl`          | API origin                                     | `https://api.relayapp.im` |
| `toleranceSeconds` | Allowed `webhook-timestamp` skew               | `300`                     |
| `dedupeWindow`     | How many handled `event_id` values to remember | `4096`                    |

## What each operation does on Relay

|     | Chat SDK operation                        | Relay                                                                        |
| :-: | ----------------------------------------- | ---------------------------------------------------------------------------- |
|  ✅  | `postMessage`                             | `POST /v1/messages`                                                          |
|  ✅  | `addReaction`, `removeReaction`           | [Reactions](/guides/reactions) on a message                                  |
|  ✅  | `startTyping`                             | [Typing indicator](/guides/typing-indicators), ephemeral and never stored    |
|  ✅  | `markAsRead`                              | [Read receipt](/guides/read-receipts) for the conversation                   |
|  ✅  | `fetchMessages`, `fetchThread`            | [Conversation history](/guides/conversation-history), paged backwards        |
|  ✅  | `getUser`                                 | The user's profile, scoped to a shared conversation                          |
|  ✅  | Attachments, both directions              | [Attachments](/guides/attachments) and [voice memos](/guides/voice-memos)    |
|  ⚠️ | `stream`                                  | Buffered, then sent as one finished message                                  |
|  ❌  | `editMessage`                             | A committed message is immutable, so this throws `NotImplementedError`       |
|  ❌  | `deleteMessage`                           | A committed message cannot be unsent, so this throws `NotImplementedError`   |
|  ❌  | `fetchMessages({ direction: "forward" })` | The history route pages backwards only, so this throws `NotImplementedError` |
|  ❌  | `openDM`                                  | A conversation starts when a person adds the agent                           |
|  ❌  | Cards                                     | Delivered as fallback text, because Relay has no interactive card surface    |

Set `streaming: false` where your host offers the choice. The adapter sends a
finished message, so nothing partial reaches the person you are answering. See
[Sending messages](/guides/sending-messages) for the send contract it uses.

## Formatting

Relay does not render Markdown. A text part carries plain text plus `styles`
runs with UTF-16 offsets, and clients draw the runs. The adapter flattens
`{ markdown }` and `{ ast }` to the text a person reads and carries emphasis
across as style ranges.

| Markdown   | Relay style     |
| ---------- | --------------- |
| `strong`   | `bold`          |
| `emphasis` | `italic`        |
| `delete`   | `strikethrough` |

Constructs Relay has no style for keep their information in the text. A link
whose label differs from its target renders as `label (url)`, a blockquote keeps
its `> ` prefix, a list keeps its markers, inline and fenced code keep their
backticks, and a table is flattened to one text line per row.

## Next steps

* [Webhooks](/guides/webhooks) for registration, verification, and secret rotation
* [Sending messages](/guides/sending-messages) for parts, styles, and idempotency
* [Group conversations](/guides/group-conversations) for how an agent takes part in a group
* [Integrations](/integrations) for every way to connect an agent
