> ## 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.

# Advertiser API Overview

> Programmatically create and manage Thrad ad campaigns and read combined Thrad + ChatGPT performance insights over a single REST surface.

The Thrad Advertiser API lets you build and run ad campaigns end-to-end from your own code — create the campaign tree, launch it, manage its lifecycle, and read back performance — without touching the Thrad Platform dashboard. It mirrors the shape of [OpenAI's Ads API](https://developers.openai.com/ads) (bare list/error objects, micros money, Unix-integer timestamps) so an existing OpenAI Ads integration ports with minimal changes.

Insights numbers combine **Thrad** and **ChatGPT (OpenAI Ads)** supply by default, so a single read gives you the full picture across both surfaces.

## Base URL

```
https://api.thrad.ai/v1
```

Every endpoint path below is relative to this base URL. Paths use OpenAI's exact spelling: underscores, public UUID ids, and **no trailing slash**.

## Resource hierarchy

Resources nest four levels deep. A campaign owns its ad groups, and each ad group owns its ads. The ad account is the top scope — it is the advertiser organization itself.

```
Ad Account
└── Campaign
    └── Ad Group
        └── Ad
```

* **Ad Account** — the org's default ad account. Read-only; it is the root scope for account-level insights.
* **Campaign** — name, budget, schedule, and targeting. Created as one nested call (campaign + `ad_groups[]`, each with `ads[]`) that auto-launches on submit.
* **Ad Group** — `context_hints` and a `bidding_config.max_bid_micros`. Add ad groups to an existing campaign.
* **Ad** — the creative (`chat_card` or `poll`), with title, body, target URL, and image.

## Two surfaces

The API is split into two surfaces that share authentication but differ in shape and purpose.

<CardGroup cols={2}>
  <Card title="Resources" icon="square-pen">
    Write surface. CRUD over campaigns, ad groups, ads, and the ad account, plus `/activate` `/pause` `/archive` lifecycle actions and `/upload` for creative images. Returns **bare** list/object shapes. Resource money is in **integer micros**.
  </Card>

  <Card title="Insights" icon="chart-line">
    Read-only analytics across the four resource levels. Combines Thrad + ChatGPT supply by default. Returns a **bare list** of metric rows where `spend`, `cpc`, and `cpm` are **dollar floats** and `ctr` is a fraction.
  </Card>
</CardGroup>

## Endpoints at a glance

| Resource   | Operation                  | Method & path                                           |
| ---------- | -------------------------- | ------------------------------------------------------- |
| Ad account | Get                        | `GET /ad_account`                                       |
| Upload     | Create image               | `POST /upload`                                          |
| Campaign   | List                       | `GET /campaigns`                                        |
| Campaign   | Create                     | `POST /campaigns`                                       |
| Campaign   | Get                        | `GET /campaigns/{id}`                                   |
| Campaign   | Update                     | `POST /campaigns/{id}`                                  |
| Campaign   | Activate / Pause / Archive | `POST /campaigns/{id}/activate` · `/pause` · `/archive` |
| Ad group   | List                       | `GET /ad_groups?campaign_id=`                           |
| Ad group   | Create                     | `POST /ad_groups`                                       |
| Ad group   | Get                        | `GET /ad_groups/{id}`                                   |
| Ad group   | Update                     | `POST /ad_groups/{id}`                                  |
| Ad group   | Activate / Pause / Archive | `POST /ad_groups/{id}/activate` · `/pause` · `/archive` |
| Ad         | List                       | `GET /ads?ad_group_id=`                                 |
| Ad         | Create                     | `POST /ads`                                             |
| Ad         | Get                        | `GET /ads/{id}`                                         |
| Ad         | Update                     | `POST /ads/{id}`                                        |
| Ad         | Activate / Pause / Archive | `POST /ads/{id}/activate` · `/pause` · `/archive`       |
| Insights   | Ad account                 | `GET /ad_account/insights`                              |
| Insights   | Campaign                   | `GET /campaigns/{id}/insights`                          |
| Insights   | Ad group                   | `GET /ad_groups/{id}/insights`                          |
| Insights   | Ad                         | `GET /ads/{id}/insights`                                |

## Conventions

### Authentication

Every request is authenticated with an org-bound Bearer key on the `Authorization` header.

<ParamField header="Authorization" type="string" required>
  `Bearer ak_...`. There is **one active key per organization**. Create the key in the Thrad Platform dashboard under **Settings → API keys**. Store it as the environment variable `THRAD_ADS_API_KEY` and never commit it to source control.
</ParamField>

```bash theme={null}
curl https://api.thrad.ai/v1/ad_account \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
```

A missing or invalid key is rejected with a uniform `401` — see [Authentication](/advertisers/api-reference/authentication).

### Response shapes

Resource and insights responses use **bare** OpenAI-style shapes, **not** the platform `{ success, data, error, meta }` envelope.

A **list** response wraps its rows and carries `"object": "list"`. Single-object responses (a campaign, ad group, ad, ad account, or upload) are returned **bare** — they do **not** include an `object` field.

```json theme={null}
{
  "object": "list",
  "data": [ ... ],
  "count": 1,
  "first_id": "...",
  "last_id": "...",
  "has_more": false
}
```

An error response carries a single `error` object:

```json theme={null}
{
  "error": {
    "message": "...",
    "type": "invalid_request_error",
    "param": "time_granularity",
    "code": "invalid_value"
  }
}
```

`type` is one of `invalid_request_error`, `authentication_error`, `rate_limit_error`, or `server_error`.

### Money — two scales

<Warning>
  Money is on **two different scales**. State the unit every time and never mix them.
</Warning>

* **Resource bodies use integer micros**, where `$1 = 1,000,000`. `budget.lifetime_spend_limit_micros` has a minimum of `990000` (`$0.99`) and **no upper cap**. `bidding_config.max_bid_micros` ranges `1`–`100000000` (`$0.000001`–`$100`).
* **Insights responses use dollar floats.** `spend`, `cpc`, and `cpm` are already divided into dollars (e.g. `0.70` means \$0.70), and `ctr` is a fraction (e.g. `0.10` means 10%) — these are **not** micros.

### Timestamps

All `/v1/` timestamps — `start_time`, `end_time`, `created_at`, `updated_at` — are **Unix seconds** as integers. (The dashboard-side key-management endpoints under `/api/organizations/{org}/api-keys/` are JWT/envelope endpoints and instead return ISO 8601 string timestamps.)

### Status and review

Each campaign, ad group, and ad exposes a writable `status` and a read-only `review_status`.

| Field                       | Values                                |
| --------------------------- | ------------------------------------- |
| `status`                    | `active` · `paused` · `archived`      |
| `review_status` (read-only) | `approved` · `in_review` · `rejected` |

Status changes go through the `/activate`, `/pause`, and `/archive` sub-actions. **For campaigns**, sending `status` in an update body is rejected with `400 use_status_action`.

<Note>
  **Ad groups and ads accept `status` in the update body.** Sending `status: active | paused | archived` on an ad-group or ad update is accepted: `active` keeps the child active, while `paused` or `archived` deactivate it. Thrad children have no separate archived state, so a deactivated child **reads back as `paused`** — the same result as the `/pause` or `/archive` sub-actions. This is a deliberate divergence from OpenAI.
</Note>

### Pagination

List endpoints are cursor-paginated. The `limit` range depends on the surface: **resource** list endpoints (campaigns, ad groups, ads) accept `1`–`500`, while **insights** list endpoints accept `1`–`2000`. Both default to `20`.

<ParamField query="limit" type="integer" default="20">
  Number of items per page. Resource lists: `1`–`500`. Insights lists: `1`–`2000`.
</ParamField>

<ParamField query="before" type="string">
  Return items before this cursor. Echo a prior response's `first_id`. Mutually exclusive with `after`.
</ParamField>

<ParamField query="after" type="string">
  Return items after this cursor. Echo a prior response's `last_id`. Mutually exclusive with `before`.
</ParamField>

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

### Rate limits

The API is throttled to **1000 requests per hour per key**. The charging `POST /v1/campaigns` create call is additionally limited to **60 per hour**.

### Idempotency

`POST /v1/campaigns` accepts an `Idempotency-Key` header so a retried create never charges twice.

<ParamField header="Idempotency-Key" type="string">
  A client-generated key. The same key replays the original response indefinitely — there is **no expiry window**. The same key with a **different** body returns `409 idempotency_key_reused`.
</ParamField>

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/advertisers/quickstart">
    Mint a key and create, launch, and read insights for your first campaign with cURL.
  </Card>

  <Card title="Authentication" icon="key-round" href="/advertisers/api-reference/authentication">
    How the Bearer key works, where to create it, and how to rotate or revoke it.
  </Card>
</CardGroup>
