About meta-api-reference
meta-api-reference is a reference skill for developers and marketers working with Meta Marketing API campaigns. It documents campaign, ad set, creative, and targeting schemas, with field requirements and API values.
Key features
- 01Campaign and ad set field reference
- 02Objectives and bidding options
- 03Targeting specification guidance
- 04Ad creative schema patterns
Use cases
- 01Planning campaign API requests
- 02Checking required ad set fields
- 03Configuring audience targeting
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ swan-gtm/ gtm-skills --skill meta-api-referenceAfter installing, type/meta-api-referencein Claude Code to confirm it loaded.
Details
From the project READMEMeta Marketing API - Complete Reference
Configuration
- API Version:
v22.0 - Base URL:
https://graph.facebook.com/v22.0/ - Two tokens in
.env:ADS_MANAGER_ACCESS_TOKEN- Marketing API (campaign management, insights, reporting)AD_LIBRARY_ACCESS_TOKEN- Ad Library API (competitor research)
- Token expiry: ~60 days from last refresh (check
.envcomment for date)
Account Hierarchy
Ad Account (act_XXXX)
-> Campaign (objective, budget if CBO, bid strategy)
-> Ad Set (targeting, budget if ABO, optimization, schedule)
-> Ad (creative reference, status)
-> Ad Creative (media, copy, CTA, url_tags)
Campaign Schema
Endpoint: POST /{account_id}/campaigns
Required Fields
| Field | Type | Description |
|---|---|---|
name | string | Campaign name |
objective | enum | Marketing objective (ODAX framework) |
status | enum | Campaign status |
special_ad_categories | array | Special category declarations (pass [] if none) |
Optional Fields
| Field | Type | Description |
|---|---|---|
daily_budget | int (cents) | Daily budget for CBO (5000 = $50.00) |
lifetime_budget | int (cents) | Lifetime budget for CBO |
bid_strategy | enum | Bidding strategy |
buying_type | enum | Buying type |
is_adset_budget_sharing_enabled | string | "true" for CBO, "false" for ABO |
execution_options | array | ["validate_only"] or ["include_recommendations"] |
Objective (ODAX Framework)
| Value | Description | Use For |
|---|---|---|
OUTCOME_AWARENESS | Brand awareness, reach | Top of funnel |
OUTCOME_ENGAGEMENT | Post engagement, video views, page likes | Mid funnel |
OUTCOME_TRAFFIC | Link clicks, landing page views | Mid funnel |
OUTCOME_LEADS | Lead gen forms, conversions (lead) | Bottom of funnel |
OUTCOME_SALES | Conversions (purchase), catalog sales | Bottom of funnel |
OUTCOME_APP_PROMOTION | App installs, app engagement | App campaigns |
Legacy objectives (deprecated for new campaigns, returned by API for old ones): CONVERSIONS, LEAD_GENERATION, LINK_CLICKS, BRAND_AWARENESS, REACH, POST_ENGAGEMENT, VIDEO_VIEWS, MESSAGES, APP_INSTALLS, PAGE_LIKES, EVENT_RESPONSES, STORE_VISITS, PRODUCT_CATALOG_SALES
Bid Strategy
| Value | Description |
|---|---|
LOWEST_COST_WITHOUT_CAP | Lowest cost, no cap (default) |
COST_CAP | Cost cap - set max cost per result |
LOWEST_COST_WITH_BID_CAP | Bid cap - set max bid per auction |
LOWEST_COST_WITH_MIN_ROAS | Min ROAS - target minimum return |
Special Ad Categories
| Value | Description |
|---|---|
CREDIT | Credit offers |
EMPLOYMENT | Job opportunities |
HOUSING | Housing/real estate |
ISSUES_ELECTIONS_POLITICS | Social issues, elections, politics |
FINANCIAL_PRODUCTS_SERVICES | Financial products |
ONLINE_GAMBLING_AND_GAMING | Gambling/gaming |
NONE | No special category |
Buying Type
| Value | Description |
|---|---|
AUCTION | Standard auction buying (default, used 99% of the time) |
RESERVED | Reserved/guaranteed delivery (Reach & Frequency) |
Campaign Status
ACTIVE, PAUSED, DELETED, ARCHIVED
CBO vs ABO
CBO (Campaign Budget Optimization):
- Set
daily_budgetorlifetime_budgeton the campaign - Set
is_adset_budget_sharing_enabled: "true"on the campaign - Do NOT set budget on ad sets
- Meta distributes budget across ad sets automatically
ABO (Ad Set Budget Optimization):
- Set
is_adset_budget_sharing_enabled: "false"on the campaign - Set
daily_budgetorlifetime_budgeton each ad set - Do NOT set budget on the campaign
- Each ad set gets its own independent budget
Ad Set Schema
Endpoint: POST /{account_id}/adsets
Required Fields
| Field | Type | Description |
|---|---|---|
name | string | Ad set name |
campaign_id | string | Parent campaign ID |
optimization_goal | enum | What to optimize for |
billing_event | enum | When to charge |
targeting | JSON object | Targeting specification |
status | enum | Ad set status |
daily_budget or lifetime_budget | int (cents) | Budget (ABO only) |
Optional Fields
| Field | Type | Description |
|---|---|---|
bid_amount | int (cents) | Bid amount (required for cost_cap or bid_cap) |
bid_strategy | enum | Overrides campaign bid strategy |
start_time | ISO 8601 | When to start delivering |
end_time | ISO 8601 | When to stop delivering |
promoted_object | JSON object | Pixel/event for conversion optimization |
destination_type | enum | Where the ad sends people |
is_dynamic_creative | bool | Enable dynamic creative |
pacing_type | array | ["standard"] or ["day_parting"] |
dsa_beneficiary | string | DSA compliance - beneficiary name (EU) |
dsa_payor | string | DSA compliance - payor name (EU) |
targeting_automation | JSON object | Advantage+ audience settings |
Optimization Goal (34 values)
Most commonly used:
| Value | Use With Objective | Description |
|---|---|---|
OFFSITE_CONVERSIONS | OUTCOME_LEADS, OUTCOME_SALES | Website conversions via pixel |
LEAD_GENERATION | OUTCOME_LEADS | On-platform lead form submissions |
LINK_CLICKS | OUTCOME_TRAFFIC | Link clicks |
LANDING_PAGE_VIEWS | OUTCOME_TRAFFIC | Landing page views (requires pixel) |
REACH | OUTCOME_AWARENESS | Maximum unique reach |
IMPRESSIONS | OUTCOME_AWARENESS | Maximum impressions |
AD_RECALL_LIFT | OUTCOME_AWARENESS | Ad recall optimization |
POST_ENGAGEMENT | OUTCOME_ENGAGEMENT | Post interactions |
PAGE_LIKES | OUTCOME_ENGAGEMENT | Page likes |
THRUPLAY | OUTCOME_ENGAGEMENT | Video views (15s+) |
VALUE | OUTCOME_SALES | Conversion value (ROAS) |
APP_INSTALLS | OUTCOME_APP_PROMOTION | App installs |
CONVERSATIONS | OUTCOME_LEADS | Messaging conversations |
QUALITY_LEAD | OUTCOME_LEADS | Quality leads (CRM-optimized) |
Full list (additional): ADVERTISER_SILOED_VALUE, APP_INSTALLS_AND_OFFSITE_CONVERSIONS, AUTOMATIC_OBJECTIVE, DERIVED_EVENTS, ENGAGED_USERS, EVENT_RESPONSES, IN_APP_VALUE, MEANINGFUL_CALL_ATTEMPT, MESSAGING_APPOINTMENT_CONVERSION, MESSAGING_PURCHASE_CONVERSION, NONE, PROFILE_AND_PAGE_ENGAGEMENT, PROFILE_VISIT, QUALITY_CALL, REACH, REMINDERS_SET, SUBSCRIBERS, VISIT_INSTAGRAM_PROFILE
Billing Event
| Value | Description |
|---|---|
IMPRESSIONS | Charge per impression (most common, used 95%+ of the time) |
LINK_CLICKS | Charge per link click |
APP_INSTALLS | Charge per app install |
THRUPLAY | Charge per video view |
CLICKS | Charge per any click |
PAGE_LIKES | Charge per page like |
POST_ENGAGEMENT | Charge per engagement |
Full list (additional): LISTING_INTERACTION, NONE, OFFER_CLAIMS, PURCHASE
Destination Type (most common)
| Value | Description |
|---|---|
WEBSITE | Send to website URL (most common) |
ON_AD | On-ad destination (lead forms, instant experiences) |
MESSENGER | Messenger conversation |
WHATSAPP | WhatsApp conversation |
INSTAGRAM_DIRECT | Instagram DM |
APP | Mobile app |
FACEBOOK | Facebook destination |
ON_POST | On post |
ON_VIDEO | On video |
SHOP_AUTOMATIC | Shop automatic |
Full list (additional): APPLINKS_AUTOMATIC, FACEBOOK_LIVE, FACEBOOK_PAGE, IMAGINE, INSTAGRAM_LIVE, INSTAGRAM_PROFILE, INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE, MESSAGING_INSTAGRAM_DIRECT_MESSENGER, MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP, MESSAGING_INSTAGRAM_DIRECT_WHATSAPP, MESSAGING_MESSENGER_WHATSAPP, ON_EVENT, ON_PAGE
Promoted Object
Required for conversion optimization:
# For pixel conversions (OUTCOME_LEADS or OUTCOME_SALES)
'promoted_object': json.dumps({
'pixel_id': 'YOUR_PIXEL_ID',
'custom_event_type': 'LEAD' # or PURCHASE, COMPLETE_REGISTRATION, etc.
})
# For lead gen forms (OUTCOME_LEADS with LEAD_GENERATION goal)
'promoted_object': json.dumps({
'page_id': 'YOUR_PAGE_ID'
})
# For app installs
'promoted_object': json.dumps({
'application_id': 'YOUR_APP_ID',
'object_store_url': 'https://apps.apple.com/...'
})
custom_event_type Values
LEAD, PURCHASE, COMPLETE_REGISTRATION, ADD_TO_CART, ADD_TO_WISHLIST, INITIATE_CHECKOUT, ADD_PAYMENT_INFO, CONTACT, CUSTOMIZE_PRODUCT, DONATE, FIND_LOCATION, SCHEDULE, START_TRIAL, SUBMIT_APPLICATION, SUBSCRIBE, SEARCH, VIEW_CONTENT, OTHER
Targeting Spec
Complete Structure
targeting = {
# GEOGRAPHIC (required)
'geo_locations': {
'countries': ['US', 'CA', 'GB'], # ISO country codes
'country_groups': ['north_america', 'europe'], # OR use region codes
'regions': [{'key': '3847'}], # State/province
'cities': [{'key': '2430536', 'radius': 10, 'distance_unit': 'mile'}],
'zips': [{'key': 'US:10001'}],
'location_types': ['home', 'recent'] # home, recent, travel_in
},
# DEMOGRAPHICS
'age_min': 25, # 13-65
'age_max': 55, # 13-65
'genders': [1], # 1=male, 2=female, omit or [0]=all
'locales': [6], # Language codes (6=English)
# INTEREST/BEHAVIOR TARGETING
'flexible_spec': [
{
'interests': [{'id': '6003107902433', 'name': 'SaaS'}],
'behaviors': [{'id': '6002714895372', 'name': 'Small business owners'}]
}
],
# flexible_spec logic:
# Within one object: fields are OR'd (interest1 OR behavior1)
# Multiple objects in array: OR'd together
# Use 'exclusions' for AND NOT logic
'exclusions': {
'interests': [{'id': '123', 'name': 'Excluded Interest'}]
},
# AUDIENCES
'custom_audiences': [{'id': 'YOUR_AUDIENCE_ID'}],
'excluded_custom_audiences': [{'id': 'YOUR_EXCLUDED_AUDIENCE_ID'}],
# ADVANTAGE+ AUDIENCE
'targeting_optimization': 'expansion_all', # Enable Advantage+ expansion
# PLATFORMS & PLACEMENTS (usually omit for Advantage+ Placements)
'publisher_platforms': ['facebook', 'instagram', 'audience_network', 'messenger'],
'device_platforms': ['mobile', 'desktop'],
'facebook_positions': ['feed', 'story', 'facebook_reels', 'right_hand_column', 'search', 'marketplace', 'video_feeds', 'instream_video', 'profile_feed', 'biz_disco_feed', 'facebook_reels_overlay'],
'instagram_positions': ['stream', 'story', 'reels', 'profile_reels', 'ig_search', 'explore', 'explore_home', 'profile_feed'],
'messenger_positions': ['story', 'inbox'],
'audience_network_positions': ['classic', 'rewarded_video'],
# CONNECTIONS
'connections': [{'id': 'YOUR_PAGE_ID'}], # Connected to page
'excluded_connections': [{'id': 'YOUR_PAGE_ID'}] # Not connected to page
}
Advantage+ Audience (v23.0+)
'targeting_automation': {
'advantage_audience': 1 # 1 = enabled, 0 = disabled
}
When enabled, Meta's AI expands beyond your defined targeting to find high-intent users. You still provide base targeting (geo, age, etc.) but Meta can go beyond it. Defaults to enabled in API v23.0+.
Geo Targeting Rules
- Use
countriesfor individual countries:['US', 'CA', 'GB'] - Use
country_groupsfor regions:['north_america', 'europe'] - Don't list 30+ individual countries when a region covers it
- Never overlap locations (e.g., both US and New York City causes error)
- Exclude Singapore to avoid regulatory compliance fields
Interest/Behavior Search
# Search for targeting interests
GET /search?type=adinterest&q=KEYWORD&limit=10000&locale=en_US&access_token=TOKEN
# Response:
# { "data": [{ "id": "6003107902433", "name": "SaaS", "audience_size": 123456, "path": ["..."], "topic": "..." }] }
# Alternative: interest suggestions
GET /search?type=adinterestsuggestion&interest_list=["SaaS"]&access_token=TOKEN
API reveals 1000s of interests not shown in Ads Manager's autocomplete (which only shows ~25).
2026 Note: Many detailed targeting interests consolidated starting June 2025. Campaigns using interests created before Oct 8, 2025 may stop delivering.
Ad Creative Schema
Endpoint: POST /{account_id}/adcreatives
Core Fields
| Field | Type | Description |
|---|---|---|
name | string | Creative name |
object_story_spec | JSON object | Main creative content container |
url_tags | string | URL parameters (SET AT CREATION ONLY - cannot update) |
instagram_user_id | string | IG business account ID (set both here AND in object_story_spec) |
threads_user_id | string | Threads profile ID (set at creative level, NOT inside object_story_spec) |
asset_feed_spec | JSON object | For placement customization or dynamic creative |
object_story_spec - Two Patterns
Pattern A: Simple ad (link_data)
'object_story_spec': json.dumps({
'page_id': 'YOUR_PAGE_ID',
'instagram_user_id': 'YOUR_IG_USER_ID',
'link_data': {
'image_hash': 'HASH',
'link': 'https://landing-page.com',
'message': 'Body text',
'name': 'Headline',
'description': 'Description',
'call_to_action': {
'type': 'SIGN_UP',
'value': {'link': 'https://landing-page.com'}
}
}
})
Pattern B: Placement customization (asset_feed_spec)
'object_story_spec': json.dumps({
'page_id': 'YOUR_PAGE_ID',
'instagram_user_id': 'YOUR_IG_USER_ID'
# NO link_data - all content goes in asset_feed_spec
})
CRITICAL: When using asset_feed_spec, object_story_spec must be MINIMAL (only page_id + instagram_user_id). Including link_data causes error.
link_data Fields
| Field | Type | Description |
|---|---|---|
link | string | Destination URL |
message | string | Ad body text |
name | string | Headline |
description | string | Description under headline |
caption | string | Link caption/domain display |
image_hash | string | Uploaded image hash |
picture | string | Image URL (alternative to image_hash) |
call_to_action | object | CTA button config |
child_attachments | array | Carousel cards (2-10) |
multi_share_optimized | bool | Let Meta show best-performing cards |
multi_share_end_card | bool | Show end card on carousel |
video_data Fields
'video_data': {
'video_id': 'VIDEO_ID', # From video upload (required)
'image_url': 'THUMBNAIL_URL', # From /{video_id}/thumbnails (required)
'title': 'Video title',
'message': 'Body text',
'link_description': 'Description',
'call_to_action': {
'type': 'LEARN_MORE',
'value': {'link': 'https://landing.com'}
}
}
Carousel child_attachments
'child_attachments': [
{
'link': 'https://product1.com',
'name': 'Card 1 Headline',
'description': 'Card 1 description',
'image_hash': 'IMAGE_HASH_1',
'call_to_action': {
'type': 'SHOP_NOW',
'value': {'link': 'https://product1.com'}
}
},
{
'link': 'https://product2.com',
'name': 'Card 2 Headline',
'description': 'Card 2 description',
'image_hash': 'IMAGE_HASH_2'
}
# 2-10 cards total, each card needs image_hash + link at minimum
]
CTA Types (Complete List)
Most commonly used in B2B:
| Value | Description |
|---|---|
SIGN_UP | Sign up |
LEARN_MORE | Learn more |
DOWNLOAD | Download |
SUBSCRIBE | Subscribe |
APPLY_NOW | Apply now |
GET_QUOTE | Get quote |
CONTACT_US | Contact us |
BOOK_NOW | Book now |
REGISTER_NOW | Register now |
SHOP_NOW | Shop now |
MESSAGE_PAGE | Send message |
Full list by category:
Commerce: BUY_NOW, BUY_TICKETS, ORDER_NOW, SELL_NOW, GET_OFFER, ADD_TO_CART, PURCHASE_GIFT_CARDS, PAY_TO_ACCESS
Information: SEE_MENU, GET_SHOWTIMES, GET_DIRECTIONS
App: INSTALL_APP, INSTALL_MOBILE_APP, USE_APP, USE_MOBILE_APP, GET_MOBILE_APP, PLAY_GAME, UPDATE_APP
Engagement: BOOK_TRAVEL, REQUEST_TIME, SEND_INVITES, REFER_FRIENDS, OPEN_MESSENGER_EXT, INSTAGRAM_MESSAGE, CALL, CALL_NOW, CALL_ME
Media: WATCH_VIDEO, WATCH_MORE, LISTEN_MUSIC, LISTEN_NOW, GO_LIVE, RECORD_NOW
Social: LIKE_PAGE, VIEW_INSTAGRAM_PROFILE, INTERESTED, SAVE, SEND_TIP, DONATE, DONATE_NOW, EVENT_RSVP, VOTE_NOW
Other: OPEN_LINK, NO_BUTTON, BET_NOW, PRE_REGISTER, GET_EVENT_TICKETS, SEARCH_MORE
url_tags
- Set on creative at creation time only - cannot update, must recreate creative
- Standard format:
utm_source=meta&utm_medium=paid-social&utm_campaign={{adset.name}}&utm_content={{ad.name}} - Dynamic params:
{{campaign.name}},{{campaign.id}},{{adset.name}},{{adset.id}},{{ad.name}},{{ad.id}},{{placement}},{{site_source_name}}
Ad Schema
Endpoint: POST /{account_id}/ads
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Ad name |
adset_id | string | Yes | Parent ad set ID |
creative | JSON object | Yes | {"creative_id": "CREATIVE_ID"} |
status | enum | Yes | PAUSED (NEVER set to ACTIVE) |
tracking_specs | JSON array | No | Additional conversion tracking |
Custom Audience Schema
Endpoint: POST /{account_id}/customaudiences
Customer List Upload
# Step 1: Create audience
'name': 'My Customer List',
'subtype': 'CUSTOM',
'customer_file_source': 'USER_PROVIDED_ONLY'
# Step 2: Add users via /{audience_id}/users
'schema': ['EMAIL', 'FN', 'LN'], # or 'PHONE', 'MOBILE_ADVERTISER_ID', 'UID'
'data': [
['sha256_hashed_email', 'sha256_hashed_firstname', 'sha256_hashed_lastname'],
# ... up to 10,000 per batch
]
All PII fields must be SHA-256 hashed before sending. Normalize: lowercase, trim whitespace, E.164 format for phones.
Website Custom Audience
'name': 'Website Visitors - Last 30 Days',
'subtype': 'WEBSITE',
'rule': json.dumps({
'inclusions': {
'operator': 'or',
'rules': [{'event_sources': [{'id': 'YOUR_PIXEL_ID', 'type': 'pixel'}], 'retention_seconds': 2592000}]
}
})
Lookalike Audience
'name': '1% Lookalike - US - Customer List',
'subtype': 'LOOKALIKE',
'origin_audience_id': 'SOURCE_AUDIENCE_ID', # min 100 members, recommended 1000-5000
'lookalike_spec': json.dumps({
'ratio': 0.01, # 1% similarity (0.01 to 0.20)
'country': 'US', # Single country per lookalike
'starting_ratio': 0.00 # Optional: for tiered lookalikes (e.g., 1-2% tier)
})
Tiered pattern: Create 0-1% (ratio=0.01), then 1-2% (ratio=0.02, starting_ratio=0.01), then 2-3% (ratio=0.03, starting_ratio=0.02).
2026 Note: lookalike_spec is MANDATORY for creating new lookalike audiences (since Jan 6, 2026).
Reach Estimation
Endpoint: GET /{account_id}/reachestimate
params = {
'access_token': token,
'currency': 'USD',
'optimize_for': 'OFFSITE_CONVERSIONS',
'targeting_spec': json.dumps({
'geo_locations': {'countries': ['US']},
'age_min': 25,
'age_max': 55
})
}
Returns estimated audience size as a range, estimated daily results, and bid estimates.
Insights / Reporting
Endpoint: GET /{object_id}/insights (works at account, campaign, ad set, or ad level)
Core Parameters
| Parameter | Type | Description |
|---|---|---|
fields | string | Comma-separated metrics |
date_preset | enum | Predefined date range |
time_range | JSON | Custom date range {"since":"2026-01-01","until":"2026-01-31"} |
level | enum | campaign, adset, ad (breakdown level) |
breakdowns | string | Comma-separated delivery breakdowns |
action_breakdowns | string | Comma-separated action breakdowns |
time_increment | int or string | 1-90 (days), "monthly", "all_days" |
limit | int | Results per page (default 25) |
filtering | JSON array | Filter conditions |
Date Presets
today, yesterday, last_3d, last_7d, last_14d, last_28d, last_30d, last_90d, this_week_mon_today, this_week_sun_today, last_week_mon_sun, last_week_sun_sat, this_month, last_month, this_quarter, last_quarter, this_year, last_year, maximum, lifetime
Common Metrics
spend, impressions, clicks, reach, frequency, ctr, cpc, cpm, actions, cost_per_action_type, action_values, conversions, cost_per_conversion, conversion_values, purchase_roas
Delivery Breakdowns
| Breakdown | Description |
|---|---|
age | Age ranges |
gender | Gender |
country | Country |
region | Region/state |
dma | DMA (US only) |
publisher_platform | Facebook, Instagram, etc. |
platform_position | Feed, stories, reels, etc. |
device_platform | Mobile, desktop |
impression_device | Device type |
frequency_value | Frequency distribution |
hourly_stats_aggregated_by_advertiser_time_zone | Hour of day (advertiser TZ) |
hourly_stats_aggregated_by_audience_time_zone | Hour of day (audience TZ) |
product_id | Product catalog ID |
Dynamic creative breakdowns: ad_format_asset, body_asset, call_to_action_asset, description_asset, image_asset, link_url_asset, title_asset, video_asset
CRITICAL: Cannot combine delivery breakdowns and action_breakdowns in the same request. Request them separately.
Action Breakdowns
| Breakdown | Description |
|---|---|
action_type | Type of action (lead, purchase, etc.) |
action_device | Device where action occurred |
action_destination | Where action led to |
action_type Values
Most commonly used:
| action_type | Description |
|---|---|
lead | On-platform lead form submission |
link_click | Link click |
landing_page_view | Landing page view |
post_engagement | Post interaction |
video_view | Video view |
page_engagement | Page interaction |
onsite_conversion.lead_grouped | Grouped on-platform lead events |
offsite_conversion.fb_pixel_lead | Pixel lead event |
offsite_conversion.fb_pixel_purchase | Pixel purchase event |
offsite_conversion.fb_pixel_add_to_cart | Pixel add to cart |
offsite_conversion.fb_pixel_initiate_checkout | Pixel checkout initiated |
offsite_conversion.fb_pixel_complete_registration | Pixel registration |
offsite_conversion.fb_pixel_view_content | Pixel content view |
offsite_conversion.fb_pixel_custom | Pixel custom conversion |
omni_purchase | Cross-channel purchase |
omni_add_to_cart | Cross-channel add to cart |
Extracting Conversions from Actions
actions = row.get('actions', [])
leads = sum(int(a['value']) for a in actions if a['action_type'] == 'lead')
pixel_leads = sum(int(a['value']) for a in actions if a['action_type'] == 'offsite_conversion.fb_pixel_lead')
Async Reports (Large Datasets)
For large date ranges or complex breakdowns:
# Request async report
params['async'] = True
r = requests.get(url, params=params)
report_id = r.json()['report_run_id']
# Poll for completion
status_url = f'https://graph.facebook.com/v22.0/{report_id}'
r = requests.get(status_url, params={'access_token': token})
# Check async_status: "Job Completed", "Job Running", etc.
# Download results
results_url = f'https://graph.facebook.com/v22.0/{report_id}/insights'
r = requests.get(results_url, params={'access_token': token})
Data Retention (2026)
- Unique-count fields: 13 months historical data
- Frequency breakdowns: 6 months
- Hourly breakdowns: 13 months
- Over 100 metrics deprecated (including unique_actions)
Attribution Settings (2026)
attribution_spec
'attribution_spec': json.dumps([
{'event_type': 'CLICK_THROUGH', 'window_days': 7},
{'event_type': 'VIEW_THROUGH', 'window_days': 1}
])
Available Windows (After Jan 12, 2026)
| Click-Through | View-Through |
|---|---|
| 1 day | 1 day |
| 7 days (default) | |
Default: 7-day click + 1-day view (7d_click / 1d_view).
Impact: 7-day and 28-day view-through windows were removed January 12, 2026. Historical reports using those windows no longer return data.
Conversions API (CAPI) - Server-Side Events
Endpoint: POST /{pixel_id}/events
Event Structure
{
'data': [
{
'event_name': 'Lead', # Standard event name
'event_time': 1709568234, # Unix timestamp
'event_id': 'unique-event-12345', # For deduplication with pixel
'action_source': 'website', # website, app, offline, etc.
'event_source_url': 'https://site.com/page',
'user_data': {
'em': ['sha256_hashed_email'], # MUST be hashed
'ph': ['sha256_hashed_phone'], # MUST be hashed, E.164 format
'fn': ['sha256_hashed_first'], # MUST be hashed, lowercase
'ln': ['sha256_hashed_last'], # MUST be hashed, lowercase
'fbp': 'fb.1.123.456', # NOT hashed (browser cookie)
'fbc': 'fb.1.123.456', # NOT hashed (click cookie)
'external_id': ['sha256_hashed_id'],
'client_ip_address': '1.2.3.4',
'client_user_agent': 'Mozilla/5.0...'
},
'custom_data': {
'value': 99.99, # Required for Purchase
'currency': 'USD', # Required for Purchase
'content_ids': ['product123'],
'content_type': 'product',
'order_id': 'order-456'
}
}
],
'access_token': token
}
Standard Event Names
Purchase, Lead, ViewContent, AddToCart, InitiateCheckout, CompleteRegistration, AddPaymentInfo, AddToWishlist, Contact, FindLocation, Schedule, Search, StartTrial, SubmitApplication, Subscribe, CustomizeProduct, Donate
Deduplication
- Both Pixel and CAPI must send identical
event_name+event_id - Meta auto-deduplicates matching events
- Without deduplication, conversions counted twice
- Generate
event_idon server, pass to both Pixel and CAPI
user_data Hashing Rules
| Field | Hash? | Notes |
|---|---|---|
em (email) | SHA-256 | Normalize to lowercase first |
ph (phone) | SHA-256 | E.164 format, no spaces/dashes |
fn (first name) | SHA-256 | Lowercase |
ln (last name) | SHA-256 | Lowercase |
external_id | SHA-256 | Your customer ID |
fbp | NO | Browser cookie, send raw |
fbc | NO | Click cookie, send raw |
client_ip_address | NO | Send raw |
client_user_agent | NO | Send raw |
CRITICAL: Hashing fbp or fbc breaks matching entirely.
Image Upload
Endpoint: POST /{account_id}/adimages
Requirements
- Format: JPG or PNG
- Max size: 30MB
- Recommended: 1080x1080px minimum
- Aspect ratios: 1:1 (feed), 4:5 (feed), 9:16 (stories/reels)
Response
{
"images": {
"filename.jpg": {
"hash": "a1b2c3d4e5f6...",
"url": "https://scontent.xx.fbcdn.net/...",
"height": 1080,
"width": 1080
}
}
}
Video Upload
Endpoint: POST /{account_id}/advideos
Requirements
- Format: MP4, MOV (H.264 codec recommended)
- Max size: 4GB
- Feed: 1s to 240 minutes, aspect 4:5 or 1:1
- Stories/Reels: 1-60s (15s recommended), aspect 9:16
- Thumbnail REQUIRED for ad creation - fetch from
GET /{video_id}/thumbnails
Chunked Upload (for files >100MB)
Three phases:
upload_phase=start+file_size-> returnsupload_session_idupload_phase=transfer+upload_session_id+start_offset+video_file_chunk(loop)upload_phase=finish+upload_session_id+title-> returns video ID
Pagination
data = r.json()
results = data.get('data', [])
while 'paging' in data and 'next' in data['paging']:
r = requests.get(data['paging']['next'])
data = r.json()
results.extend(data.get('data', []))
CRUD Operations
Create
r = requests.post(f'https://graph.facebook.com/v22.0/{parent_id}/{edge}', data={...})
# edge: campaigns, adsets, ads, adcreatives, adimages, advideos, customaudiences
Read
r = requests.get(f'https://graph.facebook.com/v22.0/{object_id}', params={
'fields': 'field1,field2,...',
'access_token': token
})
Update
r = requests.post(f'https://graph.facebook.com/v22.0/{object_id}', data={
'name': 'New Name',
'status': 'PAUSED',
'access_token': token
})
Delete
r = requests.delete(f'https://graph.facebook.com/v22.0/{object_id}', params={
'access_token': token
})
Key Gotchas & Rules
- Always Python, never curl - token contains characters that get mangled by shell interpolation
- Always load token from
.envviadotenv- never paste tokens into commands - Budgets in cents - 5000 = $50.00
is_adset_budget_sharing_enabledis a string -"true"/"false", not boolean- To update URLs, copy, or any creative content - POST to the AD (not the creative) with inline
creativeJSON containing the full spec with changes.url_tagscan also be changed this way. - Instagram account in TWO places - both top-level
instagram_user_idand insideobject_story_spec - Minimal
object_story_specwithasset_feed_spec- only page_id + instagram_user_id, no link_data optimization_type: PLACEMENTfor different images per placement,REGULARfor dynamic creativeREGULARlimits to 1 ad per ad set - usePLACEMENTwhen you need multiple ads- Video thumbnails required - fetch from
/{video_id}/thumbnailsafter upload - Files >100MB fail single upload - always use chunked upload for large videos
- Exclude Singapore from targeting to avoid regulatory compliance fields
- Custom Audience TOS - Error 1359208 requires re-accepting terms in Ads Manager UI (API cannot detect this)
- Empty
data: []= no activity in that period, not an error - Cannot combine delivery + action breakdowns in same insights request
- Ad copy formatting - always use
\n\nbetween paragraphs,\nwithin lists - Threads profile identity - set
threads_user_idat the creative level (NOT insideobject_story_spec). To find the Threads profile ID, check an existing working creative:GET /{creative_id}?fields=threads_user_id. The Threads profile is tied to the Instagram account, not the Facebook Page directly.