Skip to main content
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 (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 Groupcontext_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.

Resources

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.

Insights

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.

Endpoints at a glance

ResourceOperationMethod & path
Ad accountGetGET /ad_account
UploadCreate imagePOST /upload
CampaignListGET /campaigns
CampaignCreatePOST /campaigns
CampaignGetGET /campaigns/{id}
CampaignUpdatePOST /campaigns/{id}
CampaignActivate / Pause / ArchivePOST /campaigns/{id}/activate · /pause · /archive
Ad groupListGET /ad_groups?campaign_id=
Ad groupCreatePOST /ad_groups
Ad groupGetGET /ad_groups/{id}
Ad groupUpdatePOST /ad_groups/{id}
Ad groupActivate / Pause / ArchivePOST /ad_groups/{id}/activate · /pause · /archive
AdListGET /ads?ad_group_id=
AdCreatePOST /ads
AdGetGET /ads/{id}
AdUpdatePOST /ads/{id}
AdActivate / Pause / ArchivePOST /ads/{id}/activate · /pause · /archive
InsightsAd accountGET /ad_account/insights
InsightsCampaignGET /campaigns/{id}/insights
InsightsAd groupGET /ad_groups/{id}/insights
InsightsAdGET /ads/{id}/insights

Conventions

Authentication

Every request is authenticated with an org-bound Bearer key on the Authorization header.
Authorization
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.
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.

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.
{
  "object": "list",
  "data": [ ... ],
  "count": 1,
  "first_id": "...",
  "last_id": "...",
  "has_more": false
}
An error response carries a single error object:
{
  "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

Money is on two different scales. State the unit every time and never mix them.
  • 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 1100000000 ($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.
FieldValues
statusactive · 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.
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.

Pagination

List endpoints are cursor-paginated. The limit range depends on the surface: resource list endpoints (campaigns, ad groups, ads) accept 1500, while insights list endpoints accept 12000. Both default to 20.
limit
integer
default:"20"
Number of items per page. Resource lists: 1500. Insights lists: 12000.
before
string
Return items before this cursor. Echo a prior response’s first_id. Mutually exclusive with after.
after
string
Return items after this cursor. Echo a prior response’s last_id. Mutually exclusive with before.
order
string
default:"desc"
Sort direction: asc or desc.

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.
Idempotency-Key
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.

Next steps

Quickstart

Mint a key and create, launch, and read insights for your first campaign with cURL.

Authentication

How the Bearer key works, where to create it, and how to rotate or revoke it.