Sponsored product offers for shopping agents. Your agent asks for what the shopper wants; Zeekend answers with labeled offers from brands that pay when a purchase is made, each with the exact variant, the store's real price and a checkout link. You earn a share of every order your agent sends.
Call it alongside your normal search. It answers in tens of milliseconds and returns nothing when no brand fits, so it never slows your agent down or crowds out better results.
Base URL: https://agents-bench.zeekend.com
Try it in 30 seconds
agt_test is the sandbox key: real offers, nothing recorded, nothing billed.
curl -s -X POST https://agents-bench.zeekend.com/v1/agent/search \
-H "authorization: Bearer agt_test" \
-H "content-type: application/json" \
-d '{"query":"charles leclerc tshirt under ₹1000","constraints":{"country":"IN"},"limit":3}'
For a live key (agt_live_...) that earns, contact hello@zeekend.com. Keep it on your server: it is a secret, not a browser key.
Two ways to connect
MCP, if your agent or framework supports remote MCP servers (Claude, ChatGPT developer mode, Cursor, the OpenAI Agents SDK, LangChain, Mastra and most others). One URL, no code:
https://agents-bench.zeekend.com/mcp
Authenticate with Authorization: Bearer agt_live_.... Clients that only take a URL (ChatGPT and Claude connectors) can use https://agents-bench.zeekend.com/mcp?key=agt_live_... instead; treat that URL as a secret.
Tools: search_offers, get_offer, check_availability, get_checkout_link. The server's instructions tell the model when to call them and how to label the results.
REST, if you would rather own the tool definition. One call, shown below in each major SDK.
When to call it
On any turn that could end in someone buying something, even if they never say "buy": product searches, gift ideas, "what should I get", recommendations and comparisons. Call it in parallel with your web or catalog search and merge the results.
Search with product or interest words ("charles leclerc tshirt", "trail running shoes"), not the occasion. Words like "gift", "birthday" or "for my brother" are ignored anyway.
Always pass the shopper's country: it decides which brands ship to them and which currency price limits are in.
How to show results
These are the rules your agent must follow. They are what keep sponsored results trustworthy, and a condition of a live key.
- Label every offer as sponsored, visibly, next to the offer.
- Link
cta.url(thecheckout_url) for buying. It opens the store's cart with the exact variant already added. Never linkurl, the plain product page: a purchase through it cannot be credited. - Show the store's price (
price_display) and, when present, the "was" price (compare_at_display). Never restate a price you have not been given. - Let relevance win. If an organic result is a better fit, say so. Zeekend ranks by relevance before money, and your agent should too.
- An empty
offersarray is normal. Show your other results.
Code
TypeScript, Anthropic SDK (tool use)
const zeekendTool = {
name: "search_sponsored_offers",
description:
"Find products to buy or gift, as sponsored offers from brands. Use for shopping, gift ideas and " +
"recommendations, alongside web search. Results are ads: label them as sponsored and link cta.url to buy.",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "Product or interest words" },
country: { type: "string", description: "Shopper's ISO country code, e.g. US, IN" },
price_max: { type: "number" },
currency: { type: "string", description: "Currency of price_max, e.g. USD, INR" },
},
required: ["query", "country"],
},
};
async function searchSponsoredOffers(input: { query: string; country: string; price_max?: number; currency?: string }) {
const r = await fetch("https://agents-bench.zeekend.com/v1/agent/search", {
method: "POST",
headers: { authorization: `Bearer ${process.env.ZEEKEND_AGENT_KEY}`, "content-type": "application/json" },
body: JSON.stringify({
query: input.query,
constraints: { country: input.country, price_max: input.price_max, currency: input.currency },
limit: 3,
}),
signal: AbortSignal.timeout(1500),
});
if (!r.ok) return { offers: [] }; // never let ads break your agent
return r.json();
}
Pass zeekendTool in tools on client.messages.create, and when the model returns a tool_use block for it, call searchSponsoredOffers and send the JSON back as the tool_result.
TypeScript, Vercel AI SDK
import { tool } from "ai";
import { z } from "zod";
export const searchSponsoredOffers = tool({
description:
"Find products to buy or gift, as sponsored offers from brands. Results are ads: label them as " +
"sponsored and link cta.url to buy.",
inputSchema: z.object({
query: z.string(),
country: z.string().length(2),
price_max: z.number().optional(),
currency: z.string().length(3).optional(),
}),
execute: async ({ query, country, price_max, currency }) => {
const r = await fetch("https://agents-bench.zeekend.com/v1/agent/search", {
method: "POST",
headers: { authorization: `Bearer ${process.env.ZEEKEND_AGENT_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ query, constraints: { country, price_max, currency }, limit: 3 }),
signal: AbortSignal.timeout(1500),
});
return r.ok ? r.json() : { offers: [] };
},
});
Python, OpenAI SDK (function calling)
import os, requests
ZEEKEND_TOOL = {
"type": "function",
"function": {
"name": "search_sponsored_offers",
"description": "Find products to buy or gift, as sponsored offers from brands. "
"Results are ads: label them as sponsored and link cta.url to buy.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"country": {"type": "string", "description": "ISO country code, e.g. US, IN"},
"price_max": {"type": "number"},
"currency": {"type": "string"},
},
"required": ["query", "country"],
},
},
}
def search_sponsored_offers(query, country, price_max=None, currency=None):
try:
r = requests.post(
"https://agents-bench.zeekend.com/v1/agent/search",
headers={"authorization": f"Bearer {os.environ['ZEEKEND_AGENT_KEY']}"},
json={"query": query, "constraints": {"country": country, "price_max": price_max, "currency": currency},
"limit": 3},
timeout=1.5,
)
return r.json() if r.ok else {"offers": []}
except requests.RequestException:
return {"offers": []}
Calling it directly, without the model deciding
The most reliable integration: on every turn your agent classifies as shopping, call Zeekend from your own code in parallel with your search, and give both result sets to the model. Then sponsored offers appear whenever one fits, not only when the model thinks to ask.
const [web, sponsored] = await Promise.all([webSearch(q), searchSponsoredOffers({ query: q, country })]);
Reference
POST /v1/agent/search
{
"query": "black oversized hoodie under $80",
"constraints": {
"country": "US",
"currency": "USD",
"price_max": 80,
"price_min": null,
"categories": ["apparel"],
"attributes": { "color": "black", "size": "M" },
"exclude_advertisers": ["example.com"]
},
"limit": 5,
"session_id": "your-conversation-id"
}
| Field | |
|---|---|
query | Required. Plain words. Prices ("under ₹1000", "$50-$80"), a colour and "size M" are read from it when not given as constraints. |
constraints.country | Required. ISO 3166 code of the shopper. |
constraints.currency | Currency of the price limits. Defaults to the currency the query names, else the country's. |
constraints.price_max, price_min | Limits, checked against the exact variant returned. |
constraints.categories | Any of: apparel, footwear, accessories, jewelry, bags-luggage, beauty, skincare, haircare, fragrance, health-wellness, home, kitchen, furniture, bedding-bath, electronics, audio, sports-outdoors, fitness, food-drink, pets, baby-kids, toys-games, books-media, office-stationery, garden, automotive, arts-crafts. |
constraints.attributes | color (black, navy, white...) and size (XS-4XL, numbers, 32x30). |
limit | 1 to 10. Default 5. At most 2 offers per brand. |
Response:
{
"search_id": "srch_...",
"status": "ok",
"inferred": { "price_max": 80, "currency": "USD", "color": "black" },
"offers": [{
"offer_id": "zko_...",
"sponsored": true,
"advertiser": "Example Brand",
"title": "Oversized Heavyweight Hoodie",
"description": "...",
"price": 64, "currency": "USD", "price_display": "$64.00",
"compare_at_price": 80, "compare_at_display": "$80.00", "discount_percent": 20,
"approx_price": { "amount": 5340, "currency": "INR" },
"variant": { "id": "4482...", "options": { "Color": "Black", "Size": "M" } },
"availability": "in_stock",
"price_as_of": "2026-10-06T04:00:00Z",
"url": "https://store.example/products/hoodie?variant=4482...",
"checkout_url": "https://agents-bench.zeekend.com/v1/go/zko_...",
"cta": { "label": "Buy at Example Brand for $64.00", "url": "https://agents-bench.zeekend.com/v1/go/zko_..." },
"image": "https://...",
"match_reason": ["black", "oversized", "hoodie", "size_M_in_stock", "under_$80", "ships_US"]
}],
"disclosure": "Sponsored. Zeekend is paid if a purchase is made through checkout_url. ...",
"ms": 31
}
price is what the shopper pays, in the store's currency. approx_price is an estimate in the shopper's currency, present only when the two differ. compare_at_* appear only when the store shows the item on sale.
status | Meaning |
|---|---|
ok | Offers returned. |
no_match | The search ran; nothing qualified. Normal. |
degraded | Some offers may be missing (a secondary pass ran out of time); degraded_reason says why. |
Other endpoints
GET /v1/agent/offers/{offer_id} | Full details and every variant, with "was" prices. |
GET /v1/agent/offers/{offer_id}/availability | Live price and stock from the store (400 ms limit; falls back to the index). |
GET /v1/agent/categories?country=IN | Which categories have offers for a country. Use it to skip calls you know will be empty. |
GET /v1/go/{offer_id} | The checkout link. Redirects to the store's cart. Offer ids expire after 7 days. |
All take Authorization: Bearer <key>, except /v1/go, which shoppers open.
Errors
| Status | What to do | |
|---|---|---|
| 400 | Bad request; the body says which field and why | Fix the call |
| 401 | Missing or unknown key | Check the key |
| 403 | Country not served yet | Skip Zeekend for that shopper |
| 429 | Rate limited; retry-after says when | Back off |
| 503 | retryable: true. Temporarily unavailable or overloaded | Retry once after a second, or skip |
Treat Zeekend as optional: on any error, show your other results. It is built to fail fast (every request has a 650 ms server deadline) rather than make your agent wait.
Limits
60 requests a minute per key by default; ask for more. Every response carries a Server-Timing header with the time spent in each stage.
How you earn
Brands pay per order, at a price they set. When a shopper buys through an offer's checkout link within 7 days, the order is credited to your key and you earn a share of what the brand pays, held for the store's return window before it is paid out.
The program is in beta: earnings reporting and payouts are being finalized with the first partners. Contact hello@zeekend.com.
For coding agents
Instructions an AI coding agent can follow to add Zeekend to a project: https://agents-bench.zeekend.com/agents/skill.md