> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thrad.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Ad Formats & Rendering

> Available ad formats, how to identify them in the response, and how to render them.

> [!WARNING]
>
> **v1 is deprecated.** New integrations should use [v2](https://docs.thrad.ai/api/api-reference/bid-request-v2.md).

## Requesting an Ad Format

Use the `ad_formats` field in your bid request to specify which formats your integration can render. The server picks the best match and the response includes an `ad_format` field confirming what was returned.

When `ad_formats` is omitted, it defaults to `["sponsored_message"]` for contextual requests and `["sponsored_prompt"]` for opener requests.

Sponsored polls are contextual-only. Request them only with `request_type: "contextual"` and never on opener placements.

## Available Formats

| Format | `ad_formats` value | Status |
| - | - | - |
| Sponsored Message | `card` | Live |
| Sponsored Prompt | `sponsored_prompt` | Coming soon |
| Carousel | `carousel` | Live for contextual placements |
| Sponsored Poll | `poll` | Live for contextual placements |

The legacy spellings `sponsored_message` and `sponsored_poll` remain accepted in `ad_formats`; responses currently echo them in `ad_format` for the card and poll.

Use the `ad_format` field in the response to determine which format was returned and render accordingly.

### Sponsored Message

A native ad card displayed inline in the conversation. Includes a headline, description, brand visuals, and a call-to-action.

The card has two render modes, declared by the server in the `placement` field. When `placement` is missing, treat it as `"text"`.

**`text`** — compact card with the advertiser logo only. The response includes `logo_url`; `image_url` is absent. Forced when `config.image_enabled=false`, when no product photo is available, or when the DSP chooses text mode.

![Sponsored message in text mode (logo only)](https://mintcdn.com/thrads-5feffa3d/e8Y5mGsUZAhzRGKT/images/sponsored_message.png?fit=max\&auto=format\&n=e8Y5mGsUZAhzRGKT\&q=85\&s=06e91ef60676572f1453888150954f7f)

**`image`** — full card with a hero product photo. The response includes `image_url`; `logo_url` may also be present alongside.

![Sponsored message in image mode (full product photo)](https://mintcdn.com/thrads-5feffa3d/EdsOOrHNlfc3Ho7T/images/sponsored_message_2.png?fit=max\&auto=format\&n=EdsOOrHNlfc3Ho7T\&q=85\&s=16770810a204a7c61aa938d0dc41322b)

**Response fields:** `ad_format`, `advertiser`, `domain`, `headline`, `description`, `cta_text`, `url`, `placement`, `logo_url`, `image_url`, `restricted_click_area`, `view_url`

```json theme={null}
{
  "ad_format": "sponsored_message",
  "price": 5.50,
  "advertiser": "Nike",
  "domain": "nike.com",
  "headline": "Premium Running Shoes",
  "description": "Perfect for marathon training. Lightweight and comfortable.",
  "cta_text": "Shop Now",
  "url": "https://ssp.thrads.ai/api/v1/tracking/redirect?token=abc123",
  "placement": "image",
  "logo_url": "https://cdn.example.com/nike-logo.png",
  "image_url": "https://cdn.example.com/nike-shoes.jpg",
  "restricted_click_area": false,
  "view_url": "https://ssp.thrads.ai/api/v1/tracking/view?token=v1",
  "dsp": "thrad_dsp",
  "bidId": "bid_abc123"
}
```

| Field | Description |
| - | - |
| `ad_format` | Always `"sponsored_message"` for this format. |
| `price` | Clearing price (CPM) in dollars — what the publisher earns. |
| `advertiser` | Brand name of the advertiser (e.g. `"Nike"`). In rare cases this may return a domain (e.g. `"nike.com"`) as a fallback when the brand name is not available. Your UI should handle both gracefully. |
| `domain` | Advertiser website domain (e.g. `"nike.com"`). |
| `headline` | Ad headline. Respects `config.max_headline_chars` when set. |
| `description` | Ad body text. May be `null`. |
| `cta_text` | Call-to-action text. May be absent — default to "Learn More" in your UI. |
| `url` | Click tracking URL. Always use this for clicks — it tracks the click and redirects to the landing page. |
| `placement` | Render mode: `"text"` (logo only) or `"image"` (full product photo). When missing, treat as `"text"`. |
| `logo_url` | Advertiser brand/logo URL. **Required** when `placement="text"`. May also be present alongside `image_url` when `placement="image"`. |
| `image_url` | Product/hero photo URL. **Present** when `placement="image"`, **absent** when `placement="text"`. Setting `config.image_enabled=false` forces text mode, so `image_url` will never be returned. |
| `restricted_click_area` | **Experimental, bespoke.** Echoes the `config.restricted_click_area` you sent. When `true`, only the CTA (and logo) should route to `url`; when `false` (default), the whole card is the click target. Cards only — ignore it if your integration doesn't support restricted click areas. |
| `view_url` | Optional impression tracking pixel URL. Fire it when the ad becomes visible to the user (see [Billing & Viewability](https://docs.thrad.ai/api/guides/billing-viewability.md)). |
| `dsp` | Winning DSP identifier. |
| `bidId` | Bid identifier for tracking and debugging. |

### Sponsored Prompt

> [!NOTE]
>
> Coming soon — contact us at [contact@thrad.ai](mailto:contact@thrad.ai) for early access.

A set of sponsored follow-up questions displayed as clickable prompts before or during a conversation.

![Sponsored prompt example](https://mintcdn.com/thrads-5feffa3d/Oy6te9OMcdFzEUX1/images/sponsored_prompt.png?fit=max\&auto=format\&n=Oy6te9OMcdFzEUX1\&q=85\&s=a95658bee0874d638bbc26353a297f54)

Sponsored prompt in a chat interface

**Response fields:** `ad_format`, `advertiser`, `sponsored_prompt`, `url`, `logo_url`

The response returns an array of `bids`, each containing a sponsored prompt:

```json theme={null}
{
  "bids": [
    {
      "ad_format": "sponsored_prompt",
      "bidId": "bid_sp_001",
      "advertiser": "Nike",
      "sponsored_prompt": "What are the best running shoes for marathon training?",
      "url": "https://ssp.thrads.ai/api/v1/tracking/redirect?token=sp1",
      "logo_url": "https://cdn.example.com/nike-logo.png",
      "view_url": "https://ssp.thrads.ai/api/v1/tracking/view?token=v1"
    }
  ]
}
```

### Carousel

A horizontally scrollable strip of 3–5 product cards from a single advertiser. Each card shows a product **title, image, and optional price**, and is its own click target; the strip shares one brand logo and one sponsored label.

![Sponsored carousel example](https://mintcdn.com/thrads-5feffa3d/EdsOOrHNlfc3Ho7T/images/carousel.png?fit=max\&auto=format\&n=EdsOOrHNlfc3Ho7T\&q=85\&s=d3a6f6db72cc17f26fd37633711e37d7)

Sponsored carousel in a chat interface

**Response fields:** `ad_format`, `advertiser`, `domain`, `logo_url`, `view_url`, `items`

```json theme={null}
{
  "ad_format": "carousel",
  "price": 5.50,
  "advertiser": "De'Longhi",
  "domain": "delonghi.com",
  "logo_url": "https://cdn.example.com/delonghi-logo.png",
  "view_url": "https://ssp.thrads.ai/api/v1/tracking/view?token=v1",
  "items": [
    {
      "index": 0,
      "title": "De'Longhi ECP3220 Espresso Machine",
      "image_url": "https://cdn.example.com/ecp3220.jpg",
      "price": "119.95",
      "currency": "USD",
      "click_url": "https://ssp.thrads.ai/api/v1/tracking/redirect?token=c0"
    },
    {
      "index": 1,
      "title": "De'Longhi Dedica Grinder",
      "image_url": "https://cdn.example.com/dedica.jpg",
      "price": "89.00",
      "currency": "USD",
      "click_url": "https://ssp.thrads.ai/api/v1/tracking/redirect?token=c1"
    },
    {
      "index": 2,
      "title": "De'Longhi Milk Frother",
      "image_url": "https://cdn.example.com/frother.jpg",
      "price": "49.95",
      "currency": "USD",
      "click_url": "https://ssp.thrads.ai/api/v1/tracking/redirect?token=c2"
    }
  ],
  "dsp": "thrad_dsp",
  "bidId": "bid_car_abc123"
}
```

| Field | Description |
| - | - |
| `ad_format` | Always `"carousel"` for this format. |
| `price` | Clearing price (CPM) in dollars — what the publisher earns. |
| `advertiser` | Brand name of the advertiser. May fall back to the domain. |
| `domain` | Advertiser website domain. |
| `logo_url` | Shared brand logo for the strip's sponsored header. |
| `view_url` | Impression tracking pixel URL. Fire it **once** when the carousel becomes visible — one impression for the whole strip, regardless of item count. |
| `items` | The product cards, 3–5 entries, in serving order. |
| `items[].index` | Stable item position (0-based), echoed in click analytics. |
| `items[].title` | Product name to display on the card. |
| `items[].image_url` | Product image URL. |
| `items[].price` | Optional display price (e.g. `"119.95"`). Always paired with `currency`; both absent when the product has no price — omit the price line then. |
| `items[].currency` | ISO-4217 currency code for `price`. |
| `items[].click_url` | Click tracking URL for this card. Navigate here on click — it redirects to the product page. Do not attach your own tracking to item URLs. |
| `dsp` | Winning DSP identifier. |
| `bidId` | Bid identifier for tracking and debugging. |

**Rendering rules:**

* Render items left-to-right in the order given. You may truncate trailing items if space demands; never reorder.
* Items are always currently-servable products — render what you receive without caching across requests.

### Sponsored Poll

An interactive poll sponsored by an advertiser. Sponsored polls are **contextual-only** and appear in the same mid-conversation inventory path as sponsored messages. They are not opener or pre-chat inventory.

![Sponsored poll example](https://mintcdn.com/thrads-5feffa3d/EdsOOrHNlfc3Ho7T/images/sponsored_poll.png?fit=max\&auto=format\&n=EdsOOrHNlfc3Ho7T\&q=85\&s=b129b5b199cc47e3fbdc4542e59aa40b)

Sponsored poll in a chat interface

Request poll inventory by sending `ad_formats` on a contextual bid request:

```json Poll-only contextual request theme={null}
{
  "request_type": "contextual",
  "ad_formats": ["sponsored_poll"]
}
```

```json Mixed contextual request theme={null}
{
  "request_type": "contextual",
  "ad_formats": ["sponsored_message", "sponsored_poll"]
}
```

When you request both `sponsored_message` and `sponsored_poll`, Thrad first selects the winning campaign through the normal contextual auction. After a campaign wins, campaign delivery settings determine whether the returned creative is a message or poll.

**Response fields:** `ad_format`, `price`, `sponsored_poll_id`, `advertiser`, `domain`, `logo_url`, `question`, `options`, `vote_url`, `results_url`, `cta_text`, `cta_url`, `redirect_after_vote`, `results`, `dsp`, `bidId`

```json theme={null}
{
  "ad_format": "sponsored_poll",
  "price": 5.50,
  "sponsored_poll_id": 123,
  "advertiser": "Warner Music Group",
  "domain": "warnermusic.com",
  "logo_url": "https://img.logokit.com/warnermusic.com?token=pk_xxx",
  "question": "What kind of playlist are you making?",
  "options": [
    { "id": "0", "text": "Wedding" },
    { "id": "1", "text": "Study" },
    { "id": "2", "text": "Workout" }
  ],
  "vote_url": "https://ssp.thrads.ai/api/v1/tracking/sponsored-polls/vote?token=vote123",
  "results_url": "https://ssp.thrads.ai/api/v1/tracking/sponsored-polls/results?token=results123",
  "cta_text": "Learn more",
  "cta_url": "https://ssp.thrads.ai/api/v1/tracking/sponsored-polls/cta?token=cta123&view_url=https%3A%2F%2Fssp.thrads.ai%2Fapi%2Fv1%2Ftracking%2Fview%3Ftoken%3Dv1",
  "redirect_after_vote": true,
  "results": {
    "total_votes": 0,
    "options": [
      { "id": "0", "text": "Wedding", "votes": 0, "percentage": 0 },
      { "id": "1", "text": "Study", "votes": 0, "percentage": 0 },
      { "id": "2", "text": "Workout", "votes": 0, "percentage": 0 }
    ]
  },
  "dsp": "thrad_dsp",
  "bidId": "bid_poll_abc123"
}
```

| Field | Description |
| - | - |
| `ad_format` | Always `"sponsored_poll"` for this format. |
| `sponsored_poll_id` | Poll identifier. |
| `advertiser` | Brand name of the advertiser. |
| `domain` | Advertiser website domain. |
| `logo_url` | Advertiser logo URL, derived from `domain`. May be `null` if no domain could be resolved. |
| `question` | Poll question text. |
| `options` | Answer options. Each option has `id` and `text`; submit the selected `id` when voting. |
| `vote_url` | Signed vote URL. Option buttons submit to this URL; they do not use normal click tracking. |
| `results_url` | Signed results URL. Fetch it to refresh aggregate results without recording a vote. |
| `cta_text` | Post-vote advertiser CTA label (currently `"Learn more"`). Present **only** when the poll has a click-through. |
| `cta_url` | Post-vote advertiser click-through URL — tracks the click and redirects to the advertiser. Present **only** when the poll has a click-through. The impression pixel is embedded here as a `view_url` query parameter (see note below). |
| `redirect_after_vote` | **Experimental, bespoke.** Echoes the `config.redirect_after_vote` you sent. When `true`, send the user to the advertiser (`cta_url`) after they vote; when `false` (default), stay inline and show the results bar. Polls only — ignore it if your integration doesn't support post-vote redirects. |
| `results` | Aggregate results object with `total_votes` and per-option `votes`/`percentage`, embedded at bid time so you can render the result bar immediately. |
| `price` | Clearing price (CPM) in dollars — what the publisher earns. |
| `dsp` | Winning DSP identifier. |
| `bidId` | Bid identifier for tracking and debugging. |

> [!NOTE]
>
> Sponsored polls do **not** return a standalone `view_url` field. The impression
> pixel is embedded inside `cta_url` as a `view_url` query parameter (alongside
> `thrad_view_url`) — parse it out and fire it when the poll becomes visible. When
> a poll has no click-through, `cta_text`/`cta_url` are omitted and `view_url` is
> returned as a top-level field instead. See [Billing & Viewability](https://docs.thrad.ai/api/guides/billing-viewability.md).

### Poll Rendering

Render poll options as buttons or equivalent controls. Do not wrap the whole poll card in an advertiser click link; option clicks must call the signed `vote_url`.

```javascript theme={null}
function renderSponsoredPoll(poll) {
  const container = document.createElement("div");
  container.className = "thrad-sponsored-poll";
  container.innerHTML = `
    <div class="thrad-sponsored-label">Sponsored · ${poll.advertiser || "Advertiser"}</div>
    <h3>${poll.question}</h3>
    <div class="poll-options"></div>
    <div class="poll-results" hidden></div>
    <a class="poll-cta" target="_blank" rel="noopener" hidden>
      ${poll.cta_text || "Learn More"}
    </a>
  `;

  const optionsEl = container.querySelector(".poll-options");
  const resultsEl = container.querySelector(".poll-results");
  const ctaEl = container.querySelector(".poll-cta");

  for (const option of poll.options) {
    const button = document.createElement("button");
    button.type = "button";
    button.textContent = option.text;
    button.addEventListener("click", async () => {
      const results = await submitPollVote(poll.vote_url, option.id);
      renderPollResults(resultsEl, results);
      optionsEl.hidden = true;
      resultsEl.hidden = false;

      if (poll.cta_url) {
        ctaEl.href = poll.cta_url;
        ctaEl.hidden = false;
      }
    });
    optionsEl.appendChild(button);
  }

  return container;
}

async function submitPollVote(voteUrl, optionId) {
  const response = await fetch(voteUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ option_id: optionId })
  });

  if (!response.ok) {
    throw new Error("Poll vote failed");
  }

  // Vote responses are wrapped in { status, data } — return the data payload.
  const payload = await response.json();
  return payload.data;
}

function renderPollResults(resultsEl, results) {
  resultsEl.replaceChildren(
    ...results.options.map((option) => {
      const row = document.createElement("div");
      row.textContent = `${option.text}: ${option.percentage}%`;
      return row;
    })
  );
}
```

See [Sponsored Poll Tracking](https://docs.thrad.ai/api/api-reference/sponsored-poll-tracking.md) for vote, duplicate vote, and results endpoint behavior.

## Response Handling

### No Bid Response

```json theme={null}
{
  "requestId": "api_req_124",
  "timestamp": "2025-11-24T21:51:52.240297Z",
  "totalTime": 0.095,
  "status": "success",
  "message": "No bids",
  "data": {
    "bid": null
  },
  "error": null
}
```

> [!NOTE]
>
> A `200 OK` response with `"bid": null` is **not an error**. It means the auction ran successfully but no DSP submitted a winning bid. This is normal behavior and should be handled gracefully in your UI.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.