Zernio
APIFor you
Zernio
APIFor you
Platforms

OpenAI Ads

Overview

Build

Create a Campaign

Measure

Conversion TrackingAnalytics

Operate

Manage Ads

Reference

Limits & Errors
Dashboard
OverviewPlatformsAPI ReferenceResources
OpenAI Ads

Create a Campaign

Create a ChatGPT chat card campaign with POST /v1/ads/create, and set its budget, status, goal, bidding, conversion event, click tracking and targeting.


A chat card carries a title, a body, an image and a destination URL. So headline, body, imageUrl and linkUrl are all required.

The call creates the campaign, its ad group and the ad in one request.

The sample below runs a lifetime budget until endDate with a fixed bid. Budgets and Goals and bidding cover the daily and automatic options.

const { data: created } = await zernio.adcampaigns.createStandaloneAd({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: 'acct_abc123',
    name: 'Planner launch',
    goal: 'traffic',
    budgetAmount: 25,
    budgetType: 'lifetime',
    endDate: '2027-02-28T23:59:00Z',
    bidStrategy: 'LOWEST_COST_WITH_BID_CAP',
    bidAmount: 2,
    headline: 'Try the new planner',
    body: 'Coordinate tasks, docs and meetings in one place.',
    imageUrl: 'https://cdn.example.com/planner.png',
    linkUrl: 'https://example.com/planner',
    countries: ['US']
  }
});

console.log(created.ad._id);

Response (201):

{
  "ad": {
    "_id": "66f0a1b2c3d4e5f6a7b8c9d0",
    "name": "Planner launch",
    "platform": "openai",
    "status": "pending_review",
    "adType": "standalone",
    "goal": "traffic",
    "budget": { "amount": 25, "type": "lifetime" }
  },
  "message": "Ad created"
}

budgetAmount and bidAmount are in whole units of the ad account's currency, so 25 is $25.00 on a USD account. currency on the ad and on GET /v1/ads/tree names that currency.

Every sample below reuses the zernio and client constructors from this one.

Budgets

An OpenAI campaign carries exactly one spend cap, set by budgetType:

budgetTypeOpenAI fieldNotes
dailydaily_spend_limit_microsRequired for automatic bidding (Maximize Results)
lifetimelifetime_spend_limit_microsSend endDate to give the cap its spend window

The minimum is 1 in the ad account currency, and a lower amount returns 422.

OpenAI can require a higher daily minimum for some currencies. That check runs only on a real create, and the error names the minimum.

Status

status: "PAUSED" creates the ad paused, so you can review it before it spends.

The pause is held on the campaign the call creates, with the ad group and ad switched on. One PUT /v1/ads/campaigns/{campaignId}/status with active brings everything live.

campaignStatus overrides status for the campaign alone. campaignStatus: "ACTIVE" with status: "PAUSED" creates the campaign live, and holds the pause on the new ad group instead, with its ad switched on.

Omitted, or ACTIVE, the ad goes live after OpenAI's review.

Goals and bidding

traffic bids on clicks, awareness on impressions and conversions on conversion events. Any other goal returns 400.

The conversions goal needs an optimizable conversion event on the ad account before the create call. Without one, the request returns 400.

The event comes from a tracking tag with defaultEventType, or from ChatGPT Ads Manager. Choose the conversion event covers picking one per campaign.

bidStrategy picks how the ad group bids:

bidStrategyOpenAI biddingRequires
Omitted or LOWEST_COST_WITHOUT_CAPMaximize Results: maximize_clicks on traffic, maximize_conversions on conversionsbudgetType: "daily"
LOWEST_COST_WITH_BID_CAP or COST_CAPA fixed bid (fixed_bid), the CPA bid under conversionsbidAmount

bidAmount in whole currency units is converted to micros.

Maximize Results on a lifetime budget, or on the awareness goal, returns 400 with code missing_required_field. Send a fixed bid there.

LOWEST_COST_WITH_MIN_ROAS returns 422, because OpenAI has no ROAS-based bidding.

This body bids automatically on a daily budget:

{
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "adAccountId": "acct_abc123",
  "name": "Planner always-on",
  "goal": "traffic",
  "budgetAmount": 40,
  "budgetType": "daily",
  "headline": "Try the new planner",
  "body": "Coordinate tasks, docs and meetings in one place.",
  "imageUrl": "https://cdn.example.com/planner.png",
  "linkUrl": "https://example.com/planner",
  "countries": ["US"]
}

Choose the conversion event

On goal: "conversions", promotedObject.customEventType picks the event the campaign optimizes for.

Read the candidates from conversionEvents[] on each pixel returned by GET /v1/accounts/{accountId}/conversion-destinations (List conversion destinations):

{
  "conversionEvents": [
    { "id": "evs_123", "name": "Purchase", "eventType": "order_created", "customEventName": null, "archived": false, "optimizable": true }
  ]
}

Pass the event's id (exact match), its eventType such as order_created, or its name such as Purchase (both case-insensitive):

{
  "goal": "conversions",
  "promotedObject": { "customEventType": "order_created" }
}

Only optimizable events qualify, meaning active and standard rather than custom.

  • An unknown, archived or custom event returns 400 listing the account's optimizable events.
  • A type or name shared by several of them returns 400 asking for the id.

Omit customEventType and the campaign optimizes for the account's most recently created optimizable event.

customEventType is accepted only with the conversions goal. Every other promotedObject field returns 400 on OpenAI.

Click tracking

tracking.urlTags becomes the ad's landing_page_configuration.query_string_template. OpenAI appends it to linkUrl on click.

OpenAI's macros {campaign_id}, {ad_group_id}, {ad_id} and {oppref} (the click id) pass through raw, so OpenAI expands them. Every other character is percent-encoded.

OpenAI expands macros here, not inside linkUrl.

{
  "tracking": {
    "urlTags": [
      { "key": "utm_source", "value": "chatgpt" },
      { "key": "utm_campaign", "value": "{campaign_id}" },
      { "key": "click_id", "value": "{oppref}" }
    ]
  }
}

Validate before creating

validateOnly: true checks the request and creates nothing.

OpenAI has no dry-run of its own. Zernio runs every check it knows (creative lengths, budget, bid strategy, targeting), plus live lookups of the conversion event and the target countries.

The response is 200 with a validated result per node. On the conversions goal, the message names the event the campaign would optimize for:

{
  "validateOnly": true,
  "results": [
    { "node": "campaign", "status": "validated" },
    { "node": "adSet", "status": "validated" },
    { "node": "ad", "status": "validated" }
  ],
  "message": "Validated by Zernio against OpenAI's documented limits, plus live lookups of the conversion event and target countries; nothing was created. ..."
}

OpenAI's own write-time checks (image fetch, currency-specific budget and bid minimums, ad review) still run only on a real create.

POST /v1/ads/campaigns accepts validateOnly: true for a campaign-only check as well.

Targeting

Targeting is by country.

Every other targeting field returns 400 rather than being dropped silently. That includes ages, genders, interests, audienceInclude and audienceExclude.

Was this page helpful?

OpenAI Ads

Run ChatGPT ads on an openaiads account, from connecting an API key to creating chat card campaigns, pixels and server-side conversions.

Conversion Tracking

Create an OpenAI pixel with its Conversions API key, manage its conversion events, install it on a site and send server-side events.

On this page

BudgetsStatusGoals and biddingChoose the conversion eventClick trackingValidate before creatingTargeting