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

# Campaigns

> Create, list, retrieve, update, and control the lifecycle of advertiser campaigns.

A campaign is the top of the advertiser object tree: it carries the budget, schedule, geo targeting, and pricing model, and it owns one or more ad groups, each of which owns one or more ads. Creating a campaign is a **single nested call** — you send the campaign plus its `ad_groups[]` (each with its `ads[]`) in one request, and Thrad creates the whole tree, charges a deposit, and submits it for review.

All paths on this page are relative to the base URL `https://api.thrad.ai/v1`. Every request must send `Authorization: Bearer ak_...` — see [Authentication](https://docs.thrad.ai/advertisers/api-reference/authentication.md).

> [!NOTE]
>
> Money on campaign resources is expressed in **integer micros**, where `$1 = 1,000,000`. So `budget.amount_micros: 5000000` is `$5.00`. Timestamps are **ISO 8601 strings in UTC** (`2026-04-01T00:00:00Z`), never Unix integers.

## The campaign object

**id** `string`

Unique campaign identifier (UUID).

**name** `string`

Campaign name (3–100 characters).

**description** `string | null`

Free-text description, or `null` if none was set.

**status** `string`

Lifecycle state: `active` | `paused` | `archived`. Change it only through the [`/activate`](#activate-a-campaign), [`/pause`](#pause-a-campaign), and [`/archive`](#archive-a-campaign) sub-actions — sending `status` in a campaign update body is rejected.

**review\_status** `string`

Read-only moderation state: `in_review` | `approved` | `rejected`. A freshly created campaign is `in_review`. See [Review status](#review-status).

**start\_time** `string`

Campaign start, as an ISO 8601 timestamp in UTC. Always present — if you omit `start_time` on create, Thrad sets it to roughly two days in the future (the earliest allowed launch window), so a read response is never `null` here.

**end\_time** `string | null`

Campaign end, as an ISO 8601 timestamp in UTC. `null` means the campaign has no wall-clock stop. `end_time` is **orthogonal to `budget.period`** — all four combinations are legal.

**budget** `object`

The campaign budget, stated as an amount plus the window it applies to.

**budget properties**

**amount\_micros** `integer`

The budget amount, in micros (`$1 = 1,000,000`). It is the quantity only — how it is enforced depends on `period`.

**period** `string`

The window `amount_micros` applies to: `lifetime` | `daily` | `weekly` | `monthly` | `quarterly` | `yearly`. `lifetime` is a cumulative cap; any other value is the window the budget resets on.

**display** `string`

The amount and its period resolved into one human-readable string, e.g. `"$1,000.00 per week"` or `"$50.00 lifetime"`. For display only — never parse it; read `amount_micros` instead.

**pricing\_model** `string`

Bidding basis: `cpm` (cost per thousand impressions) or `cpc` (cost per click).

**archived\_at** `string | null`

ISO 8601 timestamp of Thrad's **reversible** archive — a list-visibility flag with no effect on delivery — or `null`. This is a different concept from the [`/archive` action](#archive-a-campaign), which is the terminal transition to `status: "archived"`. A campaign can read `status: "archived"` while `archived_at` is still `null`, or vice versa. `/v1/` lists always include reversibly-archived campaigns.

**targeting** `object`

**targeting properties**

**locations** `object`

Contains `include`, an array of location objects. Each location carries `id`, `type` (`country`), and `country_code` (ISO 3166-1 alpha-2, e.g. `"US"`).

**created\_at** `string`

Creation time, as an ISO 8601 timestamp in UTC.

**updated\_at** `string`

Last-modified time, as an ISO 8601 timestamp in UTC.

## Budget: amount and period

A Thrad budget is **self-describing** — you always send both the amount and the window it applies to, and the two together determine how spend is enforced.

| `period` | Meaning | Enforcement |
| - | - | - |
| `lifetime` | One cumulative cap for the whole campaign | A hard cap: charges are clipped to the remaining budget and the campaign auto-completes when it is exhausted. |
| `daily` · `weekly` · `monthly` · `quarterly` · `yearly` | The budget resets every window | **No cumulative cap.** Delivery stops when spend reaches the budget within each window, then resumes next window. `end_time` is the only bound on total spend. |

> [!WARNING]
>
> A periodic budget has no lifetime ceiling. A `weekly` campaign with `amount_micros: 50000000` (`$50`) spends up to \$50 **every week**, indefinitely, until you pause it or its `end_time` passes. Set an `end_time` if you need a hard stop.

> [!NOTE]
>
> `budget.period` is **required** on create and is **locked once the campaign launches** — /v1 campaigns launch on create, and the billing schedule locks with them. A later update that changes it returns `400 recurrence_locked_after_launch` on `budget.period`; restating the current period is a no-op.

`end_time` is a wall-clock stop, independent of `budget.period`. A `lifetime` budget with an `end_time` is a bounded one-off (the cap survives the stop); a periodic budget without one is an open-ended subscription.

## Review status

Every campaign created through this API is submitted to Thrad for human review. The two status fields are independent:

* `status` is the lifecycle state you control: `active` | `paused` | `archived`.
* `review_status` is the moderation verdict you do **not** control: `in_review` | `approved` | `rejected`.

A newly created campaign always comes back as `status: "paused"`, `review_status: "in_review"`. It cannot serve or be activated until Thrad approves it. Once approved, `review_status` becomes `approved` and you can [activate](#activate-a-campaign) it. If it is rejected, `review_status` becomes `rejected`.

> [!NOTE]
>
> Approval (the `in_review → approved` transition) is performed by Thrad, not through this API. Until a campaign is approved, [`/activate`](#activate-a-campaign) returns `400 campaign_not_approved`.

***

## List campaigns

Returns a paginated list of campaigns for your organization, newest first by default.

```
GET /v1/campaigns
```

**ad\_account\_id** `string`

Restrict the list to a single ad account (UUID). Omit to list across all ad accounts in your organization.

**limit** `integer` (default: 20)

Number of campaigns to return, `1`–`500`.

**order** `string` (default: desc)

Sort direction by creation time: `asc` or `desc`.

**before** `string`

Cursor for the previous page — pass a prior response's `first_id`. Mutually exclusive with `after`.

**after** `string`

Cursor for the next page — pass a prior response's `last_id`. Mutually exclusive with `before`.

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

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

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

```json 200 - List theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
      "name": "Spring running launch",
      "description": "Marathon season push",
      "status": "active",
      "review_status": "approved",
      "start_time": "2026-04-02T00:00:00.000000Z",
      "end_time": null,
      "budget": {
        "amount_micros": 50000000,
        "period": "lifetime",
        "display": "$50.00 lifetime"
      },
      "pricing_model": "cpm",
      "targeting": {
        "locations": {
          "include": [
            { "id": "US", "type": "country", "country_code": "US" }
          ]
        }
      },
      "archived_at": null,
      "created_at": "2026-03-31T00:00:00.000000Z",
      "updated_at": "2026-04-01T00:00:00.000000Z"
    }
  ],
  "count": 1,
  "first_id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "last_id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "has_more": false
}
```

***

## Create a campaign

Creates a campaign together with its ad groups and ads in a single nested request, then submits the campaign for review.

```
POST /v1/campaigns
```

This one call does several things atomically:

**1. Validate billing**

Your organization must have a **default card** on file. If it does not, the request fails with `400 billing_not_configured` and nothing is created.

**2. Create the tree**

The campaign, its `ad_groups[]`, and each ad group's `ads[]` are created together. Every ad must reference an existing asset by `asset_id` — browse the catalog with [`GET /v1/assets`](https://docs.thrad.ai/advertisers/api-reference/assets).

**3. Charge the deposit**

A **\$10 deposit** is charged off-session on your default card. A card decline returns `402 card_declined` and the campaign is removed (no lingering draft); a provider outage returns `502 payment_provider_error`.

**4. Submit for review**

The campaign is submitted to Thrad. The response comes back with `status: "paused"` and `review_status: "in_review"`.

> [!NOTE]
>
> **The whole tree comes back paused.** The ad groups and ads created by this call also read `status: "paused"` while the campaign awaits review, so nothing can serve before approval. They return to `active` **automatically** when the campaign is approved — do not call `/activate` on them. (Children added later with `POST /v1/ad_groups` or `POST /v1/ads` are created `active`.)

**Idempotency-Key** `string`

A client-generated key (e.g. a UUID) that makes retries safe. The same key replays the first response indefinitely (there is no expiry window), so a retried create never charges twice. Reusing the same key with a **different** body returns `409 idempotency_key_reused`.

**name** `string` *required*

Campaign name, 3–100 characters.

**budget** `object` *required*

**budget properties**

**amount\_micros** `integer` *required*

The budget amount, in micros (`$1 = 1,000,000`). Minimum `990000` (\$0.99). There is no upper cap.

**period** `string` *required*

The window the amount applies to: `lifetime` | `daily` | `weekly` | `monthly` | `quarterly` | `yearly`. **Required** — it is never inferred. See [Budget: amount and period](#budget-amount-and-period).

**ad\_groups** `array` *required*

One or more ad groups, each with at least one ad. All ads within a single ad group must share one creative type (all `card` or all `poll`).

**ad\_group properties**

**name** `string`

Ad group name.

**context\_hints** `array`

Array of strings describing the conversational contexts where this ad group should serve.

**bidding\_config** `object`

**bidding\_config properties**

**max\_bid\_micros** `integer`

Maximum bid, in micros. Range `100`–`100000000` ($0.0001–$100).

**ads** `array` *required*

One or more ads. See [Ads](https://docs.thrad.ai/advertisers/api-reference/ads.md) for the full `creative` schema.

**ad properties**

**asset\_id** `string` *required*

The UUID of an existing [asset](https://docs.thrad.ai/advertisers/api-reference/assets.md). The asset supplies the ad's **image** (card) and **destination URL** (card and poll). `/v1` never creates assets and accepts no inline image.

**name** `string`

Ad name. Defaults to the card headline or poll question if omitted.

**auto\_generate** `boolean` (default: false)

Cards only. When `true`, Thrad writes the headline per impression, so `creative.title` must be **omitted**. Rejected on a poll (`poll_cannot_be_ai`).

**creative** `object` *required*

Copy only. For a `card`: `title` (≤ 30 chars), `body`, optional `cta`. For a `poll`: `title` (the question, ≤ 120 chars) and 2–4 `options[]`.

**description** `string`

Free-text description.

**start\_time** `string`

Campaign start, as an ISO 8601 timestamp with a UTC offset or trailing `Z` (e.g. `2026-04-02T00:00:00Z`). Omit to default to roughly two days in the future (the earliest allowed launch window) — the response then comes back with that computed timestamp, never `null`. A bare wall-clock time (no offset) is rejected with `400 invalid_value`.

**end\_time** `string`

Campaign end, as an ISO 8601 timestamp with a UTC offset or trailing `Z`. Omit for a campaign with no wall-clock stop. This is independent of `budget.period` — see [Budget: amount and period](#budget-amount-and-period).

**targeting** `object`

**targeting properties**

**locations** `object`

Contains `include`, an array of location objects. Each entry needs a `country_code` (ISO 3166-1 alpha-2, e.g. `"US"`) — or an `id` carrying the same code. See [Campaign targeting](https://docs.thrad.ai/advertisers/api-reference/campaign-targeting.md).

**pricing\_model** `string` (default: cpm)

Bidding basis: `cpm` or `cpc`.

**ad\_account\_id** `string`

The ad account to create the campaign under (UUID). Defaults to your organization's default ad account.

```bash cURL theme={null}
curl -X POST https://api.thrad.ai/v1/campaigns \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f9619ff-8b86-d011-b42d-00cf4fc964ff" \
  -d '{
    "name": "Spring running launch",
    "description": "Marathon season push",
    "budget": { "amount_micros": 50000000, "period": "lifetime" },
    "start_time": "2026-04-02T00:00:00Z",
    "pricing_model": "cpm",
    "targeting": {
      "locations": { "include": [ { "country_code": "US" } ] }
    },
    "ad_groups": [
      {
        "name": "Lightweight shoes",
        "context_hints": ["running", "marathon training"],
        "bidding_config": { "max_bid_micros": 8000000 },
        "ads": [
          {
            "name": "Pro Racer card",
            "asset_id": "3d9e7b21-5c4a-4f8e-b1d6-7a2c9e0f4b83",
            "creative": {
              "type": "card",
              "title": "Premium Running Shoes",
              "body": "Lightweight and built for marathon pace.",
              "cta": "Shop now"
            }
          }
        ]
      }
    ]
  }'
```

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

resp = requests.post(
    "https://api.thrad.ai/v1/campaigns",
    headers={
        "Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "name": "Spring running launch",
        "budget": {"amount_micros": 50000000, "period": "lifetime"},
        "start_time": "2026-04-02T00:00:00Z",
        "targeting": {"locations": {"include": [{"country_code": "US"}]}},
        "ad_groups": [
            {
                "name": "Lightweight shoes",
                "context_hints": ["running", "marathon training"],
                "bidding_config": {"max_bid_micros": 8000000},
                "ads": [
                    {
                        "name": "Pro Racer card",
                        "asset_id": "3d9e7b21-5c4a-4f8e-b1d6-7a2c9e0f4b83",
                        "creative": {
                            "type": "card",
                            "title": "Premium Running Shoes",
                            "body": "Lightweight and built for marathon pace.",
                            "cta": "Shop now",
                        },
                    }
                ],
            }
        ],
    },
)
print(resp.json())
```

```json 201 - Created (submitted for review) theme={null}
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "paused",
  "review_status": "in_review",
  "start_time": "2026-04-02T00:00:00.000000Z",
  "end_time": null,
  "budget": {
    "amount_micros": 50000000,
    "period": "lifetime",
    "display": "$50.00 lifetime"
  },
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "archived_at": null,
  "created_at": "2026-03-31T00:00:00.000000Z",
  "updated_at": "2026-03-31T00:00:00.000000Z"
}
```

```json 400 - Billing not configured theme={null}
{
  "error": {
    "message": "Set up a default card in billing settings before creating a campaign.",
    "type": "invalid_request_error",
    "param": null,
    "code": "billing_not_configured"
  }
}
```

```json 402 - Card declined theme={null}
{
  "error": {
    "message": "Your card was declined.",
    "type": "invalid_request_error",
    "param": null,
    "code": "card_declined"
  }
}
```

> [!WARNING]
>
> The charging create endpoint is rate-limited to **60 requests/hour** per key (on top of the global 1000/hour limit). Always send an `Idempotency-Key` so a network retry replays the original result instead of charging again.

***

## Retrieve a campaign

Fetches a single campaign by ID.

```
GET /v1/campaigns/{campaign_id}
```

**campaign\_id** `string` *required*

The campaign UUID. An unknown or cross-organization ID returns `404`.

```bash cURL theme={null}
curl https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
```

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

campaign_id = "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c"
resp = requests.get(
    f"https://api.thrad.ai/v1/campaigns/{campaign_id}",
    headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
)
print(resp.json())
```

```json 200 - Campaign theme={null}
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "active",
  "review_status": "approved",
  "start_time": "2026-04-02T00:00:00.000000Z",
  "end_time": null,
  "budget": {
    "amount_micros": 50000000,
    "period": "lifetime",
    "display": "$50.00 lifetime"
  },
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "archived_at": null,
  "created_at": "2026-03-31T00:00:00.000000Z",
  "updated_at": "2026-04-01T00:00:00.000000Z"
}
```

***

## Update a campaign

Updates the mutable fields of a campaign. Only the fields you send are changed.

```
POST /v1/campaigns/{campaign_id}
```

Updatable fields are `name`, `description`, `budget.amount_micros`, `targeting`, `start_time`, and `end_time`. Every value is re-validated exactly as on create: `budget.amount_micros` must still be `>= 990000`, and any `start_time` / `end_time` must be an ISO 8601 timestamp carrying a UTC offset or trailing `Z`. An out-of-range value or a malformed timestamp returns `400 invalid_value` (never a 500).

Sending `budget.amount_micros` alone keeps the current period — there is no read-before-write. Budget edits also respect a floor rule: an amount cannot be lowered below spend already charged.

> [!WARNING]
>
> You cannot change `status` here. Sending `status` in the body returns `400 use_status_action` — use [`/activate`](#activate-a-campaign), [`/pause`](#pause-a-campaign), or [`/archive`](#archive-a-campaign) instead. `review_status` and `pricing_model` are not updatable either, and `budget.period` is locked once the campaign has launched (`400 recurrence_locked_after_launch`).

**campaign\_id** `string` *required*

The campaign UUID.

**name** `string`

New campaign name, 3–100 characters.

**description** `string`

New description.

**budget** `object`

**budget properties**

**amount\_micros** `integer`

New budget amount, in micros. Minimum `990000` (\$0.99).

**period** `string`

Locked after launch. Restating the current period is a no-op; changing it returns `400 recurrence_locked_after_launch`.

**targeting** `object`

New geo targeting. Same shape as on create — `locations.include[]` with `country_code` per entry.

**start\_time** `string`

New start, as an ISO 8601 timestamp with a UTC offset or trailing `Z`. Send `null` to clear it.

**end\_time** `string`

New end, as an ISO 8601 timestamp with a UTC offset or trailing `Z`. Send `null` to remove the wall-clock stop. This never changes the recurrence shape.

```bash cURL theme={null}
curl -X POST https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring running launch (US + CA)",
    "budget": { "amount_micros": 75000000 },
    "targeting": {
      "locations": { "include": [ { "country_code": "US" }, { "country_code": "CA" } ] }
    }
  }'
```

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

campaign_id = "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c"
resp = requests.post(
    f"https://api.thrad.ai/v1/campaigns/{campaign_id}",
    headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
    json={
        "name": "Spring running launch (US + CA)",
        "budget": {"amount_micros": 75000000},
        "targeting": {
            "locations": {
                "include": [{"country_code": "US"}, {"country_code": "CA"}]
            }
        },
    },
)
print(resp.json())
```

```json 200 - Updated theme={null}
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch (US + CA)",
  "description": "Marathon season push",
  "status": "active",
  "review_status": "approved",
  "start_time": "2026-04-02T00:00:00.000000Z",
  "end_time": null,
  "budget": {
    "amount_micros": 75000000,
    "period": "lifetime",
    "display": "$75.00 lifetime"
  },
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" },
        { "id": "CA", "type": "country", "country_code": "CA" }
      ]
    }
  },
  "archived_at": null,
  "created_at": "2026-03-31T00:00:00.000000Z",
  "updated_at": "2026-04-02T00:02:03.000000Z"
}
```

```json 400 - Status sent in body theme={null}
{
  "error": {
    "message": "Change status via /activate, /pause, or /archive.",
    "type": "invalid_request_error",
    "param": "status",
    "code": "use_status_action"
  }
}
```

***

## Activate a campaign

Resumes a previously paused campaign. The campaign must already be approved.

```
POST /v1/campaigns/{campaign_id}/activate
```

* If the campaign is **paused and approved**, it transitions to `active`.
* If the campaign is already **running**, this is a no-op and returns `200` with the unchanged campaign.
* If the campaign is **not yet approved** (still `in_review`, or rejected), it returns `400 campaign_not_approved`.

**campaign\_id** `string` *required*

The campaign UUID.

```bash cURL theme={null}
curl -X POST https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c/activate \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
```

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

campaign_id = "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c"
resp = requests.post(
    f"https://api.thrad.ai/v1/campaigns/{campaign_id}/activate",
    headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
)
print(resp.json())
```

```json 200 - Activated theme={null}
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "active",
  "review_status": "approved",
  "start_time": "2026-04-02T00:00:00.000000Z",
  "end_time": null,
  "budget": {
    "amount_micros": 50000000,
    "period": "lifetime",
    "display": "$50.00 lifetime"
  },
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "archived_at": null,
  "created_at": "2026-03-31T00:00:00.000000Z",
  "updated_at": "2026-04-02T00:33:20.000000Z"
}
```

```json 400 - Not approved theme={null}
{
  "error": {
    "message": "Only an approved, paused campaign can be activated.",
    "type": "invalid_request_error",
    "param": null,
    "code": "campaign_not_approved"
  }
}
```

***

## Pause a campaign

Pauses a running campaign so it stops serving.

```
POST /v1/campaigns/{campaign_id}/pause
```

Only a **running** (`active`) campaign can be paused. Any other state returns `400 campaign_not_running`.

**campaign\_id** `string` *required*

The campaign UUID.

```bash cURL theme={null}
curl -X POST https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c/pause \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
```

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

campaign_id = "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c"
resp = requests.post(
    f"https://api.thrad.ai/v1/campaigns/{campaign_id}/pause",
    headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
)
print(resp.json())
```

```json 200 - Paused theme={null}
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "paused",
  "review_status": "approved",
  "start_time": "2026-04-02T00:00:00.000000Z",
  "end_time": null,
  "budget": {
    "amount_micros": 50000000,
    "period": "lifetime",
    "display": "$50.00 lifetime"
  },
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "archived_at": null,
  "created_at": "2026-03-31T00:00:00.000000Z",
  "updated_at": "2026-04-02T01:23:20.000000Z"
}
```

```json 400 - Not running theme={null}
{
  "error": {
    "message": "Only a running campaign can be paused.",
    "type": "invalid_request_error",
    "param": null,
    "code": "campaign_not_running"
  }
}
```

***

## Archive a campaign

Ends a campaign permanently. An archived campaign cannot be reactivated.

```
POST /v1/campaigns/{campaign_id}/archive
```

Only a **running** or **paused** campaign can be archived. Any other state returns `400 cannot_archive`. Archiving sets `status` to `archived`.

**campaign\_id** `string` *required*

The campaign UUID.

```bash cURL theme={null}
curl -X POST https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c/archive \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
```

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

campaign_id = "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c"
resp = requests.post(
    f"https://api.thrad.ai/v1/campaigns/{campaign_id}/archive",
    headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
)
print(resp.json())
```

```json 200 - Archived theme={null}
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "archived",
  "review_status": "approved",
  "start_time": "2026-04-02T00:00:00.000000Z",
  "end_time": null,
  "budget": {
    "amount_micros": 50000000,
    "period": "lifetime",
    "display": "$50.00 lifetime"
  },
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "archived_at": null,
  "created_at": "2026-03-31T00:00:00.000000Z",
  "updated_at": "2026-04-04T00:00:00.000000Z"
}
```

```json 400 - Cannot archive theme={null}
{
  "error": {
    "message": "Only a running or paused campaign can be archived.",
    "type": "invalid_request_error",
    "param": null,
    "code": "cannot_archive"
  }
}
```

***

## Error codes

| HTTP | `code` | When |
| - | - | - |
| `400` | `required` | A required field (`name`, `budget.amount_micros`, `budget.period`, `ad_groups`, an ad group's `ads`, an ad's `asset_id`) is missing. |
| `400` | `invalid_value` | A field is the wrong type or out of range — e.g. budget below `990000`, `max_bid_micros` outside `100`–`100000000`, a malformed or offset-less timestamp, an unknown `budget.period`, an `asset_id` that isn't in this account, or mixed creative types in one ad group. |
| `400` | `use_status_action` | `status` was sent in an update body (campaign, ad group, or ad). Use `/activate`, `/pause`, or `/archive`. |
| `400` | `recurrence_locked_after_launch` | An update tried to change `budget.period` after the campaign launched. |
| `400` | `string_too_long` | A card `title` exceeds 30 characters, or a poll `title` exceeds 120. |
| `400` | `billing_not_configured` | The organization has no default card; set one up before creating a campaign. |
| `400` | `campaign_not_approved` | `/activate` was called on a campaign that is not approved and paused. |
| `400` | `campaign_not_running` | `/pause` was called on a campaign that is not running. |
| `400` | `cannot_archive` | `/archive` was called on a campaign that is not running or paused. |
| `400` | `no_ad_account` | No `ad_account_id` was supplied **and** the organization has zero ad accounts. A supplied-but-unknown `ad_account_id` returns `404 not_found` instead. |
| `401` | `auth_required` | The `Authorization` header is missing entirely. |
| `401` | `invalid_api_key` | The key is malformed, unknown, or revoked. |
| `402` | `card_declined` | The \$10 deposit charge was declined. Nothing is created. |
| `404` | `not_found` | The `campaign_id` is unknown or belongs to another organization. |
| `409` | `idempotency_key_reused` | The same `Idempotency-Key` was reused with a different request body. |
| `409` | `idempotency_key_in_use` | A request with the same `Idempotency-Key` is still in flight. Retry once it completes. |
| `409` | `name_conflict` | A campaign with that name already exists. |
| `429` | `rate_limit_exceeded` | The 1000/hour key limit (or the 60/hour create limit) was exceeded. |
| `502` | `payment_provider_error` | The payment provider was unavailable. Nothing is created; retry later. |

Every error uses the bare shape:

```json theme={null}
{
  "error": {
    "message": "A human-readable description.",
    "type": "invalid_request_error",
    "param": "budget.amount_micros",
    "code": "invalid_value"
  }
}
```

> [!TIP]
>
> Once a campaign exists, manage its ad groups and ads through [Ad groups](https://docs.thrad.ai/advertisers/api-reference/ad-groups.md) and [Ads](https://docs.thrad.ai/advertisers/api-reference/ads.md), and track delivery with [Insights](https://docs.thrad.ai/advertisers/api-reference/insights.md).


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