USE IT WITH A CODING AGENT

Paste this into Claude Code or Cursor

Read https://exchange.zeekend.com/skill.md and follow it to add Zeekend to this project.The agent reads the instructions below and makes the changes. Everything starts on the sandbox key, which bills nobody.

Zeekend places sponsored products inside AI conversations. The auction fires on the user's question and runs while the model streams, so a placement adds nothing to how long the user waits. Nothing fills on most turns, by design.

There is one integration. Do not improvise another.

Step 1 — Key

Ask the user for their publisher key. It looks like pub_live_....

If they do not have one, use pub_test and continue. That is the sandbox: no signup, fills on almost every turn, bills nobody. They can see it working immediately and swap in a live key later by changing one string.

Never invent a pub_live_ value. Tell them once, at the end, that sandbox fill rates are not representative and a live key comes from https://zeekend.com.

Step 2 — Find the messages array

Locate where the app renders assistant messages. You are looking for the array of conversation turns it maps over. Common names: messages, chatHistory, conversation, or messages from useChat() in the Vercel AI SDK.

It should look like [{ role: 'user' | 'assistant', content: '...' }]. The SDK also accepts {role, text}, {from: 'user'|'bot'}, and Anthropic-style content blocks, so do not reshape it.

If the app has no conversational surface, stop and say so.

Step 3 — Install

npm i @zeekend/sdk

Do not set an endpoint. The default is production.

Step 4 — Add one component

import { ZeekendSlot } from '@zeekend/sdk/react'

<ZeekendSlot publisherKey={process.env.NEXT_PUBLIC_ZEEKEND_KEY} messages={messages} />

Place it directly under the message list, inside the same scroll container, so the unit sits with the conversation.

If the messages live in a store rather than a prop, read them from the store in whatever component renders the slot.

That is the entire integration. Nothing else is required.

Step 5 — Do not do these things

  • Do not pass turnId, question, or answer by hand. Those props still exist from an older version and getting turnId wrong fires a request per token. messages is the correct integration.
  • Do not call Zeekend.init inside a component body.
  • Do not call requestAd, getAds, or monetize. Those belong to other ad networks. Zeekend's surface is init, client, serve, request, attach, render, impression, click, report, stats, reset.
  • Do not remove or restyle away slot.disclosure. The label is required.
  • Do not link slot.url. Use slot.clickUrl, the tracked URL.
  • Do not fire an impression on render. The SDK fires it on visibility.
  • Do not place the unit above the assistant's answer.
  • Do not add more than one slot per turn unless asked.
  • Do not set minTurns, turnGap or maxPerSession in code. Pacing is the publisher's to set from their dashboard; a value in code overrides it and cannot be changed without a redeploy.
  • Do not set includePrevious: true unless the user asks for it. It sends an earlier turn, which their privacy notice has to cover.

Step 6 — Verify before reporting success

  1. Add debug to the slot props or provider.
  2. Send two messages in the app.
  3. Check the browser console for a [zeekend] line.
  4. Confirm exactly one request per turn, not one per token. A burst means messages is being rebuilt with different content each render. Investigate before continuing.
  5. On a live key, turn one will normally skip warmup: the exchange holds the first placement to turn 2 unless the dashboard says otherwise. Correct. pub_test is not paced, so it fills from turn one.
  6. On pub_test, every turn should fill. If nothing ever fills, the integration is wrong, not the matching. On a live key, most turns correctly return nothing.
  7. Confirm errorRate is 0 via useZeekend().stats().

Only report the integration complete after steps 4 and 6 pass.

Troubleshooting

SymptomCause
Console: cannot reach the exchangeNetwork, or an endpoint was set. Remove it.
Console: initialized but never firedThe slot is not mounted where anything renders.
errorRate above 0Connection or auth. The console warning names the fix.
errorRate 0, fillRate 0 on a live keyWorking correctly. Nothing matched.
A request per tokenmessages content is changing every render.
Ad on turn oneEither the dashboard allows it, or minTurns was set in code. Remove it from code.
Two identical adsZeekend.init called inside a component. Use the component or provider.
Doubled requests in React devStrictMode double-invokes effects. The SDK dedupes; harmless.

Options

Only publisherKey and messages are required.

<ZeekendSlot
  publisherKey="pub_live_..."
  messages={messages}
  conversationId={thread.id}
  relevance={0.55}                    // quality floor 0-1
  dimensions={{ maxWidth: 640 }}      // leave one axis unbounded
  blockCategories={['gambling', 'crypto']}
  onNoFill={reason => {}}             // waterfall to another network
/>

Only if the app is not React

Use these when the component genuinely cannot apply. Do not offer them otherwise.

React Native, or custom rendering. useZeekendSlot({ publisherKey, messages }) returns a slot; render it yourself, then call zk.impression(slot) from your own viewability logic and zk.click(slot) on click. Keep slot.disclosure.

Vanilla JS. Zeekend.client({ publisherKey }) then zk.attach({ mount: el, messages }) whenever messages change.

No build step at all. <script src="https://exchange.zeekend.com/z.js" data-key="pub_test"></script> before </body>. This only works when the app calls its model from the browser, and it guesses where to place the unit. Prefer the component whenever React is available.

What leaves the app

Sent: the user's last message, the assistant's reply on the second pass only, the publisher key and placement id, coarse locale.

Never sent: user id, email, phone, cookies, device ids, the system prompt, or any earlier turn unless includePrevious: true is set (then the user's previous message, clipped to 500 characters). Text is clipped client-side before the request leaves.

Reference