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

# Messages

> What the SDK needs from each chat message and why stable, unique IDs matter.

The SDK builds every ad request from the conversation you feed it. **Messages are the only contextual signal that changes during a session**, so getting them right is the single most important thing for ad relevance and for the ad-cache mechanics to work.

## The message shape

Every SDK accepts the same four fields per message (the exact type names differ per platform — see the [SDK pages](/overview)):

| Field       | Type                  | Required | Purpose                                                                                                  |
| ----------- | --------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `id`        | string                | yes      | Stable, unique identifier for this message. The SDK keys the ad cache and routes events by this id.      |
| `role`      | `user` or `assistant` | yes      | Who sent the message. The SDK only triggers preloads on `user` messages but uses both roles for context. |
| `content`   | string                | yes      | The visible text of the message.                                                                         |
| `createdAt` | timestamp             | yes      | When the message was created.                                                                            |

`content` should be the **visible text** of the message — what the user reads on screen. Don't pre-process it (strip markdown, summarize, etc.) — the server uses the same text the user sees.

## Stable IDs

`id` is the join key between everything else the SDK does:

* The ad cache stores returned ads under each assistant message's `id` (see [Displaying ads](/concepts/displaying-ads) for the pairing rules).
* The ad component (`<InlineAd messageId="..." />` or equivalent) looks up its ad by the same `id`.
* Ad lifecycle events (`filled`, `no-fill`, `viewed`, `clicked` …) carry the `messageId` so you know which slot they refer to.

<Warning>
  **Use the same `id` across renders.** If you assign a different `id` to the same logical message across renders — for example, by generating a fresh UUID every time the React component mounts — the SDK has no way to connect the preloaded ad to the slot that's supposed to show it, and the ad will silently not appear. Use whatever id your backend already has for the message; if you don't have one, generate it once on creation and persist it for the lifetime of the message.
</Warning>

## Pass every message, both roles

Send both **user** and **assistant** messages through `addMessage`. The SDK uses the full conversation, not just the last message, to find a contextually relevant ad. Skipping assistant messages — or, conversely, only forwarding assistant messages — biases the context the server sees and degrades relevance.

Assistant messages are especially important. Kontext ads are generated to follow on directly from the preceding assistant reply — matching its tone and continuing its voice so the ad reads as a smooth continuation of the conversation rather than a hard ad break. If the SDK never sees your assistant messages, the ad has nothing to mimic and the transition becomes jarring.

This is true even when you don't want an ad on a given turn. If you'd like to suppress ads but keep the conversation context intact (for example, only show ads every Nth turn, or for users on a free trial), pass the message normally with `AddMessageOptions(trackOnly: true)` — see [Pacing](/concepts/pacing).

## When to call `addMessage`

Call `addMessage` **once per message**, when the message reaches its final form:

* **User messages** — call right after the user submits, before you forward the message to your LLM backend.
* **Assistant messages** — call when the assistant's reply has finished streaming, not per token.

The SDK debounces preloads by \~10 ms and cancels any in-flight `/preload` when a newer `addMessage` arrives. Rapid back-to-back calls — a user message immediately followed by an assistant reply, several messages restored from history, or any other burst — are coalesced into a single request for the most recent state.

## Restoring a conversation from your backend

When the user reopens a conversation, call `addMessage` once per historical message **in order**. The same debounce coalesces them into a single preload for the most recent user message — you do not pay a network round-trip per restored message.

If your backend stores its own message ids, reuse them. If you have to generate fresh ones (because old ids were not persisted), be aware the SDK will see this as a fresh conversation — fine for context, but any ad the server might have served against the original ids will not be reusable.

## Common mistakes

* **Regenerating `id` on every render.** Use a stable id per message. UUIDs created in the component body during rendering will not survive a re-render and will break ad lookup.
* **Skipping assistant messages.** Both roles go through `addMessage`. Skipping `assistant` rows weakens contextual targeting.
* **Skipping messages "because they don't need an ad".** Use `trackOnly: true` instead — see [Pacing](/concepts/pacing).

## Where to next

<CardGroup cols={2}>
  <Card title="Ad lifecycle events" icon="bell" href="/concepts/events">
    The `filled`, `no-fill`, and render-time events that flow back per `messageId`.
  </Card>

  <Card title="Pacing" icon="eye-slash" href="/concepts/pacing">
    Keeping conversation context intact while suppressing the ad for a turn.
  </Card>

  <Card title="Session lifecycle" icon="circle-play" href="/concepts/session">
    Where `addMessage` fits in the broader session lifecycle.
  </Card>
</CardGroup>
