---
name: meta-ads-oauth-app
description: >-
  The Marketing API layer on top of the shared `meta-graph-app` skill — read
  that one first for the developer account, app, redirect URIs, access levels,
  review regimes, App ID and Secret, and Graph API versioning. This file
  covers only what Meta Ads adds: the Business app type with the Marketing API
  product and a Facebook Login for Business configuration, the `ads_read` /
  `ads_management` / `business_management` permissions, the Limited→Full
  access tier, the ~60-day user token versus the non-expiring System User
  token, the Marketing API's 90-day version clock, and ad-account and insights
  throttles. Use when asked for Meta Ads or Facebook Ads OAuth credentials or
  a Marketing API app, or when an ads connector is throttled, dies after ~60
  days, or fails with `(#294)`, `(#80000)` or `(#80004)`. For Instagram use
  `instagram-oauth-app`; for Google Ads use `google-ads-oauth-app`.
---

# Meta Ads (Marketing API) OAuth2 App Registration

Get a Meta app that can actually read a customer's live ad accounts: a Business-type app carrying the **Marketing
API** product and a **Facebook Login for Business** configuration, an App ID and App Secret, and — the part that
takes weeks — the approvals and the rate-limit tier that stand between a valid credential and a working one.

The registration mechanics are not here; they are identical for every Meta product and live in the base skill. Three
things are specific to Meta Ads, and all three are decided before you write a line of code.

**First, Standard Access arrives free with the product, and that is the trap.** Meta grants the Marketing API
permissions automatically "when you add the Marketing API product to your app" — at Standard level, which reaches
only people who hold a role on your app. Everything looks configured and nothing works for a customer. All three
permissions this connector needs are also on the Access Verification list, so a second, independent gate produces the
same symptom with a different error.

**Second, the token model decides your operational burden, and you pick it in the dashboard.** A Meta user access
token is short-lived, exchanges once into a ~60-day token, and cannot be renewed — so re-authorization becomes a
scheduled campaign. Choosing a **Business Integration System User access token** in your login configuration instead
gives you tokens that default to never expire. That choice is made at registration, and changing it later
re-authorizes every customer.

**Third, the Marketing API version clock is 90 days, not two years.** The two-year rule is the Graph API's. The
Marketing API ships every few months and retires versions in about a quarter.

## Built on: `meta-graph-app`

**Read `meta-graph-app` first, in full, then come back here.** It owns:

- **The developer account, the business portfolio that claims the app, and the app itself** — the creation wizard and
  its two irreversible choices (app type, and any use case you add), plus the Business-vs-Consumer difference that
  means a Business app has **no app modes** at all.
- **Redirect URIs** — exact matching, HTTPS only, the dashboard's silently appended trailing slash, the requirement
  that authorize and token exchange send the same value, and this platform's per-data-center callback list.
- **Access levels and the four review regimes** — Standard vs Advanced Access, what an App Review submission asks
  for, Business Verification, **Access Verification / Tech Provider** (including the `(#100)` error only your
  customers ever see), and the Ongoing Review and annual Data Use Checkup that can take it all away again.
- **The App ID and App Secret**, `appsecret_proof` and the "Require App Secret" switch, and what secret rotation
  breaks; plus the rule that Meta issues no OAuth refresh tokens on any surface.
- **Graph API versioning and the two-year rule**, the `X-App-Usage` and `X-Business-Use-Case-Usage` headers, and the
  generic `#1` / `#4` / `#10` / `#17` / `#100` / `#102` / `#190` / `#200` and 80000-series error codes.

If you loaded only this file, you are missing all of the above, and nothing below substitutes for it — in particular,
creating the app, registering redirect URIs, getting Advanced Access and capturing the secret are base-skill steps.
Where this file and the base disagree, this file wins, because Business-type apps and the Marketing API deviate from
the general rules in exactly the places called out below.

## Inputs specific to Meta Ads

The base skill's input table applies in full. Collect these on top of it, in the same batch.

| Input | Notes |
| --- | --- |
| **Token type**: user access token or Business Integration System User token | The single biggest operational decision here (§1, §5) |
| **Read-only or read-write?** | `ads_read` alone vs `ads_read` + `ads_management` (§2) |
| **Does the connector resolve owning businesses or list business-owned assets such as product catalogs?** | The only reason to request `business_management` (§2) |
| **A test ad account** you control, plus one owned by someone with no role on the app | (§8) |
| **Expected call volume, and when real customer traffic starts** | Decides how long you sit at the throttled tier (§4) |

## Quick Start

1. Read **Platform state** below — the tier names and the version clock both changed recently.
2. Create a **Business**-type app per the base skill, then add the **Marketing API** product and **Facebook Login for
   Business** (§1).
3. Create a login **configuration** and pick the token type there — this is the 60-day decision (§1, §5).
4. Register the redirect URIs in the Facebook Login settings panel (§1).
5. Request only the permissions the connector calls (§2).
6. Run the base skill's four review regimes; note that all three ads permissions need Access Verification too (§3).
7. Separately upgrade the **Marketing API access tier** from Limited to Full once you have 500 clean calls (§4).
8. Wire the short-lived → long-lived exchange, or the system-user path, and schedule re-authorization (§5).
9. Pin a Marketing API version and calendar its 90-day grace window (§6).
10. Verify with an ad account belonging to someone with **no role on the app** (§8), then hand off (§9).

## Platform state (verified 2026-09-20 — re-verify before trusting)

Read this alongside the base skill's Platform state; these are the Marketing-API-specific facts.

- **The Marketing API access tier was renamed.** Meta's authorization page: "Tier labels have been updated:
  'Standard Access' is now **Limited Access**, and 'Advanced Access' is now **Full Access**." Limited is
  "Heavily rate-limited per ad account. For development only. Not for production apps running for live advertisers";
  Full is "Lightly rate limited per ad account."
- **The Full Access threshold was lowered.** "The revised qualification threshold for Full Access has been reduced
  from 1,500 to **500 Marketing API calls** in the past 15 days," alongside "an error rate of less than 15% in the
  last 500 calls."
- **Meta's own docs have not finished the rename.** The Graph API rate-limiting page and the Ads Insights
  best-practices page still describe the "Ads Management Standard Access" *feature* and report `ads_api_access_tier`
  values of `development_access` and `standard_access`. Two Meta pages, two vocabularies, one mechanism. Read both.
- **The Marketing API versions page says "The current version of the Marketing API is v25.0" while the changelog
  lists v26.0 (released 29 July 2026) as the latest.** Trust the changelog table.
- **Marketing API versions live about a year, with a documented 90-day floor.** Listed versions: v26.0 (29 Jul 2026,
  expiry TBD), v25.0 (18 Feb 2026, expiry TBD), v24.0 (8 Oct 2025 → **6 October 2026**). The rule is "at least a
  90-day grace period" after the next release, not the Graph API's two years (§6).
- **Marketing API auto-upgrade exists since May 2024 but only partly saves you** — it covers endpoints unaffected
  between versions; endpoints the new version changed fail outright (§6).
- **Facebook Login for Business is the stated preferred path** for "tech providers building integrations with Meta's
  business tools," it requires a Business-type app, and in it `config_id` "has replaced `scope`" (§1).

If the App Dashboard does not look like this, stop and report what you actually see rather than clicking on.

## 1. The two products, and the configuration that carries the real decision

Create the app as a **Business** type per the base skill, then in the dashboard:

1. **Add Product** → set up **Marketing API**. This is what makes the ads permissions available; Meta grants them
   "Automatically granted when you add the Marketing API product to your app," at Standard level only (§3).
2. **Add Product** → set up **Facebook Login for Business**.
3. Left menu → **Configurations** → **+ Create configuration** (or **Create from template**). Name it, then
   **choose the access token type**: a *User access token* (your app users log in with their personal Facebook
   account) or a *System-user access token* (granted by the business client's portfolio), plus token expiration.
   Select the assets and permissions the configuration asks for. The configuration is what a customer is shown at
   consent time, and its ID is the `config_id` you send instead of `scope`.

Redirect URIs are registered under **App Dashboard → Products → Facebook Login (for Business) → Settings → Client
OAuth Settings → Valid OAuth Redirect URIs**. The matching rules, the trailing-slash trap and the callback list are
in the base skill — re-read the saved list there before debugging anything else.

Two properties of Facebook Login for Business that change how you design the consent step:

- **It is all-or-nothing.** "All permissions that your app asks for during login must be granted by your app user or
  your app won't be granted any permissions." There is no partial grant to fall back on, so an over-broad
  configuration converts worse, not merely slower.
- `email` and `public_profile` "are automatically granted to all apps but at least one other supported permission
  must be included for each app installation." They are identity-only and grant no ads access.

You can create several configurations and present different ones to different users — a read-only tier and a full
management tier without a second app.

**Ad account IDs.** Meta writes them conventionally as `act_<id>`; the bare numeric form and the prefixed form are
both in circulation and mixing them is a routine configuration error. Which form a platform wants is a platform
question, not a Meta question.

## 2. Permissions

Three permissions matter. Each one you add is a separate App Review submission and widens the Access Verification
description you have to defend.

| Permission | Grants | Add it when |
| --- | --- | --- |
| `ads_read` | Reading ad accounts, campaigns, ad sets, ads, creatives and insights | Always |
| `ads_management` | Everything `ads_read` does, plus creating and modifying ads | Only if the connector writes |
| `business_management` | Reading and managing the business portfolio and assets it owns | Only if you resolve the owning business or list business-owned assets such as product catalogs |

Do not request `ads_management` for a read-only connector; the base skill explains what an unused permission costs at
review time.

**Product facts about the connector, as of 2026-09-20.** Recorded from one day's configuration; confirm with the
connector's owner before relying on any of it.

- It requests `ads_read` for every readable ads object, `ads_management` for every write, and `business_management`
  on exactly one path — the promoted-object listing that must resolve the owning business before it can enumerate
  that business's product catalogs. `email` and `public_profile` are its login/identity scopes.
- It uses the **classic Facebook Login manual flow, not the Facebook Login for Business `config_id` flow**: it
  authorizes at `https://www.facebook.com/v25.0/dialog/oauth` sending `client_id`, `redirect_uri`, `state` and
  `scope`, and exchanges the code at `https://graph.facebook.com/v25.0/oauth/access_token` with `client_id`,
  `client_secret`, `code` and `redirect_uri` as POST parameters. No PKCE (`code_challenge` / `code_verifier`) is
  sent, and no `appsecret_proof` is computed — so do **not** turn on "Require App Secret" for this app.
- It presents the access token as a **URL query parameter** (`access_token=…`) rather than an `Authorization:
  Bearer` header. Meta accepts both; the query-parameter form is the one that ends up in proxy and access logs.
- It pins **v25.0** — not only in the authorize and token URLs but hard-coded into the request path of essentially
  every object it implements. Re-pinning is a multi-file change, not a config edit (§6).
- It **carries no long-lived-token exchange and no token renewal at all**. Nothing in it turns the short-lived token
  from the code exchange into a 60-day token, and nothing renews one (§5). This is the single most consequential gap
  to raise with the connector's owner.
- It also supports a non-OAuth path: a directly supplied access token plus a numeric ad account ID, documented for a
  long-lived user token or a Business-Settings System User token (§5).
- On connect it reads the authorizing identity and enumerates that identity's ad accounts, storing their IDs. It
  deliberately requests only id and name there so the call needs `ads_read` and not `business_management`.
- It parses Meta's error envelope and remaps HTTP 500 responses to 400 so the provider message survives. It does
  **not** read `X-Business-Use-Case-Usage`, `X-Ad-Account-Usage` or `x-fb-ads-insights-throttle` (§7).
- Its published partnership/info link points at a Marketing API partners page that returns **404** today.

**Partial-grant risk.** Under the classic `scope` flow a user can decline individual permissions, so the connector
can hold a token that authorized cleanly and then fails only on the promoted/catalog path — a late, confusing
failure. Facebook Login for Business inverts this: all-or-nothing, so it fails early instead (§1). Decide which
failure mode you want before launch.

## 3. What the base skill's gates mean here

All four review regimes in the base apply unchanged. Three Ads-specific consequences:

**Standard Access is handed to you, which is why people miss the problem.** Adding the Marketing API product grants
the ads permissions immediately — at Standard level, reaching only app-role users. Nothing in the dashboard looks
wrong, and your own testing passes. Advanced Access is the fix, per permission, through App Review.

**All three permissions are on the Access Verification list.** `ads_management`, `ads_read` and
`business_management` are all covered, so a multi-tenant ads connector needs Tech Provider status for the claiming
business on top of App Review and Business Verification. Expect the `(#100)` symptom described in the base skill.

**Facebook Login for Business reduces the ongoing burden.** "Apps using Facebook Login for Business have reduced
requirements for certain ongoing compliance reviews because they are limited to accessing business permissions and
features" — a second, quieter reason to build on the business login product rather than the consumer one.

## 4. The Marketing API access tier: Limited → Full

Independent of every gate above, and about rate limits rather than who can connect. Check at **App Dashboard →
App Review → Permissions and Features**; upgrade with **+Upgrade** on the Marketing API Access Tier feature.

The requirements are usage-based, not a form: "at least 500 Marketing API calls in the last 15 days" with "an error
rate of less than 15% in the last 500 calls."

Note the ordering trap. You cannot accumulate 500 production calls until customers can connect, and customers cannot
connect until the gates clear — so plan for a window in which the connector is live, correct and heavily throttled.
This is also the strongest argument against spinning up a fresh app: a new App ID starts at Limited with zero call
history toward the threshold, on top of losing its App Review approvals.

The tier also caps system users on your own portfolio: Limited allows "1 system user and 1 admin system user," Full
allows "10 system users and 1 admin system user."

## 5. Tokens: 60 days, no renewal, and the way out

| Token | How you get it | Lifetime |
| --- | --- | --- |
| Short-lived user token | The code exchange at `graph.facebook.com/v{n}/oauth/access_token` | "about one to two hours" |
| Long-lived user token | `GET oauth/access_token?grant_type=fb_exchange_token&client_id=…&client_secret=…&fb_exchange_token=<short-lived>` | "about 60 days" |
| Business Integration System User token | Chosen as the token type in a Facebook Login for Business configuration (§1) | Defaults to **never expire** |
| System User token (your own business) | Meta Business Settings, on your own portfolio | Long-lived / non-expiring |

The facts that decide whether connections survive:

- **Expiry is terminal.** Meta: "You can not use an expired token to request a long-lived token. If the token has
  expired, your app must send the user through the login flow again to regenerate a new short-lived access token."
  So re-authorization at around day 55 is a **scheduled operational task with a customer email attached**, not an
  error condition. A connector that treats a 60-day expiry as an incident pages someone every 60 days per customer.
- **A connector that never exchanges dies in an hour or two**, not in 60 days. That is the more common failure and it
  presents as an intermittent auth bug.
- **The Business Integration System User token is the multi-tenant answer.** It is "associated with your business
  client's business portfolio rather than a specific user," uses the **Authorization Code grant only**, defaults to
  never expire, and access "is explicitly delegated at the time of authorization" — your app reaches only the assets
  the client designated. If you want an expiring one, `set_token_expires_in_60_days` exists, and a 60-day system-user
  token can be renewed through the system-user token API without sending the human back through consent.
- **Plain System Users do not help a multi-tenant platform.** A system user represents "servers or software making
  API calls to assets owned or managed by a Business Manager" — *that* business manager. They are the right answer
  for a customer automating their own ad account and the wrong answer for a platform connecting many customers,
  because you would need a system user created inside each customer's portfolio by each customer's admin. The
  Business Integration System User token is the productized version of that idea, and the one that scales.
- Picking the token type is a **dashboard decision on the login configuration** (§1). Switching a live app between
  user tokens and system-user tokens changes the grant flow and re-authorizes every customer. Decide before launch.

## 6. The Marketing API version clock

The base skill's Graph API two-year rule does **not** govern `/v{n}/act_…` calls. The Marketing API "has its own
versioning scheme," and it is far shorter:

- New versions land "approximately every four months."
- The previous version is supported "for at least 90 days"; after that "the deprecated version stops working."
- **Auto-upgrade is partial.** Since May 2024 a call to a deprecated version is upgraded to the next available
  version *only if that endpoint is unaffected between the two versions*. Endpoints the new version changed fail
  outright. The Marketing API changelog for each release lists exactly which endpoints changed — that list is your
  actual upgrade checklist.
- **Unversioned calls are rejected outright**: "Unversioned calls are invalid and will fail when made against
  Marketing API endpoints." This is stricter than the Graph API fallback behaviour described in the base.
- Consequence: a connector can pass every test and then start failing on *some* endpoints on a date nobody deployed,
  while other endpoints quietly answer from a newer version than the one in the URL. Both halves are worse than a
  clean break.
- A connector that uses one version string for both its OAuth dialog/token calls and its Marketing API calls is
  accepting the **shorter** of the two clocks for everything.

## 7. Rate limits: two more ceilings beyond the base

The base skill covers `X-App-Usage` and `X-Business-Use-Case-Usage`. Meta Ads adds two more, and the access tier is
worth a factor of several hundred on the first.

**What the tier is worth.** The published business-use-case formulas: Ads Management is `300 + 40 × active ads` per
hour at the lower tier versus `100000 + 40 × active ads` at the higher one; Ads Insights is
`600 + 400 × active ads − 0.001 × user errors` versus `190000 + 400 × active ads`. That is why §4 is not optional for
production.

**Per ad account.** `X-Ad-Account-Usage` carries `acc_id_util_pct` (percentage of this ad account's limit consumed)
and `reset_time_duration` in seconds. A single heavy customer can throttle themselves while your app-level headroom
looks fine, and vice versa — so backoff keyed only to app-level usage will be wrong for that customer.

**Insights has its own load limit on top of both.** `/insights` calls are subject to "a fixed load limit per
application per second," measured by resource cost rather than call count, reported in `x-fb-ads-insights-throttle`
as `{"app_id_util_pct":…, "acc_id_util_pct":…, "ads_api_access_tier":…}`, and applied "to each app sending
synchronous and asynchronous `/insights` calls combined." Breaching it returns `error_code = 4`. Meta's guidance:
"Try sync calls first and then use async calls in cases where sync calls timeout," "spread your `/insights` queries
by pacing them with wait time in your job," and "add a back-off mechanism … when you come close to hitting 100%
utility for your application, or for your ad account."

**Which 80000-series code is which**, since the two ads buckets are easy to confuse: `(#80000)` is **Ads Insights**
and `(#80004)` is **Ads Management**, both with subcode `2446079`.

One dated reporting change worth carrying into any insights work: since **10 June 2025**, `reach` (and `frequency`,
`cpp`) is not returned for standard queries that apply `breakdowns` with a `start_date` more than 13 months old;
asynchronous jobs can still retrieve it, limited to 10 requests per ad account per day and metered by
`x-Fb-Ads-Insights-Reach-Throttle`.

## 8. Capture the credentials, and verify

The App ID and App Secret, and where they live, are in the base skill. Unlike the Instagram surface there is **no
second, separately-displayed pair** here — if someone hands you an "Instagram App ID" for an ads connector, they are
on the wrong product.

Record these Ads-specific values alongside the base skill's list:

- The **Facebook Login for Business configuration ID** (`config_id`) and, per configuration, the token type and
  expiration you chose (§1, §5).
- The **pinned Marketing API version** and the date its grace window ends (§6).
- The **Marketing API access tier**, Limited or Full (§4) — separate from each permission's access level.

Verification follows the base skill's round trip, with the Ads-specific steps layered on: exchange the short-lived
token for a long-lived one and confirm the connector **stored the new value**; enumerate ad accounts; make one real
read against a campaign or insights endpoint with the pinned version; then repeat the whole thing against an ad
account owned by someone with no role on the app.

| Symptom | Cause |
| --- | --- |
| `(#294)` managing ads requires `ads_management` and allowlisted app access | A read-only credential attempting a write, or the app is not approved for the Marketing API (§2, §3) |
| Works for ~1–2 hours after each connect, then `(#190)` | The short-lived → long-lived exchange was never performed (§5) |
| Works for ~60 days, then `(#190)` for everyone at once | Long-lived tokens expired; there is no renewal, so this is scheduled re-authorization, not an outage (§5) |
| `(#200)` on one customer only | That user lacks the required role on the ad account — customer-side, not app-side (§2) |
| Authorization succeeded but only the catalog/promoted path fails | `business_management` was declined individually under the classic scope flow (§2) |
| `(#80000)` subcode `2446079` | **Ads Insights** business-use-case rate limit (§7) |
| `(#80004)` subcode `2446079` | **Ads Management** business-use-case rate limit — not the insights one (§7) |
| `(#4)` on `/insights` specifically | The per-app insights load limit; read `x-fb-ads-insights-throttle` (§7) |
| One customer throttled while your app-level usage looks healthy | Per-ad-account ceiling; read `X-Ad-Account-Usage` (§7) |
| Throttled constantly in production despite correct code | App still on the **Limited** Marketing API access tier (§4) |
| Some endpoints start failing on a date nobody deployed | The pinned Marketing API version passed its 90-day grace and those endpoints changed between versions (§6) |
| Responses subtly change shape without a deploy | The other half of the same cause: unaffected endpoints were auto-upgraded (§6) |
| `reach` missing from an insights response that used to include it | `breakdowns` with a `start_date` over 13 months old, since 10 June 2025 (§7) |

## 9. Hand off

Follow the base skill's handoff rules and add, to its closing list: the login configuration ID, token type and
expiry choice; the Marketing API access tier; the pinned Marketing API version and the date its grace window ends;
and the re-authorization cadence the platform must run if it is on user tokens.

## Stop and ask

The base skill's stop-and-ask list applies. Additionally, hand back to a human when: the choice between user access
tokens and Business Integration System User tokens is not clearly implied, since it changes the grant flow, the
ongoing operational burden and, if changed later, re-authorizes every customer; someone proposes requesting
`ads_management` or `business_management` that the connector does not actually call; the platform has no
re-authorization story for 60-day tokens; the connector's pinned Marketing API version is inside or past its 90-day
grace window and re-pinning touches many call sites; or the business decision about read-only versus full management
has not been made and it determines the review scope.

## References

Meta Ads specific. The generic Meta developer-account, app-creation, review, credential and Graph API references are
in `meta-graph-app`. Every link below verified to return HTTP 200 on 2026-09-20.

- Marketing API — https://developers.facebook.com/docs/marketing-api
- Marketing API authorization (permissions, access tiers, system user counts) — https://developers.facebook.com/docs/marketing-api/overview/authorization
- Marketing API get started: authorization — https://developers.facebook.com/docs/marketing-api/get-started/authorization
- Marketing API get started: authentication (token types) — https://developers.facebook.com/docs/marketing-api/get-started/authentication
- Marketing API access tiers — https://developers.facebook.com/docs/marketing-api/access
- Marketing API versioning, the 90-day grace and auto-upgrade — https://developers.facebook.com/docs/marketing-api/versions
- Marketing API changelog (per-version changed endpoints) — https://developers.facebook.com/docs/marketing-api/changelog
- Marketing API best practices — https://developers.facebook.com/docs/marketing-api/best-practices
- Ads Insights best practices, throttle headers and async jobs — https://developers.facebook.com/docs/marketing-api/insights/best-practices
- Marketing API system users — https://developers.facebook.com/docs/marketing-api/system-users
- System users: install apps, generate, refresh and revoke tokens — https://developers.facebook.com/docs/business-management-apis/system-users/install-apps-and-generate-tokens/
- Facebook Login for Business (configurations, `config_id`, token types) — https://developers.facebook.com/docs/facebook-login/facebook-login-for-business
- Manual login flow (dialog URL, redirect URI panel, code exchange) — https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow
- Get long-lived tokens (`fb_exchange_token`, 60 days) — https://developers.facebook.com/docs/facebook-login/guides/access-tokens/get-long-lived
