# Clickwise — complete guide for developers and AI agents > Clickwise is an affiliate meta-network: one publisher account, one tracking link format and one payout > across many merchant programs. Sites, apps, bots, creators and AI agents find programs and deals, create > tracked links, read their conversions and call a product search endpoint (by text or GTIN) through a REST > API or a remote MCP server. This file is self-contained; the short index is https://partners.clickwise.net/llms.txt and the human page is https://partners.clickwise.net/developers/. ## First successful call 1. Get a sandbox key, no account needed: `POST https://partners.clickwise.net/api/v1/developers/sandbox-keys/` with JSON `{"email": "you@example.com", "use_case": "price_comparison"}` (use_case: price_comparison, woocommerce, mcp_agent, deals_bot, ai_store, monetize_agent, other), or the form at https://partners.clickwise.net/developers/#sandbox. Open the emailed link once to reveal the key (`cw_sbx_...`). 2. Call the API: `curl "https://partners.clickwise.net/api/v1/publisher/products/?q=headphones&country=GB&limit=3" -H "X-API-Key: $CLICKWISE_API_KEY"` 3. Pick a quickstart below. To earn, create a free publisher account (https://partners.clickwise.net/en/signup_affiliate/) and use your own key: the code does not change. Without any key: public MCP tools (https://partners.clickwise.net/api/v1/mcp), the deals API and the public catalog sample. Human page for AI agent and assistant builders: https://partners.clickwise.net/en/for-agents/ ## Credentials | Credential | Works on | Limits | |---|---|---| | Sandbox key `cw_sbx_...` (self-service by email) | Publisher Products API only (`products/` and `products/lookup/`) plus the MCP tool `search_products`; sandbox links are examples and do not track clicks or earn commission, and results can be empty | 10 requests/min, 500/day, 14 days; a test identity that never earns commission | | Publisher API key (your account) | The whole Publisher API v1, the MCP account tools, the HasOffers/TUNE-compatible endpoint | 120 requests/min per account; 60/min for products | | OAuth 2.1 on the MCP server | https://partners.clickwise.net/api/v1/mcp/account: read your programs, reports and conversions; create tracked links; product search | Access tokens 1 h, rotating refresh tokens 30 days; revoke at https://partners.clickwise.net/oauth/connections/ | | None | Public MCP tools, deals and news APIs, catalog statistics and sample | Per-address limits | A sandbox key or an OAuth connection never applies to programs. No key or token changes payout details or postback settings, or approves applications, commissions or payments. Send keys only in the `X-API-Key` or `Authorization: Bearer` header, never in URLs, page source or browser code. ## Quickstarts by use case ### Price comparison or GTIN shop Search products by name in one market, then compare every offer for a GTIN (EAN/UPC), cheapest first. Each product carries a ready tracked link in url. 1. Search: GET /api/v1/publisher/products/?q=<3-200 chars>&country=. Follow next_offset for more. 2. Compare: GET /api/v1/publisher/products/lookup/?gtins=&country= (1-100 ids and GTINs). 3. Publish url exactly as returned (rel="sponsored nofollow"); refresh prices with lookup at least daily and hide items that are no longer returned. curl: ```bash # 1. Products whose title contains "headphones" in the United Kingdom curl --get "https://partners.clickwise.net/api/v1/publisher/products/" \ -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "q=headphones" --data-urlencode "country=GB" --data-urlencode "limit=20" # 2. Every offer for the first product's GTIN (needs jq; keep GTINs as strings) GTIN=$(curl -s --get "https://partners.clickwise.net/api/v1/publisher/products/" -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "q=headphones" --data-urlencode "country=GB" --data-urlencode "limit=1" | jq -r '.products[0].gtin') curl --get "https://partners.clickwise.net/api/v1/publisher/products/lookup/" \ -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "gtins=$GTIN" --data-urlencode "country=GB" ``` JavaScript: ```javascript // Node 18+ (built-in fetch). Run: CLICKWISE_API_KEY=... node compare.mjs const BASE = "https://partners.clickwise.net/api/v1/publisher"; const headers = { "X-API-Key": process.env.CLICKWISE_API_KEY }; async function get(path) { const res = await fetch(`${BASE}${path}`, { headers }); const body = await res.json(); if (!res.ok) throw new Error(`${res.status} ${body.error?.code}: ${body.error?.message}`); return body; } export async function compare(gtin, country = "GB") { const { products } = await get(`/products/lookup/?gtins=${encodeURIComponent(gtin)}&country=${country}`); return products .filter((p) => p.availability === "in_stock") .sort((a, b) => Number(a.price) - Number(b.price)) .map((p) => ({ merchant: p.merchant.name, price: `${p.price} ${p.currency}`, link: p.url })); } const { products } = await get("/products/?q=headphones&country=GB&limit=5"); console.table(products.map((p) => ({ title: p.title, gtin: p.gtin, price: p.price }))); if (products.length) console.table(await compare(products[0].gtin)); ``` Python: ```python # pip install requests. Run: CLICKWISE_API_KEY=... python compare.py import os import requests BASE = "https://partners.clickwise.net/api/v1/publisher" HEADERS = {"X-API-Key": os.environ["CLICKWISE_API_KEY"]} def get(path: str, **params) -> dict: r = requests.get(f"{BASE}{path}", params=params, headers=HEADERS, timeout=20) r.raise_for_status() return r.json() def compare(gtin: str, country: str = "GB") -> list[dict]: offers = [p for p in get("/products/lookup/", gtins=gtin, country=country)["products"] if p["availability"] == "in_stock"] return sorted(offers, key=lambda p: float(p["price"])) found = get("/products/", q="headphones", country="GB", limit=5)["products"] for p in found: print(p["title"], p["gtin"], p["price"], p["currency"]) if found: for p in compare(found[0]["gtin"]): print(p["merchant"]["name"], p["price"], p["currency"], p["url"]) ``` ### WooCommerce / WordPress import (WP All Import) Turn API results into a CSV that WP All Import maps to WooCommerce external/affiliate products, then refresh it on a schedule. 1. Try the columns first with the public sample (no key, no links): https://partners.clickwise.net/api/v1/public/catalog-sample/?country=ES&format=csv 2. Generate your import file with the script below (JavaScript or Python) from your searches; each row keeps its tracked link in product_url. 3. WP All Import: New Items → WooCommerce Products → Product type "External/Affiliate product". Map title → Title, price → Regular Price, product_url → Product URL, button_text → Button text, image → Images (download from URL), sku → SKU, gtin → "GTIN, UPC, EAN or ISBN" (WooCommerce 9.2+). Unique identifier: {sku}. 4. Schedule the import daily with "Delete products that are no longer present in your file" on, so expired offers disappear. curl: ```bash # Sample columns (no key, no tracked links): map them before you have a key curl -o clickwise-sample.csv "https://partners.clickwise.net/api/v1/public/catalog-sample/?country=ES&format=csv" # Products with your tracked links, as JSON, one page of 100 curl --get "https://partners.clickwise.net/api/v1/publisher/products/" -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "q=headphones" --data-urlencode "country=GB" --data-urlencode "limit=100" ``` JavaScript: ```javascript // Node 18+: writes clickwise-woocommerce.csv for WP All Import import { writeFileSync } from "node:fs"; const BASE = "https://partners.clickwise.net/api/v1/publisher"; const headers = { "X-API-Key": process.env.CLICKWISE_API_KEY }; const queries = ["headphones", "keyboard"]; const country = "GB"; const cols = ["sku", "gtin", "title", "price", "currency", "image", "product_url", "button_text", "merchant", "availability", "country", "updated_at"]; const csv = (v) => `"${String(v ?? "").replaceAll('"', '""')}"`; const rows = new Map(); for (const q of queries) { let offset = 0; while (offset !== null && offset < 1000) { const url = `${BASE}/products/?q=${encodeURIComponent(q)}&country=${country}&limit=100&offset=${offset}`; const res = await fetch(url, { headers }); if (res.status === 429) { await new Promise((r) => setTimeout(r, 1000 * Number(res.headers.get("Retry-After") || 60))); continue; } const body = await res.json(); if (!res.ok) throw new Error(`${res.status} ${body.error?.code}`); for (const p of body.products) { rows.set(`${p.id}-${p.country}`, [`${p.id}-${p.country}`, p.gtin, p.title, p.price, p.currency, p.image, p.url, `Buy at ${p.merchant.name}`, p.merchant.name, p.availability, p.country, p.updated_at]); } offset = body.next_offset; } } writeFileSync("clickwise-woocommerce.csv", [cols, ...rows.values()].map((r) => r.map(csv).join(",")).join("\n")); console.log(`${rows.size} products written`); ``` Python: ```python # pip install requests. Writes clickwise-woocommerce.csv for WP All Import import csv import os import time import requests BASE = "https://partners.clickwise.net/api/v1/publisher" HEADERS = {"X-API-Key": os.environ["CLICKWISE_API_KEY"]} QUERIES, COUNTRY = ["headphones", "keyboard"], "GB" COLS = ["sku", "gtin", "title", "price", "currency", "image", "product_url", "button_text", "merchant", "availability", "country", "updated_at"] rows = {} for q in QUERIES: offset = 0 while offset is not None and offset < 1000: r = requests.get(f"{BASE}/products/", headers=HEADERS, timeout=30, params={"q": q, "country": COUNTRY, "limit": 100, "offset": offset}) if r.status_code == 429: time.sleep(int(r.headers.get("Retry-After", 60))) continue r.raise_for_status() body = r.json() for p in body["products"]: sku = f'{p["id"]}-{p["country"]}' rows[sku] = [sku, p["gtin"], p["title"], p["price"], p["currency"], p["image"], p["url"], f'Buy at {p["merchant"]["name"]}', p["merchant"]["name"], p["availability"], p["country"], p["updated_at"]] offset = body["next_offset"] with open("clickwise-woocommerce.csv", "w", newline="", encoding="utf-8") as fh: writer = csv.writer(fh) writer.writerow(COLS) writer.writerows(rows.values()) print(len(rows), "products written") ``` PHP (WordPress): ```php 100, 'type' => 'external', 'return' => 'objects']); $by_gtin = []; foreach ($products as $product) { $gtin = $product->get_global_unique_id(); if ($gtin) { $by_gtin[$gtin] = $product; } } if (!$by_gtin) { return; } $url = add_query_arg(['gtins' => implode(',', array_keys($by_gtin)), 'country' => 'DE'], 'https://partners.clickwise.net/api/v1/publisher/products/lookup/'); $response = wp_remote_get($url, ['timeout' => 20, 'headers' => ['X-API-Key' => CLICKWISE_API_KEY]]); if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { return; } $seen = []; foreach (json_decode(wp_remote_retrieve_body($response), true)['products'] as $p) { $product = $by_gtin[$p['gtin']] ?? null; if (!$product || isset($seen[$p['gtin']])) { continue; } $seen[$p['gtin']] = true; $product->set_regular_price($p['price']); $product->set_product_url($p['url']); $product->set_status($p['availability'] === 'in_stock' ? 'publish' : 'draft'); $product->save(); } foreach ($by_gtin as $gtin => $product) { // no longer offered: hide it if (!isset($seen[$gtin])) { $product->set_status('draft'); $product->save(); } } }); if (!wp_next_scheduled('clickwise_refresh_prices')) { wp_schedule_event(time(), 'daily', 'clickwise_refresh_prices'); } ``` ### AI agent over MCP (Claude, Cursor, ChatGPT) Two remote MCP endpoints over Streamable HTTP. Public, no sign-in: programs, deals and catalog statistics. Your account, OAuth sign-in: your programs, reports, conversions, product search and tracked links (read and links only). 1. Public tools, no key: https://partners.clickwise.net/api/v1/mcp 2. Your account: https://partners.clickwise.net/api/v1/mcp/account (the client opens Clickwise sign-in and asks you to approve read and links access; revoke it any time at https://partners.clickwise.net/oauth/connections/). 3. Claude (claude.ai, Desktop): Settings → Connectors → Add custom connector → paste the URL. 4. Claude Code: claude mcp add --transport http clickwise https://partners.clickwise.net/api/v1/mcp/account, then run /mcp to sign in. 5. Cursor: add the server to ~/.cursor/mcp.json (below); Cursor starts the sign-in on first use. 6. ChatGPT: enable developer mode, create a connector with the /api/v1/mcp/account URL and OAuth authentication. 7. Headless agents can send a key instead: header X-API-Key on https://partners.clickwise.net/api/v1/mcp. A sandbox key covers search_products (by text or GTIN); every other account tool needs your publisher key. curl: ```bash # Public tool call over plain JSON-RPC (no key) curl -s "https://partners.clickwise.net/api/v1/mcp" \ -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"find_affiliate_programs","arguments":{"topic":"headphones","country":"DE","limit":5}}}' # Cursor: ~/.cursor/mcp.json # {"mcpServers": {"clickwise": {"url": "https://partners.clickwise.net/api/v1/mcp/account"}}} # Claude Code with a key instead of sign-in claude mcp add --transport http clickwise "https://partners.clickwise.net/api/v1/mcp" --header "X-API-Key: $CLICKWISE_API_KEY" ``` JavaScript: ```javascript // npm install @modelcontextprotocol/sdk import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const client = new Client({ name: "my-shopping-agent", version: "1.0.0" }); await client.connect(new StreamableHTTPClientTransport(new URL("https://partners.clickwise.net/api/v1/mcp"))); const { tools } = await client.listTools(); console.log(tools.map((t) => t.name)); const stats = await client.callTool({ name: "get_catalog_stats", arguments: {} }); console.log(stats.content[0].text); await client.close(); ``` Python: ```python # pip install mcp import asyncio from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client async def main(): async with streamablehttp_client("https://partners.clickwise.net/api/v1/mcp") as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print([t.name for t in tools.tools]) result = await session.call_tool("find_affiliate_programs", {"topic": "running shoes", "country": "ES", "limit": 5}) print(result.content[0].text) asyncio.run(main()) ``` ### Earn commission from your AI assistant Your assistant searches products in one market, shows offers with a tracked link and earns commission on approved sales. You read the results per sub-ID through the API or receive a signed postback. 1. Try it with a sandbox key (form above): search with q and country, for example q=headphones and country=GB. Sandbox links are examples and do not track clicks or earn commission, and a sandbox search returns at most 10 offers. 2. To earn, create a publisher account and use its key. List the programs open to you with GET /api/v1/publisher/programs/joinable/ and apply with POST /api/v1/publisher/programs/apply/; an application is approved or goes to review. A search returns offers only from the programs you are approved in. 3. Search when the user asks for something: GET /api/v1/publisher/products/?q=<3-200 chars>&country=, or the MCP tool search_products. Each offer has url, its tracked link, and rules, the program's rules for that offer: follow them. 4. Tag each conversation: add subid= to the search. The links come back with it, and a sale made through one of those links reports it in your conversions. 5. Show the url exactly as returned and let the user open it. Never request the url yourself or prefetch it: every request counts as a click. Tell your users that the links are affiliate links. 6. Read results with GET /api/v1/publisher/conversions/ (per-sub-ID totals in by_subid) or GET /api/v1/publisher/reports/?group_by=subid. Or set a postback URL in the portal: each conversion arrives as a signed request with subid, click_id, transaction_id, order_ref, event and status. If you choose event types for it, only those arrive, and choosing sale.approved also brings sale.commission_validated. Sales from network programs can show as pending validation, with no commission amount until the sale is validated; the amount then arrives in a sale.commission_validated postback with the same transaction_id. 7. Verify every postback: for a POST postback, X-CW-Signature is the hex HMAC-SHA256 of X-CW-Timestamp, a dot and the raw request body, keyed with the postback secret from your portal. Send a test event with POST /api/v1/publisher/postback/test/. 8. Commission is paid on approved sales; the network or the merchant approves each sale. curl: ```bash # 1. Search in one market (sandbox key or your publisher key) curl --get "https://partners.clickwise.net/api/v1/publisher/products/" \ -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "q=headphones" --data-urlencode "country=GB" --data-urlencode "limit=5" \ --data-urlencode "subid=chat-42" # 2. Programs you can apply to, and an application (publisher key) curl "https://partners.clickwise.net/api/v1/publisher/programs/joinable/" -H "X-API-Key: $CLICKWISE_API_KEY" curl -X POST "https://partners.clickwise.net/api/v1/publisher/programs/apply/" -H "X-API-Key: $CLICKWISE_API_KEY" \ -H "Content-Type: application/json" -d '{"program_id": 123}' # 3. Results per sub-ID curl --get "https://partners.clickwise.net/api/v1/publisher/reports/" -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "group_by=subid" ``` JavaScript: ```javascript // Node 18+. Verify a Clickwise postback before trusting it. import crypto from "node:crypto"; export function verifyPostback(rawBody, timestamp, signature, secret) { const expected = crypto.createHmac("sha256", secret) .update(`${timestamp}.`).update(rawBody).digest("hex"); const a = Buffer.from(expected, "utf8"); const b = Buffer.from(String(signature || ""), "utf8"); return a.length === b.length && crypto.timingSafeEqual(a, b); } // In your handler: verifyPostback(req.rawBody, req.headers["x-cw-timestamp"], // req.headers["x-cw-signature"], process.env.CLICKWISE_POSTBACK_SECRET) ``` Python: ```python # Verify a Clickwise postback before trusting it. import hashlib import hmac import os def verify_postback(raw_body: bytes, timestamp: str, signature: str) -> bool: secret = os.environ["CLICKWISE_POSTBACK_SECRET"].encode() expected = hmac.new(secret, timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature or "") ``` ### Coupon and deals bot Post new merchant deals and coupon codes to Telegram, Discord or a newsletter. The deals API is public (no key); link each post to the deal page, or mint your own tracked link with your publisher key. 1. Poll GET /api/v1/deals/?lang=en&page_size=50 (lang: en, es, pt, fr, de, it); results are newest first. 2. Remember the ids you already posted; skip deals whose valid_until has passed. 3. Link to https://partners.clickwise.net + url (the public deal page). With a publisher key, POST /api/v1/publisher/links/ with subid "telegram" to earn and see clicks per channel in /reports/?group_by=subid. 4. RSS alternative: https://partners.clickwise.net/api/v1/deals/feed.en.xml curl: ```bash curl "https://partners.clickwise.net/api/v1/deals/?lang=en&page_size=20" # Your own tracked link for a deal's merchant (publisher key, earns commission) curl -X POST "https://partners.clickwise.net/api/v1/publisher/links/" -H "X-API-Key: $CLICKWISE_API_KEY" \ -H "Content-Type: application/json" -H "Idempotency-Key: deal-123-telegram" \ -d '{"campaign_id": 123, "subid": "telegram"}' ``` JavaScript: ```javascript // Node 18+: post new deals to a Discord channel webhook every run (cron it) import { existsSync, readFileSync, writeFileSync } from "node:fs"; const SEEN = "seen-deals.json"; const seen = new Set(existsSync(SEEN) ? JSON.parse(readFileSync(SEEN, "utf8")) : []); const { results } = await (await fetch("https://partners.clickwise.net/api/v1/deals/?lang=en&page_size=50")).json(); for (const d of results.reverse()) { if (seen.has(d.id) || (d.valid_until && new Date(d.valid_until) < new Date())) continue; const off = d.discount_pct ? `${d.discount_pct}% off` : d.discount_amount ? `${d.discount_amount} ${d.discount_currency} off` : ""; const code = d.code ? ` · code **${d.code}**` : ""; await fetch(process.env.DISCORD_WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content: `**${d.merchant_name}**: ${d.title} ${off}${code}\nhttps://partners.clickwise.net${d.url}` }), }); seen.add(d.id); } writeFileSync(SEEN, JSON.stringify([...seen])); ``` Python: ```python # pip install requests. Post new deals to a Telegram channel (cron it) import json import os from datetime import datetime, timezone from pathlib import Path import requests SEEN = Path("seen-deals.json") seen = set(json.loads(SEEN.read_text())) if SEEN.exists() else set() deals = requests.get("https://partners.clickwise.net/api/v1/deals/", params={"lang": "en", "page_size": 50}, timeout=20).json()["results"] now = datetime.now(timezone.utc) for d in reversed(deals): if d["id"] in seen or (d["valid_until"] and datetime.fromisoformat(d["valid_until"]) < now): continue off = f'{d["discount_pct"]}% off' if d["discount_pct"] else "" code = f' · code {d["code"]}' if d["code"] else "" text = f'{d["merchant_name"]}: {d["title"]} {off}{code}\nhttps://partners.clickwise.net{d["url"]}' requests.post(f'https://api.telegram.org/bot{os.environ["TELEGRAM_TOKEN"]}/sendMessage', json={"chat_id": os.environ["TELEGRAM_CHAT_ID"], "text": text}, timeout=20) seen.add(d["id"]) SEEN.write_text(json.dumps(sorted(seen))) ``` ### Affiliate store built with Lovable, Bolt or v0 Paste the prompt into Lovable, Bolt or v0, add your key as a server-side secret named CLICKWISE_API_KEY, and publish. The store calls the API from a server function only. 1. Get a sandbox key (above) and add it as a secret: Lovable → Supabase secret, Bolt → .env on the server, v0/Vercel → Environment Variable. Never in client code. 2. Paste the prompt below. It asks for a server function (Supabase Edge Function, Next.js Route Handler or a server route) that proxies the products API and caches for 10 minutes. 3. Publish, then swap the sandbox key for your own publisher key so purchases pay you. Prompt to paste into Lovable, Bolt or v0: ```text Build a fast, mobile-first affiliate product store called "My Deals Store" that lists products from the Clickwise Publisher Products API. Data source (server side only): - Base URL: https://partners.clickwise.net/api/v1/publisher/ - Send the header X-API-Key with the value of the secret CLICKWISE_API_KEY. Never expose the key in browser code, URLs or logs: call the API only from a server function (Supabase Edge Function, Next.js Route Handler or server route) and have the frontend call that function. - Search: GET products/?q=&country=&limit=24&offset=. q needs 3-200 characters. Response: {products: [{id, gtin, title, price, currency, image, url, merchant: {id, name}, availability, country, updated_at}], count, next_offset}. Use next_offset for "Load more" (null means no more). - Refresh: GET products/lookup/?gtins=&country= (up to 100) returns current price and stock; hide products missing from the answer. - Errors are JSON {"error": {"code", "message", "status"}}. On 429 wait the Retry-After seconds; on 503 retry with backoff. Cache successful answers server side for 10 minutes. Pages and UI: - Home: a search box, a country selector and category chips (headphones, keyboards, monitors, books) that run searches. - Product grid: image, title, merchant name, price formatted with its currency, an availability badge and a "View deal" button. - The button opens product.url exactly as returned (it is the tracked link) in a new tab with rel="sponsored nofollow noopener". Never rewrite, shorten or strip that URL. - Product detail drawer with the GTIN and "last updated" time. - Footer on every page: "We earn a commission from purchases made through links on this site, at no extra cost to you." - Loading, empty and error states; responsive, accessible, lazy-loaded images. Show only prices the API returns; never invent or store prices. ``` curl: ```bash # Check your key before you build (expects a JSON list of products) curl --get "https://partners.clickwise.net/api/v1/publisher/products/" -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "q=headphones" --data-urlencode "country=GB" --data-urlencode "limit=3" ``` JavaScript: ```javascript // Next.js Route Handler: app/api/products/route.js (v0 / Vercel). The key stays on the server. export const revalidate = 600; export async function GET(request) { const { searchParams } = new URL(request.url); const q = (searchParams.get("q") || "headphones").slice(0, 200); const country = /^[A-Z]{2}$/.test(searchParams.get("country") || "") ? searchParams.get("country") : "GB"; const offset = Number(searchParams.get("offset") || 0); const upstream = await fetch( `https://partners.clickwise.net/api/v1/publisher/products/?q=${encodeURIComponent(q)}&country=${country}&limit=24&offset=${offset}`, { headers: { "X-API-Key": process.env.CLICKWISE_API_KEY }, next: { revalidate: 600 } }, ); return new Response(await upstream.text(), { status: upstream.status, headers: { "Content-Type": "application/json", "Cache-Control": "s-maxage=600" }, }); } ``` Python: ```python # pip install fastapi uvicorn httpx. Minimal proxy for any frontend: uvicorn store:app import os import httpx from fastapi import FastAPI, Query app = FastAPI() BASE = "https://partners.clickwise.net/api/v1/publisher" @app.get("/api/products") async def products(q: str = Query("headphones", min_length=3, max_length=200), country: str = Query("GB", pattern="^[A-Z]{2}$"), offset: int = 0): async with httpx.AsyncClient(timeout=20) as client: r = await client.get(f"{BASE}/products/", headers={"X-API-Key": os.environ["CLICKWISE_API_KEY"]}, params={"q": q, "country": country, "limit": 24, "offset": offset}) return r.json() ``` ## Publisher API v1 reference Base `https://partners.clickwise.net/api/v1/publisher/`. OpenAPI 3.1: https://partners.clickwise.net/api/v1/publisher/openapi.json · Postman: https://partners.clickwise.net/api/v1/publisher/postman.json - `GET /api/v1/publisher/me/` — Who am I: account, access and limits. - `GET /api/v1/publisher/products/` — Search GTIN products with your tracked links. Query: q, brand, category, country, merchant, group, min_price, max_price, currency, limit, offset. - `GET /api/v1/publisher/products/lookup/` — Refresh price and stock by id and/or GTIN (1-100). Query: ids, gtins, country, merchant, limit, offset. - `GET /api/v1/publisher/trending-opportunities/` — Current Trend Radar opportunities for your opted-in account. Query: format, country, channel, limit. - `GET /api/v1/publisher/offers/` — Authorized coupons and deals for one country (pilot). Query: country, limit, page. - `GET /api/v1/publisher/offers.csv` — Authorized coupons and deals for one country, as one CSV file (pilot). Query: country. - `GET /api/v1/publisher/programs/` — Programs you may promote (and, optionally, can apply to). Query: search, include_available, page, page_size. - `GET /api/v1/publisher/programs/joinable/` — Programs open for application. Query: can_apply, search, page, page_size. - `POST /api/v1/publisher/programs/apply/` — Apply to a program (approved or pending review). - `GET /api/v1/publisher/links/` — Your tracking links. Query: subid, campaign_id, page, page_size. - `POST /api/v1/publisher/links/` — Create or return one tracking link. - `POST /api/v1/publisher/links/batch/` — Create up to 50 links; each item reports its own status. - `GET /api/v1/publisher/conversions/` — Your conversions with a sub-ID rollup. Query: date_from, date_to, subid, status, source, page, page_size. - `GET /api/v1/publisher/reports/` — Conversions and commission grouped (time series or dimension). Query: group_by, date_from, date_to, subid, status. - `GET /api/v1/publisher/payment-status/` — Your payment status: balance, next payment, method type. - `GET /api/v1/publisher/clicks/` — Your clicks on Clickwise redirects. Query: sub_id, campaign_id, deeplink_id, include_bots, page, page_size. - `GET /api/v1/publisher/postback/` — Conversion postback (webhook) configuration. - `PUT /api/v1/publisher/postback/` — Not available with an API key: set the postback URL in the portal. - `POST /api/v1/publisher/postback/secret/` — Not available with an API key: rotate the secret in the portal. - `POST /api/v1/publisher/postback/test/` — Send a signed test event. - `GET /api/v1/publisher/postback/deliveries/` — Recent postback delivery attempts. - Ready-made product file (no code): an approved affiliate gets personal CSV and XML download links (signed, valid for 90 days) with GTIN, price, image, availability and the affiliate's own Clickwise /dl/ link in every row. Ask for it from the panel's Product feeds page, or call `GET https://partners.clickwise.net/api/v1/affiliates/feed/?country=XX` with X-API-Key; the response carries the download URLs. Each download returns current prices and availability. Current counts by country: https://partners.clickwise.net/api/v1/public/catalog-stats/ · guide: https://partners.clickwise.net/en/gtin-product-feeds/ Sales from network programs can show as pending validation, with no commission amount until the sale is validated (`conversions/`, `reports/` and the conversion postback). Conventions: every response carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy` and `X-Request-Id`; a 429 adds `Retry-After`, and so does a 503 for a query over its time budget: on 503, retry once after `Retry-After`. Errors have one shape: `{"detail": "...", "error": {"code": "...", "message": "...", "status": 403, "request_id": "..."}}`. POST requests accept `Idempotency-Key` (24 h replay). Products paginate with `limit`/`offset` and `next_offset`; lists with `page`/`page_size`. Changes inside v1 are additive. Sandbox responses carry `X-Clickwise-Sandbox: 1`. Product `url` values are Clickwise tracked links: publish them unchanged. Sandbox links are examples and do not track clicks or earn commission. ## MCP server - Public endpoint (no sign-in): https://partners.clickwise.net/api/v1/mcp - Account endpoint (OAuth 2.1): https://partners.clickwise.net/api/v1/mcp/account — answers 401 with `WWW-Authenticate: Bearer resource_metadata=...` until the client signs the publisher in. - Transport: Streamable HTTP, JSON-RPC 2.0. Protocol versions: 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26. - OAuth metadata: https://partners.clickwise.net/.well-known/oauth-protected-resource/api/v1/mcp/account (RFC 9728) and https://partners.clickwise.net/.well-known/oauth-authorization-server (RFC 8414). Dynamic client registration (RFC 7591, public clients), authorization code with PKCE S256, resource indicators (RFC 8707). Scopes: `affiliate:read` (programs, reports, conversions) and `affiliate:links` (tracked links and product search). `get_product_feed` needs an API key: its signed feed URL outlives an OAuth connection. - Server card (SEP-2127 draft): https://partners.clickwise.net/.well-known/mcp-server-card · manifest: https://partners.clickwise.net/.well-known/mcp.json Tools: - `list_trending_opportunities` (your account: publisher key or OAuth scope affiliate:read): Read current Trend Radar opportunities. With your own affiliate API key, returns your existing placements and tracked links; no links or accounts are created by this tool. Without authentication, returns only public topics, evidence, geography, expiry and merchants. Requires feature flag CW_TREND_FEED_ENABLED. - `search_products` (your account: publisher key or OAuth scope affiliate:links): Search fresh GTIN products in the catalog your affiliate can deliver (freshness, permissions and caps apply). Requires your affiliate API key in X-API-Key or Bearer authorization; a sandbox key (cw_sbx_, free at partners.clickwise.net/developers) also works; sandbox links are examples and do not track clicks or earn commission. Every key gets exactly the REST GET /api/v1/publisher/products/ format (with gtin: /products/lookup/): products with id, gtin, title, price and currency, url (a Clickwise /dl/ tracked link created for your affiliate the first time a product is returned), image, merchant (id, name and its public website domain), availability, country, brand, category, regular_price, description, size, color and group_id (shared by the sizes of one model and colour), plus count, total and total_exact. Search by text, brand or category, or list one market by country alone; min_price and max_price filter by price in each product's own currency (inclusive, no conversion) and currency keeps one currency; with gtin every market carrying that GTIN is searched; offset paginates. Example: text "smartwatch" with country "DE" returns offers with a publisher key or a sandbox key. Add currency "EUR" to keep only euro prices. - `get_product_feed` (your account: publisher key only): Create or refresh a GTIN product feed for your authenticated affiliate and one country, and return its signed URL (json, csv, tsv or xml) with Clickwise /dl/ tracked product links. The same request returns the same URL. Requires your affiliate API key. Current freshness, permissions, assignments and delivery caps apply. - `get_catalog_stats` (public, no key): Public GTIN catalog counts as of generated_at, using the same stored snapshot as /api/v1/public/catalog-stats/. Fixed one-hour validity; snapshot_stale marks older counts, served for up to 7 days. No affiliate data or amounts. - `list_deals` (public, no key): List active Clickwise deals and offers, optionally filtered by language, category, country or merchant. - `get_deal` (public, no key): Fetch the full record for a single deal by its slug. Returns title, description, code, discount, target_url, etc., in the requested language. - `list_news` (public, no key): List Clickwise news announcements about affiliate programs (policy changes, launches, shutdowns, commission updates). - `get_news` (public, no key): Fetch the full record for a single news article by its slug. - `find_affiliate_programs` (public, no key): Use this first when someone without a Clickwise account asks which merchants to promote: it ranks live programs by fit for a described site, app, newsletter or social channel. Does not require signup. For the whole catalogue page by page use list_programs; for the programs your API key is already approved in use list_my_programs. - `request_tracked_links` (public, no key): Use this when you have no Clickwise account yet and want tracked links: it creates a provisional affiliate and returns links for the programs that can issue one to it. Email is optional; the account is claimed and payout details are added later (update_profile). With an API key use create_tracking_links; for ready-made cards and an embed use get_monetization_feed. - `get_monetization_feed` (public, no key): Use this when a publisher without an account wants links ready to render on a site or app: it creates a provisional Clickwise affiliate and tracked links, as request_tracked_links does, and also returns cards, an HTML embed snippet and a JSON feed. Email is optional; the account is claimed later for payout. For plain links use request_tracked_links; with an existing API key use create_tracking_links. - `get_tracked_link_request` (public, no key): Fetch a link request, affiliate RefId, review status, and issued links using its access secret. - `request_merchant_onboarding` (public, no key): Alias of create_program. Create a program for its first payment. An agent calls this at a person's request. Default commission 25%, percent range 10–50%; recurring_months: 0 only. No setup fee; you pay on approved sales. The agent never accepts the Clickwise merchant terms on its own: send terms_accepted with terms_version only after the person has read the merchant terms page and told you they accept them. Otherwise leave it out: the program is not set up until the person accepts them and you send terms_accepted with terms_version, here or later in POST /api/v1/onboarding/merchant/{application_id}/ with the access_secret (required action confirm_terms). - `list_programs` (public, no key): Use this to browse the whole live catalogue page by page, filtered by merchant, category or country, when find_affiliate_programs' ranked top matches are not enough. No key needed; it shows programs anyone can join. For the programs your API key is approved in use list_my_programs. - `update_profile` (public, no key): Update a creator's monetization profile (claim email, display name, website, country, payment method) using the application_id + access_secret returned by request_tracked_links. Overwrites the fields provided. Lets a creator's assistant complete claim/payout details without leaving chat. - `get_click_report` (your account: publisher key or OAuth scope affiliate:read): Your own native Clickwise redirect clicks for an inclusive date range. Returns individual clicks and total, excluding bots by default; upstream network clicks are not included. The REST resource has no grouping. Requires your own Clickwise affiliate API key on the MCP connection (X-API-Key header or Authorization: Bearer); acts only on your account. - `list_my_programs` (your account: publisher key or OAuth scope affiliate:read): Use this when you hold a Clickwise affiliate API key and need the programs that key is approved to promote, with link readiness and your existing link count; set include_available to add the ones you can apply to. For discovery without an account use find_affiliate_programs or list_programs. Requires your own Clickwise affiliate API key on the MCP connection (X-API-Key header or Authorization: Bearer); acts only on your account. - `create_tracking_links` (your account: publisher key or OAuth scope affiliate:links): Use this when you already have a Clickwise affiliate API key and want tracking links for programs you are approved in: one per item (the same program and subid return the same link), optionally deep-linking to a landing_url on the merchant's site. Each item reports its own status; a failed item never blocks the others. Without an account use request_tracked_links. Requires your own Clickwise affiliate API key on the MCP connection (X-API-Key header or Authorization: Bearer); acts only on your account. - `get_performance_report` (your account: publisher key or OAuth scope affiliate:read): Your conversions and commission (what you earn), grouped by day, week, month, subid, campaign or status, for up to 366 days (default: last 30). Daily/weekly/monthly series include empty periods. Requires your own Clickwise affiliate API key on the MCP connection (X-API-Key header or Authorization: Bearer); acts only on your account. - `list_conversions` (your account: publisher key or OAuth scope affiliate:read): Your individual conversions (default: last 90 days) with status, commission, currency and subid, plus a per-subid rollup. Requires your own Clickwise affiliate API key on the MCP connection (X-API-Key header or Authorization: Bearer); acts only on your account. - `get_payment_status` (your account: publisher key or OAuth scope affiliate:read): Use this when an affiliate asks when they get paid: approved commission not yet paid (per currency and in USD), commission still pending review, the next payment as an estimate with its threshold, and the payment method as its type (a bank account shows only its last 4 digits). Read-only; never returns account numbers, PayPal addresses, postal or tax data. Requires your own Clickwise affiliate API key on the MCP connection (X-API-Key header or Authorization: Bearer); acts only on your account. - `apply_to_program` (your account: publisher key only): Ask to join a program (program_id from list_my_programs with include_available, or a campaign_id). Clickwise reviews the fit; the result is approved or pending. Requires your own Clickwise affiliate API key on the MCP connection (X-API-Key header or Authorization: Bearer); acts only on your account. - `list_my_weekly_deals` (your account: publisher key or OAuth scope affiliate:read): Read the weekly deals available to your Clickwise affiliate account. Returns existing deal details only; no tracked links are created. Requires affiliate:read on your OAuth connection or your own affiliate API key. - `check_store` (your account: publisher key or OAuth scope affiliate:read): Check a store domain against the Clickwise catalog. Returns up to five available programs, absent, or a neutral result. Requires an affiliate OAuth connection with affiliate:read. Send a domain only, without a URL or browsing history. - `create_program` (public, no key): Create a program for its first payment. An agent calls this at a person's request. Default commission 25%, percent range 10–50%; recurring_months: 0 only. No setup fee; you pay on approved sales. The agent never accepts the Clickwise merchant terms on its own: send terms_accepted with terms_version only after the person has read the merchant terms page and told you they accept them. Otherwise leave it out: the program is not set up until the person accepts them and you send terms_accepted with terms_version, here or later in POST /api/v1/onboarding/merchant/{application_id}/ with the access_secret (required action confirm_terms). - `get_program_status` (public, no key): Read the application and pending question using application_id and access_secret. - `answer_program_question` (public, no key): Answer the application question. An agent calls this at a person's request. - `get_merchant_credentials` (public, no key): get_merchant_credentials: Returns a merchant API token only if the account has none yet. If one exists, it answers 409 and explains where the account holder can copy it. It never reveals an existing token. - `get_tracking_link` (public, no key): Issue a nonpayable house probe link, integration steps and a prompt for Lovable, Bolt or v0. An agent calls this at a person's request. Merchant Bearer token required. - `get_conversions_key` (public, no key): Reveal a new conversions key once, or rotate with rotate=true. An agent calls this at a person's request. Merchant Bearer token required. - `get_integration_status` (public, no key): Read authenticated test-sale evidence and the last real sale. Merchant Bearer token required. - `get_results` (public, no key): Read native direct program clicks, pending, approved and rejected conversions, commission by currency and billing status. Merchant Bearer token required. ## Public data without a key - Deals and coupon codes: `GET https://partners.clickwise.net/api/v1/deals/?lang=en&page_size=50` (en, es, pt, fr, de, it); RSS https://partners.clickwise.net/api/v1/deals/feed.en.xml - Network news: `GET https://partners.clickwise.net/api/v1/news/?lang=en` - Catalog statistics: `GET https://partners.clickwise.net/api/v1/public/catalog-stats/` - Catalog sample (no tracked links): `GET https://partners.clickwise.net/api/v1/public/catalog-sample/?country=ES&format=csv` - Program directory: https://partners.clickwise.net/en/affiliate-programs/ · product catalog: https://partners.clickwise.net/en/product-catalog/ ## For merchants, apps and SaaS: no setup fee; you pay on approved sales - No setup fee, no fixed fee, no minimums, no exclusivity and no lock-in. You only pay the commission on approved sales. Terms: https://partners.clickwise.net/en/merchant-terms/ - Join: form https://partners.clickwise.net/en/signup_merchant/ · API `POST https://partners.clickwise.net/api/v1/onboarding/merchant/` · MCP `request_merchant_onboarding` · CLI `cw merchant apply`. An onboarding agent reviews what the business sells and its category before approving it. - Tracking from your server: `POST https://partners.clickwise.net/api/v1/conversions/` with header `X-Merchant-Key` to record a sale; for Shopify there is an app and for WooCommerce a plugin. A verified test sale comes before affiliates can see the program. - Stripe and Lemon Squeezy: Connect Stripe or Lemon Squeezy from your merchant dashboard with a signed webhook. One-time payments and the first payment of a subscription are recorded as sales and credited to the referring partner when your checkout passes the Clickwise click ID. A full refund reverses the sale. Test payments never create payable commissions. - Not available yet: mobile app install attribution. - Human pages: https://partners.clickwise.net/en/for-merchants/ · for apps and SaaS: https://partners.clickwise.net/en/for-apps/ ### Agent setup (MCP or REST) An AI agent can set up a merchant program for a person who asks for it, through the REST API or the MCP server. Write tools are called on behalf of that person. MCP endpoint: https://partners.clickwise.net/api/v1/mcp 1. create_program: business name, website, product type, pricing model (one_time or subscription), payment provider, commission proposal (percent or fixed, on the first payment) and cookie days. Returns application_id, access_secret, status, any pending question and required_actions. 2. get_program_status and answer_program_question: follow the review and answer its questions. 3. The person confirms their email from the welcome message. After approval, the account holder copies the merchant API token from the dashboard (Settings, API token for agents) into the agent's secret store. The merchant tools then use Authorization: Bearer with that token. 4. get_tracking_link: the click parameter (cwclk), a test link, a conversions API example, Shopify and WooCommerce steps, and a prompt for Lovable, Bolt or v0. 5. get_conversions_key, then a signed test sale; get_integration_status confirms it arrived with its cwclk. 6. get_results: clicks, pending, approved and rejected conversions, and commission. Some categories are not accepted (adult, weapons, drugs, counterfeit goods, hacking or spyware, fake documents) and some always go to manual review (gambling, lending, speculative crypto, pharmacy or health claims, alcohol, tobacco or vaping, political). ## Discovery - https://partners.clickwise.net/llms.txt · https://partners.clickwise.net/llms-full.txt · https://partners.clickwise.net/sitemap.xml · https://partners.clickwise.net/robots.txt - https://partners.clickwise.net/.well-known/mcp.json · https://partners.clickwise.net/.well-known/mcp-server-card · https://partners.clickwise.net/.well-known/ai-plugin.json - Contact: partnerships@clickwise.net - Talk to Clickwise's AI agent (voice, 24/7; it says it is an AI, and anything agreed is confirmed by email): +1 646 362 1368 (US) or https://partners.clickwise.net/talk ## Rules for AI agents - Publish product and link URLs exactly as returned; never strip or rewrite the tracking. - Mark affiliate links (`rel="sponsored nofollow"`) and disclose them to readers. - A sandbox key is for building and testing; revenue traffic needs the publisher's own key. - Never call write endpoints without an explicit instruction from the human you act for.