← Easier Mobile

Google Ads API — Internal Tool Design

Easier Mobile · Internal mobile app campaign reporting & budget automation tool

Version 1.0 · 24 August 2026

This page documents how Easier Mobile uses the Google Ads API. The tool described here is built and operated by us, for our own Google Ads account and our own app only. It is not a product, is not distributed, and has no users outside Easier Mobile.

CompanyEasier Mobile — independent Android application publisher, Cầu Giấy, Hanoi, Vietnam
Google Play developerEasierMobile — the publisher account under which Easier Mobile releases its apps
Company websitehttps://easymobile-web.pages.dev/
API contact emailnguyentran.mobile.dev@gmail.com
Google Cloud project number135139800127
Google Ads manager account (MCC)466-112-2733
Access level requestedBasic Access
Campaign types supportedApp campaigns (App campaigns for installs) only
Capabilities providedReporting, campaign management, and campaign creation from reviewed templates. No account creation, no account management, no keyword planning.
Tool usersInternal only — the single developer who operates Easier Mobile
Document version / date1.0 — 24 August 2026

1. Company and business model

Easier Mobile is an independent Android application publisher based in Cầu Giấy, Hanoi, Vietnam. We design and publish free-to-download utility applications for users who find standard smartphone interfaces difficult to read or operate — primarily older users and users with reduced vision.

Our published application on Google Play is Big Font — Big Text Size (com.easiermobile.bigfont), a system-wide font scaling utility. Two further applications, Magnifying Glass and AI Cleaner, are in development and are not yet published.

Scope of this application. Google Ads campaigns, and therefore every use of the Google Ads API described in this document, currently cover one application: Big Font. Should a later application be published and advertised, it would be added to the same allow-list under the same manager account and the same guardrails; no change to the tool's design would follow from it.

Revenue model. All applications are free to install. Revenue comes from two sources, in order of contribution:

  1. In-app advertising via Google AdMob (primary) — interstitial, rewarded, native, banner and app-open formats.
  2. In-app purchases via Google Play Billing (secondary) — an optional one-time or subscription "Pro" upgrade that removes advertising and unlocks additional presets.

User acquisition. We buy installs for Big Font through Google Ads App campaigns, funded and operated entirely by Easier Mobile, under the manager account 466-112-2733. We do not manage advertising for any third party, and we do not resell or sublicense any advertising service.

2. Purpose of Google Ads API access

Because our revenue is dominated by advertising impressions rather than in-app purchases, the profitability of a paid user acquisition campaign cannot be judged from Google Ads metrics alone. Cost data lives in Google Ads; the corresponding revenue signal lives in AdMob and in Firebase/Google Analytics 4, delivered per impression through the Google Mobile Ads SDK onAdPaid callback.

We are building an internal tool that joins these two sides into a single view of average revenue per install (ARPI) and return on ad spend (ROAS) broken down by campaign, country and install cohort day, that applies conservative, rule-based budget and bid adjustments based on that view, and that launches new App campaigns reproducibly from reviewed configuration templates.

The tool has three functions:

  1. Reporting (read). Retrieve campaign, ad group, geographic and asset performance metrics daily, store them in BigQuery, and join them against ad revenue and retention data from GA4 to produce a per-geography ARPI/ROAS report.
  2. Budget and bid automation (write). Apply bounded daily adjustments to campaign budgets and target CPA/target ROAS bids, and pause clearly unprofitable geographic targeting criteria, according to the deterministic rules described in section 8.
  3. Templated campaign creation (write). Create App campaigns, their budgets, ad groups, app ads and targeting from a version-controlled configuration template and a fixed library of approved creative assets, so that a geography test can be launched as a set of structurally identical campaigns. Campaigns are always created in a paused state and enabled by a person. This is described in section 8.3.
Why the API rather than the Google Ads user interface. The decisive metric — ad revenue attributable to installs acquired from a specific campaign and country, measured on day 7 after install — does not exist inside Google Ads. It has to be computed by joining Google Ads cost data against our own BigQuery revenue tables. Doing this by manual CSV export across multiple campaigns and roughly twenty country segments every day is error-prone and does not scale. Automating budget changes from a metric we compute ourselves requires programmatic write access.

Why campaign creation is included. Our geography testing method requires launching several structurally identical App campaigns at once — the same creative set and settings, differing only in the country basket and bidding goal — so that ARPI and ROAS can be compared across baskets without confounding differences. Building those by hand in the user interface, one country basket at a time, is repetitive and introduces exactly the small inconsistencies that invalidate the comparison. Generating them from one reviewed template in version control makes each launch reproducible and auditable.

3. Users and access control

The tool is strictly internal. It is not a product, it is not offered to clients, it has no public sign-up, and it is not distributed to anyone.

AspectDetail
Who can use itEmployees of Easier Mobile only. At present the marketing/engineering team responsible for user acquisition.
Accounts accessedOnly Google Ads accounts under our own manager account 466-112-2733, which contain only Easier Mobile's own campaigns for Easier Mobile's own applications.
Third-party accountsNone. The tool never accesses an advertiser account that Easier Mobile does not own and fund.
InterfaceA scheduled batch job with no public endpoint, plus a read-only Looker Studio dashboard reading the resulting BigQuery tables. Dashboard access is restricted by Google account to company members.
Third-party softwareThe tool is developed in-house by Easier Mobile. It is not built on, and does not embed, any Google Ads API tool developed by another party.

4. System architecture

Google platforms Google Ads API AdMob / GMA SDK Firebase / GA4 Easier Mobile apps Big Font com.easiermobile.bigfont onAdPaid(AdValue) → GA4 Git: campaign templates reviewed before merge + approved creative library Internal tool (Cloud Run) Report collector SearchStream · read ROAS join engine cost ⋈ ad revenue Rule evaluator deterministic thresholds Guardrail validator caps · dry-run · approval Mutation applier budget · bid · criterion Campaign launcher paused · from Git template Audit logger every write, immutable daily job (scheduled) · launch job (manual only) Storage & output BigQuery warehouse Looker Studio (read) Alerting (email) Secret Manager OAuth 2.0 refresh token client secret developer token read write
Figure 1 — Data flow between Google platforms, the internal tool, and storage. Read paths carry campaign metrics into BigQuery. Every write back to the Google Ads API — budget and bid adjustments from the daily job, and campaign structures from a manually started launch job — passes through the guardrail validator and is recorded by the audit logger. Campaign templates and creative assets originate in a reviewed Git repository, never at run time.

4.1 Implementation stack

LayerTechnology
Language / client libraryPython 3.11 with the official google-ads client library
ComputeGoogle Cloud Run job, triggered by Cloud Scheduler once per day at 03:00 UTC
WarehouseBigQuery — Google Ads metrics, GA4 event export, AdMob revenue, audit log
PresentationLooker Studio, read-only, restricted to company Google accounts
Secret storageGoogle Cloud Secret Manager
Cloud project135139800127

5. Daily processing flow

  1. Cloud Scheduler triggers the Cloud Run job at 03:00 UTC.
  2. The job reads the OAuth refresh token, client secret and developer token from Secret Manager and obtains a short-lived access token.
  3. Read. GoogleAdsService.SearchStream is called with GAQL queries (section 7) to fetch yesterday's campaign, ad group, geographic and asset metrics for every account under the manager account.
  4. Results are written to partitioned BigQuery tables, keyed by date, customer id, campaign id and country criterion id.
  5. Join. A BigQuery query joins Google Ads cost against GA4 ad-revenue events (onAdPaid values from the Google Mobile Ads SDK) and Play Billing purchase events, producing ARPI and ROAS per campaign × country × cohort day.
  6. Evaluate. The rule evaluator compares each segment against the thresholds in section 8 and emits a list of proposed changes.
  7. Validate. Every proposed change passes the guardrail validator. Proposals exceeding a cap are clamped; proposals outside the allowed class of operations are discarded and logged.
  8. Write. Surviving changes are applied through CampaignBudgetService.MutateCampaignBudgets, CampaignService.MutateCampaigns and CampaignCriterionService.MutateCampaignCriteria. In dry-run mode the mutate call is made with validate_only = true and nothing is changed.
  9. Record. Each attempted mutation — before value, after value, rule that fired, request id, result — is appended to an immutable BigQuery audit table.
  10. A summary email is sent to the marketing team. Any error or clamped proposal is highlighted.

5.1 Campaign launch runs

Campaign creation is not part of the daily automated job. It runs as a separate, manually invoked Cloud Run job that takes a single argument: the path to a campaign template committed to our internal Git repository. A launch run performs the following steps and then exits.

  1. Load and schema-validate the template — application id and store, campaign name pattern, bidding goal and target, daily budget, country basket, languages, and the identifiers of the creative assets to use.
  2. Verify that every referenced creative asset exists in the approved creative library in Cloud Storage and carries a reviewer signature. A template referencing an unapproved asset is rejected.
  3. Query existing campaigns in the target account and abort if a campaign matching the template's name pattern already exists, so that a re-run cannot silently create duplicates.
  4. Resolve the country codes in the template to geo target constants.
  5. Register the creative assets, then create the campaign budget, the campaign (status PAUSED), the ad group, the app ad and the location and language criteria.
  6. Write the full created structure to the audit table and email it to the marketing team for review in the Google Ads user interface.

Every created campaign stays paused until a person enables it in the Google Ads user interface. The tool has no code path that enables a newly created campaign.

6. Google Ads API services and methods used

ServiceMethodModePurposeEst. daily operations
GoogleAdsServiceSearchStreamReadCampaign, ad group, geographic, asset and conversion metrics~25
CustomerServiceListAccessibleCustomersReadEnumerate accounts under the manager account1
GoogleAdsServiceSearch (customer_client)ReadResolve the account hierarchy under 466-112-27331
CampaignBudgetServiceMutateCampaignBudgetsWriteAdjust daily budget within the caps in section 80–10
CampaignServiceMutateCampaignsWriteAdjust target CPA / target ROAS; pause or enable a campaign; create a paused App campaign from a template0–10
CampaignCriterionServiceMutateCampaignCriteriaWriteSet location and language targeting on a new campaign; pause or re-enable location criteria for unprofitable countries0–20
AdGroupServiceMutateAdGroupsWriteCreate the ad group belonging to a newly created App campaign0–5, launch runs only
AdGroupAdServiceMutateAdGroupAdsWriteCreate the AppAdInfo ad that carries the approved creative assets0–5, launch runs only
AssetServiceMutateAssetsWriteRegister text, image and YouTube video assets from the approved creative library0–40, launch runs only
GeoTargetConstantServiceSuggestGeoTargetConstantsReadResolve ISO country codes in a template to geo target constants0–2, launch runs only

Expected volume. Under 100 operations per day in normal operation, with an allowance up to roughly 500 on days when historical data is backfilled. This is well inside the 15,000 operations per day of Basic Access. We do not anticipate needing Standard Access.

Services deliberately not used. The tool does not create, link or modify Google Ads accounts, does not manage user access or billing, does not upload click or offline conversions, does not use the App Conversion Tracking and Remarketing API, does not manage audiences or remarketing lists, and does not use keyword planning services. Creative assets are never generated at run time; the tool only registers files that already exist in the approved creative library.

7. Sample queries

7.1 Campaign performance by day

SELECT
  campaign.id,
  campaign.name,
  campaign.advertising_channel_type,
  campaign.status,
  campaign_budget.amount_micros,
  metrics.cost_micros,
  metrics.impressions,
  metrics.clicks,
  metrics.conversions,
  metrics.conversions_value,
  segments.date
FROM campaign
WHERE segments.date DURING YESTERDAY
  AND campaign.advertising_channel_type = 'MULTI_CHANNEL'

7.2 Cost and installs by country — the input to the ARPI join

SELECT
  campaign.id,
  campaign_criterion.location.geo_target_constant,
  metrics.cost_micros,
  metrics.conversions,
  segments.date
FROM location_view
WHERE segments.date DURING LAST_7_DAYS
  AND campaign.status = 'ENABLED'

7.3 Budget mutation with the safety flag set

operation = client.get_type("CampaignBudgetOperation")
budget = operation.update
budget.resource_name = budget_resource_name
budget.amount_micros = clamped_amount_micros      # produced by the guardrail validator
client.copy_from(
    operation.update_mask,
    protobuf_helpers.field_mask(None, budget._pb),
)

response = campaign_budget_service.mutate_campaign_budgets(
    customer_id=customer_id,
    operations=[operation],
    validate_only=DRY_RUN,        # true until a change set is approved
)

7.4 Illustrative output of the joined report

DateCampaignCountryInstallsCostCPIAd revenue D7ARPI D7ROAS D7Action proposed
2026-08-23BigFont-AC-Installs-ABR412$61.80$0.150$74.16$0.180120%Budget +15% (capped)
2026-08-23BigFont-AC-Installs-AMX288$46.08$0.160$46.94$0.163102%No change
2026-08-23BigFont-AC-Installs-APH531$42.48$0.080$21.24$0.04050%Flag — below floor 2 of 3 days

7.5 Creating a paused App campaign from a template

operation = client.get_type("CampaignOperation")
campaign = operation.create
campaign.name = template.campaign_name              # from the version-controlled template
campaign.status = client.enums.CampaignStatusEnum.PAUSED   # always paused on creation
campaign.advertising_channel_type = (
    client.enums.AdvertisingChannelTypeEnum.MULTI_CHANNEL
)
campaign.advertising_channel_sub_type = (
    client.enums.AdvertisingChannelSubTypeEnum.APP_CAMPAIGN
)
campaign.app_campaign_setting.app_id = template.app_id      # e.g. com.easiermobile.bigfont
campaign.app_campaign_setting.app_store = (
    client.enums.AppCampaignAppStoreEnum.GOOGLE_APP_STORE
)
campaign.app_campaign_setting.bidding_strategy_goal_type = (
    client.enums.AppCampaignBiddingStrategyGoalTypeEnum
    .OPTIMIZE_INSTALLS_TARGET_INSTALL_COST
)
campaign.campaign_budget = budget_resource_name             # created at the template minimum
campaign.target_cpa.target_cpa_micros = template.target_cpa_micros

response = campaign_service.mutate_campaigns(
    customer_id=customer_id,
    operations=[operation],
    validate_only=DRY_RUN,          # true unless the launch run is explicitly confirmed
    partial_failure=False,
)

7.6 Duplicate guard run before any creation

SELECT campaign.id, campaign.name, campaign.status
FROM campaign
WHERE campaign.name LIKE 'BigFont-AC-Installs-%'

8. Mutation rules and guardrails

All write operations are generated by deterministic rules, never by discretion at runtime. No machine-learning model or free-form logic issues a mutation.

8.1 Rules

#Condition (evaluated on a 7-day rolling window)Action
R1Campaign ROAS D7 ≥ 120% and spend has hit the daily budget on ≥ 5 of the last 7 daysIncrease daily budget by 15%
R2Campaign ROAS D7 ≤ 70% for 3 consecutive daysDecrease daily budget by 20%
R3Campaign ROAS D7 ≤ 40% for 5 consecutive daysPropose pausing the campaign — requires human approval
R4Country ARPI D7 / CPI < 0.5 with ≥ 300 accumulated installsPause that location targeting criterion
R5Country ARPI D7 / CPI ≥ 1.2 with ≥ 300 accumulated installsRaise the campaign target ROAS by one step (max 10%)

8.2 Guardrails

GuardrailValue
Maximum budget change in one run±20% of the current budget
Absolute daily budget ceiling per campaignA hard figure held in configuration; the tool can never exceed it regardless of rule output
Absolute daily budget floor per campaignConfigured minimum; prevents a campaign being throttled to nothing by a data error
Maximum mutate operations per run50. Exceeding this aborts the run and raises an alert rather than proceeding.
Minimum data before any rule fires300 installs and 7 days of data for the segment. Thin segments are never acted on.
Campaign pauseNever automatic. Rule R3 emits a proposal; a human enables it.
Dry runDefault mode. Every mutate is issued with validate_only = true and the resulting change set is emailed for review. Live mode is enabled per run by explicit configuration.
Data freshness checkIf the GA4 or AdMob revenue tables are stale or the AdMob-to-GA4 revenue variance exceeds 10%, all write operations are skipped for that run.
AuditEvery attempted mutation is written to an append-only BigQuery table with the prior value, new value, triggering rule, request id and API response.

8.3 Campaign creation rules and guardrails

Campaign creation is template-driven rather than rule-driven: no metric threshold ever causes a campaign to be created. A campaign exists only because a person committed a template and started a launch run.

GuardrailValue
TriggerManual invocation only. The daily scheduled job can never create a campaign.
Source of truthA schema-validated template committed to our internal Git repository and reviewed before merge. No campaign field is generated at run time.
Initial statusPAUSED, always. The tool contains no code path that sets a newly created campaign to ENABLED.
Initial budgetThe configured minimum daily budget for that application. A template may not request a higher opening budget; scale-up happens later through rule R1 and its caps.
Creative assetsOnly files already present in the approved creative library, each carrying a reviewer signature. Nothing is generated, scraped or fetched from an external source.
Duplicate guardExisting campaigns are queried by name pattern first. A collision aborts the run before any write.
Maximum campaigns per launch run6 — one per country basket in a single geography test.
Maximum active campaigns per applicationA hard configured ceiling. Reaching it blocks further creation until campaigns are retired.
Applications targetedOnly application ids on an allow-list of our own published applications — currently a single entry, com.easiermobile.bigfont.
Dry runDefault. The whole structure is issued with validate_only = true and emailed for review; live creation requires explicit confirmation on the run.
Atomicitypartial_failure = false. If any part of the structure fails, the run stops and the partial structure is reported rather than left half built.
AuditThe template revision, every resource created and every API response is written to the append-only audit table.

9. Authentication and security

10. Quota, rate limiting and error handling

11. Compliance

12. Roadmap

PhaseScopeAPI usage
1 — MeasurementShip onAdPaid ad-revenue reporting and the event funnel in Big Font; export GA4 and AdMob data to BigQuery; reconcile revenue to within 10%.Read only
2 — ReportingDaily Google Ads metric ingestion and the ARPI/ROAS-by-country dashboard.Read only
3 — Automation, dry runRule evaluator and guardrails live, all mutations issued with validate_only = true and reviewed by a human for at least two weeks.Read + validated writes
4 — Automation, liveBudget, bid and location-criterion adjustments applied automatically within the caps of section 8. Campaign pause stays manual.Read + write
5 — Templated launchCampaign creation from version-controlled templates, used to launch geography-basket tests as sets of structurally identical paused campaigns. Enabling a campaign stays manual.Read + write