Skip to content
Streamkap
Esc
↑↓navigate↵open⌘Jpreview
On this page

Facebook Ads

Stream Meta ad objects, lead-form leads and insights reports into Streamkap with a system-user access token from your own Meta Business.

The Facebook Ads source reads one or more Meta ad accounts through the Marketing API: ad objects, the leads of their lead forms, and insights reports. Each resource you select becomes a topic named source_<id>.facebook_ads.<resource>.

Prerequisites

  • A Meta Business that owns the ad account, with admin access to its Business settings.
  • Access to the Meta App Dashboard to create an app in that Business.
  • A Streamkap workspace with permission to create source connectors.

The token needs the ads_read permission, and its system user needs the ad account assigned with at least View performance. To sync leads it also needs:

  • The leads_retrieval, pages_show_list, pages_read_engagement and pages_manage_ads permissions.
  • Each Page the lead ads run on assigned to the system user with Leads access.

Streamkap never requests the ad account fields that need business_management or finance access.

Meta Setup

1. Create an App

In the Meta App Dashboard, create an app of type Business owned by the same Business as the ad account, and add the Marketing API product. A new app starts on the Limited access tier, which is enough to sync; see Access Tier and Rate Limits.

2. Add a System User

In Business settings → Users → System users, add a system user or pick an existing one. Under Assign assets, give it the ad account with at least View performance, and the app from step 1. Meta generates a token only for an app the system user has installed.

3. Generate the Token

On the system user, choose Generate token, select the app, tick ads_read (plus the leads permissions above if you need leads), and pick an expiry: 60 days or never. Meta recommends expiring tokens. Copy the token.

4. Find the Ad Account ID

The ad account ID is the number shown after the account name in Ads Manager’s account menu, such as 123456789012345. A pasted act_123456789012345 also works.

5. Optional: Copy the App ID and App Secret

In the App Dashboard, open App settings → Basic and copy the App ID and App secret. Set both or neither. With them:

  • Every request carries an appsecret_proof. An app with Require App Secret on refuses requests without it, so for such an app the pair is required.
  • Test connection and every save warn when the token or Meta’s data access for it ends within 14 days, when it lacks ads_read, or when it is not a system-user or user token.

Streamkap Setup

1. Create the Source

2. Connection Settings (Auth Tab)

  • Source name: A unique name for this source, for example facebook-ads-prod.
  • Access token: The system-user token from step 3. Stored encrypted and never shown again.
  • App ID (optional): The ID of the app the token was generated for. Set together with App secret.
  • App secret (optional): The app’s secret. Required when the app has Require App Secret on.
  • Ad account ID: The numeric ad account ID; an act_ prefix is fine. Once the token is entered, the field lists the ad accounts it reaches by name.
  • Additional ad account IDs (optional): More ad accounts to sync with the same token; assign each to the token’s system user. See Several Ad Accounts.

Test connection names the ad account the token reads and checks every additional one. It warns, without failing, when an ad account’s status is not ACTIVE, and with the app pair set it adds the token warnings from step 5. Test access reads each selected resource on up to the first ten ad accounts and names each account that refused it. A report that returns no rows passes. See Test connection and Test access.

3. Settings Tab

  • Resources: The objects and insights reports to sync. The form starts with the six defaults marked in Supported Resources. Select custom_<name> for each report in Custom insights. Add a preset adds:
    • Performance overview: the six defaults: campaigns, adsets, ads, ad_account, account_insights, campaign_insights.
    • Ad-level and creative: campaigns, adsets, ads, ad_account, ad_creatives, ad_images, ad_videos, adset_insights, ads_insights.
    • Audience breakdowns: campaigns, adsets, ads, ad_account, ads_insights, ads_insights_age_and_gender, ads_insights_country, ads_insights_delivery_platform_and_device_platform. Breakdowns multiply the rows.
  • Custom insights (optional): Your own insights reports, as a JSON list. See Custom Insights.
  • Include deleted objects (off by default): Also sync deleted campaigns, ad sets, ads and creatives, with the status DELETED. Turned on later, it applies from then on. See Deleted Objects.
  • Keep creative thumbnails (off by default): Store each creative’s thumbnail in thumbnail_data_url, because Meta’s thumbnail_url link expires. See Creative Thumbnails.
  • API version: v26.0 (default) or v25.0. If Meta refuses a field under one version, switch to the other.
  • Backfill start date: Insights from about 13 months back, full object history, or Specific start date. See Backfill and Backfill start date.

4. Review and Create

Check the summary and click Create. Streamkap tests the token and every selected resource again before it creates the source. An ad account backs one source per workspace, keyed on Ad account ID; see One source per vendor account. Then send the topics to a destination: see Send topics to a destination.

Editing the Source

  • Replacing the token: click Replace on the Auth tab, paste a new token for the same system user and app, and save. Syncing continues where it stopped, and an authentication failure clears on save.
  • Adding an ad account: an account added to Additional ad account IDs after a resource has synced is read from that resource’s current position. Reset the source to backfill its history.
  • Changing a custom insight: a synced definition cannot change, because its level and breakdowns form every row’s id and its fields form the columns. A changed definition under the same name fails its poll with “Custom insight custom_<name> changed since it was first synced”. Add the new definition under a new name and select it.

For adding and removing resources, see Editing an API source.

Supported Resources

There are 14 objects and 20 insights presets, plus any custom insights you define. Default marks the form’s starting selection.

Objects

Resource What it holds How it is read Default
campaigns Objective, statuses, budgets, bid strategy, schedule By update time, every 5 minutes Yes
adsets Targeting, optimization goal, budgets, attribution, schedule By update time, every 5 minutes Yes
ads Statuses, creative reference, tracking specs By update time, every 5 minutes Yes
ad_account Name, status, currency, time zone, amount spent, balance Read whole, hourly Yes
ad_creatives Creatives used by ads: body, title, links, media, call to action Through the ads that use them, every 5 minutes
custom_audiences Subtype, approximate size, status, lookalike spec Read whole, hourly
custom_conversions Event type, rule, default value, fire times Read whole, hourly
ad_images Hash, URLs, dimensions, status Newest first, every 5 minutes
ad_videos Title, length, format, status Newest first, every 5 minutes
activities The account’s activity log Newest first, every 5 minutes
ad_labels Name and times Read whole, hourly
leads Lead-form answers (field_data), form, ad, campaign, platform Every ad’s new leads, hourly
ad_studies Lift and split studies Read whole, hourly
all_ad_creatives Every creative of the account, used by an ad or not Read whole, hourly

The ad account’s owner and funding_source_details and a study’s business fields are left out, because they need business_management or finance access.

leads are read per ad, because Meta lists leads by ad or form, never by account. Each hour Streamkap reads the leads created since the last read under every ad that is not archived, and every ad archived within the last day. Leads reach back 90 days at most. Each ad costs one request an hour, so an account with many ads needs the Full access tier. Organic leads, submitted from a Page rather than an ad, are not read. field_data holds personal data your customers submitted.

Insights Presets

Every row is one day, one object at the preset’s level, and one value of each breakdown. Breakdowns add rows; action breakdowns add none and only change how lists such as actions are split.

Resource Level Breakdowns Action breakdowns Default
account_insights account none action_type Yes
campaign_insights campaign none action_type Yes
adset_insights ad set none action_type
ads_insights ad none action_type
ads_insights_age_and_gender ad age, gender action_type
ads_insights_country ad country action_type
ads_insights_region ad region action_type
ads_insights_comscore_market ad comscore_market action_type
ads_insights_demographics_age ad age action_type
ads_insights_demographics_gender ad gender action_type
ads_insights_delivery_platform ad publisher_platform action_type
ads_insights_delivery_platform_and_device_platform ad publisher_platform, device_platform action_type
ads_insights_action_conversion_device ad device_platform action_type
ads_insights_action_product_id ad product_id none
ads_insights_platform_and_device ad publisher_platform, platform_position, impression_device action_type
ads_insights_action_carousel_card ad none action_carousel_card_id, action_carousel_card_name
ads_insights_action_reaction ad none action_reaction
ads_insights_action_video_sound ad none action_video_sound
ads_insights_action_video_type ad none action_video_type
ads_insights_hourly_advertiser ad hourly_stats_aggregated_by_advertiser_time_zone action_type

Meta serves a report without breakdowns for 37 months and one with breakdowns for 13 months.

Every preset requests the ids and names of its level and the levels above it, 90 metrics (impressions, reach, clicks, spend, CPC, CPM, CTR, the actions, action_values, conversions and cost-per lists, video and ROAS lists), and at ad level quality_ranking, engagement_rate_ranking and conversion_rate_ranking. Fields Meta refuses beside a breakdown are left out: the hourly preset has no reach, frequency, unique or video fields. Every preset reads the attribution windows 1d_click, 7d_click, 28d_click and 1d_view; Meta has returned no data for 7d_view and 28d_view since 12 January 2026.

ads_insights also stands in for other connectors’ ads_insights_action_type. ads_insights_country, ads_insights_comscore_market and ads_insights_action_conversion_device stand in for demographics_country, demographics_comscore_market_region and delivery_device. A preset never changes under its name.

Custom Insights

Custom insights holds your own reports as a JSON list. Each one becomes the resource custom_<name>, which you select in Resources:

[
  {
    "name": "campaign_by_country",
    "level": "campaign",
    "breakdowns": ["country"],
    "fields": ["campaign_id", "spend", "impressions", "clicks", "actions"]
  },
  {
    "name": "adset_by_platform",
    "level": "adset",
    "breakdowns": ["publisher_platform"],
    "attribution_windows": ["1d_click", "7d_click"]
  }
]

A definition takes only these keys, all Marketing API names:

  • name (required): lowercase letters, digits and underscores, starting with a letter, at most 40 characters, unique. audiences and conversions are taken by object resources.
  • level (required): account, campaign, adset or ad.
  • breakdowns: Meta’s breakdown names.
  • action_breakdowns: default ["action_type"]; an empty list requests none.
  • attribution_windows: default 1d_click, 7d_click, 28d_click, 1d_view.
  • action_report_time: conversion, impression, lifetime or mixed. Sent, though Meta has ignored it since 10 June 2025.
  • fields: insights fields to read; the level’s ids and names, date_start and date_stop are always added. Omitted, the level’s preset fields apply. A field must exist at the level, must not also be a breakdown, and must be one Meta reports beside the breakdowns.
  • time_increment: 1 (default, daily); a number of days up to 90, grouped from Mondays, so 7 is Monday-to-Sunday weeks; monthly; or all_days. See Weekly, Monthly and Whole-Range Reports.
  • start_date, end_date: YYYY-MM-DD. The report starts at start_date instead of the Backfill start date, and stops reading once Meta stops revising end_date.
  • insights_lookback_window: days each poll re-reads, 1 to 28, default 28. Shorter costs fewer requests and misses Meta’s later revisions.

An asset breakdown (body_asset, title_asset, image_asset, video_asset, call_to_action_asset, description_asset, link_url_asset or ad_format_asset) splits a Dynamic Creative ad’s rows by the asset shown. One per report, image_asset and video_asset not at account level, and only impressions, clicks, spend, reach, actions and action_values, which are its default fields. The asset arrives as JSON text in the breakdown’s column, and its id in <breakdown>_id and in the row’s id. ad_format_asset is a plain name.

Refused at save:

Refused Why
Breakdown frequency_value Meta returns it for 6 months at most, only asynchronously on opted-in accounts.
Breakdown mmm Meta serves it only as a separate export.
Breakdown hourly_stats_aggregated_by_audience_time_zone It needs an opt-in and is quota-limited. Use hourly_stats_aggregated_by_advertiser_time_zone.
Windows 7d_view, 28d_view Meta has returned no data for them since 12 January 2026.
Window default Meta’s reference defines it two ways. List the windows you want.
Key time_increment_period Use time_increment.
Key filtering A filter would hide rows the level’s status filter keeps.
An hourly breakdown with time_increment other than 1 Meta reports hourly breakdowns per day.
A value listed twice, an unknown key, or a field Meta no longer serves

Every selected custom_<name> needs a definition, and every definition must be selected. Streamkap runs each definition against Meta when you test or save, and reports a combination Meta refuses on that resource.

What Each Record Contains

Object records hold the resource’s fixed field list, with null for a field Meta did not send, plus:

  • id: Meta’s id, a string, and the Kafka key. An activities row’s id is object_id|actor_id|application_id|event_time|event_type.
  • Times such as updated_time in UTC (2024-03-12T22:02:47Z), converted from the ad account’s offset so they sort across daylight-saving changes.
  • account_id, added where the object has none.
  • streamkap_changed_at: the object’s update time (event_time for activities), or the time of the poll that saw the change for ad_account, custom_conversions and all_ad_creatives, or the time of the read that found a lead.
  • Nested values such as targeting or creative as JSON text with sorted keys.

Insights records look like this campaign_insights row, abridged (values illustrative):

{
  "id": "123456789012345|120210000000000001|2026-09-24",
  "account_id": "123456789012345",
  "campaign_id": "120210000000000001",
  "campaign_name": "Autumn sale",
  "date_start": "2026-09-24",
  "date_stop": "2026-09-24",
  "impressions": 18342,
  "spend": 231.57,
  "actions": "[{\"1d_click\":\"380\",\"action_type\":\"link_click\",\"value\":\"398\"}]"
}
  • id: the account id, the level’s id (none at account level), the day and each breakdown value, joined with |. A | or % inside a value is escaped as %7C or %25; an empty breakdown value is an empty part.
  • date_start: the report day in the ad account’s time zone. At a coarser grain, the first day of the period.
  • Breakdown values in their own columns, named as the breakdown.

Column types come from Meta’s API specification pinned per version, so a column keeps one type:

  • Every id and *_id field is a string.
  • On insights, impressions, clicks, reach, unique_clicks, inline_link_clicks, unique_inline_link_clicks, inline_post_engagement, full_view_impressions and full_view_reach are integers; every other metric is a double.
  • On objects, budgets, spend caps, amount_spent and balance are integers in the currency’s minor units.
  • Lists and objects are JSON text.

Behavior & Limits

Backfill

The Backfill start date bounds each resource’s first sync only.

  • Insights start on that day in the ad account’s time zone, or 393 days back by default, clamped to 37 months without breakdowns and 13 months with them.
  • Objects read by update time or newest first read changes on or after the date, or back to November 2007 by default.
  • leads read leads created on or after the date, never more than 90 days back.
  • Objects read whole ignore the date.
  • A custom insight with start_date starts there.

How Object Changes Arrive

  • campaigns, adsets, ads are read by update time up to a minute ago, each changed object sent whole. Archived objects keep updating.
  • ad_creatives are read through their ads and stamped with the ad’s update time. An edit to a creative that does not touch its ad is not read.
  • ad_images, ad_videos, activities cannot be filtered by time, so each poll reads newest first back to the last poll and sends the new rows oldest first. A large backlog is sent over several polls.
  • Objects read whole are compared with the last read every hour; only new or changed rows are sent, and a row gone from a complete read is sent as a delete marker. all_ad_creatives has no size limit. When more than about 1,500 of its creatives are added or deleted in an hour, or about 750 edited, that hour re-sends every creative and may miss a delete.

Deleted Objects

Meta does not list deleted campaigns, ad sets or ads, and refuses a filter on the DELETED status. With Include deleted objects off, a deleted campaign, ad set or ad keeps its last record and status in the destination, as do its child ad sets and ads and the creatives of deleted ads.

With Include deleted objects on, campaigns, adsets, ads and ad_creatives ask for every delivery state, and a deletion arrives as an upsert with effective_status DELETED, since Meta still reports its spend. To read deletions from before you turned it on, reset the resource.

Objects read whole send a delete marker (__deleted = true) when a row is gone from a complete read. The marker carries only id, streamkap_changed_at and __deleted. See Deletes.

Creative Thumbnails

thumbnail_url is a signed link that stops working within days. With Keep creative thumbnails on, every ad_creatives and all_ad_creatives record also carries:

  • thumbnail_data_url: the image as a data: URL, fetched when the record is sent.
  • thumbnail_url_expires_at: when the link expires, in UTC.

The fetch goes to Meta’s CDN without your token and does not count against your API budget. thumbnail_data_url is empty when the link has expired or errors, the image is over 256 KB, or it would take the record past about 900 KB.

Weekly, Monthly and Whole-Range Reports

At a coarser time_increment, Streamkap always asks for whole periods, because reach, frequency and unique counts do not add up across days. Each row is keyed on the period’s first day, and the current period is updated in place until it ends. The re-read reaches back 28 days plus one period.

all_days reads one range from the report’s start to today every poll, one row per object and breakdown value, updated in place. A range Meta refuses as too large fails the poll; set a later start_date or a coarser level.

Several Ad Accounts

Every resource reads all accounts into the same topic, each row carrying its account_id. An object two accounts share arrives once. Meta meters each account separately, so the pace scales with the number of accounts, and each insights job runs in its account’s own time zone.

When Meta holds one account for usage, objects read whole and leads read the other accounts, insights stop at the first day the held account has not read, and objects read by update time wait for it. Nothing is skipped.

When Meta refuses one account to the token, each resource skips it with a warning and the others keep syncing; its rows already in the destination stay. Insights catch up within the 28-day re-read once access returns; objects changed meanwhile arrive on their next change or after a reset. Only when the source reads a single account, or every account is refused, does the resource stop. leads skips an ad whose Page the token cannot read the same way.

Insights Restatement

Meta revises insights for up to 28 days, so every insights poll re-reads the last 28 days and today. A row whose values changed is sent again with the same id; an unchanged row is not. Set the destination to upsert on id: an append-only destination keeps every version, and summing it counts a day several times. A row Meta stops serving is not retracted. Campaign, ad set and ad reports include rows of objects in every status, archived and deleted included.

Streamkap remembers 10,000 rows per insights resource across the 29-day window, about 345 rows a day. A larger report re-sends its oldest days’ rows every poll as duplicate upserts. ads_insights passes that at about 345 delivering ads; ads_insights_age_and_gender at about 25.

Insights Report Jobs

Meta builds insights asynchronously, in jobs of 7 days (29 for impression_device, about four weeks at a coarser grain). A poll waits a short while for a job, and the next poll picks up one still running. A failed job is resubmitted once; if it fails again, or Meta says it holds too much data, the window is split, down to single days. A single day Meta still cannot report fails the poll with Meta’s message, and the next poll retries. A window with too many rows is split the same way; a single day is always read in full.

Access Tier and Rate Limits

A new app is on the Limited access tier: 60 points every 5 minutes per ad account, one point per read, and a 5-minute block when exceeded. The connector paces itself at that rate per ad account whatever your tier, one request at a time. An app qualifies for Full access after 500 Marketing API calls in 15 days; request it in the App Dashboard.

When any usage budget Meta reports reaches 90%, the resource holds what that budget covers (an ad account, a business, or the app) until Meta says it recovers, at least a minute. When every account a resource needs is held, the whole source shows Throttled and resumes on its own. A Meta block is retried within a minute when Meta says it clears that soon; otherwise the source is throttled until it does. See Connector status.

API Version

Streamkap pins the field list and types of v26.0 and v25.0. Meta upgrades a call on a deprecated version, so an old version works until a field it requests is gone. If Meta refuses a field, the poll fails naming it; switch to the other version.

Errors

A rejected token (Meta error 190 or 102), or a missing appsecret_proof on an app that requires one, stops all syncing until you save a fixed credential. A permission error on one resource pauses only that resource; see Connector status. A Meta server error is retried once, and a page too large for Meta is re-requested with half as many rows, down to one.

Troubleshooting

"Ad account ID must be a number"

Enter the number shown after the account name in Ads Manager’s account menu, with or without act_.

"App secret is missing" or "App ID is missing"

Set both from the app’s App settings → Basic page, or clear both.

"Meta rejected the access token"

The token expired, was revoked, or its system user lost the app or the ad account. Check the system user’s assets in Business settings → Users → System users, generate a new token with ads_read, and paste it with Replace.

"Your Meta app has Require App Secret turned on"

Set the App ID and App secret of the app the token was generated for, and save.

"Meta rejected the App secret"

The pair belongs to a different app than the token, or the secret was reset. Copy the App ID and App secret of the app you generated the token for.

"Meta cannot find ad account act_… for this token"

Check Ad account ID, and assign the ad account to the system user under Assign assets.

"Meta did not let the access token read ad account act_…" or "Meta refused access to ad account act_…"

The token lacks ads_read, or its system user is not assigned the ad account. The second message names an additional ad account: assign it to the system user, or remove it from Additional ad account IDs.

Warning: the access token expires, or Meta's data access for it ends

Generate a new token and paste it with Replace before that date. Saving is not blocked.

Warning: "The access token lacks the ads_read permission"

Generate a new token with ads_read ticked and paste it with Replace.

Warning: "The access token is a … token, not a system-user token"

Generate a system-user token (see Meta Setup) and paste it with Replace.

Warning: "Meta could not check when the access token expires"

Check that the App ID and App secret belong to the app the token was generated for. The token itself works.

Warning: "Ad account act_… is …, not ACTIVE"

The ad account is disabled, unsettled, under review or closing. The source syncs its history either way; resolve the status in Ads Manager.

"Meta refused to list …" or "The access token cannot read …"

Give the system user at least View performance on the ad account, accept any terms Meta names, and save; or deselect the resource. For leads, assign each ad’s Page with Leads access and generate the token with the leads permissions in Prerequisites.

"Meta refused custom insight …"

Meta does not report that level, breakdowns and fields together. Change the definition, under a new name if the old one has synced.

"Custom insight custom_… changed since it was first synced"

Restore the original definition, or add the new one under a new name. See Editing the Source.

"Meta is throttling the ad account"

The source resumes on its own. If it happens often, request Full access for the app or deselect resources you do not need.

"Meta refused … as too much data, even for one day"

Select a coarser level or fewer breakdowns, or define a custom insight with fewer fields.

"Meta could not report … for …"

Meta failed a one-day job twice; the message carries Meta’s error. The next poll retries. If the same day keeps failing, act on Meta’s message or deselect the resource.

"Meta does not report the breakdowns of … together"

Add a custom insight with a combination Meta reports, under a new name.

"Meta refused a field … requests under …"

Switch API version, or remove the field from the custom insight.

"Meta keeps insights for 37 months"

Set the Backfill start date within the last 37 months.

A deleted campaign, ad set or ad is still in my destination

Meta does not serve deleted objects. Turn on Include deleted objects to receive deletions as DELETED upserts, or archive objects instead of deleting them. See Deleted Objects.

Totals look too high, or a day appears with different values

Meta revised the day and the revision arrived under the same id. Upsert on id and do not sum the topic.

Numbers differ from Ads Manager

Ads Manager may use other attribution windows, and recent days are still being revised. Compare a day older than 28 days with the same attribution setting. Reach is not additive across days.

  • API sources - how API sources work, Test access, deletes, status and sending topics to a destination
  • Google Ads - Google Ads objects and performance reports
  • Google Analytics 4 - daily GA4 reports