# Advertisers Source: https://docs.kontext.so/advertisers Reach your ideal audience with contextually relevant, AI-powered native ads. ## Why advertise with Kontext? Kontext delivers AI-powered, contextually relevant ads that engage users naturally within conversations. With our advanced targeting capabilities, your ads appear at the perfect moment when users are most receptive to your message. * **Contextual relevance**: Ads that match the conversation and your product's audience * **High engagement rates**: Native formats that users actually interact with * **Brand safety**: AI-powered content filtering and placement * **Analytics and reports**: Access to performance metrics for full transparency ## Getting started is easy Reach out to us at [advertisers@kontext.so](mailto:advertisers@kontext.so) and our team will set you up with an account and help you launch your first campaign within our network of publishers and placements. # Ad engine Source: https://docs.kontext.so/concepts/ad-engine How Kontext's ad engine serves ads from direct partners and programmatic DSPs. ## Two sources of demand Every ad that fills on your traffic comes from one of two sources our engine aggregates into a single auction per slot: **Direct partners.** Advertisers and ad networks we have direct relationships with. Their campaigns are pre-vetted by us, often run on negotiated pricing, and tend to have higher fill rate and tighter contextual relevance — we control the integration end-to-end. **Demand-side platforms (DSPs).** Programmatic demand. DSPs are the tools advertisers use to buy ad inventory at scale. We aggregate many DSPs into the same auction, adding breadth of demand, broader geo coverage, and competitive bidding. Both sources compete in the same auction per ad slot; the winning bid is what your user sees. ## What this means for your integration * **SKAdNetwork.** Each DSP has its own SKAN identifier — list them all in `Info.plist` so DSPs can attribute installs. Direct partners don't need extra SKAN IDs. See [SKAdNetwork](/guides/skadnetwork). * **ads.txt / app-ads.txt.** DSPs check this file before bidding; it must list our authorization line. See [ads.txt](/guides/ads-txt). * **Fill rate.** Higher when more DSPs are eligible to bid on your traffic. We add demand sources over time — keep your config in sync (SKAN identifiers, ads.txt) so new partners can actually bid. * **Geo coverage.** Different DSPs serve different regions. If a region looks empty in your dashboard, it usually means we're still working on demand for that geo. # Ad formats Source: https://docs.kontext.so/concepts/ad-formats Image, video, interstitial, and banner — what each format is and how the ad server picks which one to deliver. Every Kontext ad has two parts: 1. **AI-generated text** that picks up the tone of the assistant message and continues it naturally, so the transition into the ad reads as part of the conversation rather than a hard break. 2. **A format-specific creative** rendered right below the text — an image, a video, a banner, or, for interstitial, a CTA that opens a full-screen experience. You mount the same component (`` or its platform equivalent) for all four formats — the SDK figures out the right way to render whatever the ad server returns. ## The four formats AI-generated text followed by a static image creative and a call-to-action. AI-generated text followed by a short auto-playing video with a call-to-action. Viewability is tracked through standard MRC rules. AI-generated text followed by a tappable creative that opens a full-screen modal ad. Designed for high-impact, low-frequency moments. AI-generated text followed by a compact horizontal banner — smaller footprint than an image ad, suited for slots where vertical space is at a premium. ## Personalization for interstitial ads Interstitial is our most advanced format — we use the **Character** object you pass on the session to drive the pre-roll and post-roll text and visuals so the interstitial feels like an extension of the assistant the user is already talking to. For interstitial to work well, the following Character fields are **required**: * `id` * `name` * `avatarUrl` The remaining fields are **useful but optional** — pass them when you have the data, and the personalization gets sharper: * `greeting` — drives the interstitial pre-roll text; strongly recommended for this format. * `persona` — a short personality description. * `tags` — themes, interests, audience signals. * `isNsfw` — flag adult-targeted characters so we filter creatives accordingly. The Character object is set once when you create the session (see [Session lifecycle](/concepts/session)) and pinned for that conversation. The exact field names per platform live on the [SDK pages](/overview). ## How a format is chosen You don't pick a format yourself for each ad. **The ad server picks** based on: * The placement code your `` is bound to. * The conversation context (messages, character) the SDK forwards on `/preload`. * Which creatives the auction returns and what they perform best as. * The user's device and locale. This way the same component automatically renders an image ad in one slot, a video ad in another, and an interstitial when the campaign calls for it — without you writing format-specific code. ## What you can adjust You can pass a `theme` to each ad component (typically `"light"` or `"dark"`) so the ad picks up the right base palette. Beyond that, we tailor the creative's design — typography, spacing, colors, and overall layout — for every publisher individually, so the ad blends into your app instead of feeling bolted on. If you have a specific look in mind, tell us during onboarding or reach out via [Support](/resources/support). ## Where to next Where the ad slot goes and which message id to bind it to. Events you receive as the ad renders, becomes visible, and is clicked. Viewability, OMID certification, and platform policies that apply per format. Reach out to change which formats are allowed on your placement codes. # Displaying ads Source: https://docs.kontext.so/concepts/displaying-ads Where the ad slot goes, which message id to bind it to, and the common 'latest assistant message' pattern. The ad component (`` or its platform equivalent) is a **lookup**: it renders whatever ad the SDK has cached under the `messageId` you pass. The SDK pairs each returned ad with the **latest assistant message** in your conversation — automatically. When you call `addMessage('assistant', { id: 'msg-42', ... })`, that's the id the next available ad is keyed under. So the rule for `` is simply: **pass the id of the assistant message the ad should appear next to**. Timing is handled for you — whether the preload returns before or after the assistant message arrives, the ad shows up as soon as both are in place. ## The flow Your app calls `session.addMessage({ role: 'user', ... })`. The SDK fires a debounced `POST /preload` in the background while the assistant is still composing its reply. Your app receives the assistant's message and calls `session.addMessage({ id: 'msg-42', role: 'assistant', ... })`. The SDK links the preloaded ad to this id. Render `` directly below the assistant turn in your UI. The SDK looks up the linked ad for `messageId="msg-42"` and renders it inside an iframe, written to continue the assistant's tone. The ad is bound to the **assistant** message id, not the user message id — even though the preload was triggered when the user message was added. This is how the ad ends up positioned directly under the assistant turn that gave it context. ## The "latest assistant message" pattern In the vast majority of chat UIs, the ad slot lives directly below the **most recent** assistant message. Render `` against that message's id and you've covered the common case. ```tsx React theme={null} {messages.map((m) => (
{m.role === 'assistant' && m.id === latestAssistantId && ( )}
))} ``` ```vue Vue theme={null} ``` ```swift Swift theme={null} // In your chat data source, after the latest assistant message: let ad = session.createAd(latestAssistantMessageId) let adView = InlineAdUIView(ad: ad) container.addSubview(adView) ```
## Common mistakes * **Using the user message id.** The user message is what triggers the preload, but the ad is linked to the assistant message that follows. Bind `` to the assistant message id. * **Recreating the component on every keystroke.** Use a stable Vue/React `key={messageId}` so the component is not torn down and remounted while content streams in. ## Where to next What goes into `addMessage` and why stable ids matter. Inline, interstitial, reward — what each format does and which fits your slot. What the SDK emits when the ad finally renders. # Ad lifecycle events Source: https://docs.kontext.so/concepts/events Every event the SDK can emit — filled, no-fill, viewed, clicked, render-*, video.*, reward.granted — what triggers them and what to do with them. The session emits events at every meaningful step of an ad's life, from the moment the server responds to `/preload` through to the user clicking through. You subscribe via `onEvent` (or the platform-specific equivalent — a Combine publisher on Swift, a `Flow` on Kotlin, etc.) and react however your app needs. This page lists every event in one place. For the exact API on a given platform, see the [SDK pages](/overview). ## Three stages, one stream All events arrive on a single stream, but it helps to know which stage each one belongs to: * **After `/preload` returns** — the SDK has decided whether an ad will be available for the current assistant message. Fires `ad.filled`, `ad.no-fill`, or `ad.error`. * **While the ad renders** — the iframe starts streaming content and finishes. Fires `ad.render-started`, `ad.render-completed`, and `ad.height` (when the iframe reports its size). * **As the user interacts** — fires `ad.viewed` once the MRC viewability standard is met, `ad.clicked` on tap-through, and (for video / rewarded creatives) `video.started`, `video.completed`, `reward.granted`. ## Reference | Event | Stage | When it fires | Key payload | | --------------------- | ----------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `ad.filled` | Preload | An ad was returned and linked to the current assistant message id. | `code` (placement), `revenue` | | `ad.no-fill` | Preload | Preload completed but the server returned no eligible ad. | `skipCode` — see [Debugging](/guides/debugging#skipcode-reference) for the full list | | `ad.error` | Any stage | Something went wrong during preload or render. | `message`, `errCode` | | `ad.render-started` | Render | First token of the streamed ad content arrived. | bid id | | `ad.render-completed` | Render | Last token of the streamed ad content arrived. | bid id | | `ad.height` | Render | The iframe reported a new height (use to size the surrounding container). | `messageId`, `height` | | `ad.viewed` | Interaction | MRC viewability standard met. Counts as an impression. | `messageId`, `content`, `format`, `revenue` | | `ad.clicked` | Interaction | User tapped or clicked the ad. | `messageId`, `url`, `format`, `area` | | `video.started` | Interaction | Video playback began. | bid id | | `video.completed` | Interaction | Video playback finished. | bid id | | `reward.granted` | Interaction | A rewarded-ad reward was earned by the user. | bid id | The exact field names per platform live on the SDK pages — they all carry the same semantics, just under their language-native casing. ## Where to next Where these events come from in the rendering flow. What happens to events when you suppress ads for a turn. The OMID standard behind `ad.viewed` and per-SDK certification status. `onDebugEvent` and server-side debug forwarding for deeper insight. # How it works Source: https://docs.kontext.so/concepts/how-it-works The session-to-render flow that powers every Kontext integration. Kontext serves contextually relevant ads inside text-based AI apps — chat, search, and any interface where users talk to a model. Your app feeds the SDK the messages of a conversation as they happen; the SDK contacts our ad server in the background and renders the winning ad in the slot you mount in your UI. ## The flow ```mermaid theme={null} sequenceDiagram autonumber participant App as Your app participant SDK as Kontext SDK participant Server as Kontext ad server App->>SDK: createSession(publisherToken, userId, conversationId) SDK->>Server: POST /init Server-->>SDK: enabled, preloadTimeout, ... Note over App,SDK: User sends a message App->>SDK: session.addMessage(message) SDK->>Server: POST /preload (debounced) Server-->>SDK: ads[] for the preload SDK-->>App: filled / no-fill event per messageId Note over App,SDK: Assistant replies App->>SDK: mount SDK->>App: render ad in iframe SDK-->>App: viewed / clicked / error events ``` 1. **Create a session.** Your app instantiates one `Session` per chat conversation, supplying your `publisherToken`, a stable `userId`, and a `conversationId`. The SDK calls `/init` in the background to fetch server-controlled flags (whether the session is enabled, the preload timeout, telemetry toggles). 2. **Feed messages to the SDK.** As the user and the assistant exchange messages, the app calls `session.addMessage(...)` for each one. This is the only signal the SDK needs to do its work. 3. **The SDK preloads ads in the background.** Each `addMessage` triggers a debounced `POST /preload` carrying the conversation context. The server returns one or more ads for the preload. The SDK pairs each ad to the latest assistant message id (see [Displaying ads](/concepts/displaying-ads) for the pairing rules) and emits a `filled` event for each id that got an ad, or `no-fill` otherwise. 4. **Mount an ad slot in your UI.** When the assistant's reply is on screen, your app renders `` (the component name differs per SDK). The component is a renderer — it does not fetch — it simply looks up the cached ad for that `messageId` and renders the creative inside an iframe. 5. **Render-time events flow back to your app.** As the ad loads, becomes visible, or is clicked, the SDK emits `viewed`, `clicked`, and `error` events. Subscribe to them alongside `filled` / `no-fill` to drive your own UI — for example, to reveal an assistant bubble only once the ad has resolved. ## What you provide vs. what the SDK handles | You provide | The SDK handles | | --------------------------------------------------------------------- | ----------------------------------------------------- | | `publisherToken` (issued during onboarding) | `/init`, `/preload`, `/error`, `/debug` network calls | | `userId`, `conversationId` (stable IDs you generate) | Ad cache, message-id matching, TTL | | The list of messages, via `addMessage` | Debounce, retries, request cancellation | | The placement: where `` is mounted | Iframe rendering, viewability tracking | | Optional regulatory signals (see [Compliance](/resources/compliance)) | TCF auto-collection from the standard CMP storage | ## Where to next Install an SDK, initialize a session, mount one component, see an ad — every SDK page is its own quickstart. The mental model behind every Kontext integration: sessions, IDs, messages, events, and more. Pre-launch checklist — what to set up, what to monitor, how to get the highest fill rate. Runnable example apps for every SDK — clone, drop in your publisher token, and run. # IDs and tokens Source: https://docs.kontext.so/concepts/ids-and-tokens publisherToken, placement code, userId, conversationId, IFA, IDFV — what each is, who generates it, and what it is used for. Every Kontext integration uses a handful of identifiers. Some you supply when creating a session; some are issued by Kontext during onboarding; others are read by the SDK directly from the OS. This page covers what each is, who generates it, and what it is used for. | Identifier | Who generates it | Scope | What it is for | | ----------------- | ------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `publisherToken` | Kontext, during onboarding | Your account | Identifies which publisher account a request belongs to. | | Placement `code` | Kontext, during onboarding | Per ad slot type | Identifies the kind of ad slot a component represents (`inlineAd` is the default). | | `userId` | You | Per user, stable across conversations | Lets the ad server recognize the same user across conversations — required for retargeting, rewarded ads, and frequency capping. | | `conversationId` | You | Per conversation | Scopes a single chat session in our analytics and on the wire. | | IFA (IDFA / GAID) | Apple / Google, read by the SDK | Per device, when permission is granted | Platform-standard advertising identifier; used for retargeting and frequency capping when available. | | IDFV | Apple, read by the SDK | Per app vendor on iOS device | Stable iOS-only identifier that does not require ATT; used alongside the IFA. | ## `publisherToken` Kontext issues you a `publisherToken` during onboarding. It identifies your account, governs which placements and ad formats your app may render, and is the key that the ad server uses to attribute revenue to you. You'll typically have **two tokens**: * A **development token** that returns test ads (the standard test creatives, useful for end-to-end testing during integration). * A **production token** that participates in the real auction. You can ship the production token in the client app — it is not a server secret. Treat it like a public API key with limited scope: do not actively publish it, but losing it from the client binary is not a security incident. ## Placement `code` A **placement code** identifies a specific kind of ad slot in your app. The default code is `inlineAd`, which most apps use without further configuration. Kontext issues additional codes during onboarding if your app uses more than one ad slot type. You pass the placement code on every ad component (for example, ``). If your app uses custom or multiple placement codes, also pass the list of codes you may render in `enabledPlacementCodes` when creating the session — the SDK only attempts to fill placements whose codes appear in this list. ## `userId` `userId` is **your** identifier for this user. It should be: * **Stable across conversations.** The same user starting a new conversation tomorrow should arrive with the same `userId`. This is what makes retargeting and rewarded ads work — the ad server needs to know it is talking to the same user it has seen before. * **Not personally identifiable.** Pass an anonymized internal user id. The ad server does not need PII to do its job. * **Unique per user.** Different users must get different `userId`s — anonymous fallback values (`"anonymous"`, `null`, the empty string) collapse everyone into one record and break retargeting. If your app doesn't yet have a stable user id, generate a UUID on first launch and persist it; that is enough. ## `conversationId` `conversationId` is **your** identifier for this conversation. Generate a new one whenever the user starts a new conversation, and reuse it for as long as the conversation lasts. The session is bound to one `conversationId` — see [Session lifecycle](/concepts/session) for the rules around when to create or reuse. A `conversationId` should be unique across all conversations from a given user. Reusing one will mix ad cache and analytics from unrelated chats; the SDK will keep working but the data will be wrong. ## Advertising identifier (IFA) The SDK reads the platform's standard **Identifier for Advertisers** when one is available and the user has granted permission: * **iOS — IDFA.** Gated by Apple's App Tracking Transparency (ATT) prompt since iOS 14. Your app must declare `NSUserTrackingUsageDescription` in `Info.plist` and trigger the prompt at a natural moment in the app flow. * **Android — GAID.** Declared via the `com.google.android.gms.permission.AD_ID` permission, which the SDK contributes through manifest merger. Respected by the user's Google account ad-personalization settings. * **Web.** No direct equivalent. You don't pass the IFA in — the SDK reads it directly from the OS. When the IFA is available it is forwarded on every ad request; when it is not (permission denied, OS-level opt-out, or no IFA on the platform) the SDK falls back to a first-party identifier it generates internally. See [Compliance](/resources/compliance) for the consent and platform-policy details. ## IDFV `IDFV` is the **Identifier for Vendor** on iOS — a UUID Apple assigns to your app vendor on a device. Unlike IDFA, it does not require ATT permission and is always available on iOS. The SDK reads it via `UIDevice.current.identifierForVendor` and forwards it on every ad request as a stable iOS-side companion to the IFA. It is **per app vendor on a device**: every app from the same developer team shares the same IDFV on that device, and the IDFV resets when the user uninstalls every app from that vendor. ## Where to next How these identifiers flow through the session, from create to close. How the IFA and IDFV interact with consent, ATT, AD\_ID, TCF, and what the SDK collects. # Messages Source: https://docs.kontext.so/concepts/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 (`` 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. **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. ## 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 The `filled`, `no-fill`, and render-time events that flow back per `messageId`. Keeping conversation context intact while suppressing the ad for a turn. Where `addMessage` fits in the broader session lifecycle. # Pacing Source: https://docs.kontext.so/concepts/pacing How often ads appear in a conversation — paced by us, or overridden per message with trackOnly. **Pacing** is how often ads appear in a conversation. Showing one under every assistant message is usually too much — it saturates the chat and the reader tunes out. Kontext supports two ways to control pacing, and they compose. ## Option 1: Let us pace it Pacing lives in your account configuration on our side. Tell us the cadence you want — for example, "an ad every five assistant messages", or "no ad in the first three turns of a fresh conversation" — and we set it up. The SDK then automatically decides which assistant messages get an ad. No code changes in your app. If you're unsure where to start, we'll suggest a cadence based on what works for similar publishers. ## Option 2: Override per message with `trackOnly` If you want to suppress the ad in a specific slot, simply set `trackOnly: true` on the **user** message that precedes the assistant turn you want to skip: ```ts theme={null} session.addMessage( { id: 'u-99', role: 'user', content: '…', createdAt: new Date() }, { trackOnly: true } ) ``` The assistant message that follows will not show an ad. The current turn is muted, but **the SDK keeps working in the background**: * **You still call `addMessage` for every message.** Skipping it breaks the conversation context the server uses for targeting in later turns. Always pass every message — just flag the ones you want to suppress. * **The SDK still hits `/preload`.** Server-side analytics stay accurate. * **The server prepares an ad ahead of time for the next non-`trackOnly` turn.** This is a server-side benefit, not a client-side cache: when the next ad-eligible turn arrives, fill rate is higher (the server had more time to find a good contextual match) and the ad is delivered faster (it was already prepared on our side). In short, `trackOnly` hides the ad for one turn while keeping the conversation context warm on our side, so the next ad-eligible turn fills faster and more often. ## Where to next The `addMessage` call you set `trackOnly` on. Which events still fire when an ad is suppressed. # Session lifecycle Source: https://docs.kontext.so/concepts/session How a Kontext session is created, fed messages, listened to, and closed. A **Session** represents a single chat conversation. It is the unit of work the SDK is organized around — it holds the conversation context, the ad cache, the active network calls, and the event stream. Every Kontext integration creates, uses, and disposes of sessions following the same lifecycle, even if the exact API surface differs slightly between SDKs. ## One session per conversation A session is bound to **one user in one conversation**. When the user starts a new conversation, you create a new session with a new `conversationId`. Do not reuse a session across conversations. | Situation | What to do | | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | User sends another message in the same conversation | Reuse the existing session — just call `addMessage`. | | User starts a new conversation | Close the current session and create a new one with a new `conversationId`. | | User logs out / a different user logs in | Close the current session and create a new one with the new `userId`. | | App is backgrounded and resumed within the same conversation | Reuse the session if it is still alive; otherwise create a new one and the SDK will refill the cache from the existing messages. | ## The four stages Every session goes through the same four stages, regardless of SDK. ### 1. Create You create a session by supplying the IDs and options that describe the conversation: * `publisherToken` — issued during onboarding, identifies your account. * `userId` — a stable identifier for this user that does not change between conversations. * `conversationId` — unique to this conversation. * Optional: `character`, `regulatory`, `variantId`, callbacks for events and debug output. The full list and exact field names per SDK live on the [SDK pages](/overview). See [IDs and tokens](/concepts/ids-and-tokens) for what each identifier is for. As part of creation, the SDK calls `POST /init` in the background. The response carries server-controlled flags: whether the session is `enabled` at all, the `preloadTimeout`, and the two telemetry toggles (`reportErrors`, `reportDebug`). These are stable for the lifetime of the session — recreate the session if you need them refreshed. ### 2. Feed messages As the conversation progresses, your app calls `addMessage` for each new message. Both user and assistant messages should go through this call — the SDK uses the full conversation, not just the last message, to find a contextually relevant ad. Each `addMessage` triggers a debounced `POST /preload`. The SDK pairs the returned ads with assistant message ids and emits a `filled` or `no-fill` event for each. See [Messages](/concepts/messages) for what each message must contain and why stable IDs matter. ### 3. Listen for events The session is also the channel through which the SDK reports back to your app. Events flow on a single stream: * **Preload-time events** — `filled` and `no-fill` per `messageId`, as soon as `/preload` resolves. * **Render-time events** — `viewed`, `clicked`, `error`, plus video and rewarded events when applicable, while a mounted ad component is showing the ad. How you subscribe depends on the SDK: a callback closure, a Combine publisher, a Kotlin `Flow`, a React event prop, etc. See [Ad lifecycle events](/concepts/events) for the complete event list and what each one means. ### 4. Close When the conversation ends, close the session. Closing: * Cancels any in-flight `/preload`, `/error`, or `/debug` requests. * Releases the ad cache. * Stops the event stream. Imperative SDKs (Swift, Kotlin, JS) expose this as an explicit `destroy()` call. Declarative SDKs (React, React Native, Vue, Flutter) close the session automatically when the `AdsProvider` unmounts — there is nothing extra for you to call. Forgetting to close an imperative session leaks resources, but it is not catastrophic — the next session created for the same user will work normally. ## Mutable vs. immutable options Most options you pass at creation time are **immutable** — to change them, close the session and create a new one. A few options are **mutable** at runtime; common ones include the regulatory object (consent strings can change after the CMP prompt) and, in some SDKs, the character. The mutable surface is intentionally small to keep the ad cache and the conversation context consistent. Refer to the SDK page for which options on your platform are mutable and how to update them. ## Where to next What each identifier on the session means and who generates it. What goes into `addMessage` and why stable IDs matter. The complete list of events the session emits and what each means. # ads.txt & app-ads.txt Source: https://docs.kontext.so/guides/ads-txt Host the IAB authorized-sellers file at your domain so demand partners trust your traffic. [`ads.txt`](https://iabtechlab.com/ads-txt/) (web) and [`app-ads.txt`](https://iabtechlab.com/app-ads-txt/) (mobile apps) are plain-text files published at the root of a publisher's domain. They list the companies authorized to sell that publisher's ad inventory. Most [DSPs](/concepts/ad-engine) check this file before bidding — if a seller isn't listed there, they assume the inventory is fraudulent and skip it. For Kontext to bring you demand, your publisher's `ads.txt` / `app-ads.txt` needs to list our authorization line. Adding it is a one-time setup and a hard requirement for ramping up fill rate. ## Where the file lives * **Web app** → `https://yourdomain.com/ads.txt` * **Mobile app** → `app-ads.txt` hosted at the root of your developer marketing site. The site that's linked from your App Store / Google Play listing as the developer URL — that's the domain DSPs will check, so the file must live there at `https://yourmarketingsite.com/app-ads.txt`. If you already host one of these files for other ad partners, just **append** our line — don't replace the file. ## The line you'll add We'll send you the exact line to add during onboarding. It follows the standard IAB format: ``` , , DIRECT, ``` Once you've added it, no further action is needed on your side — DSPs will pick up the change the next time they crawl your file (typically within 24 hours). ## Updating later If we add new sell-side partners or change our `seller-domain`, we'll send you an updated line. Append the new line (don't remove the old one until we tell you to) and the change will propagate the same way. If you're not sure what your file currently contains or whether our line is in there, send us your `ads.txt` URL at [support@kontext.so](mailto:support@kontext.so) and we'll verify. # Best practices Source: https://docs.kontext.so/guides/best-practices Pre-launch checklist for publishers integrating the Kontext SDK. Work through this checklist before going live. Each item links to the relevant guide or concept page. 1. **Use the latest SDK version.** We ship improvements — fill-rate fixes, new ad formats, performance, support for new DSPs and demand partners — on a steady cadence. Pin to the current major and update minors regularly. See [Changelog](/resources/changelog). 2. **Pass every message through `addMessage`.** Every user message is a preload opportunity for the SDK to fetch an ad — passing all of them **can significantly improve fill rate.** When you want to mute a specific turn, use `trackOnly: true` rather than skipping the call. See [Messages](/concepts/messages). 3. **Collect IFAs.** IDFA on iOS, GAID on Android. Both improve retargeting, frequency capping, and revenue. See [IFA & ATT](/guides/ifa). 4. **Host `ads.txt` / `app-ads.txt`** with our authorization line, and **keep it up to date.** Whenever we add new demand partners or change our seller-domain, we'll send you an updated line — append it promptly so new DSPs can actually bid on your traffic. See [ads.txt](/guides/ads-txt). 5. **Integrate a TCF v2.2 CMP** for EU / UK traffic. Required by GDPR and dramatically improves fill rate — most DSPs won't bid without a valid consent string. See [TCF & CMP](/guides/tcf-cmp). 6. **Configure SKAdNetwork** on iOS. DSPs running install campaigns rely on SKAN attribution to measure performance — without their identifiers in your `Info.plist`, they have less data to bid aggressively on your traffic. See [SKAdNetwork](/guides/skadnetwork). 7. **Let us pace it.** We've seen what works across our network and tune cadence per publisher. Tell us during onboarding — no app-side rules needed. Per-message `trackOnly` remains available for finer control. See [Pacing](/concepts/pacing). 8. **Work closely with us on ad design.** Great ads come from collaboration — share your style, brand constraints, and any content red lines so we can tune the creative to feel native to your app. The best-performing ads are the ones users love. Reach out via [Support](/resources/support) any time. # Debugging Source: https://docs.kontext.so/guides/debugging Use onEvent for ad-lifecycle signals and onDebugEvent for a detailed internal log. The SDK exposes two callbacks. Pick whichever you need. ## `onEvent` — public ad lifecycle `onEvent` fires for the small set of public ad-lifecycle events: `ad.filled`, `ad.no-fill`, `ad.viewed`, `ad.clicked`, `ad.error`, plus the video and reward events. These are the ones you typically wire your UI and analytics to. The complete list with payloads lives on [Ad lifecycle events](/concepts/events). ## `onDebugEvent` — detailed internal log `onDebugEvent` fires for **every internal step** the SDK takes — preload requests starting and resolving, message-id pairing decisions, retry attempts, iframe loading milestones, and so on. It is verbose and not stable across SDK versions; treat it as a diagnostic log, not an API. Wire it up only while you're investigating something, then turn it back off in production: ```ts theme={null} onDebugEvent: (name, data) => { console.log('[kontext]', name, data) } ``` ## When something looks off If an ad doesn't appear, fires the wrong event, or the SDK behaves unexpectedly: 1. Re-enable `onDebugEvent` in your build. 2. Reproduce the issue. 3. Capture the full `onDebugEvent` output and share it with us at [support@kontext.so](mailto:support@kontext.so). The debug log is the fastest way for us to pinpoint what happened on your side without remote-debugging the integration. ## `skipCode` reference When an `ad.no-fill` event fires, its payload carries a `skipCode` explaining why no ad was returned. The values you might see: | `skipCode` | Meaning | What to do | | ----------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | | `ads_disabled` | The SDK told the server ads are disabled for this request, because you called `addMessage` with `trackOnly: true`. | Expected in track-only mode. | | `ad_skipped` | The server intentionally skipped this turn — typically because of [pacing](/concepts/pacing). | Expected; the next eligible turn will show an ad. | | `ivt_blocked` | An invalid-traffic check blocked the request. | Real users shouldn't see this. If recurring in production, contact support. | | `unfilled_bid` | The auction ran but no bidder returned a usable ad. | Expected occasionally — fill rate is never 100%. | | `illegal_content` | The conversation content was flagged as unsafe for ads. | Review the message text — some categories don't get ads. | | `unknown` | Catch-all for reasons we don't yet expose individually. | Capture the `onDebugEvent` output and send it to support. | # IFA & ATT Source: https://docs.kontext.so/guides/ifa How the SDK reads the device advertising identifier on iOS and Android, and the one-time configuration each platform needs. The **IFA (Identifier for Advertisers)** is the platform-standard ad ID we forward with every ad request — IDFA on iOS, GAID on Android. The SDK reads it automatically; your app only needs the one-time platform configuration below. If no IFA is available (user denied permission, OS-level opt-out, web), the SDK falls back to a first-party identifier it generates internally — fill rate stays reasonable, but retargeting and frequency capping work better with a real IFA. ## iOS — IDFA + App Tracking Transparency iOS 14+ requires user consent before any SDK can read the IDFA. The system prompt is **App Tracking Transparency (ATT)**. Add the usage description to `Info.plist`: ```xml theme={null} NSUserTrackingUsageDescription We use your advertising identifier to show you more relevant ads. ``` Without this key, the app will crash on iOS 14+ as soon as the SDK requests ATT. By default the SDK calls `ATTrackingManager.requestTrackingAuthorization` automatically the first time a session is created. To manage the prompt yourself (for example, delay it until after onboarding), set `requestTrackingAuthorization: false` on `SessionOptions` and supply the `advertisingId` you collected. The ATT prompt only appears when the app is in an active state. Initializing the SDK before that — for example, in `AppDelegate.didFinishLaunchingWithOptions` — may suppress the prompt entirely. The Swift SDK also reads **IDFV** (`UIDevice.current.identifierForVendor`) as a stable iOS-only secondary identifier. IDFV does **not** require ATT and is always available. ## Android — GAID + AD\_ID permission GAID is always available on Android — there's no runtime consent prompt. Reading it requires the install-time `com.google.android.gms.permission.AD_ID` permission (Android 13+ / API level 33+). How the permission is declared depends on the SDK: * **Kotlin SDK and React Native SDK** — added automatically via manifest merger. No changes to your `AndroidManifest.xml` required. * **Flutter SDK** — declare it manually in `android/app/src/main/AndroidManifest.xml`: ```xml theme={null} ``` If your manifest has `tools:node="remove"` on the `AD_ID` permission anywhere (often a leftover from an older privacy-tightening pass), remove the override or the SDK can't read GAID. ## Web Browser environments don't have an OS-level IFA. The React, Vue, and JavaScript SDKs don't read or request one — they go straight to the first-party fallback identifier on every request. # SKAdNetwork Source: https://docs.kontext.so/guides/skadnetwork Configure SKAdNetwork identifiers on iOS so DSPs can attribute installs from your ads. [SKAdNetwork](https://developer.apple.com/documentation/storekit/skadnetwork) (SKAN) is Apple's privacy-preserving install-attribution framework. Every advertiser ([DSP](/concepts/ad-engine)) that runs on iOS has its own SKAdNetwork identifier. For a DSP to attribute an install back to one of your ads, its identifier must be listed in your app's `Info.plist`. SKAdNetwork is **iOS-only**. It applies to the Swift SDK and to the iOS half of React Native and Flutter SDKs. Android, web, React, Vue, and JavaScript SDKs don't need this configuration. ## What to add to `Info.plist` ### Kontext's own identifier (always required) Kontext also serves campaigns directly — not only through DSPs — and attributes their installs through its own registered ad network. Add Kontext's identifier regardless of which DSPs are active: ```xml theme={null} SKAdNetworkItems SKAdNetworkIdentifier mp7rpxwdrx.skadnetwork ``` Without this entry, ads still serve and render normally, but installs from directly-served campaigns are silently never attributed — there is no error or warning. ### DSP identifiers During onboarding we provide the list of SKAdNetwork identifiers for every DSP active in our network. Append them under `SKAdNetworkItems`: ```xml theme={null} SKAdNetworkItems SKAdNetworkIdentifier XXX.skadnetwork SKAdNetworkIdentifier YYY.skadnetwork ``` If your app already has an `SKAdNetworkItems` array (from another ad SDK), **append** the identifiers we give you to the existing array — don't replace it, or you'll break attribution for the other networks. Apple deduplicates identifiers automatically, so adding the same one twice is harmless. Missing an identifier means installs attributable to that DSP will silently not be counted — you lose revenue without any visible error. Add **all** identifiers we provide, not a subset. ## How the SDK uses it The SDK reads `SKAdNetworkItems` from `Info.plist` at startup. It does not modify the array or write to it. On every `POST /init` call (fired in the background when you create a `Session`), the SDK forwards the full list of identifiers it found to our ad server. The ad server uses this list to: 1. Tell each DSP which identifiers are present in your app, so they know whether they can attribute installs from your traffic. 2. Filter out DSPs whose identifier is missing from your `Info.plist` — they can still serve ads, but their install attribution won't work, and we'll surface the gap in your dashboard. This is why the identifier list needs to stay in sync with the list we provide. When we add a new DSP to the network, you'll get an updated list during onboarding — add the new identifier and re-submit your build. ## Apple's SKAN APIs Once the identifiers are listed, the rest of SKAN is handled by Apple's `SKAdNetwork` API and the DSP's measurement integration. The SDK doesn't call `SKAdImpression` or `endImpression` directly from your code — that's handled internally as part of the ad render lifecycle. ## Troubleshooting * **A DSP reports zero installs even though their ads ran.** Their identifier may be missing from your `Info.plist`, or the build with the updated list hasn't shipped yet. Check the dashboard — we flag missing identifiers there. * **Editing `Info.plist` requires a new App Store build.** SKAdNetwork identifiers are baked into the binary; they can't be changed remotely. Add the full set during onboarding and submit one build with everything we've given you. * **Need an updated list?** Contact [support@kontext.so](mailto:support@kontext.so) — we'll send the latest identifiers. # TCF & CMP Source: https://docs.kontext.so/guides/tcf-cmp How the SDK reads IAB TCF v2.2 consent from your CMP automatically, plus the regulatory object for manual control. ## TCF The **IAB Transparency and Consent Framework (TCF)** is the EU/UK ad-tech standard for collecting and propagating GDPR consent. Publishers display a consent dialog, the user picks what they're OK with, and the result is encoded into a **TCF v2.2 consent string**. Every downstream ad partner — Kontext included — reads that string to know what they may do with the user's data. ## CMP A **Consent Management Platform (CMP)** is the tool that shows the consent dialog and produces the TCF string. The CMP writes the string to a well-known storage location that every TCF-compatible SDK can read. If your app serves EU or UK users, you need a CMP: * **GDPR and the UK Data Protection Act require explicit consent** for personalized advertising. Without a CMP, you can't legally collect it. * **Without a valid TCF string, fill rate drops.** Most DSPs won't bid on traffic with no consent signal — or they only bid for the lowest-priced contextual-only inventory. * **It's the publisher's responsibility, not the SDK's.** The Kontext SDK doesn't ship a consent dialog; you integrate a CMP yourself. Any IAB-registered CMP works — OneTrust, Sourcepoint, CookieBot/Usercentrics, Didomi, and others. ## The SDK reads TCF automatically Once a TCF-compliant CMP is integrated and the user has answered the consent prompt, the SDK reads the consent string through the standard IAB-defined interface for each platform: * **iOS** — `UserDefaults` keys (`IABTCF_TCString`, `IABTCF_gdprApplies`, …) * **Android** — `SharedPreferences` keys (same key names) * **Web** — the `__tcfapi` window function exposed by the CMP You don't have to forward the string manually. `gdpr` and `gdprConsent` are picked up on every `/preload`. ## Manual override: the regulatory object If you don't have a CMP yet, want to override what the SDK reads, or need to set fields outside TCF (COPPA, GPP, US Privacy), use the **regulatory object** on the session. See [Compliance](/resources/compliance) for the full field list. Common cases: * **No CMP yet** — pass `gdpr` and `gdprConsent` manually until you wire one up. * **COPPA** — set `coppa: 1` for child-directed traffic. TCF doesn't cover this signal. * **US privacy / GPP** — pass `usPrivacy`, `gpp`, or `gppSid` for US-state regulations. # Testing Source: https://docs.kontext.so/guides/testing Use the development publisher token to integrate and test without usage limits. During onboarding you receive **two publisher tokens**: * A **development token** for testing and integration work. * A **production token** that participates in the real auction and generates revenue. The development token returns Kontext's standard **test ads** — the same creatives you'll see in the example apps. It has **no usage limits**, so you can fire as many `addMessage` calls and render as many ads as you need while you build, test, and demo the integration. When you're ready to ship, swap the development token for the production token. See [Best practices](/guides/best-practices) for the full pre-launch checklist. # Overview Source: https://docs.kontext.so/overview Transform your app into a revenue engine with native, AI-powered ads. Kontext ads showcase Kontext is the simplest way to monetize text-based & AI apps such as chatbots, search engines or instant messaging apps with unique, native ad formats. Sign up, install an SDK and start generating revenue from your audience in a couple of minutes. Swift SDK for iOS apps Kotlin SDK for Android apps Flutter SDK for Flutter-based mobile apps React Native SDK for iOS and Android apps React SDK with an npm package for React apps Vue 3 SDK with an npm package for Vue apps JavaScript SDK for web apps ## Acknowledgements **Built with Llama.** Kontext uses Meta Llama models (including Llama 3.1) as part of its ad-serving and content-classification pipeline. Llama is licensed under the [Llama Community License](https://www.llama.com/llama3_1/license/), Copyright © Meta Platforms, Inc. All Rights Reserved. # Publishers Source: https://docs.kontext.so/publishers Transform your app into a revenue engine with native, AI-powered ads. ## Why integrate with Kontext? Kontext helps you monetize your AI or messaging app with native, AI-powered ads designed specifically for text-based interfaces. * **Various ad formats**: Pick a format that fits your app — see the [ad formats overview](/concepts/ad-formats) * **Custom styling**: Take control of how ads appear in your application with simple CSS styling * **Simple integration**: Use SDKs for all frameworks, working across web, iOS, and Android * **Performance tracking**: Access analytics on revenue and ad performance ## Getting started is easy Ready to monetize your traffic? Contact us at [publishers@kontext.so](mailto:publishers@kontext.so) and our team will help you set up with Kontext. During onboarding, you'll get a `publisherToken` that's unique to your app, and a `code` that identifies your ad placement. The token and code allow your app to show ads from Kontext's network and enable you to track revenue and performance. # Changelog Source: https://docs.kontext.so/resources/changelog Version history for each Kontext SDK. Each SDK is versioned and released independently. Use the links below to find release notes, breaking-change notices, and the full version history for the SDK you are integrating. ## Mobile SDKs The mobile SDKs are open source. Release notes live in the GitHub repositories alongside the source code. CHANGELOG.md on GitHub. Distributed via Swift Package Manager and CocoaPods. CHANGELOG.md on GitHub. Artifacts published to [Maven Central](https://repo1.maven.org/maven2/so/kontext/ads/). CHANGELOG.md on GitHub. Package published to [pub.dev](https://pub.dev/packages/kontext_flutter_sdk). ## Web and React Native SDKs These packages are published as private npm packages. The links below open the npm Versions tab, which lists every release with its publish date. Version history for `@kontextso/sdk-react` on npm. Version history for `@kontextso/sdk-vue` on npm. Version history for `@kontextso/sdk-react-native` on npm. Version history for `@kontextso/sdk-js` on npm. ## Versioning policy All Kontext SDKs follow [Semantic Versioning](https://semver.org). Breaking changes are released in major versions and called out in the corresponding release notes. # Compliance Source: https://docs.kontext.so/resources/compliance OMID certification, IAB Tech Lab posture, privacy regulations, and platform policies. Kontext is built to meet the standards that publishers, advertisers, and platform owners expect from a modern ad SDK. This page summarizes our current compliance posture and where each SDK stands. ## OMID and viewability [Open Measurement (OMID)](https://iabtechlab.com/standards/open-measurement-sdk/) is the IAB Tech Lab standard for third‑party viewability and verification. * **Swift SDK (iOS)** — IAB OMID certified. * **Kotlin SDK (Android)** — IAB OMID certified. * **React Native SDK and Flutter SDK** — certification in preparation, expected to be granted within 1–2 months. If you need viewability for a specific platform sooner, reach out to [support@kontext.so](mailto:support@kontext.so). ## IAB Tech Lab The SDK supports **IAB TCF v2.2**. It reads the TCF consent string automatically from the standard IAB‑defined storage location that every TCF‑compatible CMP writes to (`UserDefaults` on iOS, `SharedPreferences` on Android, `localStorage` on web). The publisher does not need to forward it manually. ## Privacy regulations The SDK exposes a **regulatory object** on the session so publishers can forward consent and privacy signals to the ad server. All fields are optional — supply whichever applies to your user's jurisdiction. * **GDPR (EU/UK)** — the TCF v2.2 consent string is collected automatically (see [IAB Tech Lab](#iab-tech-lab) above). You can also override it explicitly with `gdpr` (`1` / `0`) and `gdprConsent` (the TCF string) on the regulatory object. * **CCPA / CPRA (US)** — pass the [IAB US Privacy](https://iabtechlab.com/standards/ccpa/) string in `usPrivacy`. * **GPP (Global Privacy Platform)** — pass the GPP string in `gpp` and the applicable section IDs in `gppSid`. * **COPPA (US)** — set `coppa` to `1` when the user is known to be under 13. The SDK and the ad server then treat the request as child‑directed. ## Data handling The SDK collects only what is needed to serve a contextually relevant ad and report on its performance: * The chat messages you pass to `addMessage` (used for contextual targeting). * A platform advertising identifier when the user has granted permission (IDFA on iOS, GAID on Android). * A first‑party `installId` generated by the SDK and persisted in app storage. * The `publisherToken`, `userId`, and `conversationId` you supply. * Standard device, app, and network attributes (model, OS version, app bundle, connection type). Nothing else is collected. The full data processing terms are covered in our publisher agreement. For the third parties that process data on our behalf, see the [subprocessor list](/resources/subprocessors). ## Platform policies * **Apple ATT (iOS 14+)** — the SDK requests App Tracking Transparency authorization before reading IDFA. The publisher app must declare `NSUserTrackingUsageDescription` in `Info.plist`. * **Google AD\_ID permission (Android 13+)** — the SDK declares `com.google.android.gms.permission.AD_ID` via manifest merger. * **SKAdNetwork (iOS)** — the publisher app must list all DSP `SKAdNetworkItems` in `Info.plist` for install attribution to work. The full list is provided during onboarding. * **Web cookies** — the JavaScript and framework SDKs do not set tracking cookies of their own; ad creatives render inside an iframe sandbox. For anything not covered here, contact [support@kontext.so](mailto:support@kontext.so). # Demos Source: https://docs.kontext.so/resources/demos Runnable example apps for every Kontext SDK. Every SDK ships with at least one runnable demo so you can see an end‑to‑end integration before wiring it into your own app. Clone the repo, drop in your `publisherToken`, and run. ## Swift (iOS) Lives inside the `sdk-swift` repository under `Example/`. A working UIKit demo you can run end‑to‑end against your own publisher token. ## Kotlin (Android) Lives inside the `sdk-kotlin` repository under `example/`. A Jetpack Compose chat app demonstrating the inline ad placement. ## Flutter Lives inside the `sdk-flutter` repository under `example/`. A cross‑platform demo running on iOS and Android. ## React Native Standalone demo repository showing the SDK integrated into a fresh React Native app. ## React Standalone Vite + React app you can run end‑to‑end against your own publisher token. ## Vue Standalone Vite + Vue 3 app you can run end‑to‑end against your own publisher token. ## JavaScript Standalone Vite app showing the SDK used directly in vanilla JavaScript. # Subprocessors Source: https://docs.kontext.so/resources/subprocessors The third-party subprocessors Kontext engages to help deliver its services. Kontext engages the third-party subprocessors listed below to help operate, secure, and deliver our services. This page is the authoritative, current list of our subprocessors — it supersedes any list embedded in our Data Processing Agreement (DPA) or privacy policy. We maintain this list on a best-effort basis and update it as our vendors change. It may not reflect every subprocessor at a given moment. To be notified of additions, email [support@kontext.so](mailto:support@kontext.so). **Last updated: 15 July 2026** | Subprocessor | Location | | ------------------------------------- | ------------- | | AC PM LLC (Postmark) | United States | | Adjust GmbH | Germany | | Amazon Web Services, Inc. | United States | | Anthropic, PBC | United States | | AppsFlyer, Inc. | United States | | ClickHouse, Inc. | United States | | Functional Software, Inc. (Sentry) | United States | | Google LLC | United States | | Kochava, Inc. | United States | | LiftOff Mobile, Inc. | United States | | Mistral AI SAS | France | | OpenAI, LLC | United States | | Pixalate, Inc. | United States | | Preset, Inc. | United States | | Redis Ltd. | United States | | SmartyAds, Inc. | United States | | Together Computer, Inc. (Together AI) | United States | | Vercel Inc. | United States | Questions about this list or how we process data? Contact [support@kontext.so](mailto:support@kontext.so). # Support Source: https://docs.kontext.so/resources/support Get in touch with the Kontext team. ## Contact Email us at [support@kontext.so](mailto:support@kontext.so) for integration help, billing questions, or anything else. # Flutter SDK Source: https://docs.kontext.so/sdk/flutter Get started with our SDK built for iOS and Android apps using Flutter We are preparing a new v4 release of the Flutter SDK. The documentation below covers the current shipping version — once v4 lands, this page will be updated. See how easy it is to integrate high-performance ads into your Flutter app using our lightweight SDK. ## Requirements * **Flutter**: 3.24.0 or later * **Dart**: 3.5.0 or later * **Android**: `minSdkVersion >= 21`, `compileSdk >= 34`, [AGP](https://developer.android.com/build/releases/gradle-plugin) version `>= 7.3.0` (use [Android Studio - Android Gradle plugin Upgrade Assistant](https://developer.android.com/build/agp-upgrade-assistant) for help), support for `androidx` (see [AndroidX Migration](https://flutter.dev/docs/development/androidx-migration) to migrate an existing app) * **iOS**: `12.0+, --ios-language swift`, Xcode version `>= 15.0` ## Getting started ### WebView prerequisites (`flutter_inappwebview`) The Flutter SDK renders ads inside a WebView using [flutter\_inappwebview](https://pub.dev/packages/flutter_inappwebview). To prevent WebView initialization errors, add this to your app entry point: ```dart theme={null} import 'package:flutter/widgets.dart'; void main() { // Must be first so plugins are ready. WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); } ``` ### 1. Installation To get started, you will need to set up a [publisher account](/publishers#getting-started-is-easy) to get a `publisherToken` and `code`. Add the package to your `pubspec.yaml`: ```yaml theme={null} dependencies: kontext_flutter_sdk: ^ ``` Install dependencies: ```bash theme={null} flutter pub get ``` Ensure your project meets the Android min/compile SDK and iOS/Xcode requirements listed above. If you run into issues, verify that your project meets the plugin’s platform requirements: [https://inappwebview.dev/docs/intro/](https://inappwebview.dev/docs/intro/) ### 2. Set up the `Character` object Define the assistant’s character information: ```dart theme={null} final character = Character( id: 'id-123', name: 'Ava', avatarUrl: 'https://example.com/avatar.png', greeting: 'Hi there! How can I help you today?' ); ``` ### 3. Set up the `Regulatory` object Next, define the user’s regulatory context: ```dart theme={null} final regulatory = Regulatory( coppa: 0, // ... other regulatory properties ); ``` ### 4. Set up IFA (Identifier for Advertisers) * **iOS:** add `NSUserTrackingUsageDescription` to `ios/Runner/Info.plist`. The SDK auto-prompts for ATT and reads IDFA from there. * **Android (13+):** declare `com.google.android.gms.permission.AD_ID` in `android/app/src/main/AndroidManifest.xml` — this is an install-time permission with no runtime prompt. See [IFA & ATT](/guides/ifa) for the full setup — required keys, prompt-timing gotchas, and how to manage the prompt yourself. ### 5. Set up SKAdNetwork (iOS only) Add the SKAdNetwork identifiers we provide during onboarding to `ios/Runner/Info.plist`. The SDK reads them and forwards them on every `/init` so DSPs can measure conversions. See [SKAdNetwork](/guides/skadnetwork) for the full guide. ### 6. Set up the `AdsProvider` Wrap your app (or the part of it that contains ad placements) with the `AdsProvider`. The `AdsProvider` is responsible for fetching and managing ads, and it requires access to the current chat `messages`. ```dart theme={null} import 'package:kontext_flutter_sdk/kontext_flutter_sdk.dart'; // Messages between user and assistant final messages = [ Message( id: 'msg-001', role: MessageRole.assistant, content: 'Hello! How can I help you today?', createdAt: DateTime.parse('2025-08-31T10:00:00Z'), ), Message( id: 'msg-002', role: MessageRole.user, content: 'Show me today's workout plan.', createdAt: DateTime.parse('2025-08-31T10:00:05Z'), ), Message( id: 'msg-003', role: MessageRole.assistant, content: 'Here's a 30-minute routine to start with.', createdAt: DateTime.parse('2025-08-31T10:00:10Z'), ), ]; Widget build(BuildContext context) { return AdsProvider( publisherToken: '', userId: 'user-1234', conversationId: 'conv-5678', enabledPlacementCodes: ['inlineAd'], messages: messages, character: character, // From section 2 regulatory: regulatory, // From section 3 otherParams: { 'theme': 'dark', }, child: YourChatWidget(), ); } ``` ### 7. Display your first ad An ad slot is a designated area in your UI where an ad can be rendered. In most cases, it appears below a chat message. During onboarding, you’ll receive a unique `code` for each ad slot you plan to use. Example using the `InlineAd` format: ```dart theme={null} ListView.builder( itemCount: messages.length, itemBuilder: (context, index) { final message = messages[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(message.content), InlineAd( code: '', messageId: message.id, ), ], ); }, ) ``` > 💡 **Note:** `InlineAd` does not always display an ad. Whether an ad is shown depends on the context of the ongoing conversation. > If no ad is available, `InlineAd` automatically returns a `const SizedBox.shrink()`, so it won’t take up any extra space in your layout. ## API documentation ### `AdsProvider` properties Your unique publisher token. List of messages between the assistant and the user. Unique ID of the message. Role of the message (`MessageRole.user` or `MessageRole.assistant`). Message text. Timestamp when the message was created. Unique identifier that remains the same for the user's lifetime (used for retargeting and rewarded ads). Email of the user. Unique ID of the conversation. Placement codes enabled for the conversation. Example: `['inlineAd']`. The character object used in a conversation. Unique identifier for the character. Name of the character. URL of the character's avatar image. Whether the character is NSFW (Not Safe For Work). A greeting message from the character. A description of the character's persona. Tags associated with the character. Additional properties that can be added to the character. Regulatory compliance information. **TCF (Transparency and Consent Framework)** signals — `gdpr` and `gdprConsent` — are handled automatically by the SDK if you have a TCF-compliant CMP (Consent Management Platform) integrated in your app. You do not need to set these manually. Flag that indicates whether or not the request is subject to GDPR regulations (0 = No, 1 = Yes, null = Unknown). The IAB Transparency and Consent Framework (TCF) consent string. Flag whether the request is subject to COPPA (0 = No, 1 = Yes, null = Unknown). Global Privacy Platform (GPP) consent string. List of the section(s) of the GPP string which should be applied for this transaction. Communicates signals regarding consumer privacy under US privacy regulation under CCPA and LSPA. Publisher-provided identifier for the user cohort (for A/B testing). Used to pass publisher-specific information to Kontext. Flag indicating if the ads are disabled. Callback triggered when an event occurs. See [AdEvent types](#adevent-types) for more details. The widget subtree wrapped by `AdsProvider`. ### `InlineAd` properties The ad format code that identifies the ad to be displayed. A unique identifier for the message associated with this ad. ### AdEvent types #### `ad.clicked` The user has clicked the ad. Ad ID. Generated ad content. Message ID. Click URL. Ad format. #### `ad.viewed` The user has viewed the ad. Ad ID. Generated ad content. Message ID. Ad format. The revenue of the ad (eCPM in USD). #### `ad.filled` Ad is available. The revenue of the ad (eCPM in USD). #### `ad.no-fill` Ad is not available. The code indicating the reason why the ad was skipped. #### `ad.render-started` Triggered before the first token is received. Ad ID. #### `ad.render-completed` Triggered after the last token is received. Ad ID. #### `ad.error` Triggered when an error occurs. Error message. Error code. #### `reward.granted` Triggered when the user receives a reward. Ad ID. #### `video.started` Triggered when the video playback starts. Ad ID. #### `video.completed` Triggered when the video playback finishes. Ad ID. ## Guides ### Handling no-fill events You can detect when no ad is available by using the `onEvent` callback and listening for the `ad.no-fill` event. ## Troubleshooting ### Missing plugin warnings If you see warnings like `MissingPluginException` or errors about a plugin not being registered, try the following: ```bash theme={null} flutter clean flutter pub get ``` This clears cached build artifacts and ensures plugins are re-registered. If the problem persists, try rebuilding your app `flutter run` or restarting your IDE. ## Links * [Flutter SDK GitHub](https://github.com/kontextso/sdk-flutter) * [Pub](https://pub.dev/packages/kontext_flutter_sdk) * [Changelog](https://github.com/kontextso/sdk-flutter/blob/main/CHANGELOG.md) # JavaScript SDK Source: https://docs.kontext.so/sdk/js A lightweight JavaScript SDK that lets you integrate Kontext ads into any web app. See how easy it is to integrate high-performance ads into your web app using our lightweight SDK. ## Requirements * A modern browser environment (the SDK is framework-agnostic — works in static pages, custom chat UIs, or any SPA). You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. Install via npm or load from CDN. Create a session, feed messages, mount an ad — the 5-minute integration. `createSession`, `Session`, `Ad`, and the full event list. Patterns and recipes: no-fill, character switching, restoring history, and more. Run the Vite demo end-to-end against your own publisher token. # API reference Source: https://docs.kontext.so/sdk/js/api createSession, Session, Ad, and the full event list. ## `createSession(options)` → `Session` Creates an ad session tied to a specific **user** and **conversation**. Your unique publisher token. Unique identifier that remains the same for the user's lifetime (used for personalization, frequency capping, and rewarded ads). Unique ID of the conversation or chat thread. Placement codes enabled for the conversation. Defaults to `['inlineAd']`. Character metadata used for contextual ad selection and personalization. Unique ID of the character. Name of the character. URL of the character's avatar. Indicates whether the character is NSFW. The character's greeting. Description of the character's personality. Tags describing the character. Privacy / consent signals. Indicates whether the request is subject to GDPR regulations. (0 = No, 1 = Yes, null = Unknown) The IAB Transparency and Consent Framework (TCF) consent string. Indicates whether the request is subject to COPPA. (0 = No, 1 = Yes, null = Unknown) Global Privacy Platform (GPP) consent string. IDs of the GPP sections that apply to this transaction. Signals regarding consumer privacy under US privacy regulations (e.g. CCPA, LSPA). Email of the user, used for frequency-cap deduplication. Publisher-provided identifier for the user cohort (for A/B testing). Callback invoked for every ad lifecycle event. See [Supported events](#supported-events) below. Callback for SDK-internal diagnostic events. Receives `(name, data?)` — useful when investigating preload/session behaviour during integration. Off by default. ## `Session` methods ### `session.addMessage(message, options?)` Register a chat message with the SDK. Triggers a debounced preload when appropriate. See [`Message` shape](#message-shape). When `true`, the preload request is still sent (for analytics) but no ad will be shown for this message. ### `session.createAd(messageId, options?)` → `Ad` Create an `Ad` bound to a specific assistant message. Doesn't wait for an ad — the ad will render as soon as one is available. Calling `createAd` twice for the same `messageId` + `code` returns the existing instance. ID of the assistant message the ad belongs to. Placement code (default: `'inlineAd'`). Visual theme passed to the ad iframe (e.g. `'light'`, `'dark'`). ### `session.render(options)` → `Ad` Convenience wrapper for `createAd` + `mount` in one call. ID of the assistant message the ad belongs to. Host element into which the ad iframe will be rendered. Placement code (default: `'inlineAd'`). Visual theme. ### `session.updateOptions(partial)` Live-update preload-scoped configuration on an active session. Accepted fields: `variantId`, `regulatory`, `userEmail`, `advertisingId`, `vendorId`. Read at the next `/preload`, so the change takes effect on the next user message — no session recreation needed. `publisherToken`, `userId`, `conversationId`, `enabledPlacementCodes`, and `character` are **not** live-updateable. Changing them mid-session would desync the `/init` registration from subsequent requests, or leave the accumulated message history targeted at the wrong persona. Recreate the session instead. ### `session.destroy()` Cleans up internal state, cancels in-flight preloads, and stops further preload requests. Call this when the conversation ends or the page unmounts. After `destroy()`, mutator methods (`addMessage`, `createAd`, `render`) throw. ### `session.getSessionId()` → `string | null` Server-assigned session ID. `null` until the first successful `/preload`. ### `session.isDisabled()` → `boolean` `true` if the server has permanently disabled this session via the `/init` response (e.g. geo-restriction). Subsequent preloads are skipped. ### `session.isDestroyed()` → `boolean` `true` after `destroy()` has been called. ### `session.getMessages()` → `readonly Message[]` Snapshot of messages tracked by the session, in insertion order. Do not mutate; do not cache long-term — the internal array is replaced with a trimmed copy when message count exceeds the cap. ### `session.getPreloadTimeout()` → `number` Current `/preload` request timeout in milliseconds. May be overridden by the server via the `/init` response. ## `Ad` methods ### `ad.mount(element)` Mounts the ad iframe inside the given DOM element. Each `Ad` instance can only be mounted once — re-mounting throws. Host element for the ad iframe. ### `ad.destroy()` Removes the iframe from the DOM and releases all listeners. Idempotent — safe to call multiple times. ## `Message` shape Unique, stable ID of the message. Role of the message author. Message text. Timestamp when the message was created. ## Supported events ### `ad.clicked` The user has clicked the ad. Ad ID. Generated ad content. Message ID. Click URL. Ad format. Click area within the creative. ### `ad.viewed` The user has viewed the ad. Ad ID. Generated ad content. Message ID. Ad format. Ad revenue per impression (eCPM in USD). ### `ad.filled` Ad is available. Ad ID. Placement code this ad was matched to. Ad revenue per impression (eCPM in USD). ### `ad.no-fill` Ad is not available. The code indicating the reason why the ad was skipped. ### `ad.render-started` Triggered before the first token is received. Ad ID. ### `ad.render-completed` Triggered after the last token is received. Ad ID. ### `ad.error` Triggered when an error occurs. Error message. Error code. ### `video.started` Triggered when video playback starts. Ad ID. ### `video.completed` Triggered when video playback finishes. Ad ID. ### `reward.granted` The user has earned a reward (rewarded ad flow). Ad ID. # Example app Source: https://docs.kontext.so/sdk/js/example Run the Vite demo against your own publisher token. The [`sdk-js-demo`](https://github.com/kontextso/sdk-js-demo) repo ships a minimal Vite app you can run end-to-end against your own publisher token. ## Clone and configure ```bash theme={null} git clone https://github.com/kontextso/sdk-js-demo.git cd sdk-js-demo npm install # Edit main.js and set PUBLISHER_TOKEN to your real token. npm run dev ``` The repo ships `PUBLISHER_TOKEN` preset to a shared demo token, so it runs out of the box. Replace it with your own token from the [publisher dashboard](/publishers#getting-started-is-easy) to test against your account — an invalid token makes the SDK return `ad.error` on `/init`. ## What you should see After `npm run dev`, open the printed local URL in a browser. The demo presents a chat interface with seeded user + assistant messages. As you send a message, the SDK fires `/preload` in the background and renders an ad below the assistant's reply. ## Links * [NPM Package](https://www.npmjs.com/package/@kontextso/sdk-js) * [JavaScript Demo Project](https://github.com/kontextso/sdk-js-demo) # Showing your first ad Source: https://docs.kontext.so/sdk/js/first-ad Create a session, feed messages, mount an ad, observe events. This is the 5-minute integration walkthrough. Before you start, make sure you've completed [Installation](/sdk/js/installation). ## 1. Create a session Import `createSession()` to configure a session for a specific user and conversation. The returned `Session` is the entry point for everything else — feeding messages, creating ads, listening to events. ```ts theme={null} import { createSession } from '@kontextso/sdk-js' const session = createSession({ publisherToken: '', userId: 'user-1234', conversationId: 'conv-5678', character: { id: 'character-1234', name: 'John Doe', avatarUrl: 'https://example.com/avatar.png', greeting: 'Hello, how can I help you today?', }, onEvent: ({ name, code, payload }) => { // process events }, }) ``` If you're using the CDN global: ```js theme={null} const { createSession } = window.KontextSdk const session = createSession({ /* ... */ }) ``` **Notes:** * `userId` should stay the same for the same user across sessions/devices — it powers personalization, frequency capping, and rewarded ads. * `conversationId` should be unique per chat thread (a single chat room, support ticket, or conversation in your app). ## 2. Feed messages to the SDK The SDK preloads ads in response to new chat messages. Call `addMessage()` whenever a user or assistant message is created — both roles are needed for Kontext to understand conversation context. ```ts theme={null} // User message session.addMessage({ id: 'msg-1', role: 'user', content: 'Hello, how are you?', createdAt: new Date(), }) // Assistant reply session.addMessage({ id: 'msg-2', role: 'assistant', content: 'I am good, thank you!', createdAt: new Date(), }) ``` **Notes:** * `id` must be unique per message and stable — the same id is referenced later when you create an ad. ## 3. Mount an ad To display an ad, create an `Ad` instance bound to the assistant message it should appear under, then mount it into a DOM element. The SDK manages the iframe and its lifecycle. ```html theme={null}
``` In a typical chat UI you'll: 1. Render your message list. 2. For each assistant message, render a placeholder `
` for the ad. 3. Call `session.createAd(messageId)` once the placeholder DOM exists, then `ad.mount(element)`. 4. When the message scrolls off-screen or the conversation ends, call `ad.destroy()` to release the iframe. ```ts theme={null} const ad = session.createAd('msg-2', { theme: 'dark' }) ad.mount(document.getElementById('ad-msg-2')) // later, when the message is removed: ad.destroy() ``` If you'd rather do it in one call, `session.render({ messageId, element, theme })` is a convenience wrapper around `createAd + mount`. ## 4. Tear down Call `session.destroy()` when the conversation ends or the page unmounts. Idempotent and required to cancel in-flight preloads and release iframe resources. ```ts theme={null} session.destroy() ``` ## Observing events Subscribe to the lifecycle of every ad via the `onEvent` callback. Every event has a stable string `name` and a typed `payload`: ```ts theme={null} const session = createSession({ // ... onEvent: (event) => { switch (event.name) { case 'ad.filled': console.log('filled:', event.payload.id, 'revenue=', event.payload.revenue) break case 'ad.clicked': console.log('clicked:', event.payload.url) break case 'ad.no-fill': console.log('no fill, skipCode=', event.payload.skipCode) break } }, }) ``` Every placement-attributed event (`ad.filled`, `ad.viewed`, `ad.clicked`, `ad.render-*`, `video.*`, `reward.granted`) also carries a top-level `code` field naming the matched placement, so publishers with multiple `enabledPlacementCodes` can disambiguate. Session-wide events (`ad.no-fill`, `ad.error`) omit it. For SDK-internal diagnostics during integration, also pass an `onDebugEvent` callback — it receives `(name, data?)` for every internal step the SDK takes. # Guides Source: https://docs.kontext.so/sdk/js/guides Patterns and recipes for common JavaScript SDK integration tasks. ## Handling no-fill Subscribe to `ad.no-fill` to know when no ad was returned (geo-restriction, frequency cap, server-side skip). The `skipCode` payload tells you why. ```ts theme={null} onEvent: ({ name, payload }) => { if (name === 'ad.no-fill') { console.log('No ad available:', payload.skipCode) } } ``` Every preload produces exactly one of `ad.filled` / `ad.no-fill` / `ad.error` per response, so you can rely on hearing back on every user message. ## Pass every message, suppress ads with `trackOnly` Always feed every message into the session, even when you don't want an ad to appear (e.g. user is on a free trial, in a no-ads region, or you've decided to show ads only every Nth message). Skipping `addMessage` calls breaks the conversation context the server relies on for targeting. See [Pacing](/concepts/pacing) for the full pattern. Use `{ trackOnly: true }` to send the preload for analytics without generating an ad: ```ts theme={null} const shouldShowAd = userMessageCount % 5 === 0 session.addMessage( { id: msg.id, role: 'user', content: msg.content, createdAt: new Date() }, { trackOnly: !shouldShowAd } ) ``` When `trackOnly: true`, the preload still fires (server keeps full analytics) but no `ad.filled` event will arrive for that message and `session.createAd(...)` won't resolve to an ad. ## Live-updating consent mid-session When the user updates their consent in your CMP, pass the new TCF string to `updateOptions`: ```ts theme={null} session.updateOptions({ regulatory: { gdpr: 1, gdprConsent: '' }, }) ``` The next preload picks up the new value — no session recreation needed. ## Switching character The active `character` cannot be live-updated — the accumulated message history belongs to the original persona, so swapping mid-session would leave messages targeted at the wrong character. To switch character, destroy the current session and create a new one: ```ts theme={null} session.destroy() session = createSession({ publisherToken: '', userId: 'user-1234', conversationId: 'conv-new', character: newCharacter, }) ``` The same applies to `publisherToken`, `userId`, `conversationId`, and `enabledPlacementCodes` — recreate the session whenever any of those change. ## Loading older messages (conversation restore) When restoring a conversation from your backend, call `addMessage(...)` once per historical message in order. Preloads are debounced (\~10 ms), so rapid sequential calls coalesce into a single preload for the most recent user message — you won't fire one preload per restored message. ```ts theme={null} for (const m of loadedFromBackend) { session.addMessage({ id: m.id, role: m.role, content: m.content, createdAt: m.timestamp, }) } ``` # Installation Source: https://docs.kontext.so/sdk/js/installation Install @kontextso/sdk-js via npm or load it from CDN. You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. ## npm ```bash theme={null} npm install @kontextso/sdk-js@4 ``` ## CDN Or load the SDK from our CDN as a global script: ```html theme={null} ``` When loaded via CDN the API is available on `window.KontextSdk`. Replace `{publisherToken}` with your actual token. # Kotlin SDK Source: https://docs.kontext.so/sdk/kotlin Integrate Kontext ads into your Android app with the Kotlin SDK. See how easy it is to integrate high-performance ads into your Android app using our lightweight SDK. ## Requirements * Android API 26+ (Android 8.0) * Kotlin 1.9+ * Jetpack Compose for the recommended `InlineAd` integration (View interop is available via `InlineAdView` if your app is not on Compose) You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. Add the SDK via Gradle, configure GAID auto-collection. Create a session, feed messages, mount an ad — the 5-minute integration. Every type and method exposed by the SDK. Patterns and recipes: no-fill, character switching, View interop, and more. Run the bundled Compose demo end-to-end against your own publisher token. # API reference Source: https://docs.kontext.so/sdk/kotlin/api Every type and method exposed by the Kotlin SDK. ## `KontextAds.createSession(...)` ```kotlin theme={null} public fun createSession(context: Context, options: SessionOptions): Session ``` Returns a new `Session` configured from the given `SessionOptions`. Fires `/init` in the background. Pass `context.applicationContext` so the session is not tied to any single Activity. ## `SessionOptions` Your unique publisher token. Stable identifier for the end user. Used for personalization, frequency capping, and rewarded ads. Unique ID of the current conversation / chat thread. Placement codes to request ads for. Defaults to `["inlineAd"]` when null or empty. Override for the ad-server base URL. Leave null to use Kontext's production endpoint. AI character metadata for contextual targeting. A `java.net.URI` — construct with `URI.create("…")`, not a raw `String`. Publisher-defined cohort identifier (e.g. for A/B testing). Privacy / consent signals. TCF (`gdpr` / `gdprConsent`) is collected automatically if a TCF-compliant CMP is integrated — set manually only for COPPA, GPP, or US Privacy. 0, 1, or null. IAB TCF v2 consent string. 0, 1, or null. Global Privacy Platform string. GPP section IDs that apply. CCPA / LSPA string. End-user email for frequency-cap deduplication. GAID you collected yourself. Takes priority over the SDK's automatic collection. Callback invoked on every ad lifecycle event. Called on the main thread. Optional diagnostic stream — fires for every internal SDK event (preload start/end, iframe lifecycle, geometry updates, etc.). Useful during integration. Leave null in production. ## `Session` Append a `Message` to the conversation. Synchronous (not `suspend`). User messages trigger a debounced preload; assistant messages let the SDK link the matched ad to the placement. ```kotlin theme={null} session.addMessage(Message(id = "m1", role = Role.USER, content = "Hi")) session.addMessage( Message(id = "m2", role = Role.USER, content = "Hi again"), AddMessageOptions(trackOnly = true), ) ``` When `trackOnly = true`, the preload is sent for analytics but no ad is generated. Returns an `Ad` for the given `messageId`. Idempotent — repeated calls with the same `messageId` + `code` + `theme` return the same `Ad`. Cache the result. ```kotlin theme={null} val ad = session.createAd("m2") val sidebar = session.createAd("m2", AdOptions(code = "sidebar", theme = "dark")) ``` Live-update preload-scoped fields. See [Guides → Live-updating session options](/sdk/kotlin/guides#live-updating-session-options). Tear down the session: cancel preloads, destroy ads, release WebView resources. Idempotent. `close()` is an alias (implements `AutoCloseable`). Hot Flow that delivers the same events as `onEvent`. Useful for ViewModels and coroutine pipelines. Read-only snapshot of messages tracked by the session. Server-assigned session ID. `null` until the first successful preload. `true` if the server has permanently disabled the session via the `/init` response (e.g. geo-restriction). Subsequent preloads are skipped. `true` after `destroy()` is called. ## `MutablePublisherOptions` Subset of `SessionOptions` accepted by `session.updateOptions(...)`. Every field is optional — non-null overwrites, null leaves unchanged. ## `Message` Unique message ID. `Role.USER` or `Role.ASSISTANT`. Message text. Defaults to `Date()`. ## `AdOptions` Placement code. Defaults to `"inlineAd"`. UI theme hint forwarded to the ad iframe (e.g. `"dark"`). ## `AddMessageOptions` When `true`, the preload is still sent (for analytics) but no ad is generated for this message. Defaults to `false`. ## UI ### `InlineAd` (Compose, recommended) ```kotlin theme={null} @Composable public fun InlineAd( messageId: String, session: Session, code: String? = null, theme: String? = null, modifier: Modifier = Modifier, ) ``` Mounts the ad for the given `messageId`. Internally calls `session.createAd(...)`, attaches a pooled `WebView`, and reports container geometry back to the iframe. Safe to compose in a `LazyColumn` — the underlying `Ad` and `WebView` survive recomposition and scroll-off-screen recycling. A second overload accepts a pre-resolved `Ad` directly: ```kotlin theme={null} @Composable public fun InlineAd(ad: Ad, modifier: Modifier = Modifier) ``` ### `InlineAdView` (View interop) ```kotlin theme={null} public class InlineAdView(context: Context) : FrameLayout { public var onHeightChange: ((Float) -> Unit)? public fun bind(messageId: String, session: Session, code: String? = null, theme: String? = null) } ``` For apps not on Compose. Call `bind(messageId, session)` once after inflating; observe `onHeightChange` to resize the surrounding container (e.g. in a `RecyclerView` row). ## `AdEvent` `AdEvent` is a sealed class with one typed payload per case. Every case has a stable string identifier accessible via `event.name`. ### `AdEvent.Filled` — wire name `ad.filled` An ad was returned and linked to the placement. Placement code this ad was matched to. Required for publishers with multiple `enabledPlacementCodes`. ### `AdEvent.NoFill` — wire name `ad.no-fill` No ad was returned for the placement (server skipped). Reason the ad was skipped. ### `AdEvent.AdHeight` — wire name `ad.height` The ad iframe reported a new height. Use to size the surrounding container in `RecyclerView` rows. The `InlineAd` Compose composable handles this for you automatically. ### `AdEvent.Viewed` — wire name `ad.viewed` The ad was viewed by the user (IAB MRC viewability standard). ### `AdEvent.Clicked` — wire name `ad.clicked` The user clicked the ad. Region of the ad that was clicked. ### `AdEvent.RenderStarted` — wire name `ad.render-started` The first token of the ad content was received. ### `AdEvent.RenderCompleted` — wire name `ad.render-completed` Ad content streaming finished. ### `AdEvent.Error` — wire name `ad.error` The SDK encountered an error while serving an ad. ### `AdEvent.VideoStarted` — wire name `video.started` ### `AdEvent.VideoCompleted` — wire name `video.completed` ### `AdEvent.RewardGranted` — wire name `reward.granted` Fired for rewarded-ad flows after the user qualifies for a reward. # Example app Source: https://docs.kontext.so/sdk/kotlin/example Run the bundled Compose demo against your own publisher token. The [`example`](https://github.com/kontextso/sdk-kotlin/tree/main/example) module in the SDK repo ships a working Jetpack Compose chat demo you can run end-to-end against your own publisher token. ## Clone and configure ```bash theme={null} git clone https://github.com/kontextso/sdk-kotlin.git cd sdk-kotlin cp local.properties.example local.properties # Edit local.properties and set `publisherToken` to your real token. ./gradlew :example:installDebug ``` Then launch the `Kontext v4 — Kotlin` app on a connected device or emulator. `local.properties` is gitignored, so your token won't be committed. The example reads `publisherToken` (and an optional `adServerUrl` override) via `BuildConfig` at build time. If the file is missing, the build falls back to placeholder strings and `/preload` will fail until a real token is configured. The default placeholder `publisherToken` won't return real ads — replace it with your token from the [publisher dashboard](/publishers#getting-started-is-easy). ## What you should see After launch, the demo presents a chat interface with seeded user + assistant messages. As you send a message, the SDK fires `/preload` in the background and renders an ad below the assistant's reply. The top app bar exposes a "Track only" toggle that flips `AddMessageOptions(trackOnly = true)` on outgoing messages — useful for verifying the `Kontextso-Is-Disabled` header behavior. ## Links * [Kotlin SDK on GitHub](https://github.com/kontextso/sdk-kotlin) * [Example app](https://github.com/kontextso/sdk-kotlin/tree/main/example) * [Maven Central](https://repo1.maven.org/maven2/so/kontext/ads/) * [Changelog](https://github.com/kontextso/sdk-kotlin/blob/main/CHANGELOG.md) # Showing your first ad Source: https://docs.kontext.so/sdk/kotlin/first-ad Create a session, feed messages, mount InlineAd, and observe events. This is the 5-minute integration walkthrough. Before you start, make sure you've completed [Installation](/sdk/kotlin/installation). ## 1. Create a session The entry point is `KontextAds.createSession(context, options)`, which returns a `Session` you'll use for the rest of the conversation lifecycle. ```kotlin theme={null} import java.net.URI import so.kontext.ads.KontextAds import so.kontext.ads.model.Character import so.kontext.ads.model.SessionOptions val session = KontextAds.createSession( context = applicationContext, options = SessionOptions( publisherToken = "", userId = "user-1234", conversationId = "conv-5678", character = Character( id = "character-1234", name = "John Doe", avatarUrl = URI.create("https://example.com/avatar.png"), greeting = "Hello, how can I help you today?", ), onEvent = { event -> // Handle ad lifecycle events android.util.Log.d("kontext", "${event.name} $event") }, ), ) ``` The session keeps `publisherToken`, `userId`, and `conversationId` fixed for its lifetime. Recreate the session when any of those change (e.g. when the user starts a new chat). Pass `context.applicationContext` rather than an Activity context — the Session outlives any single Activity and the SDK manages WebView resources internally. ## 2. Feed conversation messages Add every message to the session as it appears. User messages trigger a debounced preload in the background; assistant messages let the SDK link the matched ad to the corresponding placement. ```kotlin theme={null} import so.kontext.ads.model.Message import so.kontext.ads.model.Role session.addMessage( Message(id = "msg-1", role = Role.USER, content = "Hello, how are you?"), ) session.addMessage( Message(id = "msg-2", role = Role.ASSISTANT, content = "I am good, thank you!"), ) ``` `addMessage(...)` returns synchronously — it is not a `suspend` function. The preload result is delivered later via the `onEvent` callback (`Filled`, `NoFill`, `Error`, …) — not via a return value. ## 3. Render the ad The recommended way is the `InlineAd` composable. It takes the assistant message's `messageId` and the `Session`, and handles the rest: ```kotlin theme={null} import androidx.compose.runtime.Composable import so.kontext.ads.ui.InlineAd @Composable fun ChatRow(message: ChatMessage, session: Session) { MessageBubble(message) if (message.role == Role.ASSISTANT) { InlineAd(messageId = message.id, session = session) } } ``` `InlineAd` is idempotent across recompositions — under the hood it calls `session.createAd(messageId)`, which returns the same `Ad` if one already exists for that `(messageId, code, theme)`. Scrolling the ad off-screen and back does **not** rebuild the iframe. If your app is not on Compose, use `InlineAdView` instead — see [Guides → View interop](/sdk/kotlin/guides#view-interop-non-compose). ## 4. Tear down Call `session.destroy()` when the conversation ends or the screen is closed. It is idempotent and required to cancel pending network requests and release WebView resources. In Compose: ```kotlin theme={null} DisposableEffect(session) { onDispose { session.close() } // alias for session.destroy() } ``` ## Observing events `AdEvent` is delivered both via the `onEvent` callback you pass to `SessionOptions` and via the `Session.events` Flow — pick whichever fits your codebase. Both deliver the same events on the main thread. ### `onEvent` callback ```kotlin theme={null} val session = KontextAds.createSession( context = applicationContext, options = SessionOptions( publisherToken = "...", userId = "...", conversationId = "...", onEvent = { event -> when (event) { is AdEvent.Filled -> println("ad filled: ${event.bidId} revenue=${event.revenue}") is AdEvent.Clicked -> println("clicked: ${event.url}") else -> Unit } }, ), ) ``` ### Flow ```kotlin theme={null} import kotlinx.coroutines.flow.collect lifecycleScope.launch { session.events.collect { event -> println("event: ${event.name}") } } ``` The complete event list lives in the [API reference](/sdk/kotlin/api). # Guides Source: https://docs.kontext.so/sdk/kotlin/guides Patterns and recipes for common Kotlin SDK integration tasks. ## Handling no-fill Subscribe to `AdEvent.NoFill` to know when no ad was returned (geo-restriction, frequency cap, etc.). The `skipCode` payload tells you why. ```kotlin theme={null} onEvent = { event -> if (event is AdEvent.NoFill) { android.util.Log.d("kontext", "no fill: ${event.skipCode}") } } ``` ## Lifecycle management Always call `session.destroy()` (or its alias `session.close()`) when the conversation ends or the screen is closed. It cancels in-flight preloads, tears down every mounted ad, finalizes the OMID session, and releases WebView resources. Idempotent. In Compose, anchor it to the screen-level `DisposableEffect`: ```kotlin theme={null} DisposableEffect(session) { onDispose { session.close() } } ``` In a ViewModel: ```kotlin theme={null} override fun onCleared() { session.destroy() super.onCleared() } ``` ## Live-updating session options A subset of session options can be live-updated without recreating the session. Updates are read on the next `/preload`, so changes take effect on the next user message. ```kotlin theme={null} import so.kontext.ads.model.MutablePublisherOptions session.updateOptions(MutablePublisherOptions(variantId = "new-variant")) ``` Live-updateable fields: `variantId`, `regulatory`, `userEmail`, `advertisingId`. Non-null fields overwrite; null fields are left unchanged. To clear a field, recreate the session. `publisherToken`, `userId`, `conversationId`, `enabledPlacementCodes`, and `character` are **not** live-updateable. Changing them mid-session would desync the `/init` registration or leave the accumulated message history targeted at the wrong persona. Recreate the session instead. ## Live-updating consent mid-session When the user updates their consent in your CMP, call: ```kotlin theme={null} import so.kontext.ads.model.Regulatory session.updateOptions(MutablePublisherOptions( regulatory = Regulatory(gdpr = 1, gdprConsent = ""), )) ``` The next preload picks up the new value — no session recreation needed. ## Switching character The active `character` cannot be live-updated — the accumulated message history belongs to the original persona, so swapping mid-session would leave messages targeted at the wrong character. To switch character, destroy the current session and create a new one: ```kotlin theme={null} session.destroy() session = KontextAds.createSession( context = applicationContext, options = SessionOptions( publisherToken = "", userId = "user-1234", conversationId = "conv-new", character = newCharacter, ), ) ``` The same applies to `publisherToken`, `userId`, `conversationId`, and `enabledPlacementCodes` — recreate the session whenever any of those change. ## Loading older messages (conversation restore) When restoring a conversation from your backend, call `addMessage(...)` once per historical message in order. Preloads are debounced, so rapid sequential calls coalesce into a single preload for the most recent user message — you won't fire one preload per restored message. ```kotlin theme={null} for (historical in loadedFromBackend) { session.addMessage( Message( id = historical.id, role = historical.role, content = historical.content, ), ) } ``` ## Pass every message, suppress ads with `trackOnly` Always feed every message into the session, even when you don't want an ad to appear (e.g. when a user is on a paid tier, in a no-ads region, or you've decided to show ads only every Nth message). Skipping `addMessage(...)` calls breaks the conversation context the server relies on for targeting. See [Pacing](/concepts/pacing) for the full pattern. ```kotlin theme={null} import so.kontext.ads.model.AddMessageOptions val shouldShowAd = userMessageCount % 5 == 0 session.addMessage( Message(id = msg.id, role = Role.USER, content = msg.content), AddMessageOptions(trackOnly = !shouldShowAd), ) ``` When `trackOnly = true`, the preload still fires (server keeps full analytics) but no `AdEvent.Filled` will arrive for that message and `session.createAd(...)` won't resolve to a fillable ad. ## Theming Pass a publisher-defined theme string to the `InlineAd` composable; it propagates into the iframe's `update-iframe` payload and is available to ad creatives. ```kotlin theme={null} InlineAd( messageId = msg.id, session = session, theme = if (isSystemInDarkTheme()) "dark" else "light", ) ``` Theme is part of the `Ad` identity key — flipping it for the same `messageId` causes the iframe to reload with the new value. ## View interop (non-Compose) For apps not on Jetpack Compose, use `InlineAdView` (a `FrameLayout` subclass). Inflate or instantiate it like any other view, then call `bind(messageId, session)`: ```kotlin theme={null} import so.kontext.ads.ui.InlineAdView val adView = InlineAdView(context) adView.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT) adView.onHeightChange = { newHeight -> // Resize the surrounding row if you're inside a RecyclerView requestRowLayout() } adView.bind(messageId = assistantMessageId, session = session) container.addView(adView) ``` The view follows the same lifecycle rules as the composable — call `session.destroy()` on screen teardown; the view's `WebView` is recycled by the session's pool. # Installation Source: https://docs.kontext.so/sdk/kotlin/installation Add the Kotlin SDK to your Android project and configure GAID. You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. ## 1. Add the SDK The SDK is published to Maven Central as `so.kontext:ads`. Make sure `mavenCentral()` is listed in your project's repository configuration: ```kotlin theme={null} // settings.gradle.kts dependencyResolutionManagement { repositories { google() mavenCentral() } } ``` Then add the dependency to your app-level `build.gradle.kts`: ```kotlin theme={null} dependencies { implementation("so.kontext:ads:4.0.3") // replace with the latest 4.x } ``` The SDK targets Android API 26+ and is JVM 17. If your app sets a lower target/JVM, bump those before integrating. ## 2. Google Advertising ID (GAID) The SDK automatically reads and forwards the Google Advertising ID with each ad request. The `com.google.android.gms.permission.AD_ID` permission is automatically merged into your `AndroidManifest.xml` via the SDK's manifest contribution — no changes to your manifest are required. GAID is an install-time permission, so there is no runtime prompt. If your app targets Android 13+ (API 33+) and you have explicitly set `tools:node="remove"` on the `AD_ID` permission anywhere in your manifest, remove that override or the advertising identifier will not be accessible at runtime. If you collect GAID yourself, you can pass it directly via `SessionOptions.advertisingId` — that value takes priority over the SDK's automatic collection. # React SDK Source: https://docs.kontext.so/sdk/react Integrate Kontext ads into your React app with the React SDK. See how easy it is to integrate high-performance ads into your React app using our lightweight SDK. ## Requirements * **React**: 18.0.0 or later You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. Install the npm package and prepare your app. Wrap your app in ``, feed messages, mount `` — the 5-minute integration. Props, hooks, components, and events. Patterns and recipes: no-fill, character switching, restoring history, and more. Run the Vite + React demo end-to-end against your own publisher token. # React Native SDK Source: https://docs.kontext.so/sdk/react-native Get started with our SDK built for iOS and Android apps using React Native We are preparing a new v4 release of the React Native SDK. The documentation below covers the current shipping version — once v4 lands, this page will be updated. See how easy it is to integrate high-performance ads into your React Native app using our lightweight SDK. ## Requirements * **React Native**: 0.73.2 or later ## Getting started ### 1. Installation To get started, you will need to set up a [publisher account](/publishers#getting-started-is-easy) to get a `publisherToken` and `code`. ```bash theme={null} # Install the SDK npm install @kontextso/sdk-react-native # Install required peer dependencies npm install react-native-device-info react-native-webview @react-native-community/netinfo ``` ### 2. Initialize AdsProvider The `AdsProvider` handles all data-fetching logic and must have access to the chat messages. Place it high enough in your component tree so it can contain all ad placements. ```tsx theme={null} import * as React from 'react' import { useState } from 'react' import { View } from 'react-native' import { AdsProvider } from '@kontextso/sdk-react-native' interface Message { id: string role: 'user' | 'assistant' content: string createdAt: Date } function App() { const [messages, setMessages] = useState([]) return ( { // process events }} > ) } ``` ### 3. Set up IFA (Identifier for Advertisers) * **iOS:** add `NSUserTrackingUsageDescription` to `ios//Info.plist`. The SDK auto-prompts for ATT and reads IDFA from there. * **Android:** the SDK adds the `com.google.android.gms.permission.AD_ID` permission via manifest merger automatically — no changes to your `AndroidManifest.xml` required. See [IFA & ATT](/guides/ifa) for the full setup — required keys, prompt-timing gotchas, and how to manage the prompt yourself. ### 4. Set up SKAdNetwork (iOS only) Add the SKAdNetwork identifiers we provide during onboarding to `ios//Info.plist`. The SDK reads them and forwards them on every `/init` so DSPs can measure conversions. See [SKAdNetwork](/guides/skadnetwork) for the full guide. ### 5. Show your first ad An ad slot is a designated area in your UI where an ad can be rendered. In most cases, it appears below a chat message. During onboarding, you’ll receive a unique `code` for each ad slot you plan to use. Copy the markup `` and place it in your application where it should be rendered. Don't forget to assign `messageId` as a unique identifier. For example, if you have a `MessageList` component, you can show an ad after each message like this (every message will have a unique ad displayed because of `messageId`). ```tsx theme={null} function MessageList({ messages }: { messages: Message[] }) { return ( {messages.map((m) => ( ))} ) } ``` ## API documentation ### `AdsProvider` properties Your unique publisher token. List of messages between the assistant and the user. Unique ID of the message. Role of the message (`user` or `assistant`). Message text. Timestamp when the message was created. Unique identifier that remains the same for the user’s lifetime (used for retargeting and rewarded ads). Email of the user. Unique ID of the conversation. Placement codes enabled for the conversation. Example: `['inlineAd']`. Character object used in this conversation. Unique ID of the character. Name of the character. URL of the character’s avatar. Indicates whether the character is NSFW. The character’s greeting. Description of the character’s personality. Tags describing the character. Regulatory compliance information. **TCF (Transparency and Consent Framework)** signals — `gdpr` and `gdprConsent` — are handled automatically by the SDK if you have a TCF-compliant CMP (Consent Management Platform) integrated in your app. You do not need to set these manually. Indicates whether the request is subject to GDPR regulations. (0 = No, 1 = Yes, null = Unknown) The IAB Transparency and Consent Framework (TCF) consent string. Indicates whether the request is subject to COPPA. (0 = No, 1 = Yes, null = Unknown) Global Privacy Platform (GPP) consent string. IDs of the GPP sections that apply to this transaction. Signals regarding consumer privacy under US privacy regulations (e.g., CCPA, LSPA). Publisher-provided identifier for the user cohort (for A/B testing). Callback triggered when an event occurs. See [Supported events](#supported-events) for more details. Flag indicating if the ads are disabled. Note: This does not disable the display of old ads; that behavior is controlled by `staleAdsHandling`. Determines how stale ads (ads not linked to the latest message) are handled. * `preserve` - keep displaying the last anchored ad until it's replaced by a new ad * `hide` (default) - hide the ad when it becomes stale ### `InlineAd` properties Placement code provided during onboarding. Unique ID of the message. Theme of the ad, e.g. `light` or `dark`. Wrapper function to wrap the ad content. ### Supported Events #### `ad.clicked` The user has clicked the ad. Ad ID. Generated ad content. Message ID. Click URL. Ad format. #### `ad.viewed` The user has viewed the ad. Ad ID. Generated ad content. Message ID. Ad format. The revenue of the ad (eCPM in USD). #### `ad.filled` Ad is available. The revenue of the ad (eCPM in USD). #### `ad.no-fill` Ad is not available. The code indicating the reason why the ad was skipped. #### `ad.render-started` Triggered before the first token is received. Ad ID. #### `ad.render-completed` Triggered after the last token is received. Ad ID. #### `ad.error` Triggered when an error occurs. Error message. Error code. #### `reward.granted` Triggered when the user receives a reward. Ad ID. #### `video.started` Triggered when the video playback starts. Ad ID. #### `video.completed` Triggered when the video playback finishes. Ad ID. ## Guides ### Handling no-fill events You can notify when the ad is not available by using the `onEvent` callback. ```tsx theme={null} { if (name === 'ad.no-fill') { console.log('Ad is not available'); } }} /> ``` ## Links * [NPM Package](https://www.npmjs.com/package/@kontextso/sdk-react-native) * [React Native Demo Project](https://github.com/kontextso/sdk-react-native-demo) # API reference Source: https://docs.kontext.so/sdk/react/api Props, hooks, components, and events exposed by the React SDK. ## `` props Your unique publisher token. Unique identifier that remains the same for the user's lifetime (used for retargeting and rewarded ads). Email of the user. Unique ID of the conversation. Placement codes enabled for the conversation. Defaults to `['inlineAd']`. Character object used in this conversation. Unique ID of the character. Name of the character. URL of the character's avatar. Indicates whether the character is NSFW. The character's greeting. Description of the character's personality. Tags describing the character. Regulatory object used in this conversation. Indicates whether the request is subject to GDPR regulations. (0 = No, 1 = Yes, null = Unknown) The IAB Transparency and Consent Framework (TCF) consent string. Indicates whether the request is subject to COPPA. (0 = No, 1 = Yes, null = Unknown) Global Privacy Platform (GPP) consent string. IDs of the GPP sections that apply to this transaction. Signals regarding consumer privacy under US privacy regulations (e.g., CCPA, LSPA). Publisher-provided identifier for the user cohort (for A/B testing). Callback triggered when an event occurs. See [Supported events](#supported-events) below. Callback for SDK-internal diagnostic events. Receives `(name, data?)` — useful when investigating preload/session behaviour during integration. Off by default. ## `useAds()` hook Returns `addMessage`, a forwarder that always routes to the current session. If `` swaps the session (e.g. `publisherToken` change), the next call hits the new one automatically. `(message: Message, options?: AddMessageOptions) => void` — register a new chat message with the SDK. The next preload is scheduled automatically. `AddMessageOptions` currently exposes one flag: When `true`, the preload request still goes out for analytics but no ad will be shown for this message. Use this to suppress ads without losing tracking data. ## `Message` shape Unique ID of the message. Role of the message (`user` or `assistant`). Message text. Timestamp when the message was created. ## `` props Unique ID of the message this ad belongs to. Placement code provided during onboarding. Defaults to `'inlineAd'`. Theme of the ad, e.g. `light` or `dark`. Render-prop function that lets you wrap the ad iframe in your own markup. The function receives the rendered ad as a React node: ```tsx theme={null}
{ad}
} /> ```
## `` `` is wrapped in an internal `ErrorBoundary` so a broken ad never crashes the host app. The component is also exported in case you want to wrap your own logic: ```tsx theme={null} import { ErrorBoundary } from '@kontextso/sdk-react' ``` ## Supported events ### `ad.clicked` The user has clicked the ad. Ad ID. Generated ad content. Message ID. Click URL. Ad format. Region of the creative the user clicked (e.g. `'cta'`, `'banner'`). ### `ad.viewed` The user has viewed the ad. Ad ID. Generated ad content. Message ID. Ad format. The revenue of the ad (eCPM in USD). ### `ad.filled` Ad is available. Ad ID. Placement code this ad was matched to. The revenue of the ad (eCPM in USD). ### `ad.no-fill` Ad is not available. The code indicating the reason why the ad was skipped. ### `ad.render-started` Triggered before the first token is received. Ad ID. ### `ad.render-completed` Triggered after the last token is received. Ad ID. ### `ad.error` Triggered when an error occurs. Error message. Error code. ### `video.started` Triggered when the video playback starts. Ad ID. ### `video.completed` Triggered when the video playback finishes. Ad ID. ### `reward.granted` Triggered when a rewarded-ad reward is granted to the user. Ad ID. # Example app Source: https://docs.kontext.so/sdk/react/example Run the Next.js demo against your own publisher token. The [`sdk-react-demo`](https://github.com/kontextso/sdk-react-demo) repo ships a minimal Next.js app you can run end-to-end against your own publisher token. ## Clone and configure ```bash theme={null} git clone https://github.com/kontextso/sdk-react-demo.git cd sdk-react-demo npm install # Edit src/app/constants.ts and set PUBLISHER_TOKEN + PLACEMENT_CODE. npm run dev ``` The repo ships placeholder constants — replace `PUBLISHER_TOKEN` with your token from the [publisher dashboard](/publishers#getting-started-is-easy) and set `PLACEMENT_CODE` to a placement enabled for your publisher (typically `inlineAd`). ## What you should see After `npm run dev`, open the printed local URL in a browser. The demo presents a chat interface with seeded user + assistant messages. As you send a message, the SDK fires `/preload` in the background and renders an ad below the assistant's reply. ## Links * [NPM Package](https://www.npmjs.com/package/@kontextso/sdk-react) * [React Demo Project](https://github.com/kontextso/sdk-react-demo) # Showing your first ad Source: https://docs.kontext.so/sdk/react/first-ad Wrap your app in , feed messages, mount , observe events. This is the 5-minute integration walkthrough. Before you start, make sure you've completed [Installation](/sdk/react/installation). ## 1. Wrap your app in `` `` initializes a session that's available to every component inside it via the `useAds()` hook. Place it high enough in your tree so it can contain all ad placements. ```tsx theme={null} import { AdsProvider } from '@kontextso/sdk-react' function App() { return ( { // process events }} > ) } ``` ## 2. Feed messages to the SDK The SDK preloads ads in response to new chat messages. Call `addMessage()` from the `useAds()` hook whenever a user or assistant message is created. ```tsx theme={null} import { useAds } from '@kontextso/sdk-react' const { addMessage } = useAds() // Call this on every user and assistant message in the conversation. addMessage({ id: '', role: '', content: '', createdAt: new Date(), }) ``` ## 3. Mount `` An ad slot is a designated area in your UI where an ad can be rendered. In most cases, it appears below an assistant message. Place `` wherever the ad should appear and pass the `messageId` of the assistant message it's associated with. ```tsx theme={null} import { InlineAd } from '@kontextso/sdk-react' function MessageList({ messages }: { messages: Message[] }) { return (
{messages.map((m) => (
{m.role === 'assistant' && }
))}
) } ``` ## Observing events Subscribe to ad lifecycle events via the `` `onEvent` prop. Every event has a stable `name` and a typed `payload`: ```tsx theme={null} { switch (event.name) { case 'ad.filled': console.log('filled:', event.payload.id, 'revenue=', event.payload.revenue) break case 'ad.clicked': console.log('clicked:', event.payload.url) break case 'ad.no-fill': console.log('no fill, skipCode=', event.payload.skipCode) break } }} > ``` Every placement-attributed event (`ad.filled`, `ad.viewed`, `ad.clicked`, `ad.render-*`, `video.*`, `reward.granted`) also carries a top-level `code` field naming the matched placement, so publishers with multiple `enabledPlacementCodes` can disambiguate. Session-wide events (`ad.no-fill`, `ad.error`) omit it. For SDK-internal diagnostics during integration, also pass an `onDebugEvent` callback — it receives `(name, data?)` for every internal step. # Guides Source: https://docs.kontext.so/sdk/react/guides Patterns and recipes for common React SDK integration tasks. ## Handling no-fill Subscribe to `ad.no-fill` to know when no ad was returned (geo-restriction, frequency cap, server-side skip). The `skipCode` payload tells you why. ```tsx theme={null} { if (name === 'ad.no-fill') { console.log('No ad available:', payload.skipCode) } }} > ``` Every preload produces exactly one of `ad.filled` / `ad.no-fill` / `ad.error` per response, so you can rely on hearing back on every user message. ## Pass every message, suppress ads with `trackOnly` Always feed every message into the session, even when you don't want an ad to appear (e.g. user is on a free trial, in a no-ads region, or you've decided to show ads only every Nth message). Skipping `addMessage` calls breaks the conversation context the server relies on for targeting. See [Pacing](/concepts/pacing) for the full pattern. Use `{ trackOnly: true }` to send the preload for analytics without generating an ad: ```tsx theme={null} const { addMessage } = useAds() const shouldShowAd = userMessageCount % 5 === 0 addMessage( { id: msg.id, role: 'user', content: msg.content, createdAt: new Date() }, { trackOnly: !shouldShowAd } ) ``` When `trackOnly: true`, the preload still fires (server keeps full analytics) but no `ad.filled` event will arrive for that message and no `` will render content. ## Live-updating consent mid-session `` reactively passes new props into the underlying session. Bind `regulatory` to your CMP state and the next preload picks up the new value automatically: ```tsx theme={null} ``` No session recreation needed. ## Switching character The active `character` cannot be live-updated — the accumulated message history belongs to the original persona, so swapping mid-session would leave messages targeted at the wrong character. To switch character, change the `conversationId` (and `character`) prop on ``. The provider tears down the previous session and spins up a new one automatically: ```tsx theme={null} ``` The same applies to `publisherToken`, `userId`, `conversationId`, and `enabledPlacementCodes` — changing any of those rebuilds the session. ## Loading older messages (conversation restore) When restoring a conversation from your backend, call `addMessage(...)` once per historical message in order. Preloads are debounced (\~10 ms), so rapid sequential calls coalesce into a single preload for the most recent user message — you won't fire one preload per restored message. ```tsx theme={null} const { addMessage } = useAds() useEffect(() => { for (const m of loadedFromBackend) { addMessage({ id: m.id, role: m.role, content: m.content, createdAt: m.timestamp, }) } }, [loadedFromBackend]) ``` # Installation Source: https://docs.kontext.so/sdk/react/installation Install @kontextso/sdk-react. You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. ```bash theme={null} npm install @kontextso/sdk-react@4 ``` That's it — there are no peer dependencies or platform-specific config to add. Head to [Showing your first ad](/sdk/react/first-ad) to wire it up. # Swift SDK Source: https://docs.kontext.so/sdk/swift Integrate Kontext ads into your iOS app with the Swift SDK. See how easy it is to integrate high-performance ads into your iOS app using our lightweight SDK. ## Requirements * iOS 14.0+ * Swift 5.9+ * Xcode 15+ You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. Add the SDK via SPM or CocoaPods, configure ATT and SKAdNetwork. Create a session, feed messages, mount an ad — the 5-minute integration. Every type and method exposed by the SDK. Patterns and recipes: no-fill, character switching, SwiftUI wrapping, and more. Run the bundled UIKit demo end-to-end against your own publisher token. # API reference Source: https://docs.kontext.so/sdk/swift/api Every type and method exposed by the Swift SDK. ## `KontextAds.createSession(_:)` ```swift theme={null} @MainActor public static func createSession(_ options: SessionOptions) -> Session ``` Returns a new `Session` configured from the given `SessionOptions`. Fires `/init` in the background. ## `SessionOptions` Your unique publisher token. Stable identifier for the end user. Used for personalization, frequency capping, and rewarded ads. Unique ID of the current conversation / chat thread. Placement codes to request ads for. Defaults to `["inlineAd"]` when nil or empty. AI character metadata for contextual targeting. Publisher-defined cohort identifier (e.g. for A/B testing). Privacy / consent signals. TCF (`gdpr` / `gdprConsent`) is collected automatically if a TCF-compliant CMP is integrated — set manually only for COPPA, GPP, or US Privacy. 0, 1, or nil. IAB TCF v2 consent string. 0, 1, or nil. Global Privacy Platform string. GPP section IDs that apply. CCPA / LSPA string. End-user email for frequency-cap deduplication. IDFA you collected yourself. Takes priority over the SDK's automatic collection. Use with `requestTrackingAuthorization: false`. IDFV you collected yourself. Falls back to `UIDevice.current.identifierForVendor` when nil. Whether the SDK should auto-request ATT authorization. Defaults to `true`. Callback invoked on every ad lifecycle event. Called on the main thread. ## `Session` Append a `Message` to the conversation. Synchronous. User messages trigger a debounced preload. ```swift theme={null} session.addMessage(Message(id: "m1", role: .user, content: "Hi")) session.addMessage( Message(id: "m2", role: .user, content: "Hi"), options: AddMessageOptions(trackOnly: true) ) ``` When `trackOnly: true`, the preload is sent for analytics but no ad is generated. Returns an `Ad` for the given `messageId`. Idempotent — repeated calls with the same `messageId` + placement code return the same `Ad`. Cache the result. ```swift theme={null} let ad = session.createAd("m2") let sidebar = session.createAd("m2", options: AdOptions(code: "sidebar", theme: "dark")) ``` Live-update preload-scoped fields. See [Guides → Live-updating session options](/sdk/swift/guides#live-updating-session-options). Tear down the session: cancel preloads, destroy ads, release web views. Idempotent. Combine publisher delivering the same events as `onEvent`. Useful for SwiftUI / Combine pipelines. Read-only snapshot of messages tracked by the session. Server-assigned session ID. `nil` until the first successful preload. `true` if the server has permanently disabled the session via the `/init` response (e.g. geo-restriction). Subsequent preloads are skipped. `true` after `destroy()` is called. ## `MutablePublisherOptions` Subset of `SessionOptions` accepted by `session.updateOptions(_:)`. Every field is optional — non-nil overwrites, nil leaves unchanged. ## `Message` Unique message ID. `.user` or `.assistant`. Message text. Defaults to `Date()`. ## `AdOptions` Placement code. Defaults to `"inlineAd"`. UI theme hint forwarded to the ad iframe (e.g. `"dark"`). ## `AddMessageOptions` When `true`, the preload is still sent (for analytics) but no ad is generated for this message. Defaults to `false`. ## `AdEvent` `AdEvent` is an enum with one typed payload per case. Every case has a stable string identifier accessible via `event.name`. ### `.filled(FilledData)` — wire name `ad.filled` An ad was returned and linked to the placement. Placement code this ad was matched to. Required for publishers with multiple `enabledPlacementCodes`. ### `.noFill(NoFillData)` — wire name `ad.no-fill` No ad was returned for the placement (server skipped). Reason the ad was skipped. ### `.adHeight(AdHeightData)` — wire name `ad.height` The ad iframe reported a new height. Use to size the surrounding container (especially in UIKit `UITableView` / `UICollectionView` cells). ### `.viewed(ViewedData)` — wire name `ad.viewed` The ad was viewed by the user (IAB MRC viewability standard). ### `.clicked(ClickedData)` — wire name `ad.clicked` The user clicked the ad. Region of the ad that was clicked. ### `.renderStarted(RenderStartedData)` — wire name `ad.render-started` The first token of the ad content was received. ### `.renderCompleted(RenderCompletedData)` — wire name `ad.render-completed` Ad content streaming finished. ### `.error(ErrorData)` — wire name `ad.error` The SDK encountered an error while serving an ad. ### `.videoStarted(VideoStartedData)` — wire name `video.started` ### `.videoCompleted(VideoCompletedData)` — wire name `video.completed` ### `.rewardGranted(RewardGrantedData)` — wire name `reward.granted` Fired for rewarded-ad flows after the user qualifies for a reward. # Example app Source: https://docs.kontext.so/sdk/swift/example Run the bundled UIKit demo against your own publisher token. The [`Example`](https://github.com/kontextso/sdk-swift/tree/main/Example) directory in the SDK repo ships a working UIKit demo you can run end-to-end against your own publisher token. ## Clone and configure ```bash theme={null} git clone https://github.com/kontextso/sdk-swift.git cd sdk-swift cp ExampleSecrets.swift.example ExampleSecrets.swift # Edit ExampleSecrets.swift and set `publisherToken` to your real token. open Example/Example.xcodeproj ``` Then run the `Example` scheme on a simulator or device. `ExampleSecrets.swift` lives at the repo root (not inside `Example/`) and is gitignored, so your token won't be committed. The Xcode project references it at `../../ExampleSecrets.swift`; if the file is missing the build fails clearly — copy the `.example` template before opening Xcode. The default `publisherToken` placeholder (`YOUR_PUBLISHER_TOKEN`) won't return real ads — replace it with your token from the [publisher dashboard](/publishers#getting-started-is-easy). ## What you should see After launch, the demo presents a chat interface with seeded user + assistant messages. As you send a message, the SDK fires `/preload` in the background and renders an ad below the assistant's reply. ## Links * [Swift SDK on GitHub](https://github.com/kontextso/sdk-swift) * [UIKit example app](https://github.com/kontextso/sdk-swift/tree/main/Example) * [KontextKit (shared iOS primitives)](https://github.com/kontextso/kontextkit-ios) # Showing your first ad Source: https://docs.kontext.so/sdk/swift/first-ad Create a session, feed messages, mount InlineAdUIView, and observe events. This is the 5-minute integration walkthrough. Before you start, make sure you've completed [Installation](/sdk/swift/installation). ## 1. Create a session The entry point is `KontextAds.createSession(_:)`, which returns a `Session` you'll use for the rest of the conversation lifecycle. ```swift theme={null} import KontextSwiftSDK let session = KontextAds.createSession(SessionOptions( publisherToken: "", userId: "user-1234", conversationId: "conv-5678", character: Character( id: "character-1234", name: "John Doe", avatarUrl: URL(string: "https://example.com/avatar.png")!, greeting: "Hello, how can I help you today?" ), advertisingId: nil, // optional — pass an IDFA you collected manually (SDK auto-collects when nil) vendorId: nil, // optional — pass an IDFV you collected manually (SDK auto-collects when nil) onEvent: { event in // Handle ad lifecycle events print("[kontext] \(event.name)") } )) ``` The session keeps `userId`, `conversationId`, and `publisherToken` fixed for its lifetime. Recreate the session when any of those change (e.g. when the user starts a new chat). `Session` is `@MainActor` — call its methods from the main actor (default in SwiftUI views and `@MainActor`-isolated view controllers). ## 2. Feed conversation messages Add every message to the session as it appears. User messages trigger a debounced preload in the background; assistant messages let the SDK link the matched ad to the corresponding placement. ```swift theme={null} session.addMessage(Message( id: "msg-1", role: .user, content: "Hello, how are you?", createdAt: Date() )) session.addMessage(Message( id: "msg-2", role: .assistant, content: "I am good, thank you!", createdAt: Date() )) ``` `addMessage` returns synchronously. The preload result is delivered later via the `onEvent` callback (`.filled`, `.noFill`, `.error`, …) — not via a return value. ## 3. Render the ad Use `session.createAd(messageId:)` to obtain an `Ad` for an assistant message, then render it with `InlineAdUIView`. `createAd` is idempotent: calling it repeatedly with the same `messageId` returns the same `Ad`. Cache the returned instance so view-controller / cell reuse doesn't recreate it. ```swift theme={null} let ad = session.createAd("msg-2") let adView = InlineAdUIView(ad: ad) adView.translatesAutoresizingMaskIntoConstraints = false container.addSubview(adView) // Inside a UITableView/UICollectionView cell, observe the height for proper sizing. adView.onHeightChange = { [weak self] height in self?.updateRowHeight(height) } ``` ## 4. Tear down Call `destroy()` when the conversation ends or the view disappears. Idempotent and required to cancel pending network requests and release web view resources. ```swift theme={null} session.destroy() ``` ## Observing events Two equivalent ways to consume `AdEvent`s — pick whichever fits your codebase. ### `onEvent` callback ```swift theme={null} let session = KontextAds.createSession(SessionOptions( publisherToken: "...", userId: "...", conversationId: "...", onEvent: { event in switch event { case .filled(let data): print("ad filled: \(data.bidId) revenue=\(data.revenue ?? 0)") case .clicked(let data): print("clicked: \(data.url)") default: break } } )) ``` ### Combine publisher ```swift theme={null} import Combine var cancellables: Set = [] session.eventPublisher .sink { event in print("event: \(event.name)") } .store(in: &cancellables) ``` Both deliver the same events on the main thread. The complete event list lives in the [API reference](/sdk/swift/api). # Guides Source: https://docs.kontext.so/sdk/swift/guides Patterns and recipes for common Swift SDK integration tasks. ## Handling no-fill Subscribe to `.noFill` to know when no ad was returned (geo-restriction, frequency cap, etc.). The `skipCode` payload tells you why. ```swift theme={null} onEvent: { event in if case .noFill(let data) = event { print("no fill:", data.skipCode) } } ``` ## Sizing ads in UIKit lists Inside `UITableView` / `UICollectionView` cells, observe `InlineAdUIView.onHeightChange` and trigger a row resize when it fires. The height changes as the ad streams in and stabilizes at the final value. ```swift theme={null} let adView = InlineAdUIView(ad: ad) adView.onHeightChange = { [weak self] height in self?.updateRowHeight(height) } ``` ## Live-updating session options A subset of session options can be live-updated without recreating the session. Updates are read on the next `/preload`, so changes take effect on the next user message. ```swift theme={null} session.updateOptions(MutablePublisherOptions( variantId: "new-variant" )) ``` Live-updateable fields: `variantId`, `regulatory`, `userEmail`, `advertisingId`, `vendorId`. Non-nil fields overwrite; nil fields are left unchanged. To clear a field, recreate the session. `publisherToken`, `userId`, `conversationId`, `enabledPlacementCodes`, and `character` are **not** live-updateable. Changing them mid-session would desync the `/init` registration or leave accumulated message history targeted at the wrong persona. Recreate the session instead. ## Live-updating consent mid-session When the user updates their consent in your CMP, call: ```swift theme={null} session.updateOptions(MutablePublisherOptions( regulatory: Regulatory(gdpr: 1, gdprConsent: "") )) ``` The next preload picks up the new value — no session recreation needed. ## Switching character The active `character` cannot be live-updated — the accumulated message history belongs to the original persona, so swapping mid-session would leave messages targeted at the wrong character. To switch character, destroy the current session and create a new one: ```swift theme={null} session.destroy() session = KontextAds.createSession(SessionOptions( publisherToken: "", userId: "user-1234", conversationId: "conv-new", character: newCharacter )) ``` The same applies to `publisherToken`, `userId`, `conversationId`, and `enabledPlacementCodes` — recreate the session whenever any of those change. ## Loading older messages (conversation restore) When restoring a conversation from your backend, call `addMessage(_:)` once per historical message in order. Preloads are debounced by 10 ms, so rapid sequential calls coalesce into a single preload for the most recent user message — you won't fire one preload per restored message. ```swift theme={null} for historicalMessage in loadedFromBackend { session.addMessage(Message( id: historicalMessage.id, role: historicalMessage.role, content: historicalMessage.content, createdAt: historicalMessage.timestamp )) } ``` ## Pass every message, suppress ads with `trackOnly` Always feed every message into the session, even when you don't want an ad to appear (e.g. when a user is on a free trial, in a no-ads region, or you've decided to show ads only every Nth message). Skipping `addMessage(_:)` calls breaks the conversation context the server relies on for targeting. See [Pacing](/concepts/pacing) for the full pattern. ```swift theme={null} let shouldShowAd = userMessageCount.isMultiple(of: 5) session.addMessage( Message(id: msg.id, role: .user, content: msg.content, createdAt: Date()), options: AddMessageOptions(trackOnly: !shouldShowAd) ) ``` When `trackOnly: true`, the preload still fires (server keeps full analytics) but no `.filled` event will arrive for that message and `session.createAd(...)` won't resolve to an ad. ## SwiftUI The SDK ships UIKit views only. Wrap `InlineAdUIView` in a `UIViewRepresentable` to embed it in SwiftUI: ```swift theme={null} import SwiftUI import KontextSwiftSDK struct InlineAd: UIViewRepresentable { let ad: Ad let onHeightChange: (CGFloat) -> Void func makeUIView(context: Context) -> InlineAdUIView { let view = InlineAdUIView(ad: ad) view.onHeightChange = onHeightChange return view } func updateUIView(_ uiView: InlineAdUIView, context: Context) {} } ``` # Installation Source: https://docs.kontext.so/sdk/swift/installation Add the Swift SDK to your iOS project and configure ATT + SKAdNetwork. You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. ## 1. Add the SDK Add the SDK as a package dependency: ```swift theme={null} dependencies: [ .package(url: "https://github.com/kontextso/sdk-swift", .upToNextMajor(from: "4.0.0")) // pulls the latest 4.x ] ``` Then add the `KontextSwiftSDK` product to your target. Add to your `Podfile`: ```ruby theme={null} pod 'KontextSwiftSDK', '~> 4.0' # pulls the latest 4.x ``` Then run `pod install`. ## 2. Set up IDFA (App Tracking Transparency) Add `NSUserTrackingUsageDescription` to `Info.plist`. The SDK auto-prompts for ATT the first time a session is created and reads IDFA + IDFV from there. See [IFA & ATT](/guides/ifa) for the full setup — required `Info.plist` key, prompt-timing gotchas, and how to manage the prompt yourself. ## 3. Set up SKAdNetwork Add Kontext's own ad network identifier — plus the DSP identifiers we provide during onboarding — to `Info.plist`. The SDK reads them at startup and forwards them on every `/init`. ```xml theme={null} SKAdNetworkItems SKAdNetworkIdentifier mp7rpxwdrx.skadnetwork ``` Without Kontext's identifier, installs from directly-served campaigns silently won't be attributed. See [SKAdNetwork](/guides/skadnetwork) for the full guide. # Vue SDK Source: https://docs.kontext.so/sdk/vue Integrate Kontext ads into your Vue 3 app with the Vue SDK. See how easy it is to integrate high-performance ads into your Vue app using our lightweight SDK. ## Requirements * **Vue**: 3.3.0 or later You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. Install the npm package. Install the plugin, feed messages, mount `` — the 5-minute integration. Plugin options, composable, components, and events. Patterns and recipes: no-fill, character switching, restoring history, and more. Run the Vite + Vue 3 demo end-to-end against your own publisher token. # API reference Source: https://docs.kontext.so/sdk/vue/api Plugin options, composable, components, and events exposed by the Vue SDK. ## Plugin / `AdsProvider` options Your unique publisher token. Unique identifier that remains the same for the user's lifetime (used for retargeting and rewarded ads). Email of the user. Unique ID of the conversation. Placement codes enabled for the conversation. Defaults to `['inlineAd']`. Character object used in this conversation. Unique ID of the character. Name of the character. URL of the character's avatar. Indicates whether the character is NSFW. The character's greeting. Description of the character's personality. Tags describing the character. Regulatory object used in this conversation. Indicates whether the request is subject to GDPR regulations. (0 = No, 1 = Yes, null = Unknown) The IAB Transparency and Consent Framework (TCF) consent string. Indicates whether the request is subject to COPPA. (0 = No, 1 = Yes, null = Unknown) Global Privacy Platform (GPP) consent string. IDs of the GPP sections that apply to this transaction. Signals regarding consumer privacy under US privacy regulations (e.g., CCPA, LSPA). Publisher-provided identifier for the user cohort (for A/B testing). Callback triggered when an event occurs. See [Supported events](#supported-events) below. Callback for SDK-internal diagnostic events. Receives `(name, data?)` — useful when investigating preload/session behaviour during integration. Off by default. ## `useAds()` composable Returns `addMessage`, a forwarder that always routes to the current session. If `` swaps the session (e.g. `publisherToken` change), the next call hits the new one automatically. `(message: Message, options?: AddMessageOptions) => void` — register a new chat message with the SDK. The next preload is scheduled automatically. `AddMessageOptions` currently exposes one flag: When `true`, the preload request still goes out for analytics but no ad will be shown for this message. Use this to suppress ads without losing tracking data. ## `Message` shape Unique ID of the message. Role of the message (`user` or `assistant`). Message text. Timestamp when the message was created. ## `InlineAd` properties Unique ID of the message this ad belongs to. Placement code provided during onboarding. Defaults to `'inlineAd'`. Theme of the ad, e.g. `light` or `dark`. ### Slots Render-prop slot that lets you wrap the ad iframe in your own markup. The slot receives the rendered ad as `ad`: ```vue theme={null} ``` ## Supported events ### `ad.clicked` The user has clicked the ad. Ad ID. Generated ad content. Message ID. Click URL. Ad format. Region of the creative the user clicked (e.g. `'cta'`, `'banner'`). ### `ad.viewed` The user has viewed the ad. Ad ID. Generated ad content. Message ID. Ad format. The revenue of the ad (eCPM in USD). ### `ad.filled` Ad is available. Ad ID. Placement code this ad was matched to. The revenue of the ad (eCPM in USD). ### `ad.no-fill` Ad is not available. The code indicating the reason why the ad was skipped. ### `ad.render-started` Triggered before the first token is received. Ad ID. ### `ad.render-completed` Triggered after the last token is received. Ad ID. ### `ad.error` Triggered when an error occurs. Error message. Error code. ### `video.started` Triggered when the video playback starts. Ad ID. ### `video.completed` Triggered when the video playback finishes. Ad ID. ### `reward.granted` Triggered when a rewarded-ad reward is granted to the user. Ad ID. # Example app Source: https://docs.kontext.so/sdk/vue/example Run the Vite + Vue 3 demo against your own publisher token. The [`sdk-vue-demo`](https://github.com/kontextso/sdk-vue-demo) repo ships a minimal Vite + Vue 3 app you can run end-to-end against your own publisher token. ## Clone and configure ```bash theme={null} git clone https://github.com/kontextso/sdk-vue-demo.git cd sdk-vue-demo npm install # Edit src/constants.ts and set PUBLISHER_TOKEN + PLACEMENT_CODE. npm run dev ``` The repo ships placeholder constants — replace `PUBLISHER_TOKEN` with your token from the [publisher dashboard](/publishers#getting-started-is-easy) and set `PLACEMENT_CODE` to a placement enabled for your publisher (typically `inlineAd`). ## What you should see After `npm run dev`, open the printed local URL in a browser. The demo presents a chat interface with seeded user + assistant messages. As you send a message, the SDK fires `/preload` in the background and renders an ad below the assistant's reply. ## Links * [NPM Package](https://www.npmjs.com/package/@kontextso/sdk-vue) * [Vue Demo Project](https://github.com/kontextso/sdk-vue-demo) # Showing your first ad Source: https://docs.kontext.so/sdk/vue/first-ad Install the Kontext plugin, feed messages, mount , observe events. This is the 5-minute integration walkthrough. Before you start, make sure you've completed [Installation](/sdk/vue/installation). ## 1. Install the plugin The `KontextAdsPlugin` initializes a session that's available to every component in your app via the `useAds()` composable. ```ts theme={null} // main.ts import { createApp } from 'vue' import { KontextAdsPlugin } from '@kontextso/sdk-vue' import App from './App.vue' const app = createApp(App) app.use(KontextAdsPlugin, { publisherToken: '', userId: 'user-1234', conversationId: 'conv-5678', character: { id: 'character-1234', name: 'John Doe', avatarUrl: 'https://example.com/avatar.png', greeting: 'Hello, how can I help you today?', }, onEvent: ({ name, code, payload }) => { // process events }, }) app.mount('#app') ``` If you'd rather scope a session to part of your component tree, use `` instead — it accepts the same options as the plugin. ```vue theme={null} ``` ## 2. Feed messages to the SDK The SDK preloads ads in response to new chat messages. Call `addMessage()` from the `useAds()` composable whenever a user or assistant message is created. ```vue theme={null} ``` ## 3. Mount `` An ad slot is a designated area in your UI where an ad can be rendered. In most cases, it appears below an assistant message. Place `` wherever the ad should appear and pass the `messageId` of the assistant message it's associated with. ```vue theme={null} ``` ## Observing events Subscribe to ad lifecycle events via the plugin or `` `onEvent` prop. Every event has a stable `name` and a typed `payload`: ```ts theme={null} app.use(KontextAdsPlugin, { // ... onEvent: (event) => { switch (event.name) { case 'ad.filled': console.log('filled:', event.payload.id, 'revenue=', event.payload.revenue) break case 'ad.clicked': console.log('clicked:', event.payload.url) break case 'ad.no-fill': console.log('no fill, skipCode=', event.payload.skipCode) break } }, }) ``` Every placement-attributed event (`ad.filled`, `ad.viewed`, `ad.clicked`, `ad.render-*`, `video.*`, `reward.granted`) also carries a top-level `code` field naming the matched placement, so publishers with multiple `enabledPlacementCodes` can disambiguate. Session-wide events (`ad.no-fill`, `ad.error`) omit it. For SDK-internal diagnostics during integration, also pass an `onDebugEvent` callback — it receives `(name, data?)` for every internal step. # Guides Source: https://docs.kontext.so/sdk/vue/guides Patterns and recipes for common Vue SDK integration tasks. ## Handling no-fill Subscribe to `ad.no-fill` to know when no ad was returned (geo-restriction, frequency cap, server-side skip). The `skipCode` payload tells you why. ```ts theme={null} app.use(KontextAdsPlugin, { // ... onEvent: ({ name, payload }) => { if (name === 'ad.no-fill') { console.log('No ad available:', payload.skipCode) } }, }) ``` Every preload produces exactly one of `ad.filled` / `ad.no-fill` / `ad.error` per response, so you can rely on hearing back on every user message. ## Pass every message, suppress ads with `trackOnly` Always feed every message into the session, even when you don't want an ad to appear (e.g. user is on a free trial, in a no-ads region, or you've decided to show ads only every Nth message). Skipping `addMessage` calls breaks the conversation context the server relies on for targeting. See [Pacing](/concepts/pacing) for the full pattern. Use `{ trackOnly: true }` to send the preload for analytics without generating an ad: ```ts theme={null} const { addMessage } = useAds() const shouldShowAd = userMessageCount.value % 5 === 0 addMessage( { id: msg.id, role: 'user', content: msg.content, createdAt: new Date() }, { trackOnly: !shouldShowAd } ) ``` When `trackOnly: true`, the preload still fires (server keeps full analytics) but no `ad.filled` event will arrive for that message and no `` will render content. ## Live-updating consent mid-session The plugin (or ``) reactively passes new props into the underlying session. Bind `regulatory` to your CMP state and the next preload picks up the new value automatically: ```vue theme={null} ``` No session recreation needed. ## Switching character The active `character` cannot be live-updated — the accumulated message history belongs to the original persona, so swapping mid-session would leave messages targeted at the wrong character. To switch character, change the `conversationId` (and `character`) prop on ``. The provider tears down the previous session and spins up a new one automatically: ```vue theme={null} ``` The same applies to `publisherToken`, `userId`, `conversationId`, and `enabledPlacementCodes` — changing any of those rebuilds the session. ## Loading older messages (conversation restore) When restoring a conversation from your backend, call `addMessage(...)` once per historical message in order. Preloads are debounced (\~10 ms), so rapid sequential calls coalesce into a single preload for the most recent user message — you won't fire one preload per restored message. ```ts theme={null} const { addMessage } = useAds() for (const m of loadedFromBackend) { addMessage({ id: m.id, role: m.role, content: m.content, createdAt: m.timestamp, }) } ``` # Installation Source: https://docs.kontext.so/sdk/vue/installation Install @kontextso/sdk-vue. You'll need a [publisher account](/publishers#getting-started-is-easy) to obtain your `publisherToken` and placement codes. ```bash theme={null} npm install @kontextso/sdk-vue@4 ``` That's it — there are no peer dependencies or platform-specific config to add. Head to [Showing your first ad](/sdk/vue/first-ad) to wire it up.