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

# Ads

> Create and manage ads (the creative that runs inside an ad group) — chat cards and polls.

An **ad** is a single creative inside an [ad group](/advertisers/api-reference/ad-groups). Every ad belongs to exactly one ad group, and its **creative type must match the ad group's type** — an ad group is either all chat cards or all polls, never a mix.

Two creative types are supported:

* **`chat_card`** — an in-chat sponsored message (title, body, CTA, image).
* **`poll`** — a Thrad-only sponsored poll (question + 2–4 options + click-through URL).

<Note>
  All paths are relative to the base URL `https://api.thrad.ai/v1`. Authenticate every request with `Authorization: Bearer ak_...`. See [Authentication](/advertisers/api-reference/authentication).
</Note>

## The ad object

<ResponseField name="id" type="string">
  The ad's unique identifier (UUID).
</ResponseField>

<ResponseField name="ad_group_id" type="string">
  The UUID of the ad group this ad belongs to.
</ResponseField>

<ResponseField name="name" type="string">
  Human-readable ad name. When omitted on create, it defaults to the creative title (card headline or poll question).
</ResponseField>

<ResponseField name="status" type="string">
  One of `active`, `paused`, or `archived`. Change it through [/activate](#activate-an-ad), [/pause](#pause-an-ad), and [/archive](#archive-an-ad), or by sending `status` in an [update body](#update-an-ad).

  <Note>
    Thrad ads have no separate archived state. Sending `status: archived` (or calling `/archive`) deactivates the ad, so it **reads back as `paused`**. A newly created ad is `active`.
  </Note>
</ResponseField>

<ResponseField name="review_status" type="string">
  Read-only review state inherited from the parent campaign: `approved`, `in_review`, or `rejected`. You cannot set this — it reflects where the campaign sits in Thrad's approval flow.
</ResponseField>

<ResponseField name="creative" type="object">
  The ad creative. Shape depends on `type`.

  <Expandable title="chat_card creative">
    <ResponseField name="type" type="string">
      Always `chat_card`.
    </ResponseField>

    <ResponseField name="title" type="string">
      The card headline (max 30 characters).
    </ResponseField>

    <ResponseField name="body" type="string">
      The card description text.
    </ResponseField>

    <ResponseField name="cta" type="string">
      Call-to-action text. Present only when set.
    </ResponseField>

    <ResponseField name="image_url" type="string">
      Resolved URL of the card image, or `null` when the card has no image.
    </ResponseField>
  </Expandable>

  <Expandable title="poll creative">
    <ResponseField name="type" type="string">
      Always `poll`.
    </ResponseField>

    <ResponseField name="title" type="string">
      The poll question (max 120 characters).
    </ResponseField>

    <ResponseField name="options" type="array">
      The poll answer options (2–4 strings).
    </ResponseField>

    <ResponseField name="target_url" type="string">
      The advertiser landing page users are sent to from the poll.
    </ResponseField>
  </Expandable>
</ResponseField>

## List ads

`GET /v1/ads`

Lists the ads in a single ad group. `ad_group_id` is **required** — there is no all-ads listing.

<ParamField query="ad_group_id" type="string" required>
  The UUID of the ad group whose ads to list.
</ParamField>

<ParamField query="limit" type="integer" default="20">
  Number of ads to return, `1`–`500`.
</ParamField>

<ParamField query="before" type="string">
  Cursor for the previous page — pass a prior response's `first_id`. Mutually exclusive with `after`.
</ParamField>

<ParamField query="after" type="string">
  Cursor for the next page — pass a prior response's `last_id`. Mutually exclusive with `before`.
</ParamField>

<ParamField query="order" type="string" default="desc">
  Sort order by creation time: `asc` or `desc`.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl https://api.thrad.ai/v1/ads?ad_group_id=ag_8f1c2d3e \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY"
  ```

  ```python Python theme={null}
  import os, requests

  resp = requests.get(
      "https://api.thrad.ai/v1/ads",
      headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
      params={"ad_group_id": "ag_8f1c2d3e", "limit": 20},
  )
  print(resp.json())
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "object": "list",
    "data": [
      {
        "id": "ad_4a9b6c1d",
        "ad_group_id": "ag_8f1c2d3e",
        "name": "Premium Running Shoes",
        "status": "active",
        "review_status": "approved",
        "creative": {
          "type": "chat_card",
          "title": "Premium Running Shoes",
          "body": "Lightweight and built for marathon training.",
          "cta": "Shop Now",
          "image_url": "https://cdn.thrad.ai/assets/abc123.jpg"
        }
      }
    ],
    "count": 1,
    "first_id": "ad_4a9b6c1d",
    "last_id": "ad_4a9b6c1d",
    "has_more": false
  }
  ```
</ResponseExample>

## Create an ad

`POST /v1/ads`

Adds an ad to an existing ad group. The creative `type` must match the ad group's type, or the request is rejected with `400 invalid_value` on `creative.type`.

<ParamField body="ad_group_id" type="string" required>
  The UUID of the ad group to add this ad to.
</ParamField>

<ParamField body="name" type="string">
  Ad name. Defaults to the creative title (card headline or poll question) when omitted.
</ParamField>

<ParamField body="auto_generate" type="boolean" default="false">
  **Chat cards only.** When `true`, Thrad's AI generates the card copy, so `creative.title` and `creative.body` become optional. Setting `auto_generate` on a `poll` creative is rejected with `poll_cannot_be_ai`.
</ParamField>

<ParamField body="creative" type="object" required>
  The ad creative. Its `type` must match the ad group.

  <Expandable title="chat_card creative">
    <ParamField body="type" type="string" required>
      Must be `chat_card`.
    </ParamField>

    <ParamField body="title" type="string" required>
      Card headline, max 30 characters. Optional when `auto_generate` is `true`.
    </ParamField>

    <ParamField body="body" type="string">
      Card description text.
    </ParamField>

    <ParamField body="cta" type="string">
      Call-to-action text.
    </ParamField>

    <ParamField body="file_id" type="string">
      An uploaded image reference returned by [`POST /v1/upload`](/advertisers/api-reference/files). Use either `file_id` or `image_url`.
    </ParamField>

    <ParamField body="image_url" type="string">
      A publicly reachable image URL. Thrad downloads and stores it. Use either `file_id` or `image_url`.
    </ParamField>
  </Expandable>

  <Expandable title="poll creative">
    <ParamField body="type" type="string" required>
      Must be `poll`.
    </ParamField>

    <ParamField body="title" type="string" required>
      The poll question, max 120 characters.
    </ParamField>

    <ParamField body="options" type="array" required>
      2–4 answer option strings.
    </ParamField>

    <ParamField body="target_url" type="string" required>
      The advertiser landing page users are sent to from the poll.
    </ParamField>
  </Expandable>
</ParamField>

<Tip>
  Provide a card image with either `file_id` (from [`POST /v1/upload`](/advertisers/api-reference/files)) or `image_url`. Polls do not take an image.
</Tip>

<RequestExample>
  ```bash Card (cURL) theme={null}
  curl https://api.thrad.ai/v1/ads \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "ad_group_id": "ag_8f1c2d3e",
      "name": "Premium Running Shoes",
      "creative": {
        "type": "chat_card",
        "title": "Premium Running Shoes",
        "body": "Lightweight and built for marathon training.",
        "cta": "Shop Now",
        "file_id": "file_7d2a1b9c"
      }
    }'
  ```

  ```bash Card, AI-generated (cURL) theme={null}
  curl https://api.thrad.ai/v1/ads \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "ad_group_id": "ag_8f1c2d3e",
      "auto_generate": true,
      "creative": {
        "type": "chat_card",
        "image_url": "https://cdn.example.com/shoes.jpg"
      }
    }'
  ```

  ```bash Poll (cURL) theme={null}
  curl https://api.thrad.ai/v1/ads \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "ad_group_id": "ag_poll_1a2b",
      "creative": {
        "type": "poll",
        "title": "What kind of playlist are you making?",
        "options": ["Wedding", "Study", "Workout"],
        "target_url": "https://example.com/playlists"
      }
    }'
  ```

  ```python Python theme={null}
  import os, requests

  resp = requests.post(
      "https://api.thrad.ai/v1/ads",
      headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
      json={
          "ad_group_id": "ag_8f1c2d3e",
          "name": "Premium Running Shoes",
          "creative": {
              "type": "chat_card",
              "title": "Premium Running Shoes",
              "body": "Lightweight and built for marathon training.",
              "cta": "Shop Now",
              "file_id": "file_7d2a1b9c",
          },
      },
  )
  print(resp.json())
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "id": "ad_4a9b6c1d",
    "ad_group_id": "ag_8f1c2d3e",
    "name": "Premium Running Shoes",
    "status": "active",
    "review_status": "in_review",
    "creative": {
      "type": "chat_card",
      "title": "Premium Running Shoes",
      "body": "Lightweight and built for marathon training.",
      "cta": "Shop Now",
      "image_url": "https://cdn.thrad.ai/assets/abc123.jpg"
    }
  }
  ```
</ResponseExample>

## Retrieve an ad

`GET /v1/ads/{ad_id}`

<ParamField path="ad_id" type="string" required>
  The UUID of the ad to retrieve.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl https://api.thrad.ai/v1/ads/ad_4a9b6c1d \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "id": "ad_4a9b6c1d",
    "ad_group_id": "ag_8f1c2d3e",
    "name": "Premium Running Shoes",
    "status": "active",
    "review_status": "approved",
    "creative": {
      "type": "chat_card",
      "title": "Premium Running Shoes",
      "body": "Lightweight and built for marathon training.",
      "cta": "Shop Now",
      "image_url": "https://cdn.thrad.ai/assets/abc123.jpg"
    }
  }
  ```
</ResponseExample>

## Update an ad

`POST /v1/ads/{ad_id}`

Patches the ad's `name`, `creative`, and/or `status`. Only the fields you send are changed.

<ParamField path="ad_id" type="string" required>
  The UUID of the ad to update.
</ParamField>

<ParamField body="name" type="string">
  New ad name.
</ParamField>

<ParamField body="status" type="string">
  Optionally set `active`, `paused`, or `archived`. `active` activates the ad; `paused` and `archived` both deactivate it, so it reads back as `paused`. (Unlike campaigns, ads accept `status` in the update body — the dedicated [/activate](#activate-an-ad), [/pause](#pause-an-ad), and [/archive](#archive-an-ad) actions are equivalent shortcuts.)
</ParamField>

<ParamField body="creative" type="object">
  A replacement creative. It must match the ad group's type and follow the same field rules as on create (card `title` ≤ 30; poll `title` ≤ 120 with 2–4 `options` and a `target_url`). For card creatives, send a new `file_id` or `image_url` to swap the image.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl https://api.thrad.ai/v1/ads/ad_4a9b6c1d \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Premium Trail Shoes",
      "creative": {
        "type": "chat_card",
        "title": "Premium Trail Shoes",
        "body": "Grip for any terrain.",
        "cta": "Shop Now",
        "image_url": "https://cdn.example.com/trail.jpg"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "id": "ad_4a9b6c1d",
    "ad_group_id": "ag_8f1c2d3e",
    "name": "Premium Trail Shoes",
    "status": "active",
    "review_status": "approved",
    "creative": {
      "type": "chat_card",
      "title": "Premium Trail Shoes",
      "body": "Grip for any terrain.",
      "cta": "Shop Now",
      "image_url": "https://cdn.thrad.ai/assets/def456.jpg"
    }
  }
  ```
</ResponseExample>

## Activate an ad

`POST /v1/ads/{ad_id}/activate`

Reactivates a paused ad. The parent campaign must be approved.

<ParamField path="ad_id" type="string" required>
  The UUID of the ad to activate.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.thrad.ai/v1/ads/ad_4a9b6c1d/activate \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "id": "ad_4a9b6c1d",
    "ad_group_id": "ag_8f1c2d3e",
    "name": "Premium Running Shoes",
    "status": "active",
    "review_status": "approved",
    "creative": {
      "type": "chat_card",
      "title": "Premium Running Shoes",
      "body": "Lightweight and built for marathon training.",
      "cta": "Shop Now",
      "image_url": "https://cdn.thrad.ai/assets/abc123.jpg"
    }
  }
  ```
</ResponseExample>

## Pause an ad

`POST /v1/ads/{ad_id}/pause`

Deactivates the ad.

<ParamField path="ad_id" type="string" required>
  The UUID of the ad to pause.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.thrad.ai/v1/ads/ad_4a9b6c1d/pause \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "id": "ad_4a9b6c1d",
    "ad_group_id": "ag_8f1c2d3e",
    "name": "Premium Running Shoes",
    "status": "paused",
    "review_status": "approved",
    "creative": {
      "type": "chat_card",
      "title": "Premium Running Shoes",
      "body": "Lightweight and built for marathon training.",
      "cta": "Shop Now",
      "image_url": "https://cdn.thrad.ai/assets/abc123.jpg"
    }
  }
  ```
</ResponseExample>

## Archive an ad

`POST /v1/ads/{ad_id}/archive`

<Note>
  Thrad ads have no distinct archived state, so `/archive` deactivates the ad and it **reads back as `paused`** — the same result as `/pause`. This is a deliberate divergence from OpenAI's surface.
</Note>

<ParamField path="ad_id" type="string" required>
  The UUID of the ad to archive.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.thrad.ai/v1/ads/ad_4a9b6c1d/archive \
    -H "Authorization: Bearer $THRAD_ADS_API_KEY"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "id": "ad_4a9b6c1d",
    "ad_group_id": "ag_8f1c2d3e",
    "name": "Premium Running Shoes",
    "status": "paused",
    "review_status": "approved",
    "creative": {
      "type": "chat_card",
      "title": "Premium Running Shoes",
      "body": "Lightweight and built for marathon training.",
      "cta": "Shop Now",
      "image_url": "https://cdn.thrad.ai/assets/abc123.jpg"
    }
  }
  ```
</ResponseExample>

## Errors

Errors use the bare shape `{ "error": { "message", "type", "param", "code" } }`.

| Code                  | HTTP | When                                                                                                                                                    |
| --------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `required`            | 400  | A required field is missing (e.g. card `title` when not auto-generated, poll `title` / `target_url`).                                                   |
| `invalid_value`       | 400  | `creative.type` doesn't match the ad group's type, a missing or non-object `creative`, an unknown `creative.type`, or poll `options` not a list of 2–4. |
| `string_too_long`     | 400  | Card `title` over 30 characters, or poll `title` over 120 characters.                                                                                   |
| `poll_cannot_be_ai`   | 400  | `auto_generate` set on a `poll` creative — AI polls are not supported.                                                                                  |
| `not_found`           | 404  | The ad or ad group doesn't exist or belongs to another organization.                                                                                    |
| `rate_limit_exceeded` | 429  | Over the 1000-requests/hour per-key limit.                                                                                                              |
