Adfacten Developers

AI Recommendations: sponsored product cards in chat assistants

Your AI chat app, bot or agent sends the user's question to Adfacten, and we return up to three related products from advertisers' product catalogs as cards labelled “Sponsored”. When the user clicks a card and buys (or signs up), you earn the campaign's normal commission.

Rules

These rules are part of the publisher terms; assistants that break them are suspended.

  1. The label is mandatory. Always show disclosure.label (“Sponsorlu” / “Sponsored”) and disclosure.text next to the cards. Do not hide, shrink or fade them. The JavaScript package draws a label that cannot be removed.
  2. Separate from the answer. Do not blend cards into the model's answer or present them as the model's own advice; show them as a separate block below it. Each card's why line explains why it is shown.
  3. No children. Child-directed assistants cannot be added, and an assistant later marked as child-directed gets no cards.
  4. No sensitive topics. For questions about health, mental health, sexuality, gambling, debt distress, politics, religion, weapons/drugs, self-harm or minors the API returns no cards (204). You are expected not to ask on these topics either.
  5. Links as they are. Open url unchanged; do not shorten it, open it without a user click or fetch it with bots. This link records the click and the sale.

Quick start

  1. In the panel go to AI assistants → Add assistant: name, type (web chat, mobile app, messaging bot, AI agent), your website and whether it is directed at children.
  2. On the assistant page, Add placement for each place cards appear (plc_…), with the label language and the maximum number of cards (1–3).
  3. Copy the public key (rpk_…) and the placement ID. Type a question in the Live preview box to see cards right away.
  4. Add the code below. New assistants are in test mode: you get sample product cards, links go to adfacten.com and nothing is recorded.
  5. Once the network team approves your assistant, turn test mode off and real cards start.

In the browser (JavaScript)

<div id="adfacten-cards"></div>
<script src="https://cdn.adfacten.com/js/chat-1.0.0.js"></script>
<script>
  // Call it while you send the user's message to your model.
  AdfactenChat.recommend({ key: "rpk_…", placement: "plc_…", query: userMessage })
    .then((result) => AdfactenChat.render(document.getElementById("adfacten-cards"), result));
</script>

For browser calls, add your site to Allowed origins on the assistant page (e.g. https://www.yoursite.com); the key then stops working on other sites.

From your server

curl -X POST https://api.adfacten.com/v1/recommendations \
  -H "X-Adfacten-Key: rpk_…" \
  -H "Content-Type: application/json" \
  -d '{"placement": "plc_…", "query": "best coffee machine under 3000 TL", "lang": "en", "limit": 3}'
import httpx

r = httpx.post(
    "https://api.adfacten.com/v1/recommendations",
    headers={"X-Adfacten-Key": "rpk_…"},
    json={"placement": "plc_…", "query": user_message, "lang": "en", "limit": 3},
    timeout=0.4,
)
cards = r.json() if r.status_code == 200 else None  # 204: no cards, carry on

On the server you can also use an API key with the recommendations:read scope instead of rpk_: Authorization: Bearer af_live_….

API reference

POST https://api.adfacten.com/v1/recommendations

Header
X-Adfacten-Key: rpk_… The assistant's public key (safe in browsers)
Authorization: Bearer af_live_… Or an API key with recommendations:read (server only)

Request body:

Field Type
placement string Placement ID (plc_…), required
query string The user's latest message or the shopping intent you extracted, up to 500 characters, required
lang string tr (default) or en; language of the label and why
country string ISO country code, e.g. TR
currency string E.g. TRY
price_max number Upper price limit in currency
limit number 1–3; never more than the placement's maximum
session string Optional opaque conversation ID; we keep only a one-way hash
agent_checkout boolean true when your agent completes the purchase itself (see below)
test boolean Ask for test cards even for an approved assistant

Response 200:

{
  "request_id": "rec_…",
  "test": false,
  "disclosure": { "label": "Sponsored", "text": "These are ads; if you buy, Adfacten and the publisher earn a commission." },
  "items": [
    {
      "id": "prd_…",
      "title": "Filter coffee machine 1.2 L",
      "brand": "Brand",
      "merchant": "Shop name",
      "price": { "amount": "1299.90", "currency": "TRY", "as_of": "2026-11-09T08:00:00Z" },
      "image_url": "https://…",
      "url": "https://trk.adfacten.com/c/…",
      "shown_url": "https://trk.adfacten.com/i?…&e=rec_shown",
      "why": "Related to your question: coffee machine; the advertiser pays a commission"
    }
  ]
}

Errors use the RFC 9457 format:

HTTP code
401 unknown_key Invalid or renewed key
403 placement_mismatch The placement does not belong to this assistant or was deleted (a paused placement returns 204)
403 assistant_suspended The assistant is suspended or rejected
403 origin_not_allowed The browser origin is not in the allowed origins
422 invalid_request Missing or invalid field
429 rate_limited Per-key or per-IP rate limit; wait for Retry-After

JavaScript package: @adfacten/chat

No dependencies, under 8 KB gzipped. Use it from the CDN (chat-1.0.0.js, global AdfactenChat) or npm.

Function
recommend(options) Makes the request and resolves to the cards or null. Never throws. Default timeout 400 ms (timeout). Use onError to see failures.
render(element, result, { theme, lang, onClick }) Draws the cards. The label is on top and is put back if removed. Links open in a new tab with rel="sponsored noopener". The view beacon is sent for you. theme: light, dark, auto.
toPlainText(result) Plain text for messaging bots: one Sponsored · Product – price link line per card, then the disclosure sentence.

For React:

import { AdfactenRecommendations } from "@adfacten/chat/react";

<AdfactenRecommendations request={{ key: "rpk_…", placement: "plc_…", query: userMessage }} theme="auto" />

Colors can be adjusted on the container with --adf-bg, --adf-fg, --adf-muted, --adf-border, --adf-accent, --adf-radius and --adf-font; the label keeps fixed colors.

MCP tool

Through the MCP server, agents can call recommend_products:

Parameter
query Required
placement Optional; defaults to the first placement of your first assistant
lang, country, price_max, limit Same as the API

The tool returns the same cards as the API. When the agent shows them to the user, it must show the label and the disclosure sentence too. The key needs the recommendations:read scope.

The search_products tool now returns tracking links instead of raw shop URLs.

Agent checkout

If your agent completes the purchase for the user (without opening a browser), send "agent_checkout": true. Each card then has an attribution_token and expires_at (30 minutes). Pass the token to the shop's checkout (a referral/coupon field or a parameter the shop supports); the advertiser sends it back when reporting the conversion and the sale is credited to you. Details: Attribution tokens.

Privacy

FAQ

Why do I get no cards? The question may not be about shopping, the topic may be sensitive, no product may match, or time may have run out; all return 204. Try the live preview in the panel.

Do test cards earn money? No. Test cards are sample products; no clicks or sales are recorded.

Do I earn from views or clicks? No. You earn only when a sale or sign-up from a card is approved. Views and clicks appear in reports (CTR, EPC) only.

Can I show the cards with my own design? Yes, call the API directly. You must show the label, the disclosure sentence, the why line and the price time, and call shown_url once when the card is seen.

Can I put cards inside the model's answer? No. Cards must be a separate, labelled block.

My key is being abused. Renew the key on the assistant page and set the allowed origins.