API GuidesAugust 10, 202611 min readingMarcel Czuryszkiewicz

Facebook API Rate Limits: Three Systems, One Error

Meta counts your calls in three parallel systems, and the token you send decides which one applies. Maps all three against Graph API v23.0, both usage headers, and the throttle codes Meta splits across two separate pages - including code 9 and subcode 1390008, which appear on neither. Plus 75/90.

Facebook API rate limits are not one limit. Meta runs three parallel systems (Platform Rate Limits, Business Use Case (BUC) limits, and Ad Account limits) and which one governs a request depends on the token you send, not the endpoint you call. That is why the same (#4) Application request limit reached symptom has three different fixes. This guide maps all three, both usage headers, and the throttle codes Meta splits across two separate pages.

Long-exposure photograph of a wet road at night, white, orange and teal light trails curving through the frame past a road sign reading NANU OYA ROAD, no vehicles visible
Rate limiting is flow control, not a wall - the useful signal arrives before the red light.

Three limit systems, not one

Every formula below was checked against Meta's live rate-limiting reference on 4 August 2026, on Graph API v23.0, the version we run.

SystemCountsWindowFormula in the docsHeaderCode
Platform - app tokenYour app's calls1 h200 × Number of UsersX-App-Usage4
Platform - user tokenThat user, across every app1 hNot disclosedX-App-Usage17
BUC - PagesPer app, per Page24 h4800 × Number of Engaged UsersX-Business-Use-Case-Usage80001
BUC - Instagram / ThreadsPer app + app-user24 h4800 × Number of ImpressionsX-Business-Use-Case-Usage80002
BUC - Ads ManagementPer ad account1 h300 + 40 × Active ads; Advanced 100000 + 40 × Active adsX-Business-Use-Case-Usage80004
Ad Accountv3.3-and-older Ads APIreset_time_durationacc_id_util_pctX-Ad-Account-Usage17:2446079

Meta settles the overlap in one sentence: "If both Platform and Business Use Case rate limits can be applied to a request, BUC rate limits will be applied."

Two corrections. BUC quota is per product and per asset, not per ad spend or verification status: pages, instagram, ads_insights, ads_management, leadgen, messenger and custom_audience each carry their own budget, and the header's type names the one you are burning. The instagram bucket runs on a different formula and carries a publishing cap on top of the call budget; that side is in Instagram API rate limits. And the Users in 200 × Number of Users are daily active users: 100 engaged users buys 20,000 calls an hour, 100,000 dormant signups buys nothing.

Disclosure: we run the Facebook Graph API, not the Marketing API. The two Ads rows are read from Meta's docs, not from our traffic.

Which token you use decides which limit applies

Token typeSystemWindowCode
App access tokenPlatform1 h4
User access tokenPlatform1 h17, or 32 on Pages API
Page access tokenBUC24 h80001
System user access tokenBUC24 h80001

The same GET /{page-id}/ratings call lands in a different bucket depending on which token signs it. With a user token it counts against a rolling one-hour Platform budget and fails with 32. With a Facebook Page access token it counts against a 24-hour BUC budget sized by engaged users and fails with 80001. Same URL, different quota, different error, different recovery time.

That is why Meta recommends system user tokens for high-volume Page reads. The calls are not cheaper; a per-asset budget means one noisy customer cannot drain the bucket everyone else shares.

A vintage control room console photographed from below, dozens of round analogue dials with Cyrillic labels above rows of switches and coloured indicator lamps
Meta reports usage as a percentage of a budget it never discloses.

Reading the headers: percentages, not remaining calls

The two headers are different shapes, not synonyms.

x-app-usage: {"call_count": 28, "total_time": 25, "total_cputime": 25}
x-business-use-case-usage: {
  "10153848260347724": [
    { "type": "pages", "call_count": 97, "total_cputime": 23,
      "total_time": 23, "estimated_time_to_regain_access": 0 }
  ]
}

X-App-Usage is a flat object with three numbers. X-Business-Use-Case-Usage is a map of business-object-id to an array of objects, one per BUC type, and Meta states it can return up to 32 objects in one response. A parser written for the first will not survive the second.

FieldWhat the number isThrottles at
call_countPercentage of allowed calls used100
total_cputimePercentage of CPU time allotted100
total_timePercentage of total time allotted100

All three are percentages of a budget Meta never discloses, not counts of calls remaining. One operational consequence: you can sit at call_count: 12 and still get throttled, because total_cputime hit 100 on a handful of expensive insights queries. Steer on the maximum of the three, never on call_count alone.

One caveat we owe you: do not assume which header arrives where. Our recommendation importer parses only x-app-usage on /{page-id}/ratings, a path that should sit under BUC with a Page token. If the response carries the BUC header alone, that parser returns null and the throttle never fires. Treat a missing header as no signal, not as safe.

The throttle codes, and where Meta hides them

Verified 4 August 2026 against both Meta pages and our own classifier in retryability.util.ts.

CodeSystemMeaningWhat to doDocumented
4Platform, appApp hit its hourly countStop app-wide, not per jobBoth pages
9Rate limitThrottledTransientNeither
17Platform, userThat user hit their countBack off per userBoth pages
17:2446079Ad AccountOld Ads API token limitBack off per ad accountLimits page
32Pages, Platform tokenPage limit hit with a user or app tokenMove the path to a Page tokenLimits page
341Application limitDowntime or throttlingBack off; transientErrors guide only
613CustomProduct-specific custom limitCheck that product's docsLimits page
613:1996CustomInconsistent volume from your appCheck your last deployLimits page
80001BUC PagesToo many calls to this PagePause, resume from a cursorLimits page
80002BUC InstagramInstagram quota exhaustedSameLimits page
80004:2446079BUC Ads ManagementQuota per ad accountSame, per accountLimits page
sub 1390008Posting rate limitPublish throttledTransientNeither

No single Meta page carries this list: 341 is in the error-handling guide only, 32, 613 and the 80001-80014 family only on the rate-limiting page, and just 4 and 17 on both.

Two rows are ours, not Meta's. Code 9 and subcode 1390008 appear in neither document, nor anywhere in our mirror of the Graph API docs. We classify both as transient because they arrived in production traffic, not because anyone documented them.

Two field notes. Do not key on HTTP status: our import processor catches status === 429 || error.code === 80001 because the Graph code is reliable and the status is not. And Meta's rate-limiting reference never mentions Retry-After, in any section; our adapter records it if it arrives, but nothing promises it. Guides telling you to sleep for Retry-After seconds describe a different API. Wider taxonomy: social media API error handling.

bundle.social

Three Facebook quota systems behind one rate-limit layer.

Three upload hosts and two auth header words, handled behind one endpoint.

Shipping containers stacked several high in red, blue and green rows at a port, an orange gantry crane rising behind them against a clear blue sky
Three quotas running in parallel - the one you hit first decides the fix.

estimated_time_to_regain_access and what 0 means

This field arrives only in X-Business-Use-Case-Usage, in minutes. Meta's X-App-Usage header-contents table lists exactly three keys and this is not one of them, so read it if present, never require it.

The trap is the zero. Meta's own sample shows estimated_time_to_regain_access: 0 on entries sitting at call_count: 95 and 97, and 19 on the entry that is actually blocked. 0 means you are not currently blocked. It does not mean retry now. A backoff reading 0 as a green light will spin at 97% quota, and every failed call keeps the count climbing.

Steering on headers before you get blocked

These thresholds are our operational decision, not Meta's numbers. Meta only tells you that 100 throttles.

// apps/api/src/bull-mq/queue.consts.ts
APP_USAGE_THROTTLE_THRESHOLD: 75,     // soft: slow down
APP_USAGE_THROTTLE_DELAY_MS: 30_000,
APP_USAGE_CRITICAL_THRESHOLD: 90,     // hard: pause and checkpoint
const peak = Math.max(usage.callCount, usage.totalTime, usage.totalCputime);

if (peak >= APP_USAGE_CRITICAL_THRESHOLD) {
  await markRateLimited({ paginationCursor: cursor, rateLimitResetAt: inOneMinute });
  return; // exit the job, do not sleep holding a worker slot
}

await wait(peak >= APP_USAGE_THROTTLE_THRESHOLD ? APP_USAGE_THROTTLE_DELAY_MS : jitteredBaseDelay);

Pick your own numbers. What matters is the Math.max across all three metrics, and that the hard threshold persists a cursor and exits instead of sleeping in-process.

There is a reason we steer rather than retry. Facebook's video publish uploads bytes and creates the post in one non-idempotent step with no idempotency key, so it runs with maximumAttempts: 1 and the first failure is terminal. Every guide on this phrase sells exponential backoff. Backoff is right for reads. On a non-idempotent publish path a rate limit is something you get ahead of, not recover from, which is what the brake on read traffic protects.

Two ways to burn quota without touching your code

Batching does not save quota. Meta's FAQ is blunt: GET /photos?ids=4,5,6 counts as three API calls, identical to three separate requests. Batches cap at 50, and "each call within the batch is counted separately". You save round trips, not budget, so a capacity plan built on batching your way under the limit is planning against a number that does not move.

A deprecated insights metric turns one call into eight. We request eight post metrics in a single /{post-id}/insights?metric=... call. When Meta deprecates one, the whole request fails and our fallback re-requests each metric individually, in parallel: one call becomes eight, at whatever refresh concurrency is running. Nothing errors, the data comes back, the quota is just gone. Every platform has a version of this, see social media API rate limits.

One rate-limit layer instead of fifteen

Everything above is Facebook alone: three quota systems, two header shapes, twelve throttle codes across two documentation pages, two of them undocumented. TikTok, LinkedIn, X and Pinterest each have their own scheme, headers and opinions about what a retry means.

The Facebook API integration in bundle.social absorbs that layer. We parse the usage headers, hold the 75/90 brake, classify every Meta code as retryable or terminal, and route non-idempotent publishes so a throttle cannot become a duplicate post. One error contract across fifteen platforms instead of fifteen taxonomies.

This is not for you if you are building against the Marketing API: we do not run ad-account traffic, and the Ads quota systems above are documentation, not experience. If you publish to Pages and read Page data at volume, it is the layer we needed and could not buy.

Frequently asked questions

What does Facebook error code 4 mean?

(#4) Application request limit reached means your app hit its Platform Rate Limit, a rolling one-hour budget of 200 × Number of Users, counted on daily active users. It is app-wide, so throttling one job will not clear it. Cut call volume across every process using that app token.

How long does a Facebook API rate limit last?

It depends which system you hit. Platform limits use a rolling one-hour window, so pressure eases as old calls age out. BUC Pages limits use a rolling 24-hour window, considerably longer. Meta publishes no fixed reset time; the only estimate is estimated_time_to_regain_access, in minutes.

What is the difference between X-App-Usage and X-Business-Use-Case-Usage?

Different systems, different shapes. X-App-Usage reports Platform usage as a flat object with call_count, total_cputime and total_time. X-Business-Use-Case-Usage reports BUC usage as a map of business-object-id to arrays of objects, up to 32 per response, each with a type. Parse both.

Why do I get error 80001 instead of 32?

Your token. Pages API calls made with a Page or system user access token fall under Business Use Case limits and fail with 80001. The identical call made with a user or app access token falls under Platform Rate Limits and fails with 32. Same endpoint, different quota, different recovery window.

Does Facebook send a Retry-After header on rate limit errors?

Meta's rate-limiting documentation never mentions Retry-After, not for 4, 17, 32 or 80001. Do not build a backoff on it. The documented signals are the usage headers before you are blocked, and estimated_time_to_regain_access after. Record it if it arrives, but treat its absence as normal.

Marcel Czuryszkiewicz
Written by

Marcel Czuryszkiewicz

Co-Founder

Marcel is one of the builders behind bundle.social, creating a social media API with a strong focus on developer experience, reliability, and real support. Before bundle.social, he worked at Samsung R&D, Reply AI, and Docplanner, building internal tools that helped companies grow. He brought that same practical, product-focused mindset to bundle.social, so you can trust that your project is in good hands.

Keep reading