# 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 search products by GTIN, find > programs and deals, create tracked links and read their conversions 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 in 5 minutes 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, 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=sneaker&country=DE&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. ## Credentials | Credential | Works on | Limits | |---|---|---| | Sandbox key `cw_sbx_...` (self-service by email) | Publisher Products API only: `products/` and `products/lookup/`, real catalog data and example tracked links | 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 "sneaker" in Germany curl --get "https://partners.clickwise.net/api/v1/publisher/products/" \ -H "X-API-Key: $CLICKWISE_API_KEY" \ --data-urlencode "q=sneaker" --data-urlencode "country=DE" --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=sneaker" --data-urlencode "country=DE" --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=DE" ``` 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 = "DE") { 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=sneaker&country=DE&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 = "DE") -> 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="sneaker", country="DE", 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" # Real 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=sneaker" --data-urlencode "country=DE" --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 = ["sneaker", "dress"]; const country = "DE"; 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 = ["sneaker", "dress"], "DE" 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 publisher key instead: header X-API-Key on https://partners.clickwise.net/api/v1/mcp. 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()) ``` ### 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 (sneakers, dresses, shirts, jackets) 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=jacket" --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") || "sneaker").slice(0, 200); const country = /^[A-Z]{2}$/.test(searchParams.get("country") || "") ? searchParams.get("country") : "DE"; 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("sneaker", min_length=3, max_length=200), country: str = Query("DE", 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, country, merchant, 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/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: 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/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. Conventions: every response carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy` and `X-Request-Id`; a 429 adds `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. ## 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: - `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 delivery caps apply). Requires your affiliate API key in X-API-Key or Bearer authorization. Returns Clickwise /dl/ tracked links only, created for your affiliate the first time a product is returned. country is required unless gtin is given (then every market carrying that GTIN is searched); offset paginates the currently deliverable subset. - `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 less than six hours. 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): Find live Clickwise merchant programs that fit a publisher's site, app, newsletter or social channel, ranked by fit. Does not require signup. - `request_tracked_links` (public, no key): Create a provisional Clickwise affiliate and return tracked links for the programs that can issue one now. Email is optional; the account is claimed and payout details are added later (update_profile). - `get_monetization_feed` (public, no key): Create a provisional Clickwise affiliate and tracked links, as request_tracked_links does, and return them as renderable cards, an HTML embed snippet and a JSON feed for a publisher's site or app. Email is optional; the account is claimed later for payout. - `get_tracked_link_request` (public, no key): Fetch an instant-link request, affiliate RefId, review status, and issued links using its access secret. - `request_merchant_onboarding` (public, no key): Start a Clickwise merchant (advertiser) onboarding application. Describe the store; an automated evaluator replies with a follow-up question, an approval or a decline. Returns the status and an access secret to continue. - `list_programs` (public, no key): Browse and paginate live Clickwise programs, filtered by merchant, category or country. Use it to go beyond find_affiliate_programs' top matches. - `update_profile` (public, no key): Update a creator's instant-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. - `list_my_programs` (your account: publisher key or OAuth scope affiliate:read): Programs you are approved to promote, with link readiness and your existing link count. Set include_available to also list programs you can apply to. 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): Create (or return existing) tracking links for programs you are approved in, one per item, optionally deep-linking to a landing_url on the merchant's site and tagging a subid. Each item reports its own status; a failed item never blocks the others. 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. - `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. ## 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/ ## 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 ## 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.