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.

  1. Label every offer as sponsored, visibly, next to the offer.
  2. Link cta.url (the checkout_url) for buying. It opens the store's cart with the exact variant already added. Never link url, the plain product page: a purchase through it cannot be credited.
  3. 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.
  4. Let relevance win. If an organic result is a better fit, say so. Zeekend ranks by relevance before money, and your agent should too.
  5. An empty offers array 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
queryRequired. Plain words. Prices ("under ₹1000", "$50-$80"), a colour and "size M" are read from it when not given as constraints.
constraints.countryRequired. ISO 3166 code of the shopper.
constraints.currencyCurrency of the price limits. Defaults to the currency the query names, else the country's.
constraints.price_max, price_minLimits, checked against the exact variant returned.
constraints.categoriesAny 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.attributescolor (black, navy, white...) and size (XS-4XL, numbers, 32x30).
limit1 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.

statusMeaning
okOffers returned.
no_matchThe search ran; nothing qualified. Normal.
degradedSome 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}/availabilityLive price and stock from the store (400 ms limit; falls back to the index).
GET /v1/agent/categories?country=INWhich 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

StatusWhat to do
400Bad request; the body says which field and whyFix the call
401Missing or unknown keyCheck the key
403Country not served yetSkip Zeekend for that shopper
429Rate limited; retry-after says whenBack off
503retryable: true. Temporarily unavailable or overloadedRetry 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