Limits and Best Practices

Facebook Insights API provides performance data from Facebook marketing campaigns. To protect system performance and stability, we have protective measures to equally distribute system resources among applications. All policies we describe below are subject to change.

Data Per Call Limits

We use data-per-call limits to prevent a query from retrieving too much data beyond what the system can handle. There are 2 types of data limits:

  1. By number of rows in response, and
  2. By number of data points required to compute the total, such as summary row.

These limits apply to both sync and async /insights calls, and we return an error:

error_code = 100,  CodeException (error subcode: 1487534)

Best Practices, Data Per Call Limits

  • Limit your query by limiting the date range or number of ad ids. You can also limit your query to metrics that are necessary, or break it down into multiple queries with each requesting a subset of metrics.
  • Avoid account-level queries that include high cardinality breakdowns such as action_target_id or product_id, and wider date ranges like lifetime.
  • Use /insights edge directly with lower level ad objects to retrieve granular data for that level. For example, first use the account-level query to fetch the list of lower-level object ids with level and filtering parameters. In this example, we fetch all campaigns that recorded some impressions:
curl -G \
-d 'access_token=<ACCESS_TOKEN>' \
-d 'level=campaign' \
-d 'filtering=[{field:"ad.impressions",operator:"GREATER_THAN",value:0}]' \
  • We can then use /<campaign_id>/insights with each returned value to query and batch the insights requests for these campaigns in a single call:
curl \
-F 'access_token=<ACCESS_TOKEN>' \
-F 'batch=[ \
  { \
    "method": "GET", \
    "relative_url": "v19.0/<CAMPAIGN_ID_1>/insights?fields=impressions,spend,ad_id,adset_id&level=ad" \
  }, \
  { \
    "method": "GET", \
    "relative_url": "v19.0/<CAMPAIGN_ID_2>/insights?fields=impressions,spend,ad_id,adset_id&level=ad" \
  }, \
  { \
    "method": "GET", \
    "relative_url": "v19.0/<CAMPAIGN_ID_3>/insights?fields=impressions,spend,ad_id,adset_id&level=ad" \
  } \
]' \
  • Use filtering parameter only to retrieve insights for ad objects with data. The field value specified in filtering uses DOT notation to denote the fields under the object. Please note that filtering with STARTS_WITH and CONTAIN does not change the summary data. In this case, use the IN operator. See example of a filtering request:
curl -G \
-d 'access_token=<ACCESS_TOKEN>' \
-d 'level=ad' \
-d 'filtering=[{field:"ad.impressions",operator:"GREATER_THAN",value:0},]' \
  • Use date_preset if possible. Custom date ranges are less efficient to run in our system.
  • Use batch requests for multiple sync calls and async to query for large volume of data to avoid timeouts.
  • Try sync calls first and then use async calls in cases where sync calls timeout
  • Insights refresh every 15 minutes and do not change after 28 days of being reported

Insights Call Load Limits

Ninety days from the release of v3.3 and effective for all public versions, we change the ad account level rate limit to better reflect the volume of API calls needed. We compute the rate limit quota on your Marketing API access tier and the business owning your app. see Access and Authentication. This change applies to all Ads Insights API endpoints: GET {adaccount_ID}/insights, GET {campaign_ID}/insights, GET {adset_ID}/insights, GET {ad_ID}/insights, POST {adaccount_ID}/insights, POST {campaign_ID}/insights, POST {adset_ID}/insights, POST {ad_ID}/insights.

We use load limits for optimal reporting experience. We measure API calls for their rate as well as the resources they require. We allow a fixed load limit per application per second. When you exceed that limit, your requests fail.

Check the x-fb-ads-insights-throttle HTTP header in every API response to know how close your app is to its limit as well as to estimate how heavy a particular query may be. Insights calls are also subject to the default ad account limits shown in the x-ad-account-usage HTTP header. More details can be found here Marketing API, Best Practices

Once an app reaches its limit, the call gets an error response with error_code = 4, CodedException. You should stay well below your limit. If your app reaches its allowed limits, only a certain percentage of requests go through, depending on the query, and the rate.

We apply rate limiting to each app sending synchronous and asynchronous /insights calls combined. The two main parameters limits are counted against are by application, and by ad account.

Here's an example of the HTTP header with an application's accrued score as a percentage of the limits:

X-FB-Ads-Insights-Throttle: { "app_id_util_pct": 100, "acc_id_util_pct": 10, "ads_api_access_tier": "standard_access" }

The header "x-fb-ads-insights-throttle" is a JSON value containing these info:

  • app_id_util_pct — The percentage of allocated capacity for the associated app_id has consumed.
  • acc_id_util_pct — The percentage of allocated capacity for the associated ad account_id has consumed.
  • ads_api_access_tier — Tiers allows your app to access the Marketing API. standard_access enables lower rate limiting.

Rate Limits Best Practices

  • Sending several queries at once are more likely to trigger our rate limiting. Try to spread your /insights queries by pacing them with wait time in your job.
  • Use the rate information in the HTTP response header to moderate your calls. Add a back-off mechanism to slow down or pause your /insights queries when you come close to hitting 100% utility for your application, or for your ad account.
  • We report ad insights data in the ad account's timezone. To retrieve insights data for the associated ad account daily, consider the time of day using the account timezone. This helps pace queries throughout the day.
  • Check the ads_api_access_tier that allows you to access the Marketing API. By default, apps are in the development_access tier and standard_access enables lower rate limiting. To get a higher rate limit and get to the standard tier, you can apply for the "Advanced Access" to the Ads Management Standard Access feature.

Insights API Asynchronous Jobs

Fetch stats on many objects and apply filtering and sorting; we made the asynchronous workflow simpler:

  1. Send a POST request to <AD_OBJECT>/insights endpoint, which responds with the id of an Ad Report Run.
  "report_run_id": 6023920149050,

Do not store the report_run_id for long term use, it expires after 30 days.

  1. Ad Report Runs contain information about this asynchronous job, such as async_status. Poll this field until async_status is Job Completed and async_percent_completion is 100.
  "id": "6044775548468",
  "account_id": "1010035716096012",
  "time_ref": 1459788928,
  "time_completed": 1459788990,
  "async_status": "Job Completed",
  "async_percent_completion": 100
  1. Then you can query <AD_REPORT_RUN_ID>/insights edge to fetch the final result.
  "data": [
      "impressions": "9708",
      "date_start": "2009-03-28",
      "date_stop": "2016-04-04"
      "impressions": "18841",
      "date_start": "2009-03-28",
      "date_stop": "2016-04-04"
  "paging": {
    "cursors": {
      "before": "MAZDZD",
      "after": "MQZDZD"

This job gets all stats for the account and returns an asynchronous job ID:

curl \
  -F 'level=campaign' \
  -F 'access_token=<ACCESS_TOKEN>' \<CAMPAIGN_ID>/insights
curl -G \
  -d 'access_token=<ACCESS_TOKEN>' \
curl -G \
  -d 'access_token=<ACCESS_TOKEN>' \

Async Job Status


Job Not Started

Job has not started yet.

Job Started

Job has been started, but is not yet running.

Job Running

Job has started running.

Job Completed

Job has successfully completed.

Job Failed

Job has failed. Review your query and try again.

Job Skipped

Job has expired and skipped. Please resubmit your job and try again.

Export Reports

We provide a convenience endpoint for exporting <AD_REPORT_RUN_ID> to a localized human-readable format.

Note: this endpoint is not part of our versioned Graph API and therefore does not conform to its breaking-change policy. Scripts and programs should not rely on the format of the result as it may change unexpectedly.

  curl -G \
  -d 'report_run_id=<AD_REPORT_RUN_ID>' \
  -d 'name=myreport' \
  -d 'format=xls' \



Name of downloaded file



Format of file



ID of report to run



Permissions granted by the logged-in user. Provide this to export reports for another user.