Typed Google Ads API client for Foundry North SEM publishing. Wraps the
google-ads-api Node client with:
- Strongly-typed mutate builders for every entity we publish (campaigns, budgets, ad groups, keywords, RSAs, geo/language/device targeting, conversion actions and links).
- Atomic mutate executor with
partial_failure: false, explicit retry classification, and a per-customer circuit breaker from@foundrynorth/core/resilience. - GAQL query wrapper exposing a streaming
AsyncIterableplus a bounded bufferedqueryAll. - Sandbox-vs-production mode routing that fails closed before any network I/O if a foreign customer ID leaks into sandbox mode.
| Variable | Required | Description |
|---|---|---|
GOOGLE_ADS_DEVELOPER_TOKEN |
always | Google Ads developer token (approved via Google Ads API Center). |
GOOGLE_ADS_CLIENT_ID |
always | OAuth 2.0 client ID. |
GOOGLE_ADS_CLIENT_SECRET |
always | OAuth 2.0 client secret. |
GOOGLE_ADS_REFRESH_TOKEN |
always | Long-lived refresh token obtained during OAuth consent. |
GOOGLE_ADS_MCC_CUSTOMER_ID |
always | 10-digit Manager Account (MCC) ID; dashes are stripped. |
GOOGLE_ADS_LOGIN_CUSTOMER_ID |
optional | Overrides the login-customer-id header; defaults to the MCC. |
GOOGLE_ADS_MODE |
optional | sandbox (default) or prod. |
GOOGLE_ADS_CUSTOMER_ID_SANDBOX |
when mode=sandbox | 10-digit sandbox customer ID. |
GOOGLE_ADS_CUSTOMER_ID_PROD |
when mode=prod | 10-digit production customer ID. |
GOOGLE_ADS_SANDBOX_ALLOWLIST |
optional | Comma-separated extra sandbox customer IDs (dashes stripped). |
Missing any required variable throws GoogleAdsConfigError immediately —
there is no "disabled" no-op mode. Publishing must fail closed.
| Import | Surface |
|---|---|
@foundrynorth/google-ads/auth |
createGoogleAdsClient, readGoogleAdsEnv, resolveActiveCustomerId, normalizeCustomerId. |
@foundrynorth/google-ads/mutate |
All 9 builders + buildMutateBatch + entity-ordering table. |
@foundrynorth/google-ads/execute |
executeMutateAtomic, dryRunMutate. |
@foundrynorth/google-ads/query |
query<T> AsyncIterable + queryAll<T>. |
@foundrynorth/google-ads/sandbox |
assertSandboxSafety. |
@foundrynorth/google-ads/errors |
Error taxonomy (GoogleAdsError + 6 subclasses). |
@foundrynorth/google-ads/types |
Spec types + branded IDs + ExecuteResult. |
import { createGoogleAdsClient } from "@foundrynorth/google-ads/auth";
import {
buildCampaignBudgetMutate,
buildCampaignMutate,
buildMutateBatch,
} from "@foundrynorth/google-ads/mutate";
import { executeMutateAtomic } from "@foundrynorth/google-ads/execute";
const client = createGoogleAdsClient();
const budget = buildCampaignBudgetMutate({
resourceName: `customers/${client.config.activeCustomerId}/campaignBudgets/-1`,
name: "Brand Search Budget",
amountMicros: 50_000_000,
deliveryMethod: "STANDARD",
});
const campaign = buildCampaignMutate({
name: "Brand Search",
advertisingChannelType: "SEARCH",
status: "PAUSED",
campaignBudgetResourceName: budget.resource.resource_name!,
biddingStrategy: { kind: "manual_cpc", enhancedCpcEnabled: false },
});
const result = await executeMutateAtomic(
client.config.activeCustomerId,
buildMutateBatch([campaign, budget]),
);
console.log(result.resourceNames);When GOOGLE_ADS_MODE=sandbox, assertSandboxSafety (invoked at the top
of executeMutateAtomic, dryRunMutate, and query) rejects any
customer ID that is neither GOOGLE_ADS_CUSTOMER_ID_SANDBOX nor present
in GOOGLE_ADS_SANDBOX_ALLOWLIST. A stray production ID in a dev
environment throws GoogleAdsSandboxViolation before any network traffic.
- Retried (
GoogleAdsTransientError):DEADLINE_EXCEEDED,UNAVAILABLE,INTERNAL,ABORTED,RESOURCE_EXHAUSTED, rate-limit, quota, concurrent-modification. Retries use theAPI_RETRYpreset from@foundrynorth/core/resilience(3 attempts, 2s–15s exponential backoff, jitter). - Thrown immediately (
GoogleAdsRequestError):INVALID_ARGUMENT,FAILED_PRECONDITION,NOT_FOUND, and any other client-side spec violation. Fix the input; no retry helps. - Thrown immediately (
GoogleAdsAuthError):UNAUTHENTICATED,PERMISSION_DENIED,INVALID_CREDENTIALS. Likely refresh-token rot; the Trigger task orchestrator should alert the on-call. - Circuit breaker is keyed per customer ID (
google-ads:<id>). One exhausted customer cannot block mutate traffic on unrelated customers. Defaults: 5 failures / 60s reset / 5-minute monitoring window.
query uses INTEGRATION_RETRY (5 attempts, 1s–30s) instead of
API_RETRY — reporting volume justifies a longer retry budget.
- Performance Max asset groups — out of scope for the Search launch phase. Add alongside Performance Max rollout.
- Smart Shopping / Demand Gen / DV360 — different API surfaces.
- Recommendation / Experiment / Asset Library mutations — Track C/D
will read via
querybut not mutate in phase 1. - Offline Conversion uploads — separate endpoint; will land when offline-conversion import ships.
- CSV export — already provided by fn-flux; no duplicate here.
- LLM routing — this package is pure Google Ads;
callLLMbelongs in the orchestration layer.
Until Google approves the Foundry North developer token and
GOOGLE_ADS_DEVELOPER_TOKEN is populated in the target environment,
every surface fails closed with GoogleAdsConfigError. The integration
test in __tests__/integration/auth.integration.test.ts automatically
skips when the variable is absent.
pnpm build
pnpm test
npm version patch
npm publishpnpm build runs tsc; pnpm test runs vitest with v8 coverage at the
thresholds lines=80 / functions=80 / branches=70 / statements=80.
npm publish is gated on prepublishOnly running pnpm build first.