Skip to main content
Thrad campaigns are targeted by country. You set targeting on the campaign at create time (or update it later) through targeting.locations.include, an array of country objects. There is no separate targeting resource — targeting lives on the campaign body.
Targeting is country-level only. Region, state, and DMA (metro) targeting are not supported. This is a deliberate difference from OpenAI Ads — locations are resolved to a country and any sub-country detail is ignored.

Authentication

All requests use your organization’s API key.
Authorization
string
required
Bearer token: Authorization: Bearer ak_.... One active key per organization — create it in the Thrad Platform dashboard under Settings → API keys. Examples below read it from the THRAD_ADS_API_KEY environment variable.

The targeting object

targeting
object
Geographic targeting for the campaign.
Every location in include must resolve to a country code. An entry with neither country_code nor id is rejected with 400 invalid_value and param: "targeting.locations.include".

Setting targeting on create

Pass targeting inside the campaign create body. Country codes are uppercased and normalized before the campaign is stored.
curl https://api.thrad.ai/v1/campaigns \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f1c2b7a-0e3d-4a51-8c6e-1d2f3a4b5c6d" \
  -d '{
    "name": "US + Germany launch",
    "budget": { "lifetime_spend_limit_micros": 50000000 },
    "targeting": {
      "locations": {
        "include": [
          { "country_code": "US" },
          { "country_code": "de" }
        ]
      }
    },
    "ad_groups": [
      {
        "name": "Prospecting",
        "bidding_config": { "max_bid_micros": 2000000 },
        "ads": [
          {
            "name": "Card A",
            "creative": {
              "type": "chat_card",
              "title": "Try our app",
              "body": "Fast, simple, free to start.",
              "target_url": "https://example.com",
              "image_url": "https://cdn.example.com/card.jpg"
            }
          }
        ]
      }
    ]
  }'
budget.lifetime_spend_limit_micros is in integer micros ($1 = 1,000,000); 50000000 is $50.00. max_bid_micros is likewise micros (2000000 = $2.00).

Read-back shape

On read, each targeted country is expanded to an object with id, type, and country_codetype is always "country", and id mirrors the uppercased country code. The campaign you created above reads back like this:
{
  "id": "c4d5e6f7-1234-4abc-9def-0123456789ab",
  "name": "US + Germany launch",
  "description": null,
  "status": "paused",
  "review_status": "in_review",
  "start_time": 1751414400,
  "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" },
        { "id": "DE", "type": "country", "country_code": "DE" }
      ]
    }
  },
  "created_at": 1751241600,
  "updated_at": 1751241600
}
The lowercase "de" you sent reads back as "DE". Timestamps (start_time, end_time, created_at, updated_at) are Unix seconds. A freshly created campaign submits to review, so it reads back as status: "paused" with review_status: "in_review".You omitted start_time on create, so it defaulted to roughly two days out (the earliest allowed launch window) — that is why start_time reads back as a non-null Unix timestamp rather than null. end_time was also omitted and stays null (open-ended, delivering until the budget is exhausted).

Updating targeting

Send a new targeting object to POST /v1/campaigns/{id} to replace the campaign’s countries. The same normalization and validation apply on update as on create.
cURL
curl https://api.thrad.ai/v1/campaigns/c4d5e6f7-1234-4abc-9def-0123456789ab \
  -H "Authorization: Bearer $THRAD_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targeting": {
      "locations": {
        "include": [
          { "country_code": "US" }
        ]
      }
    }
  }'
Do not send status in a campaign update body — campaign status changes go only through /activate, /pause, and /archive. Sending status on a campaign update returns 400 use_status_action. (Ad groups and ads do accept status in their update body — that restriction is campaign-only.)

Errors

Errors use the bare error shape:
{
  "error": {
    "message": "Each targeting location needs a country_code.",
    "type": "invalid_request_error",
    "param": "targeting.locations.include",
    "code": "invalid_value"
  }
}
HTTPcodeWhen
400invalid_valueA location entry has neither country_code nor id. param is targeting.locations.include.
400use_status_actionstatus was sent in a campaign update body. Use /activate, /pause, or /archive.
401auth_requiredNo Authorization header was sent.
401invalid_api_keyThe Authorization: Bearer ak_... key is malformed, unknown, or revoked.
404not_foundThe campaign {id} does not exist for your organization.

Tips

Create the campaign, then open it in the Thrad Platform dashboard and confirm the targeted countries before it goes live. The campaign starts in review (review_status: "in_review") and will not deliver until it is approved, so this is the right moment to catch a wrong or missing country.
If you need finer-grained delivery than country level, that is not available through targeting. Region, state, and DMA targeting are intentionally unsupported — design your campaigns around country-level reach.