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

# Quickstart

> Create your API key, upload a creative, launch a campaign in one nested call, and read performance — end to end with cURL.

This guide takes you from zero to a live campaign on the Thrad Advertiser API. You'll create a key, confirm access, upload a creative, launch a campaign in a single nested call, manage delivery, and read performance.

All paths are relative to the base URL:

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

Every request is authenticated with your organization's API key as a Bearer token:

```
Authorization: Bearer ak_...
```

<Note>
  Money in **resource** bodies (budgets, bids) is sent and returned as **integer micros** (`$1 = 1,000,000`). Money in **insights** responses is different — `spend`, `cpc`, and `cpm` are **dollar floats** and `ctr` is a fraction. Each step below states the unit it uses.
</Note>

<Steps>
  <Step title="Create your API key">
    In the Thrad Platform dashboard, go to **Settings → API keys** and create a key. Your organization has **one active key**; the secret (`ak_...`) is shown **once** at creation — copy it immediately.

    Export it so the cURL examples below can reuse it:

    ```bash theme={null}
    export THRAD_ADS_API_KEY="ak_your_key_here"
    ```

    <Warning>
      Treat the key like a password. If it leaks, rotate it from **Settings → API keys** — rotation keeps the previous key valid for a short grace period, then it stops working.
    </Warning>
  </Step>

  <Step title="Confirm access">
    Fetch your organization's default ad account. A `200` confirms the key works and shows the account your campaigns will live under.

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

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

      resp = requests.get(
          "https://api.thrad.ai/v1/ad_account",
          headers={"Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}"},
      )
      resp.raise_for_status()
      print(resp.json())
      ```
    </CodeGroup>

    ```json 200 OK theme={null}
    {
      "id": "8f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
      "name": "Acme Inc.",
      "url": "https://acme.example.com",
      "timezone": "UTC",
      "currency_code": "USD"
    }
    ```

    If the key is missing or invalid, you get a bare `401`:

    ```json 401 Unauthorized theme={null}
    {
      "error": {
        "message": "Invalid API key.",
        "type": "authentication_error",
        "param": null,
        "code": "invalid_api_key"
      }
    }
    ```
  </Step>

  <Step title="Upload a creative image">
    Upload the image you want to use in your ad. The response returns a `file_id` you'll reference when creating the campaign.

    <CodeGroup>
      ```bash cURL (file upload) theme={null}
      curl https://api.thrad.ai/v1/upload \
        -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
        -F "file=@/path/to/creative.png"
      ```

      ```bash cURL (image URL) theme={null}
      curl https://api.thrad.ai/v1/upload \
        -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{ "image_url": "https://cdn.example.com/creative.png" }'
      ```
    </CodeGroup>

    ```json 201 Created theme={null}
    {
      "file_id": "file_3a9b1c7d2e4f5a6b8c0d1e2f3a4b5c6d"
    }
    ```

    <Note>
      Billing must be set up before the next step. Your campaign launch charges a deposit on the organization's **default card** — add one in the Platform dashboard first, or the create call returns `400 billing_not_configured` (with no charge).
    </Note>
  </Step>

  <Step title="Create and launch a campaign">
    Create the whole campaign tree — campaign, one ad group, one card ad — in a **single nested call**. Use the `file_id` from Step 3 on the ad's creative.

    This call:

    * requires a **default card** on the org (else `400 billing_not_configured`, no charge),
    * atomically creates the campaign, ad group, and ad,
    * **charges a \$10 deposit off-session** on the default card,
    * submits the campaign to review.

    Budgets and bids are **integer micros** (`$1 = 1,000,000`). The example below sets a `$25.00` lifetime budget (`25000000`) and a `$2.00` max bid (`2000000`). The minimum budget is `990000` (`$0.99`); `max_bid_micros` accepts `1`–`100000000` (`$0.000001`–`$100`).

    Pass an `Idempotency-Key` header so a retried request never charges twice.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.thrad.ai/v1/campaigns \
        -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: 3f6c1a90-1d2e-4b8a-9c7f-0a1b2c3d4e5f" \
        -d '{
          "name": "Summer launch",
          "description": "Q3 awareness push",
          "budget": { "lifetime_spend_limit_micros": 25000000 },
          "targeting": { "locations": { "include": ["US"] } },
          "ad_groups": [
            {
              "name": "Core audience",
              "context_hints": ["running shoes", "marathon training"],
              "bidding_config": { "max_bid_micros": 2000000 },
              "ads": [
                {
                  "name": "Hero card",
                  "creative": {
                    "type": "chat_card",
                    "title": "Premium Running Shoes",
                    "body": "Lightweight and built for the long run.",
                    "target_url": "https://example.com/shoes",
                    "file_id": "file_3a9b1c7d2e4f5a6b8c0d1e2f3a4b5c6d"
                  }
                }
              ]
            }
          ]
        }'
      ```

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

      resp = requests.post(
          "https://api.thrad.ai/v1/campaigns",
          headers={
              "Authorization": f"Bearer {os.environ['THRAD_ADS_API_KEY']}",
              "Idempotency-Key": "3f6c1a90-1d2e-4b8a-9c7f-0a1b2c3d4e5f",
          },
          json={
              "name": "Summer launch",
              "description": "Q3 awareness push",
              "budget": {"lifetime_spend_limit_micros": 25000000},
              "targeting": {"locations": {"include": ["US"]}},
              "ad_groups": [
                  {
                      "name": "Core audience",
                      "context_hints": ["running shoes", "marathon training"],
                      "bidding_config": {"max_bid_micros": 2000000},
                      "ads": [
                          {
                              "name": "Hero card",
                              "creative": {
                                  "type": "chat_card",
                                  "title": "Premium Running Shoes",
                                  "body": "Lightweight and built for the long run.",
                                  "target_url": "https://example.com/shoes",
                                  "file_id": "file_3a9b1c7d2e4f5a6b8c0d1e2f3a4b5c6d",
                              },
                          }
                      ],
                  }
              ],
          },
      )
      resp.raise_for_status()
      print(resp.json())
      ```
    </CodeGroup>

    ```json 201 Created theme={null}
    {
      "id": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b",
      "name": "Summer launch",
      "description": "Q3 awareness push",
      "status": "paused",
      "review_status": "in_review",
      "budget": { "lifetime_spend_limit_micros": 25000000 },
      "targeting": { "locations": { "include": ["US"] } },
      "recurring_frequency": "daily",
      "pricing_model": "cpm",
      "start_time": 1775260800,
      "end_time": null,
      "created_at": 1775088000,
      "updated_at": 1775088000
    }
    ```

    The campaign is now **submitted to review**: it reads back as `status: "paused"` with `review_status: "in_review"`. The \$10 deposit has been charged. There is no draft state — if the card is declined, the call returns `402 card_declined` and the campaign is removed.

    The request omitted `start_time`, so it defaults to **\~2 days in the future** (the earliest allowed launch window) — the response shows that non-null Unix timestamp. Omitting `end_time` leaves it `null` (open-ended, spend until budget).

    <Warning>
      A second `POST /v1/campaigns` with the **same** `Idempotency-Key` replays the original response (there is no expiry window). The same key with a **different** body returns `409 idempotency_key_reused`.
    </Warning>
  </Step>

  <Step title="Manage delivery once approved">
    Thrad reviews the campaign. Once it's approved (`review_status` becomes `approved`), you control delivery with the status actions. Sending `status` in an update body is rejected (`400 use_status_action`) — always use these endpoints.

    Pause a running campaign:

    ```bash theme={null}
    curl -X POST https://api.thrad.ai/v1/campaigns/c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b/pause \
      -H "Authorization: Bearer $THRAD_ADS_API_KEY"
    ```

    Resume a paused campaign:

    ```bash theme={null}
    curl -X POST https://api.thrad.ai/v1/campaigns/c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b/activate \
      -H "Authorization: Bearer $THRAD_ADS_API_KEY"
    ```

    ```json 200 OK theme={null}
    {
      "id": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b",
      "name": "Summer launch",
      "status": "active",
      "review_status": "approved",
      "updated_at": 1775091600
    }
    ```

    <Note>
      `/activate` only **resumes** a paused, approved campaign — it never launches one. Calling it on a campaign still in review returns `400 campaign_not_approved`. Calling `/pause` on a campaign that isn't running returns `400 campaign_not_running`. `/archive` ends a running or paused campaign.
    </Note>
  </Step>

  <Step title="Read performance">
    Fetch metrics for the campaign. Insights default to the trailing 30-day window and combine **Thrad + ChatGPT (OpenAI Ads)** supply.

    ```bash theme={null}
    curl "https://api.thrad.ai/v1/campaigns/c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b/insights?fields[]=impressions&fields[]=clicks&fields[]=spend&fields[]=ctr&fields[]=cpc&fields[]=cpm" \
      -H "Authorization: Bearer $THRAD_ADS_API_KEY"
    ```

    ```json 200 OK theme={null}
    {
      "object": "list",
      "data": [
        {
          "id": "start=1775088000:end=1775174400:entity_id=c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b",
          "start_time": 1775088000,
          "end_time": 1775174400,
          "readable_time": "2026-04-02",
          "timezone": "UTC",
          "campaign_id": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b",
          "campaign_name": "Summer launch",
          "impressions": 500,
          "clicks": 50,
          "spend": 0.70,
          "ctr": 0.10,
          "cpc": 0.014,
          "cpm": 1.40
        }
      ],
      "count": 1,
      "first_id": "start=1775088000:end=1775174400:entity_id=c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b",
      "last_id": "start=1775088000:end=1775174400:entity_id=c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6a7b",
      "has_more": false
    }
    ```

    <Warning>
      Insights money is in **dollar floats** — `spend`, `cpc`, and `cpm` are already in dollars (`0.70` = \$0.70) and `ctr` is a fraction (`0.10` = 10%). This is the opposite of resource bodies, which use integer micros.
    </Warning>
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/advertisers/api-reference/authentication">
    Bearer keys, one active key per org, rotation, and error shapes.
  </Card>

  <Card title="Campaigns" icon="megaphone" href="/advertisers/api-reference/campaigns">
    Full nested-create schema, updates, and status actions.
  </Card>

  <Card title="Insights" icon="chart-line" href="/advertisers/api-reference/insights">
    Granularity, aggregation levels, fields, filters, segments, and supply splits.
  </Card>
</CardGroup>

## Rate limits

| Limit                  | Scope                                                 |
| ---------------------- | ----------------------------------------------------- |
| `1000` requests / hour | Per API key, across all `/v1/` endpoints              |
| `60` requests / hour   | Additional limit on the charging `POST /v1/campaigns` |
