Skip to content

t1k:marketing:monetization:admob

FieldValue
Modulemonetization
Version1.16.9
Efforthigh
Tools—
/t1k:marketing:monetization:admob

Google AdMob setup, optimization, mediation, and revenue reporting integration.

  • Integrating AdMob SDK (Unity / iOS / Android)
  • Setting up AdMob mediation waterfall or bidding
  • Configuring eCPM floors / implementing UMP consent
  • Pulling mediation revenue via the AdMob API mediation report (per-network/ad-source dashboards)
  • AdMob API (REST): accounts.mediationReport.generate and accounts.networkReport.generate. Base: https://admob.googleapis.com/v1/accounts/{publisherId}/mediationReport:generate
  • Auth: OAuth 2.0 (Google service account / user consent) — NOT a static key.
  • The request body is a ReportSpec (dateRange, dimensions, metrics, dimensionFilters, sortConditions). Response streams rows as dimensionValues + metricValues objects.

Per-network dimension — AD_SOURCE (no two-call conflict)

Section titled “Per-network dimension — AD_SOURCE (no two-call conflict)”
  • The mediation report exposes AD_SOURCE and AD_SOURCE_INSTANCE dimensions — this IS the per-network/mediated-source breakdown (the AdMob equivalent of AppLovin’s network).
  • Unlike AppLovin MAX, AdMob has no requests ⊥ network conflict — you can request AD_SOURCE alongside request-style metrics in a single call.
  • Standard dimensions: DATE, APP, AD_UNIT, FORMAT, COUNTRY, plus AD_SOURCE / AD_SOURCE_INSTANCE (mediation report only).
  • Metrics: ESTIMATED_EARNINGS, IMPRESSIONS, CLICKS, AD_REQUESTS, MATCHED_REQUESTS, IMPRESSION_CTR, OBSERVED_ECPM, etc.
  • ESTIMATED_EARNINGS is reported in micros of the account currency (divide by 1,000,000). It is estimated and finalized later.
  • When grouped by AD_SOURCE, per-source earnings decompose the mediated total — sum back to the all-source total per (date, app).
  • Response dimension/metric values are nested objects (row.dimensionValues['DATE'].value, row.metricValues['ESTIMATED_EARNINGS'].microsValue), NOT flat columns — different shape from a columnar revenue table.
  • Date range max per request is large but paginate via the streaming response; respect Google API quotas.
  • This platform’s apps/admob-api already requests AD_SOURCE / AD_SOURCE_INSTANCE in admob.service.ts and serves revenue from HistoricalDataAggregated (not a revenue_data columnar table) — admob’s per-network path differs from applovin/adjust/mintegral.
  • The googleads dashboard pulls its revenue numbers from admob-api (Google monetization = AdMob).

Diagnosing accounts.list → bare 400 INVALID_ARGUMENT (READ BEFORE concluding “no publisher”)

Section titled “Diagnosing accounts.list → bare 400 INVALID_ARGUMENT (READ BEFORE concluding “no publisher”)”

A bare 400 INVALID_ARGUMENT (no details[].fieldViolations) from GET /v1[beta]/accounts does NOT prove the identity has no AdMob publisher. accounts.list is a quirky discovery call — per the docs it “lists the AdMob publisher account most recently signed in to from the AdMob UI” — and it returns this bare 400 in several distinct situations. Do NOT jump to “no publisher / retire the integration” from accounts.list alone. Work the checklist in order:

  1. Console access ≠ API access. A user added to an AdMob account (even full Administrator in console.admob.com → Settings → Users) can see everything in the UI yet still get accounts.list 400. The AdMob API authorizes the Google identity differently from the console.
  2. Confirm scope on the MINTED token, not the consent request. Refresh the token and read the /token response scope=. It MUST contain admob.readonly (needed for accounts.list/apps/adUnits) AND admob.report (needed for reports). Old refresh tokens keep their old scopes — a re-consent is required after any scope change.
  3. Don’t trust accounts.list as the access test — test the publisher DIRECTLY. Get the real pub-XXXXXXXXXXXXXXXX from console.admob.com → Settings → Account information, then call GET /v1beta/accounts/pub-XXXX (accounts.get) and a tiny mediationReport:generate with the token. An added Admin CAN read via the API even when accounts.list returns 400 — configure the known pub-id and skip accounts.list entirely.
  4. googleapis-client path may silently drop auth (returns 401 CREDENTIALS_MISSING). If using the googleapis OAuth2Client, verify that client_id, client_secret, and refresh_token are all set on the client before any call — missing client credentials cause the library to omit the Authorization header entirely. Working alternative: mint an access token via a manual token-refresh POST and use Authorization: Bearer <accessToken> on direct HTTP calls; the googleapis streaming *Report:generate can drop the header when the client is misconfigured.
  5. API-enabled check: if mediationReport/networkReport returns a field-violation 400 (e.g. “Empty metrics”), the API IS enabled and the token works — that is NOT the no-publisher signature. A truly disabled API returns 403 SERVICE_DISABLED with an activation URL. A 401 CREDENTIALS_MISSING means the token wasn’t attached (see #4), NOT an auth/permission denial.

Net rule: never conclude “no AdMob publisher” / “no access” / “retire integration” from accounts.list 400 — verify with accounts.get on the real pub-id first.

Standalone AdMob mediation vs AdMob-as-a-MAX-adapter — use MEDIATION_GROUP to prevent double-count

Section titled “Standalone AdMob mediation vs AdMob-as-a-MAX-adapter — use MEDIATION_GROUP to prevent double-count”

When a studio runs AdMob both as a standalone mediator AND as an adapter inside AppLovin MAX (the common case), the AdMob account report double-reports every impression AdMob serves through MAX (AdMob logs it too). So the full AdMob account total = standalone + AdMob-as-MAX-adapter, and the MAX-adapter slice is already counted in MAX (demandNetwork='admob'). Adding the full AdMob account total to MAX double-counts.

Do NOT use subtraction (account_total − MAX(demandNetwork=admob)) — it depends on MAX per-network data (often only recent dates) and is approximate. Use the MEDIATION_GROUP dimension instead — it’s API-native, exact, and works for ALL dates:

  • mediationReport:generate with reportSpec.dimensions=['MEDIATION_GROUP', …] returns earnings per mediation group, matching the AdMob console (Mediation → Mediation groups) to the cent.
  • Standalone AdMob = sum of the NAMED custom mediation groups; EXCLUDE AdMob (default). The named groups are the studio’s own mediation setups; AdMob (default) is the catch-all that absorbs the MAX-adapter + unmatched traffic (the overlap already in MAX).
  • Ingest standalone (named-group) revenue as the authoritative source='admob' line (additive to MAX, zero double-count). Store/keep AdMob (default) rows as non-authoritative reference only.

Rule of thumb for ANY mediation network that is also a bidder in another mediator: never sum the network’s full account with the other mediator’s revenue — isolate the network’s own-mediation slice (here, named MEDIATION_GROUPs) before counting it as a separate line.

  • UMP SDK is required for EU/UK/CH traffic — without consent UMP-handled, AdMob will throttle or zero-fill those geos. Test with a VPN before claiming integration is done.
  • SKAdNetwork conversion-value model affects iOS revenue, not delivery — wrong CV mapping silently underreports campaign ROAS without breaking ad serving.
  • Mediation A/B tests require a 7-14 day ramp — earlier reads are noise, especially in low-fill geos.
  • Banner refresh rate has a recommended floor (60s) — going faster gets you flagged for invalid traffic and revenue clawbacks.