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
{
"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
Field Description Example 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 definitions See below
Ad Definition Object
Field Description Example 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"
Each ad references an asset_feed_spec JSON file generated by the ad-copy-generator skill:
{
"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:
Validate - Check copy length, image dimensions, file exists
Upload Image - POST to /adimages endpoint, get hash
Create Creative - POST to /adcreatives with asset_feed_spec
Create Ad - POST to /ads in PAUSED status
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
Headlines
Body
Description
CTA
Max 40 characters (soft limit)
Hard stop at 50 characters
Error if truncation would occur on mobile
Min 50 characters
Max 500 characters per variant
At least 1 variant required, max 5
Max 30 characters
Optional but recommended
Image Validation
Check Rule Error File exists Must be valid path ”Image not found: “ Format JPG or PNG only ”Format not supported” File size Under 30MB ”Image too large: MB” Dimensions Min 600x600px ”Image too small: x” Aspect ratio 1: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
{
"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:
| 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 |
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:
{
"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
{
"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