Skip to main content
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.
Money on campaign resources is expressed in integer micros, where $1 = 1,000,000. So budget.lifetime_spend_limit_micros: 5000000 is $5.00. This is different from the Insights API, which returns dollar floats. State the unit you mean — micros here, dollars there.

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, /pause, and /archive 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.
start_time
integer
Campaign start, as a Unix timestamp in seconds. 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
integer | null
Campaign end, as a Unix timestamp in seconds. null means open-ended — the campaign spends until the budget is exhausted.
budget
object
recurring_frequency
string
Spend cadence: daily | weekly | monthly | quarterly | yearly.
pricing_model
string
Bidding basis: cpm (cost per thousand impressions) or cpc (cost per click).
targeting
object
created_at
integer
Creation time, as a Unix timestamp in seconds.
updated_at
integer
Last-modified time, as a Unix timestamp in seconds.

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 it. If it is rejected, review_status becomes rejected.
Approval (the in_review → approved transition) is performed by Thrad, not through this API. Until a campaign is approved, /activate 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, 1500.
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.
curl https://api.thrad.ai/v1/campaigns?limit=20 \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
{
  "object": "list",
  "data": [
    {
      "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
      "name": "Spring running launch",
      "description": "Marathon season push",
      "status": "active",
      "review_status": "approved",
      "start_time": 1775088000,
      "end_time": null,
      "budget": { "lifetime_spend_limit_micros": 50000000 },
      "recurring_frequency": "daily",
      "pricing_model": "cpm",
      "targeting": {
        "locations": {
          "include": [
            { "id": "US", "type": "country", "country_code": "US" }
          ]
        }
      },
      "created_at": 1774915200,
      "updated_at": 1775001600
    }
  ],
  "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. Each ad’s creative image becomes a stored asset.
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".
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
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 chat_card or all poll).
description
string
Free-text description.
start_time
integer
Campaign start, as a Unix timestamp in seconds. 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.
end_time
integer
Campaign end, as a Unix timestamp in seconds. Omit for an open-ended campaign that spends until the budget is exhausted.
targeting
object
recurring_frequency
string
default:"daily"
Spend cadence: daily | weekly | monthly | quarterly | yearly.
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.
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": { "lifetime_spend_limit_micros": 50000000 },
    "start_time": 1775088000,
    "recurring_frequency": "daily",
    "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",
            "creative": {
              "type": "chat_card",
              "title": "Premium Running Shoes",
              "body": "Lightweight and built for marathon pace.",
              "cta": "Shop now",
              "image_url": "https://cdn.example.com/shoes.jpg"
            }
          }
        ]
      }
    ]
  }'
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "paused",
  "review_status": "in_review",
  "start_time": 1775088000,
  "end_time": null,
  "budget": { "lifetime_spend_limit_micros": 50000000 },
  "recurring_frequency": "daily",
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "created_at": 1774915200,
  "updated_at": 1774915200
}
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.
curl https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "active",
  "review_status": "approved",
  "start_time": 1775088000,
  "end_time": null,
  "budget": { "lifetime_spend_limit_micros": 50000000 },
  "recurring_frequency": "daily",
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "created_at": 1774915200,
  "updated_at": 1775001600
}

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, targeting, start_time, and end_time. Numeric values are re-validated exactly as on create: budget.lifetime_spend_limit_micros must still be >= 990000, and any start_time / end_time must be an integer Unix timestamp. An out-of-range or non-integer value returns 400 invalid_value.
You cannot change status here. Sending status in the body returns 400 use_status_action — use /activate, /pause, or /archive instead. review_status, recurring_frequency, and pricing_model are not updatable either.
campaign_id
string
required
The campaign UUID.
name
string
New campaign name, 3–100 characters.
description
string
New description.
budget
object
targeting
object
New geo targeting. Same shape as on create — locations.include[] with country_code per entry.
start_time
integer
New start, as a Unix timestamp in seconds.
end_time
integer
New end, as a Unix timestamp in seconds.
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": { "lifetime_spend_limit_micros": 75000000 },
    "targeting": {
      "locations": { "include": [ { "country_code": "US" }, { "country_code": "CA" } ] }
    }
  }'
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch (US + CA)",
  "description": "Marathon season push",
  "status": "active",
  "review_status": "approved",
  "start_time": 1775088000,
  "end_time": null,
  "budget": { "lifetime_spend_limit_micros": 75000000 },
  "recurring_frequency": "daily",
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" },
        { "id": "CA", "type": "country", "country_code": "CA" }
      ]
    }
  },
  "created_at": 1774915200,
  "updated_at": 1775088123
}

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.
curl -X POST https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c/activate \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "active",
  "review_status": "approved",
  "start_time": 1775088000,
  "end_time": null,
  "budget": { "lifetime_spend_limit_micros": 50000000 },
  "recurring_frequency": "daily",
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "created_at": 1774915200,
  "updated_at": 1775090000
}

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.
curl -X POST https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c/pause \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "paused",
  "review_status": "approved",
  "start_time": 1775088000,
  "end_time": null,
  "budget": { "lifetime_spend_limit_micros": 50000000 },
  "recurring_frequency": "daily",
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "created_at": 1774915200,
  "updated_at": 1775093000
}

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.
curl -X POST https://api.thrad.ai/v1/campaigns/8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c/archive \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY"
{
  "id": "8b1f0c2e-4d3a-4f1b-9a2c-1e2d3f4a5b6c",
  "name": "Spring running launch",
  "description": "Marathon season push",
  "status": "archived",
  "review_status": "approved",
  "start_time": 1775088000,
  "end_time": null,
  "budget": { "lifetime_spend_limit_micros": 50000000 },
  "recurring_frequency": "daily",
  "pricing_model": "cpm",
  "targeting": {
    "locations": {
      "include": [
        { "id": "US", "type": "country", "country_code": "US" }
      ]
    }
  },
  "created_at": 1774915200,
  "updated_at": 1775260800
}

Error codes

HTTPcodeWhen
400requiredA required field (name, budget.lifetime_spend_limit_micros, ad_groups, an ad group’s ads) is missing.
400invalid_valueA field is the wrong type or out of range — e.g. budget below 990000, max_bid_micros outside 1100000000, a non-integer timestamp, or mixed creative types in one ad group.
400use_status_actionstatus was sent in an update body. Use /activate, /pause, or /archive.
400billing_not_configuredThe organization has no default card; set one up before creating a campaign.
400campaign_not_approved/activate was called on a campaign that is not approved and paused.
400campaign_not_running/pause was called on a campaign that is not running.
400cannot_archive/archive was called on a campaign that is not running or paused.
400no_ad_accountNo ad_account_id was supplied and the organization has zero ad accounts. A supplied-but-unknown ad_account_id returns 404 not_found instead.
401auth_requiredThe Authorization header is missing entirely.
401invalid_api_keyThe key is malformed, unknown, or revoked.
402card_declinedThe $10 deposit charge was declined. Nothing is created.
404not_foundThe campaign_id is unknown or belongs to another organization.
409idempotency_key_reusedThe same Idempotency-Key was reused with a different request body.
409name_conflictA campaign with that name already exists.
429rate_limit_exceededThe 1000/hour key limit (or the 60/hour create limit) was exceeded.
502payment_provider_errorThe payment provider was unavailable. Nothing is created; retry later.
Every error uses the bare shape:
{
  "error": {
    "message": "A human-readable description.",
    "type": "invalid_request_error",
    "param": "budget.lifetime_spend_limit_micros",
    "code": "invalid_value"
  }
}
Once a campaign exists, manage its ad groups and ads through Ad groups and Ads, and track delivery with Insights.