Skip to content

btkennett/google-ads

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@foundrynorth/google-ads

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 AsyncIterable plus a bounded buffered queryAll.
  • Sandbox-vs-production mode routing that fails closed before any network I/O if a foreign customer ID leaks into sandbox mode.

Environment variables

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.

Subpath exports

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.

Minimal example

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);

Sandbox safety

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.

Retry and breaker behavior

  • Retried (GoogleAdsTransientError): DEADLINE_EXCEEDED, UNAVAILABLE, INTERNAL, ABORTED, RESOURCE_EXHAUSTED, rate-limit, quota, concurrent-modification. Retries use the API_RETRY preset 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.

What is NOT in this package (deferred)

  • 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 query but 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; callLLM belongs 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.

Publish workflow

pnpm build
pnpm test
npm version patch
npm publish

pnpm 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.

About

Typed Google Ads API client for Foundry North SEM publishing

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors