Paid Media Guide

Bulk Campaign Management in ChatGPT Ads: The Bulk API for Agencies in 2026

For an agency, the Bulk API turns a fragile chain of single requests into one auditable job. It is also in limited preview, enabled per account, and offers no rollback. Here is how to use it without leaving half-built campaigns behind.

Distk Editorial Oct 2026 11 min read

The ChatGPT Ads Bulk API creates or updates campaigns, ad groups and ads in one asynchronous job of up to 1,000 operations, submitted to POST /bulk_mutation_jobs. OpenAI states it is in limited preview, enabled per ad account and not in the downloadable OpenAPI spec, so a 404 means contacting your OpenAI account team. Children reference parents created in the same job through idempotency keys rather than IDs. partial_failure defaults to true; setting it false skips later operations but does not roll back completed ones. validate_only checks fields and dependencies but not update-target existence, image fetching or entity limits. campaign.create defaults to an impression-billed, lifetime, paused campaign with a minimum budget of 1,000,000 micros. Create requests are limited to 10 per 10 seconds per ad account, with 16 MiB request bodies and 512 KiB per operation. Results are only final once complete is true, and retries should reuse the original create-operation idempotency keys so successful creates are reused.

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.

Access check before anything else in 2026

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.

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

FieldRequiredWhat it does
operationsYesBetween 1 and 1,000 create or update operations
validate_onlyNoValidates fields and dependencies without changing resources. Defaults to false
partial_failureNoContinues 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.

SettingWhat it doesWhat it does not do
partial_failure: trueKeeps running independent operations after a failureDoes not retry the failed one
partial_failure: falseSkips later operations after a failureDoes not roll back operations already completed
validate_only: trueChecks fields and dependencies, changes nothingDoes 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.

TypeRequired inputOther supported input
campaign.createname, max_budget_microsbilling_event_type, budget_type, status, target_countries, location_ids
campaign.updateAt least one supported fieldname, description, status, max_budget_micros, budget_type, start_time, end_time and others
ad_group.createcampaign_idempotency_key, namecontext_hints, exclusion_hints, max_bid_micros, max_cpm_bid_micros, status
ad_group.updateAt least one supported fieldname, description, status, context_hints, exclusion_hints, bids
ad.createcampaign_idempotency_key, ad_group_idempotency_key, title, body, target_url, source_image_urlstatus
ad.updateAt least one supported fieldname, status, creative

The defaults and limits that catch people out

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 statusMeaning
pendingWaiting to run
in_progressProcessing operations
completedAll operations completed successfully
partially_failedAt least one succeeded and another failed or was skipped
failedNo 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 statusMeaning
createdThe create succeeded
updatedThe update succeeded
validatedPassed validation in a validation-only job
failedReturned an error; check retryable and retry_after_seconds
skippedDid 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.

LimitValue
Operations per job1,000
Request body size16 MiB
Serialised operation size512 KiB
Create requests per ad account10 requests per 10 seconds
Operation results per page100
Self-serve campaigns per ad account5,000 non-archived
Self-serve ad groups per ad account5,000 non-archived
Self-serve ads per ad account5,000 active or paused
operation_id and idempotency key lengthUp 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.

  1. Confirm access per client account before building a workflow around it, since it is limited preview and enabled per account.
  2. Run validate_only first, knowing it does not catch write-time errors.
  3. Create everything paused so a partial job cannot leave live spend behind.
  4. Use deterministic idempotency keys derived from your own campaign naming, so retries reuse rather than duplicate.
  5. Read all results only after complete is true, then reconcile failed and skipped operations.
  6. Activate deliberately afterwards, through the standard endpoints, once each level is verified.
  7. 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?

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.

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

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.

ChatGPT Ads Bulk API in 2026: FAQs

Is the ChatGPT Ads Bulk API available to every account?

No. OpenAI states it is in limited preview and enabled per ad account, and it is not included in the downloadable OpenAPI spec. If a bulk endpoint returns 404, contact your OpenAI account team to confirm access for the account tied to your Ads API key.

How many operations can one bulk job contain?

Between 1 and 1,000 create or update operations, within a 16 MiB request body and 512 KiB per serialised operation. Create requests are limited to 10 per 10 seconds per ad account.

Does partial_failure false roll back a failed bulk job?

No. Setting partial_failure to false skips later operations after one fails, but OpenAI states it does not roll back operations that already completed. Create resources paused so a partial job cannot leave live spend behind.

What does validate_only not check in the Bulk API?

It validates request fields and dependencies without changing resources, but OpenAI states it does not check update-target existence, image fetching, entity limits or other write-time errors. A clean validation run does not guarantee the real job succeeds.

How do I reference a campaign created in the same bulk job?

Use idempotency keys. Set campaign_idempotency_key on the ad group to the campaign operation's idempotency_key, and ad_group_idempotency_key on the ad to the ad group's key. Updates cannot target resources created in the same job.

How should I retry a failed bulk job?

Submit the same body with a new request-level Idempotency-Key; OpenAI states successful creates are reused. For an operation marked retryable, wait for retry_after_seconds where provided and resubmit in a new job reusing the original create-operation idempotency keys.

Run bulk changes like a deployment, not a script

Distk manages multi-account paid media with the same discipline as a production release: validated first, created paused, reconciled operation by operation, then activated on purpose.

Start the conversation →