The ChatGPT Ads API: How to Set It Up and Launch Your First Campaign

TL;DR

  • ChatGPT Ads run inside ChatGPT conversations as chat cards or product ads, managed through OpenAI's Ads Manager (currently in beta) or programmatically via the Advertiser API.
  • Auth is refreshingly simple: one API key per ad account, created in Ads Manager under Settings → General → API Keys, sent as a Bearer token. No OAuth, no refresh tokens.
  • The hierarchy is Campaign → Ad Group → Ad. Budgets and bids are in micros (1,000,000 micros = 1 unit of currency); insights spend comes back in plain currency units. Yes, both, in the same API.
  • We integrated this API into Adstudio this week, tested everything below against a live ad account, and collected the gotchas so you don't have to.

OpenAI opened up advertising in ChatGPT, and with it an Advertiser API that will feel familiar if you've ever worked with the Meta or Google Ads APIs — except it's about a tenth of the surface area, which frankly is a feature. We just finished building a ChatGPT Ads integration for Adstudio, so this guide is written from the trenches: every request below was run against a real ad account this week.

What are ChatGPT Ads?

ChatGPT Ads are sponsored placements that appear in ChatGPT conversations. Two creative formats exist today:

  • Chat cards — a title (3–50 chars), body copy (max 100 chars), an image, and a destination URL. Think of it as a compact native ad rendered in the conversation.
  • Product ad templates — for e-commerce: the creative pulls its image, price, and destination URL from a product feed you upload, so one template can serve your whole catalog.

Targeting is contextual rather than demographic: ad groups carry context_hints (e.g. ["productivity", "team collaboration"]) that describe the conversations where your ad is relevant. Geotargeting by country, region, and DMA is also supported. There's no lookalike audience, no pixel-based retargeting — the conversation is the targeting signal.

You manage everything from the OpenAI Ads Manager (labeled Beta as of July 2026), or via the API described below.

Step 0: Get access and create an API key

  1. Create an Ads Manager account at ads.openai.com and complete account setup — your account goes through an integrity review, and ads won't serve until billing is configured (the banner reminds you loudly).
  2. Go to Settings → General → API Keys and click Create New Key.
  3. Copy the key immediately — it's shown exactly once. Keys start with sk-svcacct-.

Two things worth knowing about these keys:

  • One key = one ad account. The API works in the context of a single ad account; there's no account-selection parameter anywhere. If you manage multiple accounts, you need a key per account (OpenAI says to contact them for multi-account management).
  • The key grants full Ads Management API access — treat it like a password. If it leaks, revoke it in Ads Manager.

The API at a glance

ThingValue
Base URLhttps://api.ads.openai.com/v1
AuthAuthorization: Bearer $OPENAI_ADS_API_KEY
HierarchyCampaign → Ad Group → Ad
Content typeJSON (uploads also accept multipart/form-data)
Rate limits600 req/min per endpoint, 1,200 req/min overall
UpdatesPOST to the object URL (not PATCH/PUT)

Verify your key works before anything else:

curl "https://api.ads.openai.com/v1/ad_account" \
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY"

You get back your account's ID, name, currency, timezone, and review status. Note the endpoint is /ad_account — not /account, which returns a 404. We learned this the hard way (more in the gotchas section).

Step 1: Upload a creative

Upload an image by URL (or multipart) and keep the returned file_id:

curl -X POST "https://api.ads.openai.com/v1/upload" \
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://example.com/assets/launch-card.png"}'
# → {"file_id": "file_901"}

Step 2: Create a campaign

curl -X POST "https://api.ads.openai.com/v1/campaigns" \
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring launch",
    "status": "active",
    "budget": { "lifetime_spend_limit_micros": 25000000 }
  }'

That budget is 25000000 micros = 25.00 in your account currency (minimum is 1,000,000 = one unit). Omit start_time and the campaign starts delivering as soon as it's allowed to; omit location targeting and it can serve in all available locations.

Step 3: Create an ad group

curl -X POST "https://api.ads.openai.com/v1/ad_groups" \
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "cmpn_101",
    "name": "US English",
    "status": "active",
    "context_hints": ["productivity", "team collaboration"],
    "bidding_config": {
      "billing_event_type": "impression",
      "max_bid_micros": 60000
    }
  }'

The context_hints array is the interesting part — this is where you tell OpenAI which conversation contexts your ads belong in. Billing is per impression, and your max bid is again in micros (60000 micros = 0.06 per impression here).

Step 4: Create the ad

curl -X POST "https://api.ads.openai.com/v1/ads" \
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ad_group_id": "adgrp_301",
    "name": "Planner launch card",
    "status": "active",
    "creative": {
      "type": "chat_card",
      "title": "Try the new workspace planner",
      "body": "Coordinate tasks, docs, and meetings in one place.",
      "target_url": "https://example.com/planner",
      "file_id": "file_901"
    }
  }'

The response includes "review_status": "in_review" — every ad passes review before serving (typically minutes, per OpenAI). For an ad to actually show, the ad and its parent ad group and campaign all have to be active, and the review has to pass.

Step 5: Read your performance data

Insights live under each scope — GET /ad_account/insights, /campaigns/{id}/insights, /ad_groups/{id}/insights, /ads/{id}/insights. The account-level endpoint plus aggregation_level is the workhorse: one call returns a row per campaign (or ad group, or ad):

curl -G "https://api.ads.openai.com/v1/ad_account/insights" \
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" \
  --data-urlencode "aggregation_level=campaign" \
  --data-urlencode "time_granularity=none" \
  --data-urlencode 'time_ranges[]={"type":"date_range","since":"2026-07-01","until":"2026-07-14"}'

Metrics available: impressions, clicks, spend, ctr, cpc, cpm. You can segment by product, country, or device, filter, sort, and bucket by hour/day/month. Two details that cost us time:

  • time_ranges must use the array bracket syntax (time_ranges[]=...) with a JSON-encoded range object as the value. Passing it as a plain parameter returns a 400.
  • Insights spend comes back in currency units (e.g. 42.75), even though every budget and bid you sent in was in micros. Don't divide by a million twice.

Gotchas from a real integration

We integrated this API into Adstudio's cross-platform dashboard this week. The docs are good, but here's what actually bit us:

  1. It's /ad_account, not /account. Our connect flow validated keys against GET /account and got Invalid URL errors from the live API. If you're getting 404s with a valid key, check your paths against the API reference — don't guess them from other ad platforms' conventions.
  2. Micros in, currency units out. Budgets and bids: micros. Insights spend: decimal currency. Pick a canonical unit at your storage layer and convert at the edges.
  3. Updates are POSTs. POST /campaigns/{id} with partial fields updates the campaign. There is no PATCH. And if you update budget, you must send the complete budget object.
  4. Archive is forever. POST /campaigns/{id}/archive is irreversible. Use pause unless you're certain.
  5. Account currency isn't always USD. Our test account came back as GBP. Read currency_code from GET /ad_account instead of assuming.
  6. List endpoints paginate with cursors (after, has_more, last_id), max 500 per page for campaigns. Same object: "list" envelope OpenAI uses in its platform API.

Do you need the API at all?

If you're running one account with a handful of campaigns, Ads Manager is enough. The API earns its keep when ChatGPT Ads is one of several platforms you're running — because it's another dashboard, another currency/timezone context, and another place to check every morning.

That's the case we built for: Adstudio connects your ChatGPT Ads account with a single API key (Settings → Integrations → ChatGPT Ads) and shows campaigns, ad groups, and ads with their metrics next to your Google, Meta, and TikTok data — same tables, same date ranges, one view. The step-by-step connection guide is in our docs.

FAQ

Does ChatGPT actually show ads? Yes — OpenAI serves sponsored placements in ChatGPT conversations, managed through the Ads Manager (in beta). Ads render as chat cards or product ads and are marked as sponsored.

How much do ChatGPT Ads cost? There's no rate card; it's an auction. You're billed per impression against the max_bid_micros you set at the ad group level, bounded by campaign lifetime budgets. Minimum campaign budget is one unit of your currency (1,000,000 micros).

Can I manage ChatGPT Ads entirely through the API? Almost — campaigns, ad groups, ads, creatives, product feeds, geotargeting, conversion setup, and reporting are all in the API. Account creation and billing setup happen in Ads Manager.

Is there an official SDK? Not yet — it's a straightforward REST API with an OpenAPI spec you can download and generate a client from. Any HTTP client works.

How do I target specific audiences? Contextual, not demographic: context_hints on ad groups describe relevant conversation topics, and campaign targeting supports country/region/DMA locations. There are no interest audiences or retargeting lists.

Can I see ChatGPT Ads performance alongside Google and Meta? Not natively — Ads Manager only shows OpenAI data. That's what cross-platform tools are for; Adstudio does this with a one-time API key connection.