Files
meta/documentation/meta-ads.md
T
JiriUhlir a7cb53e0e6 first
2026-07-20 07:36:55 +02:00

8.5 KiB

Meta Marketing API

Proxy over the Meta Graph / Marketing API: reading the ad account structure (ad accounts, campaigns, ad sets, ads, creatives) and insights (spend, impressions, clicks, conversions).

This phase is read-only. Every endpoint issues a GET upstream; campaign management is phase 2 (see overview.md, "Deliberately not wired").

Base URL: https://graph.facebook.com/{version} — the version comes from META_API_VERSION (default v25.0) and can be overridden per request with X-Meta-Api-Version, so a Graph upgrade needs no deploy.

Credentials

Header Required Meaning
X-Meta-Access-Token one of these Access token → sent upstream as Authorization: Bearer.
Authorization one of these Standard Authorization: Bearer <token> — equivalent alternative (the X- header wins if both are sent).
X-Meta-App-Secret no, recommended App secret; the proxy derives appsecret_proof per request.
X-Meta-Api-Version no Graph version override, e.g. v25.0.

Use a System User token from Business Manager. Unlike a user OAuth token it does not stop working when someone leaves the company or changes their password, and when generated without an expiry it needs no refreshing at all.

Required permissions for this read-only phase: ads_read, business_management.

Endpoints

Structure

Method Path Purpose
GET /ads/me/adaccounts Ad accounts the token can access.
GET /ads/me/businesses Business Manager accounts the token can access.
GET /ads/businesses/{business_id}/adaccounts Accounts owned by a business (owned=false → accounts shared with it).
GET /ads/accounts/{account_id} Ad account detail.
GET /ads/accounts/{account_id}/campaigns Campaigns in an account.
GET /ads/accounts/{account_id}/adsets Ad sets in an account.
GET /ads/accounts/{account_id}/ads Ads in an account.
GET /ads/accounts/{account_id}/adcreatives Creatives in an account.
GET /ads/campaigns/{campaign_id} Campaign detail.
GET /ads/campaigns/{campaign_id}/adsets Ad sets in a campaign.
GET /ads/campaigns/{campaign_id}/ads Ads in a campaign.
GET /ads/adsets/{adset_id} Ad set detail.
GET /ads/adsets/{adset_id}/ads Ads in an ad set.
GET /ads/ads/{ad_id} Ad detail.
GET /ads/adcreatives/{creative_id} Creative detail.

account_id is accepted with or without the act_ prefix.

Every list endpoint takes fields (comma-separated Graph fields, with a useful default), limit, after (cursor) and all_pages (auto-paging, on by default — see Pagination). The account-level campaign / adset / ad endpoints also take effective_status, a JSON array such as ["ACTIVE","PAUSED"].

Insights

Method Path Purpose
GET /ads/insights/{object_id} Synchronous insights — small reports.
POST /ads/insights/{object_id}/jobs Start an async report job → report_run_id.
GET /ads/insights/jobs/{report_run_id} Job status (async_status, async_percent_completion).
GET /ads/insights/jobs/{report_run_id}/results Read a finished job.
POST /ads/insights/{object_id}/run Start, wait for completion, return the rows.

object_id is an ad account (act_123), campaign, ad set or ad id.

Parameters (identical across all of them): fields, level, date_preset, time_range, time_increment, breakdowns, action_breakdowns, filtering, sort.

Sync or async? Meta computes large reports asynchronously and will reject or time out a sync call that is too big. Rule of thumb: one account or a handful of campaigns over a short period → GET. Long periods, several breakdowns, or a whole account at ad level → /run.

/run polls until the job finishes or the wait budget (META_ASYNC_MAX_WAIT_SECONDS, default 120 s, overridable per call with max_wait_seconds) runs out. On timeout the job is not cancelled — the response carries completed: false and the report_run_id, so you can keep polling /ads/insights/jobs/{report_run_id} instead of losing the work.

Fields and levels

The default fields list is deliberately level-agnostic:

spend,impressions,clicks,ctr,cpc,cpm,reach,frequency,actions,action_values,date_start,date_stop

Name/id fields are level-specific — asking for ad_id at campaign level is an upstream error. Add them together with level:

  • level=campaigncampaign_id,campaign_name,…
  • level=adsetadset_id,adset_name,campaign_name,…
  • level=adad_id,ad_name,adset_name,campaign_name,…

Conversions live in actions / action_values (arrays keyed by action_type), not in a top-level metric.

Generic read passthrough

Method Path Purpose
GET /graph/{graph_path} Any Graph API GET.

For everything the typed endpoints do not cover (custom audiences, ad rules, Instagram accounts, Pages…) without needing a deploy. GET-only by construction, so the read-only stance holds here too. Give the path without the version prefix — the version comes from config or X-Meta-Api-Version. Unknown query params are forwarded verbatim.

Pagination

The proxy follows paging.next for you by default (all_pages=true), so a list call returns the complete list:

{ "data": [ ... ], "pages_read": 3, "truncated": false }

truncated: true means the META_MAX_PAGES cap (default 100) was hit; the response also carries the next URL and a warning is logged. Graph's own page size defaults to 25 — pass a bigger limit (up to 500 on most edges) to fetch more rows in fewer round trips.

Pass all_pages=false to get a single raw page with the upstream paging.cursors untouched and drive the cursor yourself.

Rate limits

X-App-Usage, X-Ad-Account-Usage and X-Business-Use-Case-Usage from the upstream response are copied onto our response — use them to pace calls.

Errors

Status Meaning
401 missing_credentials No token, or a malformed X-Meta-Api-Version.
400 upstream_error Meta rejected the request (bad field, bad id, missing permission).
502 / 504 Meta unreachable, 5xx, or a request timeout.

Upstream errors keep Meta's status and the whole error object in upstream_body, including code, error_subcode and fbtrace_id (quote it when opening a case with Meta). detail prefers Meta's human-readable error_user_msg when present.

Common ones:

  • code 100, "Invalid appsecret_proof" — the app enforces app-secret proof; send X-Meta-App-Secret.
  • code 190 — token expired or revoked (a user OAuth token after a password change; a System User token generated with an expiry).
  • code 200 — the token lacks ads_read on that ad account.
  • code 17 / 613 — rate limited; check the usage headers and back off.

Kde získat údaje (návod pro klienta)

  • Access token (System User): Business ManagerNastavení firmy → Uživatelé → Systémoví uživateléPřidatPřidat aktiva (vyberte reklamní účty, oprávnění Správa kampaní) → Vygenerovat nový token. Bez zadané expirace je token trvalý. Oprávnění pro tuto fázi: ads_read, business_management.
  • App secret: developers.facebook.com → vaše aplikace → Nastavení → Základní → App Secret. Povinný, pokud má aplikace zapnuté Require app secret proof for server API calls.
  • ID reklamního účtu: Business Manager → Nastavení firmy → Reklamní účty, nebo vlevo nahoře v Ads Manageru. Prefix act_ je volitelný.

curl examples

Campaigns in an account:

curl "https://services.csbot.cz/apps/meta/ads/accounts/123456789/campaigns?limit=200" \
  -H "X-Meta-Access-Token: EAAG..." \
  -H "X-Meta-App-Secret: <APP_SECRET>"

Daily campaign insights for June, run asynchronously:

curl -X POST "https://services.csbot.cz/apps/meta/ads/insights/act_123456789/run\
?level=campaign\
&fields=campaign_id,campaign_name,spend,impressions,clicks,ctr,actions\
&time_range=%7B%22since%22%3A%222026-06-01%22%2C%22until%22%3A%222026-06-30%22%7D\
&time_increment=1" \
  -H "X-Meta-Access-Token: EAAG..." \
  -H "X-Meta-App-Secret: <APP_SECRET>"

Anything not covered by a typed endpoint:

curl "https://services.csbot.cz/apps/meta/graph/act_123456789/customaudiences?fields=id,name,approximate_count" \
  -H "X-Meta-Access-Token: EAAG..."