OpenAI Ads
Run and measure ChatGPT ads via Zernio API - campaigns, reporting, pixels and server-side conversions
Included with the Usage plan. OpenAI Ads has no OAuth: you connect by pasting an API key from ChatGPT Ads Manager (Settings tab). One key is scoped to exactly one ad account, so connect each ad account separately.
The key you paste has full campaign write access. OpenAI does not offer a read-only key scope. Zernio uses it to read your ads and performance, and to create and manage campaigns you set up in Zernio.
What's Supported
| Feature | Status |
|---|---|
| Standalone campaigns (Campaign > Ad group > Chat card ad) | Yes |
| Daily performance sync (impressions, clicks, spend) | Yes |
| Pause / resume ads and campaigns, budget updates | Yes |
| Location targeting (country, region, DMA) | Yes |
| Bid caps (max bid, oCPC target CPA) | Yes |
| Conversion pixels + server-side Conversions API | Yes |
| Conversion-optimized bidding (oCPC) | Yes, requires a conversion event setting |
| Lifetime budgets | Yes (OpenAI has no daily budgets) |
| Boost an existing post | No, ChatGPT has no organic posts |
| Custom audiences | No, OpenAI exposes audiences in Ads Manager only |
| Product feed upload | No, feeds are managed in Ads Manager (SFTP) |
| Video creatives | No, OpenAI supports image chat cards only |
Availability. OpenAI currently opens Ads Manager to US-based businesses; ads serve to ChatGPT Free and Go tier users in the US, Canada, Australia and New Zealand.
Connect an Ad Account
Create the key in ChatGPT Ads Manager under Settings, then either connect from the Zernio dashboard (Connections tab) or via API:
const { data: account } = await zernio.connect.connectOpenAIAdsCredentials({
body: {
apiKey: "YOUR_OPENAI_ADS_API_KEY",
profileId: "PROFILE_ID",
},
});
console.log(`Connected: ${account.accountId}`);account = client.connect.connect_open_ai_ads_credentials(
api_key="YOUR_OPENAI_ADS_API_KEY",
profile_id="PROFILE_ID",
)curl -X POST "https://zernio.com/api/v1/connect/openai-ads/credentials" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"apiKey": "YOUR_OPENAI_ADS_API_KEY",
"profileId": "PROFILE_ID"
}'The key is validated against OpenAI before anything is stored. Re-posting a rotated key for the same ad account updates the connection in place. For sending your own customers a connect link, the endpoint also accepts the x-connect-token header flow used by the other connect endpoints.
Create a Campaign
Chat card ads have a title (3 to 50 characters), a body (up to 100 characters), an image, and a destination URL. adAccountId is the OpenAI ad account id (acct_...) returned when you connect. OpenAI budgets are lifetime-only, so budgetType must be "lifetime" and endDate is required.
const { data: ad } = await zernio.ads.createStandaloneAd({ body: {
accountId: "acc_openaiads_123",
adAccountId: "acct_abc123",
name: "Workspace planner launch",
goal: "traffic",
budgetAmount: 25,
budgetType: "lifetime",
endDate: "2026-08-31",
headline: "Try the new workspace planner",
body: "Coordinate tasks, docs, and meetings in one place.",
imageUrl: "https://cdn.example.com/planner.png",
linkUrl: "https://example.com/workspace-planner",
countries: ["US"],
}});ad = client.ads.create_standalone_ad(
account_id="acc_openaiads_123",
ad_account_id="acct_abc123",
name="Workspace planner launch",
goal="traffic",
budget_amount=25,
budget_type="lifetime",
end_date="2026-08-31",
headline="Try the new workspace planner",
body="Coordinate tasks, docs, and meetings in one place.",
image_url="https://cdn.example.com/planner.png",
link_url="https://example.com/workspace-planner",
countries=["US"],
)curl -X POST "https://zernio.com/api/v1/ads/create" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "acc_openaiads_123",
"adAccountId": "acct_abc123",
"name": "Workspace planner launch",
"goal": "traffic",
"budgetAmount": 25,
"budgetType": "lifetime",
"endDate": "2026-08-31",
"headline": "Try the new workspace planner",
"body": "Coordinate tasks, docs, and meetings in one place.",
"imageUrl": "https://cdn.example.com/planner.png",
"linkUrl": "https://example.com/workspace-planner",
"countries": ["US"]
}'Goals map to OpenAI bidding: traffic bids on clicks, awareness on impressions, conversions on conversion events. The conversions goal requires an active conversion event setting on the ad account; create one with a tracking tag (below) or in OpenAI Ads Manager.
Targeting is location-only. Anything else (ages, genders, interests) returns a validation error rather than being silently dropped. The same applies to bidding: bidStrategy and bidAmount map to OpenAI's max bid (a CPA bid under the conversions goal), and ROAS-based strategies return a validation error since OpenAI has no ROAS bidding.
Manage Ads
Pause, resume and budget changes work like every other ads platform (PUT /v1/ads/{adId}/status, campaign status, budget endpoints).
Deleting archives. OpenAI has no delete API; archive is terminal. Cancelling an ad in Zernio archives the ad, its ad group, and its campaign on OpenAI, and marks the ad cancelled in Zernio.
Conversion Tracking
Create a pixel and a server-side Conversions API key in one call. Pass defaultEventType to also provision the conversion event setting that goal: "conversions" needs:
const { data: tag } = await zernio.trackingtags.createTrackingTag({
path: { accountId: "ACCOUNT_ID" },
body: { name: "Acme website", defaultEventType: "order_created" },
});tag = client.tracking_tags.create_tracking_tag(
"ACCOUNT_ID",
name="Acme website",
default_event_type="order_created",
)curl -X POST "https://zernio.com/api/v1/accounts/ACCOUNT_ID/tracking-tags" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Acme website", "defaultEventType": "order_created" }'Creating a tag is not idempotent and OpenAI pixels cannot be deleted. Never auto-retry this call; Zernio also guards to one managed pixel per ad account.
Send server-side conversion events through the shared conversions endpoint. The destination id comes from GET /v1/accounts/{accountId}/conversion-destinations:
await zernio.ads.sendConversions({ body: {
accountId: "acc_openaiads_123",
destinationId: "134534000000",
events: [{
eventName: "Purchase",
eventTime: Math.floor(Date.now() / 1000),
eventId: "order_12345",
sourceUrl: "https://shop.example.com/checkout/confirmation",
value: 25.99,
currency: "USD",
user: { email: "customer@example.com" },
}],
}});client.ads.send_conversions(
account_id="acc_openaiads_123",
destination_id="134534000000",
events=[{
"eventName": "Purchase",
"eventTime": int(time.time()),
"eventId": "order_12345",
"sourceUrl": "https://shop.example.com/checkout/confirmation",
"value": 25.99,
"currency": "USD",
"user": {"email": "customer@example.com"},
}],
)curl -X POST "https://zernio.com/api/v1/ads/conversions" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "acc_openaiads_123",
"destinationId": "134534000000",
"events": [{
"eventName": "Purchase",
"eventTime": 1773892800,
"eventId": "order_12345",
"sourceUrl": "https://shop.example.com/checkout/confirmation",
"value": 25.99,
"currency": "USD",
"user": { "email": "customer@example.com" }
}]
}'Zernio batches to OpenAI's Conversions API (up to 1,000 events per batch), maps Zernio standard events to OpenAI names (Purchase to order_created, Lead to lead_created, AddToCart to items_added, and friends; unmapped names go as custom), hashes email identifiers, and sends monetary amounts in minor units as OpenAI requires. Note OpenAI fails a whole batch if any single event is invalid; Zernio surfaces which batch failed.
Limits Cheat Sheet
| Limit | Value |
|---|---|
| Title | 3 to 50 characters |
| Body | Up to 100 characters |
| Budget | Lifetime only, minimum $1 |
| Targeting | Country, region, DMA only |
| Creative | Static image chat cards, no video |
| Rate limits (OpenAI side) | 600 requests/min per endpoint, 1,200/min overall |
| Events per Conversions API batch | 1,000 |