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=campaign→campaign_id,campaign_name,…level=adset→adset_id,adset_name,campaign_name,…level=ad→ad_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_readon 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 Manager
→ Nastavení firmy → Uživatelé → Systémoví uživatelé → Přidat →
Př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..."