API guide
Every report on the catalogue — and your network's own saved reports, ad units, placements, teams, apps and MCM child publishers — can be worked with by a program. Authenticate with an API key you created on your account page; the key stands for you — all the Google accounts you connected and the networks they see — and can do only what its capabilities allow, on the network it is pinned to or the one a call names.
Authentication
Send the key in a header on every request — either form works:
X-Api-Key: rfx_…
Authorization: Bearer rfx_…
Keys are shown once, at creation. Each has a name and an expiry you choose — at most one year, or never (such a key stays valid until you revoke it); revoke a key from the account page at any time. A key cannot create or revoke keys, choose the network or delete the account — those stay on the page.
Capabilities
When you create a key you tick the capabilities it holds. A call needing a capability the key lacks answers 403 {"error":"this API key lacks the capability …"}. Keys created before capabilities existed hold reports only.
reports | run and download the catalogue reports |
|---|---|
saved-reports | list and run the saved reports of the chosen network |
inventory:read | read ad units, placements, teams and mobile apps of the chosen network |
inventory:write | create ad units and placements, change their status, teams and placement membership |
mcm:read | list the network's MCM child publishers |
mcm:write | invite child publishers, resend or renew invitations, end agreements |
Which network
You may connect several Google accounts side by side; each brings the Ad Manager networks it can see. GET /api/gam/accounts lists the accounts with their networks, GET /api/gam/networks the networks with the account each comes through. There is no default network. Every call names the network it works on:
- a
networkfield in the JSON body, a?network=query parameter, or anX-Networkheader (the network code, e.g.network=12345678); - else the network the key is pinned to (chosen when the key was created; a pinned key that names another network gets
403 network_pinned); - else, only when your connected accounts see exactly one network, that one — otherwise
400 network_required, whose message lists the codes to choose from.
A named network must be one of the listed ones — 404 network_unknown otherwise. The service picks the connected Google account that sees it; you never say which account to use.
GET /api/gam/download?report=network-daily&preset=yesterday&network=12345678
POST /api/gam/inventory/ad-units {"network":"12345678","name":"…","sizes":["320x50"]}
Reports on several networks at once. Report calls (/api/gam/jobs and /api/gam/download) also accept network=all — every network your connected accounts see (a pinned key's "all" is its own network) — or a comma-separated list, network=12345678,23456789. The report is submitted once per network and the answer is a batch (below); a network that could not be started is listed under notSubmitted with its reason and the rest go ahead.
Everything a key does happens in your network with your Ad Manager rights — a call Ad Manager would refuse for your Google account is refused here too (502 with Ad Manager's reason). Every write is recorded in your change log (GET /api/gam/actions, and on the account page), whether it succeeded or failed.
One call: download a report
Submits the report, waits for Ad Manager to build it (up to about three minutes) and streams the file. The simplest way for a script when the report is quick.
GET /api/gam/download?report=ad-unit-daily&preset=yesterday&format=csv
GET /api/gam/download?report=country-daily&from=2026-08-01&to=2026-08-31&format=zip
curl -H "X-Api-Key: rfx_…" -o ad-units.csv \
"https://appmanager.revenueflex.com/api/gam/download?report=ad-unit-daily&preset=yesterday"
| report | a catalogue id (GET /api/gam/catalog lists them) |
|---|---|
| preset | today, yesterday, last7, last30, thisMonth, lastMonth — or give from and to as YYYY-MM-DD, inclusive, at most 92 days (7 for hourly reports) |
| format | csv (default, text/csv) or zip (application/zip, one CSV inside) |
Should Ad Manager take longer than the wait, the answer is 504 still_running with a JSON body naming the ticket (or batch) URL; the report keeps running and can be fetched through it for 24 hours. The CSV is Ad Manager's own CSV_DUMP export: a header row of column names, then one row per line; revenue columns are in micros of the network's currency.
Three calls: submit, poll, download
For larger reports, or to avoid a long-lived connection. POST /api/gam/jobs answers at once with a ticket: a handle you probe until Ad Manager has built the report, then download. Tickets are stored on the server, survive its restarts, and stay fetchable for 24 hours after submission; at most 10 may be open (submitted and not yet downloaded) per account.
POST /api/gam/jobs {"report":"ad-unit-daily","from":"2026-08-01","to":"2026-08-31","format":"zip","network":"12345678"}
→ {"id":"…","state":"IN_PROGRESS","networkCode":"12345678","statusUrl":"/api/gam/jobs/…","downloadUrl":"/api/gam/jobs/…/download?format=zip"}
GET /api/gam/jobs/{id} → {"state":"IN_PROGRESS" | "COMPLETED" | "FAILED", "error":"…" when failed, …}
GET /api/gam/jobs/{id}/download?format=zip (409 in_progress while still running)
With network=all or a list, the answer is a batch of tickets — one per network — with its own handle:
POST /api/gam/jobs {"report":"ad-unit-daily","preset":"yesterday","format":"zip","network":"all"}
→ {"batch":"…","state":"IN_PROGRESS","reports":[{"id":"…","networkCode":"12345678","state":"IN_PROGRESS","statusUrl":"…","downloadUrl":"…"}, …],
"notSubmitted":[{"networkCode":"…","error":"…","code":"…"}], (only when some network could not start)
"statusUrl":"/api/gam/batches/…","downloadUrl":"/api/gam/batches/…/download?format=zip"}
GET /api/gam/batches/{id} → the same shape; "state" is IN_PROGRESS while any report is, COMPLETED when at least one can be fetched, else FAILED
GET /api/gam/batches/{id}/download?format=zip|csv
A batch download is one ZIP with a CSV per network (named by network code, plus errors.txt when a network failed), or — format=csv — one CSV with the header once and a leading network_code column. The single tickets of a batch can also be fetched one by one. GET /api/gam/download with network=all waits for the whole batch and streams the same bundle.
Saved reports — capability saved-reports
The reports you saved in Ad Manager's own Reports page. They run exactly as saved — with their date range replaced when you give one.
GET /api/gam/saved
→ {"networkCode":"…","saved":[{"id":14356061544,"name":"…","compatible":true,"dateRangeType":"LAST_WEEK","dimensions":[…],"columns":[…]}, …]}
GET /api/gam/download?saved=14356061544 (the saved date range)
GET /api/gam/download?saved=14356061544&from=2026-08-01&to=2026-08-31 (that range instead)
POST /api/gam/jobs {"savedId":14356061544,"preset":"lastMonth","format":"zip"}
A saved report whose compatible is false was built with features the API version this service uses cannot run; Ad Manager reports it that way and the call answers 409. Saved reports carry no day limit of their own; the daily download cap still applies.
Inventory — capabilities inventory:read / inventory:write
GET /api/gam/inventory/network | the named network: name, time zone, currency, root ad unit id |
|---|---|
GET /api/gam/inventory/ad-units | ad units; filters status (ACTIVE | INACTIVE | ARCHIVED), parentId, q (name contains), limit (≤ 500), offset; answers {"total","offset","limit","results":[…]} |
GET /api/gam/inventory/ad-units/{id} | one ad unit: id, name, adUnitCode, parentId, status, description, targetWindow, sizes, hasChildren, appliedTeamIds, effectiveTeamIds, lastModified |
POST /api/gam/inventory/ad-units | {"name":"…","sizes":["320x50","300x250"],"parentId":"…","adUnitCode":"…","description":"…","targetWindow":"TOP","teamIds":["…"]} — name and at least one size are required; parentId defaults to the root ad unit; Ad Manager derives the code when none is given. Answers the created ad unit. |
POST /api/gam/inventory/ad-units/{id}/status | {"status":"ACTIVE" | "INACTIVE" | "ARCHIVED"} |
POST /api/gam/inventory/ad-units/{id}/teams | {"teamIds":["…"]} — the complete new team list (empty clears) |
GET /api/gam/inventory/placements | placements; filters status, q, limit, offset |
GET /api/gam/inventory/placements/{id} | one placement with its adUnitIds |
POST /api/gam/inventory/placements | {"name":"…","description":"…","placementCode":"…","adUnitIds":["…"]} |
POST /api/gam/inventory/placements/{id}/ad-units | {"add":["…"],"remove":["…"]} — membership change; answers the placement |
POST /api/gam/inventory/placements/{id}/status | {"status":"ACTIVE" | "INACTIVE" | "ARCHIVED"} |
GET /api/gam/inventory/teams | the network's teams (id, name, status, hasAllInventory, hasAllCompanies) |
GET /api/gam/inventory/apps | the mobile apps registered in the network (id, name, stores, storeId, approvalStatus, archived) |
Nothing is ever deleted: Ad Manager has no delete for ad units or placements, and neither has this API — archive instead. Status changes and membership changes are applied by Ad Manager as a whole and answer {"changed":n} or the placement.
Every change is reversible
Before each ad unit or placement change the service reads the object's state and records, with the change-log entry, exactly what would put it back. GET /api/gam/actions shows each entry with reversible and, when it can be reversed, a revertUrl:
GET /api/gam/actions
→ [{"id":91,"network":"12345678","via":"apikey","action":"AD_UNIT_INACTIVE","target":"123","status":"OK","detail":"1 changed (was ACTIVE)",
"at":"…","reversible":true,"revertUrl":"/api/gam/actions/91/revert"}, …]
POST /api/gam/actions/{id}/revert (capability inventory:write; the change's own network)
→ {"reverted":91,"action":"AD_UNIT_INACTIVE","result":{…the reversing call's answer…}}
| change | how it is reversed |
|---|---|
| ad unit / placement created | archived (Ad Manager cannot delete; the entry stays reversible until then) |
| status changed | set back to the status it had before |
| teams changed | the team list it had before is applied |
| placement membership changed | the ad units that actually left are added back, the ones that came in are removed |
| MCM invitation / action | not reversible (reversible:false) |
The reversal is itself a logged change (REVERT_…, with reverts naming the original), and can be reversed in turn. A change already reversed (revertedBy) answers 409 already_reverted; a failed or non-reversible one 409 not_reversible. The reversal is made in the state of the network at that moment — if something else changed the object in between, the recorded before-state is still what is applied.
MCM child publishers — capabilities mcm:read / mcm:write
GET /api/gam/mcm/children | the child publishers of the named (parent) network: companyId, name, email, childNetworkCode, invitationStatus, accountStatus, approved / proposed delegation type, proposedRevenueSharePercent |
|---|---|
POST /api/gam/mcm/children | {"name":"…","email":"…","delegationType":"MANAGE_ACCOUNT" | "MANAGE_INVENTORY","revenueSharePercent":30} — invites the publisher; Ad Manager sends the e-mail. Answers the child publisher record. |
POST /api/gam/mcm/children/{companyId}/action | {"action":"resend"} (resend the invitation), {"action":"reinvite","delegationType":"…","revenueSharePercent":…,"email":"…"} (a new proposal), {"action":"end"} (end the agreement) |
Other endpoints
GET /api/gam/catalog | the reports, presets, formats and capabilities (no authentication) |
|---|---|
GET /api/gam/token | is this key still good? Always 200: {"status":"expired","reason":"unknown|expired|revoked|account disabled"} when the key cannot be used, else {"status":"active","capabilities":[…],"network":…,"expiresAt":…,"expiresInSeconds":…,"name":…} — for a key that never expires expiresAt and expiresInSeconds are null and neverExpires is true. The one endpoint a bad key may call (no authentication beyond the key itself) |
GET /api/gam/status | the account behind the key or session: e-mail, enabled, connected, Google accounts, networks (count), the key in use, and credits — downloadsToday (200 per UTC day, with resetsAt), openReports (10 waiting for download), reportRequestsThisHour (5), activeKeys (20), each as {used, limit, remaining} |
GET /api/gam/me | who the key belongs to, how many Google accounts are connected and how many networks they see |
GET /api/gam/networks | every network your connected Google accounts can see — code, name, timeZone, accountId, accountEmail; refreshed from Ad Manager on each call |
GET /api/gam/accounts | the connected Google accounts — id, email, connected, tokenAt (when Google last issued access) and the networks each last listed — plus addUrl, the page that connects another account (connecting and disconnecting is done in the browser, with Google's consent screen) |
POST /api/gam/accounts/refresh | ask Google for a fresh access token for every connected account, or one: {"accountId":2}. Answers {"accounts":[{"id","email","ok":true,"refreshedAt"} | {"id","email","ok":false,"error","code","reconnectUrl"…}]} — an account whose Google access is gone says google_reconnect and the account is marked not connected until it signs in again |
GET /api/gam/log | the account's download log |
GET /api/gam/actions | the account's change log: every write, its target, who made it (page or key), whether it succeeded, and whether it can be reversed (above) |
Limits and errors
- At most 10 reports waiting to be downloaded at a time, 200 downloads per day (UTC) and 20 active keys per account; tickets and batches live 24 hours.
- Every error is JSON:
{"error":"human-readable reason","code":"machine_code","status":NNN}— sometimes with more fields (agoogle_reconnectcarriesaccountId,accountEmailandreconnectUrl). Programs should switch oncode; the message may change, the code will not.
| HTTP | code | meaning |
|---|---|---|
| 400 | network_required | the call named no network and your accounts see more than one — the message lists them |
| 400 | report_unknown, range_invalid, range_too_long, bad_request | a bad parameter; the message says which |
| 401 | key_unknown, key_expired, key_revoked, account_disabled, not_signed_in | the key (or session) cannot be used; GET /api/gam/token tells the same without an error |
| 403 | capability_missing, network_pinned, session_only | the key lacks the capability; a pinned key named another network; the call is for the account page only (keys, delete account) |
| 404 | network_unknown, ad_unit_unknown, placement_unknown, saved_report_unknown, ticket_unknown, batch_unknown, action_unknown, account_unknown | no such thing in your network / account |
| 409 | google_reconnect | Google no longer honours the account's access (revoked at myaccount.google.com/permissions, password changed, token expired) — sign in with Google again at reconnectUrl; the account is marked not connected |
| 409 | google_not_connected, no_network | no connected Google account reaches the network / none sees any network |
| 409 | in_progress, already_reverted, not_reversible | the download was asked for before the report finished; the change was reversed already / cannot be |
| 429 | too_many_open_reports, daily_limit, too_many_keys, too_many_requests | a limit above |
| 502 | ad_manager_rejected, ad_manager_error, google_token_error, download_failed | Ad Manager refused or failed the call, or Google could not issue a token for a reason other than revoked access — its own reason is quoted |
| 504 | still_running | the one-call download waited its three minutes; the body names the ticket or batch URL to fetch within 24 hours |
| 500 | internal | a fault of the service — please tell us |