What Is the ChatGPT Ads Bulk API in 2026?
The Bulk API creates or updates campaigns, ad groups and ads in a single asynchronous job. You submit up to 1,000 operations, poll the job, and inspect the result of each operation. It is the tool an agency reaches for when a launch involves dozens of campaigns, or a weekly change touches hundreds of ad groups, and making one request per object would be slow and fragile.
One status line governs everything below. OpenAI states the Bulk API is in limited preview and is enabled per ad account. It is not included in the downloadable OpenAPI spec, and if a bulk endpoint returns 404, OpenAI's instruction is to contact your OpenAI account team to confirm access for the account associated with your Ads API key. Plan around that gate before you build tooling that depends on it.
Because access is enabled per ad account, an agency can find bulk working on one client account and returning 404 on the next. Confirm access for every client account you intend to manage this way before committing to a bulk-based workflow, and keep a non-bulk fallback using the standard endpoints covered in the Ads API guide.
Why Would an Agency Use the Bulk API Instead of Single Requests?
Because a launch built from single requests is a sequence of dependencies, and every dependency is a place for a script to fail halfway. You create a campaign, wait for its ID, create an ad group using that ID, wait, create an ad. Bulk collapses that into one submission where operations can refer to parents created in the same job, and returns a per-operation result you can audit.
- One submission for a whole structure. Campaign, ad groups and ads in a single job, linked by idempotency keys rather than returned IDs.
- Per-operation results. Every operation reports created, updated, validated, failed or skipped, with retry guidance on failures.
- A dry-run mode.
validate_onlychecks fields and dependencies without changing anything. - Safer retries. Idempotency at both the request and operation level, so a rerun reuses successful creates.
- A separate rate limit. Bulk job creation has its own budget distinct from the standard per-endpoint limits.
How Do You Submit a Bulk Job?
One POST to /bulk_mutation_jobs containing an operations array. OpenAI's own example creates a campaign, an ad group and an ad in one request, all paused so they can be verified before delivery starts.
curl -X POST "https://api.ads.openai.com/v1/bulk_mutation_jobs" -H "Authorization: Bearer $OPENAI_ADS_API_KEY" -H "Content-Type: application/json" -H "Idempotency-Key: spring-launch-job-001" -d '{
"validate_only": false,
"partial_failure": true,
"operations": [
{
"operation_id": "create-campaign",
"type": "campaign.create",
"idempotency_key": "campaign-spring-launch",
"input": {
"name": "Spring launch",
"max_budget_micros": 100000000,
"billing_event_type": "impression",
"budget_type": "lifetime",
"status": "paused"
}
},
{
"operation_id": "create-ad-group",
"type": "ad_group.create",
"idempotency_key": "ad-group-prospecting",
"input": {
"campaign_idempotency_key": "campaign-spring-launch",
"name": "Prospecting",
"context_hints": ["shoes", "spring fashion"],
"status": "paused"
}
},
{
"operation_id": "create-ad",
"type": "ad.create",
"idempotency_key": "ad-prospecting-1",
"input": {
"campaign_idempotency_key": "campaign-spring-launch",
"ad_group_idempotency_key": "ad-group-prospecting",
"title": "Fresh shoes",
"body": "Find your next pair",
"target_url": "https://example.com/shoes",
"source_image_url": "https://developers.openai.com/showcase/openai-imagegen-demo.png",
"status": "paused"
}
}
]
}'
The API returns 202 Accepted with a job ID and a status of pending. Notice two differences from the standard endpoints that will trip up anyone porting existing code: the bulk campaign input uses max_budget_micros with a budget_type field rather than the separate daily and lifetime budget fields, and the ad input takes a source_image_url rather than an uploaded file ID.
The three request-level fields
| Field | Required | What it does |
|---|---|---|
operations | Yes | Between 1 and 1,000 create or update operations |
validate_only | No | Validates fields and dependencies without changing resources. Defaults to false |
partial_failure | No | Continues independent operations after an error. Defaults to true |
What Do partial_failure and validate_only Actually Guarantee?
Less than their names suggest, and this is the section to read before you trust a bulk run on a client account. With partial_failure set to false, later operations are skipped after one fails, but OpenAI states this setting does not roll back operations that already completed. There is no transaction. A failed job can leave a campaign created and its ad group missing.
validate_only has a similar limit. OpenAI states that validation-only jobs do not guarantee operations can complete successfully, because they do not check update-target existence, image fetching, entity limits or other write-time errors. It catches malformed requests and broken references, not everything that can go wrong at write time.
| Setting | What it does | What it does not do |
|---|---|---|
partial_failure: true | Keeps running independent operations after a failure | Does not retry the failed one |
partial_failure: false | Skips later operations after a failure | Does not roll back operations already completed |
validate_only: true | Checks fields and dependencies, changes nothing | Does not check update-target existence, image fetching, entity limits or other write-time errors |
The practical rule for agencies in 2026: always create bulk resources as paused, as OpenAI's example does, so a half-completed job leaves inert objects rather than partially-built live campaigns spending money.
Which Operations Does the Bulk API Support?
Six operation types: create and update for each of campaigns, ad groups and ads. Every entry needs a unique operation_id, a type and an input object. Creates also need a unique idempotency_key; updates need a target_resource_id and at least one input field.
| Type | Required input | Other supported input |
|---|---|---|
campaign.create | name, max_budget_micros | billing_event_type, budget_type, status, target_countries, location_ids |
campaign.update | At least one supported field | name, description, status, max_budget_micros, budget_type, start_time, end_time and others |
ad_group.create | campaign_idempotency_key, name | context_hints, exclusion_hints, max_bid_micros, max_cpm_bid_micros, status |
ad_group.update | At least one supported field | name, description, status, context_hints, exclusion_hints, bids |
ad.create | campaign_idempotency_key, ad_group_idempotency_key, title, body, target_url, source_image_url | status |
ad.update | At least one supported field | name, status, creative |
The defaults and limits that catch people out
campaign.createdefaults to an impression-billed, lifetime, paused campaign. If you omit billing and budget type, that is what you get, which is not what most performance campaigns want.- Campaign budget must be at least
1000000currency micros, which is one unit of the account currency. - Ad group bids must match the parent's billing event. Provide only one of
max_bid_microsfor clicks ormax_cpm_bid_microsfor impressions. OpenAI notes CPM requires account access. - Names run 3 to 1,000 characters for campaigns and ad groups. Ad titles allow 3 to 50, bodies up to 100, URLs up to 2,048.
- Campaigns support up to 2,500 location IDs, and ad groups up to 2,000 context hints.
- Create statuses are
activeorpaused; update statuses also supportarchived. - Updating an ad creative requires
title,body,target_urlandfile_idtogether.
How Do Parent and Child References Work in One Job?
Through idempotency keys rather than IDs, because the IDs do not exist yet when you submit. Set campaign_idempotency_key on an ad group to the campaign operation's key, and set ad_group_idempotency_key on an ad to the ad group operation's key. OpenAI adds that the campaign reference on ad.create must match the campaign reference on its parent ad_group.create.
Three rules constrain how you can mix operations. You can combine creates and updates in one job. Updates can only target resources that exist when you submit, so you cannot update something created in the same job. And each resource can be updated only once per job. If your tooling generates changes from a diff, deduplicate per resource before submitting.
How Do You Track a Job and Read the Results?
Poll the job until it reaches a terminal status, then page through the per-operation results. completed, partially_failed and failed are terminal.
curl -X GET "https://api.ads.openai.com/v1/bulk_mutation_jobs/blkmtnjob_6a2b773d47b481908aa6078025a64ad3" -H "Authorization: Bearer $OPENAI_ADS_API_KEY"
| Job status | Meaning |
|---|---|
pending | Waiting to run |
in_progress | Processing operations |
completed | All operations completed successfully |
partially_failed | At least one succeeded and another failed or was skipped |
failed | No operations succeeded |
Results come from GET /bulk_mutation_jobs/{job_id}/operations, 1 to 100 per page, defaulting to 100. Each result carries its operation_id, type, status and nullable error fields. OpenAI notes that pagination cursors are only available after complete is true, and that while a job is running the endpoint can return an incomplete snapshot. Do not treat a mid-run read as final.
| Operation status | Meaning |
|---|---|
created | The create succeeded |
updated | The update succeeded |
validated | Passed validation in a validation-only job |
failed | Returned an error; check retryable and retry_after_seconds |
skipped | Did not run because a dependency or earlier operation failed |
What Are the Limits and Retry Rules in 2026?
The limits are generous for a single client and tight for an agency running many accounts in parallel, mainly because of the create rate.
| Limit | Value |
|---|---|
| Operations per job | 1,000 |
| Request body size | 16 MiB |
| Serialised operation size | 512 KiB |
| Create requests per ad account | 10 requests per 10 seconds |
| Operation results per page | 100 |
| Self-serve campaigns per ad account | 5,000 non-archived |
| Self-serve ad groups per ad account | 5,000 non-archived |
| Self-serve ads per ad account | 5,000 active or paused |
operation_id and idempotency key length | Up to 255 characters |
Retries have two layers. The request-level Idempotency-Key header makes it safe to retry an uncertain request with the same body; reusing it with a different body returns an error. To rerun a failed or partially_failed job, submit the same body with a new request-level key, and OpenAI states successful creates are reused. At operation level, if a result is retryable, wait for retry_after_seconds where provided, then resubmit in a new job reusing the original create-operation idempotency_key values.
How Should an Agency Use the Bulk API Safely?
Treat it as a deployment tool with the same discipline you would apply to production code. The documentation gives you the primitives for safe operation; the workflow is yours to impose.
- Confirm access per client account before building a workflow around it, since it is limited preview and enabled per account.
- Run
validate_onlyfirst, knowing it does not catch write-time errors. - Create everything paused so a partial job cannot leave live spend behind.
- Use deterministic idempotency keys derived from your own campaign naming, so retries reuse rather than duplicate.
- Read all results only after
completeis true, then reconcile failed and skipped operations. - Activate deliberately afterwards, through the standard endpoints, once each level is verified.
- Pace jobs across accounts to stay inside 10 create requests per 10 seconds per account.
One header detail matters for multi-client tooling: OpenAI states each Ads API key works with one ad account, so you do not add an OpenAI-Ad-Account header when using an API key. Account switching is therefore a key-management problem, which pushes agencies toward a proper secrets store per client rather than a shared credential. The account management guide covers the partner-side setup.
What Are the Common Bulk API Mistakes in 2026?
- Assuming bulk is available everywhere. It is limited preview, enabled per ad account, and absent from the OpenAPI spec.
- Expecting rollback.
partial_failure: falseskips later operations but does not undo completed ones. - Trusting
validate_onlyas a full test. It skips update-target existence, image fetching and entity-limit checks. - Omitting billing and budget type.
campaign.createdefaults to impression-billed and lifetime. - Updating a resource created in the same job. Updates can only target resources that exist at submission.
- Reading results mid-run. The snapshot can be incomplete until
completeis true. - Generating new idempotency keys on retry. Reuse the original create-operation keys so successful creates are reused.
- Creating resources active. A partial job then leaves partially-built live campaigns.
Key Takeaways for 2026
The Bulk API is the right tool for agency-scale launches and changes on ChatGPT Ads, provided you treat its guarantees precisely and plan around its preview status.
- Up to 1,000 create or update operations per asynchronous job, submitted to
/bulk_mutation_jobs. - Limited preview, enabled per ad account, not in the OpenAPI spec; a 404 means contact your OpenAI account team.
- Children reference parents in the same job through idempotency keys, not IDs.
partial_failure: falseskips later work but does not roll back;validate_onlydoes not catch write-time errors.campaign.createdefaults to impression-billed, lifetime and paused, with a minimum budget of one currency unit.- Create requests are limited to 10 per 10 seconds per ad account, with 16 MiB request bodies.
- Read results only once
completeis true, and retry with the original create-operation keys. - Create everything paused, and activate deliberately afterwards.
Distk runs paid media operations for growth teams across India and internationally, and for multi-account work we treat bulk changes like a production deployment: validated, paused on creation, reconciled, then activated. If you are scaling ChatGPT Ads across several accounts in 2026, that operating discipline is the part we would set up first.
Sources
- OpenAI, Bulk API, including operations, job statuses, limits and retries.
- OpenAI, API Partner Setup.
- OpenAI, Ads API Overview, including standard rate limits.
Every field, limit and status in this guide is quoted from OpenAI's published Ads documentation as of 1 October 2026. The Bulk API is documented as limited preview; confirm access and current behaviour before relying on it.