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.amount_micros: 5000000 is $5.00. Timestamps are ISO 8601 strings in UTC (2026-04-01T00:00:00Z), never Unix integers.

The campaign object

string
Unique campaign identifier (UUID).
string
Campaign name (3–100 characters).
string | null
Free-text description, or null if none was set.
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.
string
Read-only moderation state: in_review | approved | rejected. A freshly created campaign is in_review. See Review status.
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.
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.
object
The campaign budget, stated as an amount plus the window it applies to.
string
Bidding basis: cpm (cost per thousand impressions) or cpc (cost per click).
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, 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.
object
string
Creation time, as an ISO 8601 timestamp in UTC.
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.
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.
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 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.
string
Restrict the list to a single ad account (UUID). Omit to list across all ad accounts in your organization.
integer
default:"20"
Number of campaigns to return, 1–500.
string
default:"desc"
Sort direction by creation time: asc or desc.
string
Cursor for the previous page — pass a prior response’s first_id. Mutually exclusive with after.
string
Cursor for the next page — pass a prior response’s last_id. Mutually exclusive with before.

Create a campaign

Creates a campaign together with its ad groups and ads in a single nested request, then submits the campaign for review.
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.
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".
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.)
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.
string
required
Campaign name, 3–100 characters.
object
required
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).
string
Free-text description.
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.
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.
object
string
default:"cpm"
Bidding basis: cpm or cpc.
string
The ad account to create the campaign under (UUID). Defaults to your organization’s default ad account.
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.
string
required
The campaign UUID. An unknown or cross-organization ID returns 404.

Update a campaign

Updates the mutable fields of a campaign. Only the fields you send are changed.
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.
You cannot change status here. Sending status in the body returns 400 use_status_action — use /activate, /pause, or /archive 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).
string
required
The campaign UUID.
string
New campaign name, 3–100 characters.
string
New description.
object
object
New geo targeting. Same shape as on create — locations.include[] with country_code per entry.
string
New start, as an ISO 8601 timestamp with a UTC offset or trailing Z. Send null to clear it.
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.

Activate a campaign

Resumes a previously paused campaign. The campaign must already be approved.
  • 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.
string
required
The campaign UUID.

Pause a campaign

Pauses a running campaign so it stops serving.
Only a running (active) campaign can be paused. Any other state returns 400 campaign_not_running.
string
required
The campaign UUID.

Archive a campaign

Ends a campaign permanently. An archived campaign cannot be reactivated.
Only a running or paused campaign can be archived. Any other state returns 400 cannot_archive. Archiving sets status to archived.
string
required
The campaign UUID.

Error codes

Every error uses the bare shape:
Once a campaign exists, manage its ad groups and ads through Ad groups and Ads, and track delivery with Insights.