Skip to content

Google Ads

Create Search and Display campaigns via Zernio API - No MCC or Standard Access application required

Included with the Usage plan. No MCC or Standard Access application needed. Zernio operates under our own approved developer token.

Display campaigns need two images. Google's Responsive Display Ads (RDA) require BOTH a landscape (1.91:1, e.g. 1200x628) AND a square (1:1, e.g. 1080x1080) marketing image. Sending only one is rejected upstream as "Too few." (NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET or NOT_ENOUGH_MARKETING_IMAGE_ASSET). Pass both via images: { landscape, square }.

What's Supported

FeatureStatus
Search campaigns (Responsive Search Ads)Yes
Display campaigns (Responsive Display Ads)Yes
Campaign > Ad Group > Ad hierarchyYes
Keyword targeting (Broad, Phrase, Exact)Yes
Geo + language targetingYes
Real-time analytics (spend, CPC, CPM)Yes
Conversions API (offline/enhanced) via Data ManagerYes
Performance MaxRoadmap
Shopping, Video, Demand GenRoadmap
Customer Match audiencesRoadmap

Create a Search Campaign

typescript
const ad = await zernio.ads.createStandaloneAd({ body: {
  accountId: "acc_googleads_123",
  adAccountId: "987-654-3210",
  name: "US DevOps Search",
  campaignType: "search",
  goal: "traffic",
  budgetAmount: 50,
  budgetType: "daily",
  headline: "Internal Developer Platforms",
  body: "Spec-driven platform engineering.",
  linkUrl: "https://example.com/platform",
  keywords: ["platform engineering tools", "internal developer platform"],
  additionalHeadlines: ["Build your Dev Platform", "Ship DevEx Faster", "Internal Platforms, Done Right"],
  additionalDescriptions: ["Spec-driven platform. Free tier available.", "Enterprise features. Startup price."],
  countries: ["US", "CA", "GB"],
}});
python
ad = client.ads.create_standalone_ad(
    account_id="acc_googleads_123",
    ad_account_id="987-654-3210",
    name="US DevOps Search",
    campaign_type="search",
    goal="traffic",
    budget_amount=50,
    budget_type="daily",
    headline="Internal Developer Platforms",
    body="Spec-driven platform engineering.",
    link_url="https://example.com/platform",
    keywords=["platform engineering tools", "internal developer platform"],
    additional_headlines=["Build your Dev Platform", "Ship DevEx Faster", "Internal Platforms, Done Right"],
    additional_descriptions=["Spec-driven platform. Free tier available.", "Enterprise features. Startup price."],
    countries=["US", "CA", "GB"],
)
bash
curl -X POST "https://zernio.com/api/v1/ads/create" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "acc_googleads_123",
    "adAccountId": "987-654-3210",
    "name": "US DevOps Search",
    "campaignType": "search",
    "goal": "traffic",
    "budgetAmount": 50,
    "budgetType": "daily",
    "headline": "Internal Developer Platforms",
    "body": "Spec-driven platform engineering.",
    "linkUrl": "https://example.com/platform",
    "keywords": ["platform engineering tools", "internal developer platform"],
    "additionalHeadlines": ["Build your Dev Platform", "Ship DevEx Faster", "Internal Platforms, Done Right"],
    "additionalDescriptions": ["Spec-driven platform. Free tier available.", "Enterprise features. Startup price."],
    "countries": ["US", "CA", "GB"]
  }'

Valid goal values for Google Ads: engagement, traffic, awareness, video_views. Conversion-goal campaigns aren't yet supported on /v1/ads/create, pass traffic for click-optimised delivery. campaignType is lowercase (search or display). keywords is a flat array of strings; match types default to broad.

Create a Display Campaign

typescript
const ad = await zernio.ads.createStandaloneAd({ body: {
  accountId: "acc_googleads_123",
  adAccountId: "987-654-3210",
  name: "Retargeting Display",
  campaignType: "display",
  goal: "traffic",
  budgetAmount: 40,
  budgetType: "daily",
  headline: "Finish your signup",
  body: "Come back and upgrade today.",
  linkUrl: "https://example.com/upgrade",
  businessName: "Acme",
  images: {
    landscape: "https://cdn.example.com/retarget-1200x628.jpg",
    square: "https://cdn.example.com/retarget-1080x1080.jpg",
  },
  additionalHeadlines: ["Finish your signup", "Get 30% off this week"],
  additionalDescriptions: ["Come back and upgrade today."],
  countries: ["US"],
}});
python
ad = client.ads.create_standalone_ad(
    account_id="acc_googleads_123",
    ad_account_id="987-654-3210",
    name="Retargeting Display",
    campaign_type="display",
    goal="traffic",
    budget_amount=40,
    budget_type="daily",
    headline="Finish your signup",
    body="Come back and upgrade today.",
    link_url="https://example.com/upgrade",
    business_name="Acme",
    images={
        "landscape": "https://cdn.example.com/retarget-1200x628.jpg",
        "square": "https://cdn.example.com/retarget-1080x1080.jpg",
    },
    additional_headlines=["Finish your signup", "Get 30% off this week"],
    additional_descriptions=["Come back and upgrade today."],
    countries=["US"],
)
bash
curl -X POST "https://zernio.com/api/v1/ads/create" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "acc_googleads_123",
    "adAccountId": "987-654-3210",
    "name": "Retargeting Display",
    "campaignType": "display",
    "goal": "traffic",
    "budgetAmount": 40,
    "budgetType": "daily",
    "headline": "Finish your signup",
    "body": "Come back and upgrade today.",
    "linkUrl": "https://example.com/upgrade",
    "businessName": "Acme",
    "images": {
      "landscape": "https://cdn.example.com/retarget-1200x628.jpg",
      "square": "https://cdn.example.com/retarget-1080x1080.jpg"
    },
    "additionalHeadlines": ["Finish your signup", "Get 30% off this week"],
    "additionalDescriptions": ["Come back and upgrade today."],
    "countries": ["US"]
  }'

Display campaigns require businessName (max 25 chars) on top of the Search fields. longHeadline defaults to headline if not supplied (max 90 chars). Both images.landscape and images.square are required, Google rejects RDA with only one.

Conversions API

Import offline conversions (deal closed, lead qualified, subscription renewed) into Google Ads via the Data Manager API ingestEvents method, the endpoint Google recommends for all new integrations as of December 2025 (replacing the legacy uploadClickConversions). Zernio uses the Google Ads account you already connected; no additional OAuth, no developer token to apply for.

The same endpoint handles both attribution paths:

  • Click attribution, include a gclid, gbraid, or wbraid from the originating ad click
  • Enhanced Conversions for Leads, include hashed email / phone when no click ID is available

PII is SHA-256 hashed server-side. For @gmail.com / @googlemail.com addresses, we strip dots and +suffix from the local part before hashing per Google's spec so john.doe+promo@gmail.com matches johndoe@gmail.com.

Discover available conversion actions

typescript
const { data } = await zernio.ads.listConversionDestinations({
  path: { accountId: 'ACCOUNT_ID' },
});
python
data = client.ads.list_conversion_destinations(account_id="ACCOUNT_ID")
bash
curl "https://zernio.com/api/v1/accounts/ACCOUNT_ID/conversion-destinations" \
  -H "Authorization: Bearer YOUR_API_KEY"

Returns every enabled conversion action across accessible customers. The id is the conversion action resource name (customers/{customerId}/conversionActions/{id}). The type field reflects the action's category (PURCHASE, LEAD, SIGN_UP, etc.), Google locks the event type to the conversion action, not the per-event eventName.

Send a conversion event

typescript
const result = await zernio.ads.sendConversions({ body: {
  accountId: 'ACCOUNT_ID',
  destinationId: 'customers/1234567890/conversionActions/987654321',
  consent: { adUserData: 'GRANTED', adPersonalization: 'GRANTED' }, // required for EEA/UK
  events: [{
    eventName: 'Purchase',
    eventTime: Math.floor(Date.now() / 1000),
    eventId: 'order_abc_123', // dedup, mapped to transactionId
    value: 129.99,
    currency: 'USD',
    user: {
      email: 'customer@example.com',
      phone: '+14155551234',
      clickIds: { gclid: 'EAIaIQobChMI...' },
    },
  }],
}});
python
result = client.ads.send_conversions(
    account_id="ACCOUNT_ID",
    destination_id="customers/1234567890/conversionActions/987654321",
    consent={"adUserData": "GRANTED", "adPersonalization": "GRANTED"},
    events=[{
        "eventName": "Purchase",
        "eventTime": int(time.time()),
        "eventId": "order_abc_123",
        "value": 129.99,
        "currency": "USD",
        "user": {
            "email": "customer@example.com",
            "phone": "+14155551234",
            "clickIds": {"gclid": "EAIaIQobChMI..."},
        },
    }],
)
bash
curl -X POST "https://zernio.com/api/v1/ads/conversions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "ACCOUNT_ID",
    "destinationId": "customers/1234567890/conversionActions/987654321",
    "consent": { "adUserData": "GRANTED", "adPersonalization": "GRANTED" },
    "events": [{
      "eventName": "Purchase",
      "eventTime": 1744732800,
      "eventId": "order_abc_123",
      "value": 129.99,
      "currency": "USD",
      "user": {
        "email": "customer@example.com",
        "phone": "+14155551234",
        "clickIds": { "gclid": "EAIaIQobChMI..." }
      }
    }]
  }'

Google enforces strict consent signaling for European users under the February 2026 Data Manager restrictions. Pass consent at the request root with adUserData and adPersonalization set to GRANTED or DENIED. Omit the field for non-EEA requests where you don't have consent data; Google applies region defaults.

Deduplication

Pass a stable eventId on every event, Zernio maps it to Google's transactionId so repeated uploads of the same conversion are deduped.

Batching + reporting

Up to 2,000 events per request (Zernio chunks larger batches automatically). Conversions take up to 3 hours to appear in Google Ads reports. The response carries a requestId you can reference if conversions don't appear as expected.

Media Requirements

TypeFormatMax SizeNotes
Responsive Search AdText15 headlines / 4 descriptions30/90 chars
Responsive Display (landscape)JPEG, PNG5120 KB1.91:1, 1200x628 recommended. Required.
Responsive Display (square)JPEG, PNG5120 KB1:1, 1080x1080 recommended. Required.
KeywordText80 charsBROAD, PHRASE, EXACT

Version Migrations

Google Ads API ships v22, v23, v24... twice a year with breaking changes. Zernio absorbs these at our layer, so your integration keeps working without code changes.