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, oranswerby hand. Those props still exist from an older version and gettingturnIdwrong fires a request per token.messagesis the correct integration. - Do not call
Zeekend.initinside a component body. - Do not call
requestAd,getAds, ormonetize. Those belong to other ad networks. Zeekend's surface isinit,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. Useslot.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,turnGapormaxPerSessionin 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: trueunless the user asks for it. It sends an earlier turn, which their privacy notice has to cover.
Step 6 — Verify before reporting success
- Add
debugto the slot props or provider. - Send two messages in the app.
- Check the browser console for a
[zeekend]line. - Confirm exactly one request per turn, not one per token. A burst means
messagesis being rebuilt with different content each render. Investigate before continuing. - 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_testis not paced, so it fills from turn one. - 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. - Confirm
errorRateis 0 viauseZeekend().stats().
Only report the integration complete after steps 4 and 6 pass.
Troubleshooting
| Symptom | Cause |
|---|---|
| Console: cannot reach the exchange | Network, or an endpoint was set. Remove it. |
| Console: initialized but never fired | The slot is not mounted where anything renders. |
errorRate above 0 | Connection or auth. The console warning names the fix. |
errorRate 0, fillRate 0 on a live key | Working correctly. Nothing matched. |
| A request per token | messages content is changing every render. |
| Ad on turn one | Either the dashboard allows it, or minTurns was set in code. Remove it from code. |
| Two identical ads | Zeekend.init called inside a component. Use the component or provider. |
| Doubled requests in React dev | StrictMode 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
- Package and full README: https://www.npmjs.com/package/@zeekend/sdk
- API base: https://exchange.zeekend.com/v1
- Health: https://exchange.zeekend.com/v1/health
- Request a publisher key: https://zeekend.com/#for-ai-platforms
- Also want this app's activity to show up on a live dashboard? Use https://exchange.zeekend.com/skill-dashboard.md instead of this file — same integration, plus one extra key.