Skip to content

Marketing Integration

Google Ads API in the SLT Dashboard — Campaign Spend and ROAS Without Login Sharing

Developer Tokens, Manager Accounts, and GAQL at Scale

💰 📈 🔐

Sharing Google Ads login credentials with a dashboard vendor is a security nightmare. You've given them access to budget shifts, campaign pause authority, and audience data on top of the metrics you actually want. Google Ads API solves this with a developer token + OAuth flow that grants read-only access to spend, ROAS, and campaign performance without touching your account keys. Velocity X uses this pattern to pull real-time ad spend into the SLT dashboard so leadership sees performance across Google, Meta, and organic channels in one place. Here's the architecture.

Developer Tokens and the Manager Account Hierarchy

Google Ads API starts with a developer token — a unique identifier that proves your app is registered with Google and approved for API access. You apply for this in the Google Ads UI under "Admin" → "API Center". Google approves it in 24–48 hours; rejection is rare but usually means "your app description doesn't match your API usage" (e.g. you claim read-only but request the budget-mutate scope). Once approved, the token lives in your server env and never expires. Store it like an API key — long string, no quotes, keep it out of client-side bundles.

Behind the developer token sits a manager account (MCC, multi-client account). This is a special Google Ads account that can see and query every client account linked to it. If your company runs ads across 3 different Google Ads accounts (brand, performance, retargeting), you'd link all three under a single manager account, then use the manager account's token to query all three at once. This is the unlock for enterprise dashboards: one API token reads 50+ child accounts without re-authing. Velocity X stores the manager-account ID in config and queries it with login_customer_id headers that say "ask this manager account to fetch data from all linked child accounts".

OAuth for Client-Specific Permissions

The developer token grants access to your infrastructure; OAuth grants access to a specific advertiser's account. When a Velocity X user clicks "Connect Google Ads", they hit Google's OAuth flow and approve scopes https://www.googleapis.com/auth/adwords (read-only) and https://www.googleapis.com/auth/userinfo.email (identify them). Google returns a short-lived access token (1 hour) and a refresh token (valid indefinitely, until the user revokes). Velocity X exchanges the refresh token for access tokens server-side and stores the refresh token encrypted in Postgres. On every request to the Google Ads API, the app refreshes the access token, uses it for 1 hour, then refreshes again. Users never see "reconnect"; the flow is invisible.

POST https://oauth2.googleapis.com/token
  ?client_id=YOUR_CLIENT_ID
  &client_secret=YOUR_CLIENT_SECRET
  &grant_type=refresh_token
  &refresh_token=STORED_REFRESH_TOKEN

The OAuth flow gives you the advertiser's email and their customer ID (the 10-digit number you see in the Google Ads UI, formatted with hyphens: 1234-5678-9012). Store this customer ID alongside the refresh token. It's your target for all GAQL queries.

GAQL: The Query Language for Campaign Metrics

Google Ads API uses GAQL (Google Ads Query Language), a SQL-like syntax for pulling campaign data. Unlike Meta's REST endpoints, GAQL is strict: every field you request must be valid, and you can't nest queries. Velocity X builds GAQL statements to fetch spend, impressions, clicks, conversions, and ROAS at the campaign, ad group, and keyword level. Here's a campaign-level example:

SELECT
  campaign.id,
  campaign.name,
  metrics.impressions,
  metrics.clicks,
  metrics.cost_micros,
  metrics.conversions,
  metrics.conversion_value_micros
FROM campaign
WHERE segments.date >= '2026-06-01'
  AND segments.date <= '2026-06-12'
ORDER BY campaign.name

Cost and conversion value come back in micros (divide by 1,000,000 to get the real number). Impressions and clicks are integers. Conversions is a decimal (e.g. 3.5 if 3 full conversions and 1 view-through). Store these in a table: (customer_id, campaign_id, campaign_name, date, impressions, clicks, cost, conversions, conversion_value). ROAS = conversion_value / cost.

Manager Account Queries and Cross-Account Reporting

If your manager account has 5 child accounts, you can't query all of them in a single GAQL statement. Instead, you iterate: for each child account, you send a query with the login_customer_id header set to the manager's ID and a customer_id param set to each child. Google routes the request to the correct child, returns that child's campaigns, and your app aggregates across all five. This is slow (5 API calls for 5 accounts), but it's the only way to do cross-account reporting. Velocity X batches these calls in parallel using Promise.all and stores all results in one unified table keyed by (child_account_id, campaign_id, date).

// Pseudocode: query all child accounts
const childIds = ['1234567890', '0987654321', '1122334455'];
const allCampaigns = await Promise.all(
  childIds.map(id => queryGaql({
    customerId: id,
    loginCustomerId: managerAccountId,
    query: gaqlStatement
  }))
);

Real-Time vs. Delayed Metrics

Google Ads API returns metrics from the prior day (yesterday's spend, yesterday's conversions). Today's data is incomplete and won't finalize until 02:00 UTC the next day. Velocity X queries yesterday's data on a daily cron and shows it on the dashboard with a "as of yesterday" note. For estimated current spend (how much have you burned today?), Velocity X divides yesterday's total by 24 and multiplies by the current hour. This is a rough estimate and can be 20% off if today's spend patterns differ, but it's accurate enough for exec dashboards. The SLT dashboard shows exact historical numbers and an asterisked "estimated today" row.

Structuring Queries for Performance

GAQL doesn't have a LIMIT clause, so if you query 90 days of data across 100 campaigns, you get 9,000 rows back. The Google Ads API paginates these in batches of 10,000; if you exceed that, you get a page_token that you use for the next request. Velocity X manages pagination automatically but logs any query returning more than 50,000 rows — those are worth optimizing. Split them by date range (query 7 days at a time instead of 90), or query by ad-group level instead of keyword level. Keyword-level queries are the slowest; if you're tracking 1,000 keywords, that's overkill for a leadership dashboard. Stick to campaign and ad-group level.

One more gotcha: segments (date, device, geo) are optional but powerful. If you want to see spend by device (desktop vs mobile), add segments.device to your SELECT and GROUP BY. This multiplies your row count but gives you device-level granularity. Velocity X defaults to date + campaign level, with optional device/geo breakdowns in the dashboard's advanced filters.

Frequently Asked Questions

Do I need a manager account to use the API?

No. A single Google Ads account can connect directly. A manager account (MCC) is only needed if you want to query multiple accounts under one OAuth flow. For single-account dashboards, just use your advertiser ID directly.

Can I use the Google Ads API to pause campaigns or shift budgets?

Yes, but Velocity X doesn't. The read-only scope (adwords) grants read and write access by design — Google doesn't have a "read-only" scope. Use a separate OAuth integration if you want mutation access (campaign pause, budget increase), and keep it behind strict permissions. Never give your dashboard vendor write access.

What's the difference between cost_micros and conversion_value_micros?

cost_micros = what you spent (campaign budget). conversion_value_micros = revenue attributed to those conversions (sum of all purchase values). ROAS = conversion_value / cost.

Why does my GAQL query timeout?

You're querying too much data at once. Split into smaller date ranges (7 days at a time), lower the segment cardinality (remove device/geo if not needed), or query campaign-level instead of keyword-level. The API has a 10-minute timeout per request; most timeouts happen with keyword-level queries on high-volume accounts.

How often does Velocity X refresh Google Ads data?

Once daily at 03:00 UTC (after Google's data warehouse finishes processing yesterday). You see yesterday's final metrics in the morning. If you need more real-time data, use Google Ads' native dashboard; API is fundamentally once-daily.

Can I pull conversion data without conversion tracking?

You'll get the metrics row, but metrics.conversions will be 0 if you haven't set up conversion tracking in Google Ads. Set up at least one conversion action (website purchases, app installs, phone calls) in Google Ads Admin → Conversions. Then run the GAQL query again.

The Bottom Line

The Google Ads API removes the login-sharing nightmare: OAuth grants read-only access, developer tokens don't expire, and GAQL scales to hundreds of campaigns without touching account permissions. The real work is pagination and structuring queries to stay under the 10-minute timeout — start with campaign-level queries, add segmentation only when you need it. Once you've got the flow running, updating your dashboard is a daily cron job. See pricing or explore how we tie Google, Meta, and organic metrics together in our unified marketing analytics guide.

Let us make some quick suggestions?
Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.