Skip to main content

Overview

Batch mode lets you upload multiple ads in a single session. Instead of running the upload chain manually for each ad, you define all ads in a JSON configuration file and Meta Ads Kit processes them sequentially.

When to Use Batch Mode

  • Launching a new campaign with 5+ ad variants
  • Testing multiple image + copy combinations
  • Refreshing creatives across an entire ad set
  • Migrating ads from another platform

Batch Configuration File

Location

workspace/campaigns/{campaign-name}/ads/batch-upload.json

Format

batch-upload.json
{
  "campaign": "summer-sale-2026",
  "adset_id": "23847000000001",
  "page_id": "123456789",
  "destination_url": "https://yoursite.com/lp",
  "ads": [
    {
      "name": "hero-notes-app",
      "image_path": "/path/to/notes-app.jpg",
      "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/notes-app.json"
    },
    {
      "name": "hero-receipt",
      "image_path": "/path/to/receipt.jpg",
      "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/receipt.json"
    },
    {
      "name": "hero-split-test",
      "image_path": "/path/to/split-test.jpg",
      "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/split-test.json"
    }
  ]
}

Required Fields

FieldDescriptionExample
campaignCampaign identifier for logging"summer-sale-2026"
adset_idTarget ad set ID (must exist)"23847000000001"
page_idFacebook Page ID"123456789"
destination_urlLanding page URL"https://yoursite.com/lp"
adsArray of ad definitionsSee below

Ad Definition Object

FieldDescriptionExample
nameAd name (for identification)"hero-notes-app"
image_pathAbsolute path to image file"/path/to/image.jpg"
asset_feed_spec_pathPath to asset_feed_spec JSON"workspace/campaigns/.../ad.json"

Asset Feed Spec Format

Each ad references an asset_feed_spec JSON file generated by the ad-copy-generator skill:
notes-app.json
{
  "bodies": [
    {
      "text": "Track expenses in seconds. No categories, no tags — just snap a photo and let AI handle the rest. Your money, finally organized."
    },
    {
      "text": "Tired of manual expense tracking? Snap a photo of any receipt and watch AI categorize, tag, and log it instantly. Built for people who hate budgeting apps."
    },
    {
      "text": "Stop losing receipts. Snap, AI extracts the data, files it, and builds your spending report. Simple enough for humans, smart enough for accountants."
    }
  ],
  "titles": [
    {
      "text": "AI That Reads Your Receipts"
    },
    {
      "text": "Never Lose a Receipt Again"
    },
    {
      "text": "Expense Tracking, Zero Effort"
    }
  ],
  "descriptions": [
    {
      "text": "Snap. AI logs it. Done."
    },
    {
      "text": "Finally, expense tracking that works."
    }
  ],
  "call_to_actions": [
    {
      "type": "LEARN_MORE",
      "value": {
        "link": "https://yoursite.com/lp",
        "link_caption": "yoursite.com"
      }
    }
  ]
}
You typically generate these files using the ad-copy-generator skill rather than writing them manually.

Running a Batch Upload

Natural Language

"Upload all ads in summer-sale-2026 campaign"
"Batch upload ads from batch-upload.json"

Processing Flow

For each ad in the batch:
  1. Validate - Check copy length, image dimensions, file exists
  2. Upload Image - POST to /adimages endpoint, get hash
  3. Create Creative - POST to /adcreatives with asset_feed_spec
  4. Create Ad - POST to /ads in PAUSED status
  5. Log Result - Save to {ad-name}.upload.json

Progress Output

[1/3] notes-app
  ✓ Validation passed
  ✓ Image uploaded (hash: a1b2c3d4...)
  ✓ Creative created (ID: 23847293847)
  ✓ Ad created (ID: 23847111111) - PAUSED

[2/3] receipt
  ✓ Validation passed
  ✓ Image uploaded (hash: d4e5f6g7...)
  ✓ Creative created (ID: 23847293848)
  ✓ Ad created (ID: 23847111112) - PAUSED

[3/3] split-test
  ✓ Validation passed
  ✓ Image uploaded (hash: g7h8i9j0...)
  ✓ Creative created (ID: 23847293849)
  ✓ Ad created (ID: 23847111113) - PAUSED

Batch complete: 3 ads created, all PAUSED
Review at: https://www.facebook.com/adsmanager/manage/ads?act=123456789

Validation Rules

Before uploading, Meta Ads Kit validates each ad:

Copy Validation

  • Max 40 characters (soft limit)
  • Hard stop at 50 characters
  • Error if truncation would occur on mobile

Image Validation

CheckRuleError
File existsMust be valid path”Image not found:
FormatJPG or PNG only”Format not supported”
File sizeUnder 30MB”Image too large: MB”
DimensionsMin 600x600px”Image too small: x
Aspect ratio1:1, 4:5, 16:9, or 9:16”Unsupported aspect ratio”
Images with >20% text overlay may be rejected by Meta’s ad review. The upload will succeed, but delivery will be limited.

Valid CTA Types

LEARN_MORE, SHOP_NOW, SIGN_UP, BOOK_TRAVEL, DOWNLOAD,
GET_OFFER, GET_QUOTE, SUBSCRIBE, WATCH_MORE, APPLY_NOW,
CONTACT_US, GET_DIRECTIONS, ORDER_NOW, REQUEST_TIME,
SEE_MENU, SEND_MESSAGE, BUY_TICKETS, CALL_NOW

Error Handling

If any ad fails validation or upload, the batch pauses:
[1/3] notes-app - ✓ Complete
[2/3] receipt - ✗ Failed
  Error: Headline "This headline is way too long for mobile and will be truncated" exceeds 50 chars

Batch paused. Fix the error and retry.

Retry from Failure Point

Meta Ads Kit tracks which ads completed successfully. When you fix the error and re-run, it skips already-uploaded ads:
[1/3] notes-app - ⊙ Skipped (already uploaded)
[2/3] receipt - ✓ Complete (fixed headline)
[3/3] split-test - ✓ Complete

Batch complete: 3 ads total, 2 newly uploaded

Dry Run Mode

Test your batch configuration without hitting the API:
"Dry run: batch upload summer-sale-2026 ads"
Output shows:
  • Validation results for each ad
  • Exact JSON payloads that would be sent
  • Estimated creative/ad names
  • No API calls made, no IDs returned
[DRY RUN] notes-app
  ✓ Validation passed
  
  Would POST to /act_123456789/adimages:
  {
    "filename": "notes-app.jpg",
    "source": "<binary data>"
  }
  
  Would POST to /act_123456789/adcreatives:
  {
    "name": "summer-sale-2026 - notes-app",
    "object_story_spec": {...},
    "asset_feed_spec": {...}
  }
  
  Would POST to /act_123456789/ads:
  {
    "name": "summer-sale-2026 - notes-app",
    "adset_id": "23847000000001",
    "status": "PAUSED"
  }

[DRY RUN] Complete - 3 ads validated, 0 uploaded

Advanced: Multiple Images per Ad

You can test multiple images with the same copy by providing an array of image paths:
{
  "name": "hero-multi-image",
  "image_paths": [
    "/path/to/variant-1.jpg",
    "/path/to/variant-2.jpg",
    "/path/to/variant-3.jpg"
  ],
  "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/hero.json"
}
Meta’s algorithm will test all image × copy combinations and find the winners. 3 images × 3 headlines × 3 bodies = 27 combinations tested automatically.

Output Files

After batch upload completes, Meta Ads Kit saves results:
workspace/campaigns/summer-sale-2026/ads/
  batch-upload.json           ← Your input configuration
  notes-app.json              ← asset_feed_spec (from ad-copy-generator)
  notes-app.upload.json       ← API response (creative ID, ad ID, status)
  notes-app.md                ← Human-readable copy document
  receipt.json
  receipt.upload.json
  receipt.md
  split-test.json
  split-test.upload.json
  split-test.md

upload.json Format

notes-app.upload.json
{
  "uploaded_at": "2026-03-04T08:00:00Z",
  "campaign": "summer-sale-2026",
  "ad_name": "hero-notes-app",
  "image_hash": "a1b2c3d4e5f6789abc",
  "creative_id": "23847293847293847",
  "ad_id": "23847111111111111",
  "adset_id": "23847000000001",
  "status": "PAUSED",
  "review_url": "https://www.facebook.com/adsmanager/manage/ads?act=123456789"
}
These IDs are also appended to workspace/brand/assets.md for tracking:
assets.md
| summer-sale-2026 hero-notes-app | ad | 2026-03-04 | creative: 238472... ad: 238471... | PAUSED |
| summer-sale-2026 hero-receipt | ad | 2026-03-04 | creative: 238472... ad: 238471... | PAUSED |
| summer-sale-2026 hero-split-test | ad | 2026-03-04 | creative: 238472... ad: 238471... | PAUSED |

Performance Considerations

Rate Limits

Meta’s Marketing API uses score-based rate limiting. Batch uploads consume more of your rate limit budget.
Recommended batch sizes:
  • Small account: 5-10 ads per batch
  • Medium account: 10-20 ads per batch
  • Large account: 20-50 ads per batch
If you hit rate limits (error 294), Meta Ads Kit automatically retries with exponential backoff:
[2/10] receipt - Rate limited (error 294)
  Waiting 10s before retry (attempt 1/3)...
  ✓ Retry successful

Sequential vs Parallel

Batch mode processes ads sequentially by default to avoid rate limits. For large batches (50+), you can enable parallel upload:
batch-upload.json
{
  "campaign": "summer-sale-2026",
  "parallel": true,
  "max_concurrent": 5,
  "ads": [...]
}
Parallel upload increases speed but also increases rate limit consumption. Only use for accounts with high API limits.

Example Workflow

Step 1: Generate Copy

"Generate copy for these 5 images" (attach images)
Outputs:
workspace/campaigns/summer-sale-2026/ads/
  image-1.json
  image-2.json
  image-3.json
  image-4.json
  image-5.json

Step 2: Create Batch Config

batch-upload.json
{
  "campaign": "summer-sale-2026",
  "adset_id": "23847000000001",
  "page_id": "123456789",
  "destination_url": "https://yoursite.com/summer-sale",
  "ads": [
    {"name": "image-1", "image_path": "/path/to/image-1.jpg", "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/image-1.json"},
    {"name": "image-2", "image_path": "/path/to/image-2.jpg", "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/image-2.json"},
    {"name": "image-3", "image_path": "/path/to/image-3.jpg", "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/image-3.json"},
    {"name": "image-4", "image_path": "/path/to/image-4.jpg", "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/image-4.json"},
    {"name": "image-5", "image_path": "/path/to/image-5.jpg", "asset_feed_spec_path": "workspace/campaigns/summer-sale-2026/ads/image-5.json"}
  ]
}

Step 3: Dry Run

"Dry run: batch upload summer-sale-2026 ads"
Review validation results, fix any errors.

Step 4: Upload

"Batch upload summer-sale-2026 ads"
All 5 ads created in PAUSED status.

Step 5: Review & Activate

Open Ads Manager, review each ad, activate the ones you want to run.

Next Steps

Graph API

Direct API usage and rate limits

OpenClaw Integration

Natural language ad management

Build docs developers (and LLMs) love