Meta Marketing API: Managing Many Clients' Ad Accounts

By Nick ReyesPublished 11 min read

The short answer

The Meta Marketing API is Meta's API for creating, managing, and reporting on ads. To run it across many clients, an agency gets partner access to each client's ad account in Business Manager, while a platform has each customer connect through Facebook Login for Business. Both need Advanced Access to ads_management and Full Access in the Marketing API Access Tier. Adeli's Ads API gives platforms that access without running Meta's review.

The Meta Marketing API is Meta's API for creating, managing, and reporting on ads across Facebook, Instagram, and Meta's other apps. Calling it for one ad account takes an afternoon. Calling it for fifty client ad accounts is a different build: you need a way into accounts you don't own, permissions Meta only grants after review, and a scheduler that respects quotas counted per ad account.

This guide is for two readers. Agencies building internal tools to run many clients' ad accounts, and ad and marketing platforms putting Meta ads inside their own product for their customers. It covers the access model each one uses, the review and tiers both need, and how to keep rate limits and reporting under control as the account count grows. For single-topic depth, it links to the rest of our Meta series instead of repeating it.

What does the Meta Marketing API do for a multi-account tool?

The Meta Marketing API lets one app read and change ads in every ad account it has been granted, through the Graph API at graph.facebook.com. Each ad account holds campaigns, each campaign holds ad sets, and each ad set holds ads. A multi-account tool runs the same calls across many accounts, so the hard parts are access, quotas, and reporting, not the objects.

The objects, in one line each:

Object ID format Holds
Ad account act_<digits> Billing, currency, time zone, the client's spend
Campaign digits One objective
Ad set digits Budget, schedule, targeting
Ad digits The creative that delivers

Our Meta Ads API guide walks through the object model and a first call. This post starts where that one stops: at the second client.

Which access model fits: partner access or customer login?

Agencies usually reach client ad accounts through Business Manager partner access and a system user; platforms usually have each customer connect through Facebook Login for Business. The right model depends on who owns the relationship with the ad account and whether clients self-serve.

Agency internal tool Platform for its customers
Who uses the tool Your own team Your customers, inside your product
How you get into an ad account Client shares it with your business portfolio as a partner Customer clicks "Connect" and picks assets in Facebook Login for Business
Token One system user token from your own portfolio One token per customer: a user access token or a business integration system user token
Onboarding a new client A request the client approves in Business Settings Self-serve, in your app
Meta status you need Advanced Access, business verification Advanced Access, business verification, Tech Provider (for business integration tokens)
Breaks when A client removes your partner access A customer revokes the connection or loses their ad-account role

Many teams end up with both: a partner-access setup for managed-service clients, and a login flow for self-serve ones. Store which model each ad account came through, because the token, the error handling, and the offboarding all differ.

How do agencies get access to client ad accounts?

An agency's business portfolio requests partner access to each client's ad account, the client approves it, and the agency assigns its system user to that account. Meta's Business-to-Business guide puts it this way: "A Meta Business Manager may request access to an ad account or Page owned by another Business Manager."

The request goes to your own business's client_ad_accounts edge, with the tasks you need:

curl -X POST "https://graph.facebook.com/v26.0/$AGENCY_BUSINESS_ID/client_ad_accounts" \
  -F "adaccount_id=act_$CLIENT_AD_ACCOUNT_ID" \
  -F "permitted_tasks=['ADVERTISE','ANALYZE']" \
  -F "access_token=$AGENCY_ADMIN_TOKEN"

The request shows as CLIENT_RESPONSE_PENDING until an admin on the client side accepts it. Three details from Meta's docs trip people up:

  • The client has to expect you. Meta says you can only send a request "to the Business Manager that you intend to approve and that they must already know your business." Agree on it on a call first.
  • The endpoints are gated. The same page says "As of June 8, 2021, access to these endpoints is limited." If your app gets an error here, clients can still add your business as a partner by hand in Business Settings.
  • Don't claim ownership. The owned_ad_accounts edge transfers an account into your portfolio. Meta's ad accounts guide says "Most marketing companies won't need to claim ad accounts from their clients." Partner access keeps the client the owner, which is what they want when they leave.

Once the client approves, the account shows up under your business with access_type set to AGENCY. Assign your system user to it with the tasks it needs, then generate one system user token for the whole book of clients. Our Meta system user access token guide has the assignment and token calls step by step.

One system user is a single point of failure. Meta recommends a separate system user for each kind of access, so split reporting (ANALYZE) from campaign changes (ADVERTISE). On Limited Access you get one system user plus one admin system user; Full Access raises that to ten.

How do platforms connect their customers' ad accounts?

A platform sends each customer through Facebook Login for Business, which returns a token scoped to the business assets that customer selected. Meta's Facebook Login for Business docs call it "the preferred authentication and authorization solution for tech providers building integrations with Meta's business tools."

You set up a configuration in the App Dashboard (the permissions, the asset types, and the token type) and pass its config_id instead of scope. The token type is the main decision:

User access token Business integration system user access token
Tied to The person who logged in The customer's business portfolio
Meta says use it when "your app takes actions in real time, based on input from the user" "your app performs programmatic, automated actions on your business clients' assets"
Lifetime Short-lived, exchangeable for about 60 days Defaults to never expire
Survives the person leaving No Yes
Available to Any Business app Tech Providers only

For an ad platform that syncs metrics overnight and runs automations, the business integration system user access token is the better fit. It needs the authorization code grant, so the login call sets response_type and override_default_response_type:

FB.login(
  (response) => sendCodeToServer(response.authResponse?.code),
  {
    config_id: META_CONFIG_ID,
    response_type: "code",
    override_default_response_type: true,
  },
);

Your server exchanges the code, then asks which business the token belongs to so you can file it against the right customer:

curl -G "https://graph.facebook.com/v26.0/oauth/access_token" \
  --data-urlencode "client_id=$APP_ID" \
  --data-urlencode "client_secret=$APP_SECRET" \
  --data-urlencode "code=$CODE"
 
curl -G "https://graph.facebook.com/v26.0/me" \
  --data-urlencode "fields=client_business_id" \
  --data-urlencode "access_token=$BUSINESS_INTEGRATION_TOKEN"

Meta's docs say your app "can only access the assets that were designated by your business client" during that login. If a customer adds a new ad account later, they go through the flow again to grant it. Our Meta Ads API access token guide covers the plain user-token flow and how to debug a token that stops working.

What access does a Meta app need to manage other businesses' ad accounts?

An app that manages other businesses' ad accounts needs Advanced Access to ads_management (or ads_read), a verified business, and Full Access in the Marketing API Access Tier. Meta's authorization guide draws the line plainly: "If your app is managing other people's ad accounts, you need advanced access."

Each gate is separate, and passing one doesn't pass the others:

Gate What it controls How you get it
Advanced Access to ads_management, ads_read, business_management Whether people outside your app's roles can grant the permission App Review, per permission
Business verification Whether other businesses can grant your app anything Documents in Business Settings
Marketing API Access Tier Rate limits and system user quota Limited by default; Full after 500+ calls in 15 days with under 15% errors, then review
Tech Provider Business integration system user tokens Upgrade the app, or request the advanced permissions that make it one
The person's role Which ad accounts a token can reach The client grants it in Business Settings

Meta renamed the tiers on May 4, 2026: Ads Management Standard Access became the Marketing API Access Tier, and its Standard and Advanced levels became Limited Access and Full Access. Meta describes Limited Access as "For development only. Not for production apps running for live advertisers." Guides that talk about a development or standard tier mean Limited Access. The tier is separate from Standard and Advanced Access to a permission, which kept their names.

Two notes for multi-tenant apps:

  • Request what your screencast shows. A reviewer approves ads_management against a flow they can watch: a client connecting, picking an ad account, and a change made through your app. Our Meta app review guide covers what to record and how long a round takes.
  • Business verification comes first. Until it clears, other businesses can't grant your app permissions at all. If yours is stuck, see Meta business verification stuck in review. Tech Provider Access Verification is gone, as our Tech Provider guide explains.

How do rate limits work across many ad accounts?

Meta counts most Marketing API quotas per ad account, per use case, over a rolling hour, so a multi-account tool needs a scheduler that tracks each account separately. A busy client can be throttled while every other client's calls still go through.

On Limited Access, the ads_management quota is 300 calls an hour plus 40 per active ad, per ad account. Full Access raises the base to 100,000. Reads and writes to one account also share a points score, and an ad set's budget can change only four times an hour. The full table of quotas and error codes is in our Meta Marketing API rate limit guide.

The X-Business-Use-Case-Usage header reports usage on every response, keyed by business object ID. Read it on every call and park the account before it hits 100%:

type BucEntry = {
  type: string; // "ads_management", "ads_insights", ...
  call_count: number;
  total_cputime: number;
  total_time: number;
  estimated_time_to_regain_access: number; // minutes
};
 
const pausedUntil = new Map<string, number>(); // "<objectId>:<type>" -> epoch ms
 
export async function metaGet(path: string, token: string, params: Record<string, string> = {}) {
  const url = new URL(`https://graph.facebook.com/v26.0/${path}`);
  for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
  url.searchParams.set("access_token", token);
 
  const res = await fetch(url);
  const header = res.headers.get("x-business-use-case-usage");
  if (header) {
    const usage = JSON.parse(header) as Record<string, BucEntry[]>;
    for (const [objectId, entries] of Object.entries(usage)) {
      for (const e of entries) {
        const peak = Math.max(e.call_count, e.total_cputime, e.total_time);
        if (peak >= 75) {
          const waitMin = Math.max(e.estimated_time_to_regain_access, 5);
          pausedUntil.set(`${objectId}:${e.type}`, Date.now() + waitMin * 60_000);
        }
      }
    }
  }
 
  const body = await res.json();
  if (!res.ok) throw Object.assign(new Error(body.error?.message ?? res.statusText), { meta: body.error });
  return body;
}

Before each job, your queue checks pausedUntil for that account and use case and skips ahead to another client. The 75% threshold and five-minute floor are our defaults, not Meta's; tune them to your traffic.

How do I pull insights for dozens of ad accounts?

Start one asynchronous insights report per ad account, poll each report run, and read the results when it completes. Synchronous GET /insights calls time out on long date ranges or ad-level breakdowns, and an async job can take up to an hour, so a nightly sync should never wait on accounts one at a time.

export async function startReport(adAccountId: string, token: string): Promise<string> {
  const res = await fetch(`https://graph.facebook.com/v26.0/${adAccountId}/insights`, {
    method: "POST",
    body: new URLSearchParams({
      level: "campaign",
      fields: "campaign_id,campaign_name,spend,impressions,clicks",
      date_preset: "last_30d",
      time_increment: "1",
      access_token: token,
    }),
  });
  const body = await res.json();
  if (!res.ok) throw new Error(body.error?.message ?? res.statusText);
  return body.report_run_id;
}
 
export async function waitForReport(reportRunId: string, token: string): Promise<void> {
  for (;;) {
    const run = await metaGet(reportRunId, token, { fields: "async_status,async_percent_completion" });
    if (run.async_status === "Job Completed") return;
    if (run.async_status === "Job Failed" || run.async_status === "Job Skipped") {
      throw new Error(`${reportRunId}: ${run.async_status}`);
    }
    await new Promise((resolve) => setTimeout(resolve, 15_000));
  }
}

Read the rows from GET /{report_run_id}/insights and page with the cursors. In a multi-account sync:

  • Fan out a few accounts at a time. Insights have their own quota per ad account and an app-wide limit, so cap concurrency and watch the X-FB-Ads-Insights-Throttle header.
  • Retry per account. A failed or skipped job for one client shouldn't fail the whole night. Resubmit that account's report.
  • Don't keep report IDs. A report_run_id expires after 30 days.

Fields, breakdowns, attribution windows, and retention are in our Facebook Ads Insights API guide.

What breaks when a Meta Marketing API tool goes multi-tenant?

Most multi-tenant failures come from access changing under you: a client revokes a partner, a person leaves, or a token loses a permission. None of these show up while the only ad account is your own.

  • Testing with your own admin token. Your token has a role on the app, so it works on Standard Access. A client's token won't until you have Advanced Access. Test with an account outside your app's roles before launch.
  • One token for everything. A single user token from an employee breaks every client the day that person leaves. Use a system user token per job for partner access, and a token per customer for platform connections.
  • No record of how each account was connected. Partner-access accounts fail with permission errors when the client removes your business; login-based accounts fail when the customer revokes the app. Store the access model per ad account so your support team knows who has to act.
  • Global retry loops. Retrying a throttled call on a schedule shared by all clients turns one client's limit into everyone's delay. Back off per ad account.
  • Budget edits on every optimization pass. An ad set's budget can change four times an hour, so collect the changes and send the final number once.
  • Creating campaigns live. Create with status=PAUSED and activate in a separate step, so a bug never spends a client's money. Our create-a-campaign guide shows each object's create call.

New clients sometimes need a fresh ad account as well. Meta's limits on how many a new business can create are in our create an ad account guide.

Should you build on the Meta Marketing API or use an API that already has access?

Build directly when Meta ads are your core product and you can staff the review, the token storage, and the version upgrades; use an API like Adeli when Meta ads are one feature of a larger product. Both paths call the same Meta endpoints underneath. The difference is who carries the Meta app.

Build on the Meta Marketing API Use Adeli's Ads API
Meta app, App Review, business verification Yours to pass and keep Adeli's
Token storage and refresh per customer You build it Adeli holds each customer's connection
Rate-limit handling Per-account scheduler you write Throttling returns 429 rate_limited with Retry-After
Version upgrades Yours, on Meta's schedule Adeli's
Control over every Marketing API field Full The operations Adeli's endpoints expose
Other networks One integration per network Organic posting, plus TikTok and LinkedIn Ads, through the same key

Adeli runs the Meta app and its review for you. When we submitted ads_management, pages_manage_ads, and leads_retrieval, Meta's reviewer approved all three on the first submission (result received on October 15, 2026). We followed Meta's instructions for each permission as written, with a screencast of the full flow: login, consent, choosing the Page, creating a paused campaign, and the Leads tab.

Each of your customers connects a Meta Ads account to their Adeli profile, and Adeli's Ads endpoints then work across every ad account that connection reaches:

  • List businesses and ad accounts. List ad accounts returns accounts assigned directly and those owned by a business portfolio the customer administers.
  • Read campaigns and live metrics. The campaign tree rolls up spend, impressions, reach, clicks, CTR, CPC, and CPM at every level, and daily insights come back per account or per object. Adeli reads these from Meta on each request instead of caching them, so a dashboard never shows yesterday's spend.
  • Create safely. Create campaign promotes a post the Page already published, creates everything paused, and requires an Idempotency-Key so a retry never builds a second campaign.
  • Read leads. Lead form submissions come back with the campaign, ad set, and ad each lead came from.
curl -G "https://app.tryadeli.com/api/v1/ads/accounts" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  --data-urlencode "accountId=$META_ADS_ACCOUNT_ID"

Meta's access tier and each customer's ad-account role still apply on top of Adeli's grants. If you're building an AI marketing platform or vertical SaaS, each customer is one Adeli profile with its own connections, and pricing shows how those connections are billed.

faq

Frequently asked questions

Does each client need their own Meta app?

No. One Meta app, owned by your business portfolio, serves every client. Each client grants that app access, either by sharing an ad account with your business as a partner or by connecting through Facebook Login for Business. Your app's App Review and access tier cover all of them.

Can one system user manage every client's ad account?

For an agency, yes: assign the system user to each client ad account your business has partner access to. For a platform, each customer's Facebook Login for Business connection produces its own business integration system user token, scoped to the assets that customer chose, so tokens stay separate per customer.

Do I need to be a Tech Provider to use the Marketing API?

Only to use business integration system user tokens from Facebook Login for Business, which Meta reserves for Tech Providers. An agency that works through partner access and its own system user doesn't need Tech Provider status, but still needs Advanced Access and business verification to touch clients' accounts.

Power your next project
with one ads & social media API

Give your users publishing, scheduling, and analytics through one integration your team can actually maintain.

Try with agents

Start with free credits. No credit card required.