- name:
- meta-api-reference
- description:
- Use this skill when working with the Meta Marketing API directly — endpoints, objects, fields, and request patterns for campaigns, ad sets, ads, audiences, and insights.
Meta 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.
