← All skills
  • Marketing
  • v1.0.0
  • ads

Meta Marketing API reference

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.

SKILL.md

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 .env comment 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_budget or lifetime_budget on 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_budget or lifetime_budget on 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

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 countries for individual countries: ['US', 'CA', 'GB']
  • Use country_groups for 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
# 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.

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'}
    }
}
'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) 7 days REMOVED
28 days REMOVED

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_id on 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:

  1. upload_phase=start + file_size -> returns upload_session_id
  2. upload_phase=transfer + upload_session_id + start_offset + video_file_chunk (loop)
  3. 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

  1. Always Python, never curl - token contains characters that get mangled by shell interpolation
  2. Always load token from .env via dotenv - never paste tokens into commands
  3. Budgets in cents - 5000 = $50.00
  4. is_adset_budget_sharing_enabled is a string - "true" / "false", not boolean
  5. To update URLs, copy, or any creative content - POST to the AD (not the creative) with inline creative JSON containing the full spec with changes. url_tags can also be changed this way.
  6. Instagram account in TWO places - both top-level instagram_user_id and inside object_story_spec
  7. Minimal object_story_spec with asset_feed_spec - only page_id + instagram_user_id, no link_data
  8. optimization_type: PLACEMENT for different images per placement, REGULAR for dynamic creative
  9. REGULAR limits to 1 ad per ad set - use PLACEMENT when you need multiple ads
  10. Video thumbnails required - fetch from /{video_id}/thumbnails after upload
  11. Files >100MB fail single upload - always use chunked upload for large videos
  12. Exclude Singapore from targeting to avoid regulatory compliance fields
  13. Custom Audience TOS - Error 1359208 requires re-accepting terms in Ads Manager UI (API cannot detect this)
  14. Empty data: [] = no activity in that period, not an error
  15. Cannot combine delivery + action breakdowns in same insights request
  16. Ad copy formatting - always use \n\n between paragraphs, \n within lists
  17. Threads profile identity - set threads_user_id at the creative level (NOT inside object_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.

Files

1 file ship with this skill. They are installed beside the SKILL.md.

NOTICE1.4 KB
Meta Marketing API reference
Imported from the GTM Skills library by Swan (https://www.gtmskills.com), skills/ivan-falco/meta-api-reference
at https://github.com/swan-gtm/gtm-skills/commit/5d786aad0c7b2ca59b1abf1f601f9407ca261996. Written by Ivan Falco (https://linkedin.com/in/ivanfalco).
The skill is shared under the MIT License below; this notice must stay with any copy.

MIT License

Copyright (c) 2026 Swan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.