Facebook Ads Insights API: Fields, Breakdowns, Limits
The Facebook Ads Insights API is the insights edge on every Marketing API ad object: GET /act_{id}/insights, or the same edge on a campaign, ad set, or ad. Choose fields, a level, breakdowns, and a date_preset or time_range. Large queries run as async report runs. Data goes back 37 months, and Meta removed 7-day and 28-day view attribution in January 2026. Adeli returns daily spend per ad account in one call.
The Facebook Ads Insights API is the insights edge of Meta's Marketing API. You call GET /act_{ad-account-id}/insights, or the same edge on a campaign, ad set, or ad, and choose four things: the fields (metrics), the level to aggregate at, any breakdowns, and a date range. Big queries run as asynchronous report runs, data goes back 37 months, and Meta removed the 7-day and 28-day view attribution windows on January 12, 2026.
This guide covers each parameter, the async workflow, and the retention and rate limits that break reporting jobs. It's part of our Meta Ads API series, and it's about paid ads. For organic Page metrics, see the Facebook Page Insights API.
What do you need before you start?
- A token with ad account access. Meta's Insights API guide lists
ads_read. An app that holdsads_managementcan read the accounts it manages too. - An ad account ID in
act_<digits>form, or the ID of a campaign, ad set, or ad. - Conversion tracking, if you want results beyond clicks. Purchases and leads only show up when the Meta Pixel, the Conversions API, or instant forms record them.
How do I call the Facebook Ads Insights API?
Send a GET to the insights edge of any ad object with a fields list. Without parameters, Meta returns basic metrics for roughly the last 30 days.
curl -G "https://graph.facebook.com/v26.0/act_$AD_ACCOUNT_ID/insights" \
--data-urlencode "fields=campaign_name,spend,impressions,reach,clicks,ctr,cpm,actions" \
--data-urlencode "level=campaign" \
--data-urlencode "date_preset=last_7d" \
--data-urlencode "access_token=$ACCESS_TOKEN"The response has a data array with one row per object at the requested level, each with date_start and date_stop, plus cursor paging. Metric values come back as strings, so parse spend as a decimal before you add it up.
The four edges, from Meta's guide:
| Edge | Returns |
|---|---|
/act_{ad-account-id}/insights |
The whole account, or any level below it with level |
/{campaign-id}/insights |
One campaign |
/{ad-set-id}/insights |
One ad set |
/{ad-id}/insights |
One ad |
How do level and breakdowns work?
level sets which object each row represents, and breakdowns split each row by a dimension such as age or placement. Use level on the account edge to get every campaign, ad set, or ad in one call instead of looping over IDs.
leveltakesaccount,campaign,adset, orad.breakdownssplit rows by audience or delivery:age,gender,country,region,publisher_platform,platform_position,device_platform, and hourly options. Only some combinations work together; Meta's breakdowns page lists them.action_breakdownssplit theactionsfield, byaction_typeby default.filteringkeeps only rows that match, such as[{"field":"ad.impressions","operator":"GREATER_THAN","value":0}]to skip ads that never delivered.
Hourly breakdowns don't support unique fields: reach and frequency return 0 when you use them.
How do I set the date range?
Use date_preset for a relative window or time_range for exact dates, and time_increment to split the window into days. From the ad account insights reference:
| Parameter | Format | Notes |
|---|---|---|
date_preset |
today, yesterday, last_7d, last_28d, last_30d, last_90d, this_month, maximum, and more |
Default last_30d. Ignored if time_range is set. |
time_range |
{"since":"2026-10-01","until":"2026-10-25"} |
Dates only, no Unix timestamps. |
time_ranges |
A list of time_range objects |
One result set per range. Overrides the other three. |
time_increment |
1 to 90, monthly, or all_days |
1 gives one row per day. Default all_days. |
Insights are reported in the ad account's time zone, so "yesterday" depends on where the account is set, not where your server runs.
Which attribution windows still work?
The default attribution window is 7-day click plus 1-day view, and the 7-day and 28-day view windows return no data since January 12, 2026. Meta's out-of-cycle changelog announced the removal on October 13, 2025, for all API versions.
action_attribution_windowsstill accepts1d_click,7d_click,28d_click,1d_view, and1d_ev(engaged view), among others.use_unified_attribution_setting=truereports each ad set's own attribution setting, which is what Ads Manager shows. Since June 10, 2025, Meta applies this behavior to attributed values regardless of the parameter.
If your reports used 7d_view or 28d_view, conversion counts fell on January 12, 2026, when those windows started returning no data. Attribution also explains other mismatches with your own data:
"Ads Manager results can include multiple conversions by the same person." Jon Loomer, Meta ads consultant, How Meta Ads Attribution Works in 2026 (March 10, 2026)
Put that next to any conversion count you show your customers, so a figure higher than their CRM's doesn't read as an error.
How do async report runs work?
For large queries, POST to the same insights edge, poll the report run until it completes, then read its results. Meta recommends trying a synchronous call first and switching to async when it times out. The steps, from Meta's limits and best practices page:
- Start the job.
POST /act_{id}/insightswith the same parameters returns areport_run_id. - Poll the run.
GET /{report_run_id}untilasync_statusisJob Completedandasync_percent_completionis100. Other statuses includeJob FailedandJob Skipped, which means resubmit. - Read the results.
GET /{report_run_id}/insights, paging with the cursors.
curl -X POST "https://graph.facebook.com/v26.0/act_$AD_ACCOUNT_ID/insights" \
-F "level=ad" \
-F "fields=ad_id,spend,impressions,clicks,actions" \
-F 'time_range={"since":"2026-01-01","until":"2026-09-30"}' \
-F "time_increment=1" \
-F "access_token=$ACCESS_TOKEN"An async job can take up to an hour, and a report_run_id expires after 30 days, so don't store it as a permanent reference.
What are the data retention and rate limits?
Meta keeps 37 months of ads insights, less for some breakdowns, and throttles by app and by ad account. Plan your backfills and nightly syncs around these:
| Limit | Value |
|---|---|
| Overall history | 37 months; older start dates return error 3018 |
| Unique-count fields with breakdowns, hourly breakdowns | 13 months |
frequency_value breakdown |
6 months |
reach with breakdowns, older than 13 months |
Omitted from standard queries; async jobs allow 10 such requests per ad account per day |
| Freshness | Refreshes every 15 minutes; final 28 days after reporting |
| Rate limit error | Code 4; subcode 1504022 under heavy global load |
| Too much data in one call | Code 100, subcode 1487534 |
Watch the x-fb-ads-insights-throttle header on every response. It reports app_id_util_pct and acc_id_util_pct, and Meta's advice is to back off well before either reaches 100. Its tips for the data-per-call error: shorten the date range, request fewer fields, and avoid account-level queries with high-cardinality breakdowns. Our Meta Ads API guide covers the access tiers that set these limits.
Is there a faster way to do this?
Adeli returns one row per day of spend, impressions, clicks, and conversions for a customer's ad account, or any campaign, ad set, or ad in it, from one call. Adeli holds the Meta app and its reviews and manages each customer's token, so you skip the permissions and throttle headers above. The endpoint is daily insights:
curl -G "https://app.tryadeli.com/api/v1/ads/insights" \
-H "Authorization: Bearer $ADELI_API_KEY" \
--data-urlencode "accountId=$ACCOUNT_ID" \
--data-urlencode "adAccountId=act_1234567890" \
--data-urlencode "objectId=120210000000000001" \
--data-urlencode "since=2026-10-01" \
--data-urlencode "until=2026-10-25"Leave out objectId for the whole account, and leave out the dates for the last 28 days. For a dashboard, campaign tree returns every campaign, ad set, and ad with spend, impressions, reach, clicks, ctr, cpc, and cpm rolled up at each level, and an object that didn't deliver reports zeroes instead of disappearing.
Adeli stores no campaign structure or metrics, so every figure is as fresh as Meta's response. When Meta throttles an ad account, Adeli returns 429 rate_limited with a Retry-After header. The same API key reads organic analytics for Instagram, Facebook, TikTok, and YouTube, so paid and organic numbers can sit in one report. Pricing is per customer profile.