Create ads
Create a Google Search, Display or Performance Max campaign in one POST /v1/ads/create call, edit RSA headlines and descriptions with pinning, and manage sitelink, callout and structured-snippet assets.
By the end of this page, a Google Search or Display campaign is live, with its ad group and ad, from one POST /v1/ads/create call.
Before you start
You need a googleads account and the customer id from List ad accounts.
Create a campaign
campaignType (lowercase search, display, pmax or demand_gen) selects the campaign type. Demand Gen has its own page.
This first example builds a Search campaign. Call POST /v1/ads/create with campaignType: "search", the ad copy and keywords.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: created } = await zernio.adcampaigns.createStandaloneAd({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
adAccountId: '1234567890',
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']
}
});
console.log(created.ad._id);Response (201):
{
"ad": {
"_id": "66f0a1b2c3d4e5f6a7b8c9d0",
"name": "US DevOps Search",
"platform": "google",
"status": "pending_review",
"adType": "standalone",
"goal": "traffic",
"budget": { "amount": 50, "type": "daily" },
"platformAdId": "735198246013",
"platformCampaignId": "21874563210",
"platformAdSetId": "165489732105"
},
"message": "Ad created"
}platformAdSetId is the Google ad group id (Zernio's adSetId).
_id is what the Insights and tracking tag endpoints take as adId.
budgetAmount is whole units of the ad account's currency, not micros and not cents: 50 with budgetType: "daily" is $50.00 a day on a USD account.
Google's own API takes micros here, so a micros value copied from a GAQL report is a million times too large.
goalisengagement,trafficorawareness.- Google Ads rejects
video_viewsat create with a422and codefeature_not_available. - Conversion-goal campaigns are not supported on
/v1/ads/createeither. Passtrafficfor click-optimised delivery.
- Google Ads rejects
keywordsis a flat array of strings, created as broad-match keywords on the new ad group (up to 1,000 per call, 80 characters each).- An entry can also be
{ text, matchType }withexact,phraseorbroad. Keywords covers editing them later.
- An entry can also be
countriesand the other audience fields are on Targeting.additionalHeadlinesandadditionalDescriptionsapply to Search Responsive Search Ads only.- Each entry accepts either a string or an object with
textand optionalpinnedField. pinnedFieldtakesHEADLINE_1,HEADLINE_2orHEADLINE_3for headlines, andDESCRIPTION_1orDESCRIPTION_2for descriptions. For example,{ "text": "Build your Dev Platform", "pinnedField": "HEADLINE_2" }pins an extra headline.- After including the primary text and deduplicating, the effective lists must contain 3 to 15 headlines and 2 to 4 descriptions. Excess entries return
400.
- Each entry accepts either a string or an object with
status is pending_review on every Google create. Later syncs re-derive it from Google:
status | When |
|---|---|
active | The ad is approved |
rejected | Google disapproves it |
paused | Paused |
completed | The campaign has ended |
cancelled | The ad is removed |
error | Google reports a state Zernio does not recognise |
Read GET /v1/ads/{adId} to see where it landed. The ad.status_changed webhook fires for Meta only.
Create a Display campaign
Call POST /v1/ads/create with campaignType: "display", businessName and both images.
const { data: display } = await zernio.adcampaigns.createStandaloneAd({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
adAccountId: '1234567890',
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'
},
countries: ['US']
}
});
console.log(display.ad.platformCampaignId);Response (201), same shape as the Search create:
{
"ad": {
"_id": "66f0a1b2c3d4e5f6a7b8c9d1",
"name": "Retargeting Display",
"platform": "google",
"status": "pending_review",
"adType": "standalone",
"goal": "traffic",
"budget": { "amount": 40, "type": "daily" },
"platformAdId": "735198246014",
"platformCampaignId": "21874563211",
"platformAdSetId": "165489732106"
},
"message": "Ad created"
}Display campaigns require businessName (max 25 characters) on top of the Search fields.
longHeadline defaults to headline when omitted (max 90 characters).
Discovered campaigns of every type, including Shopping and Video, sync into GET /v1/ads/tree with metrics. /v1/ads/create builds Search, Display, Performance Max and Demand Gen.
Create a Performance Max campaign
Call POST /v1/ads/create with campaignType: "pmax" and an assetGroup.
The campaign, its budget and the asset group with all its assets are created in one atomic Google mutate. You either get the whole thing or nothing.
const { data: pmax } = await zernio.adcampaigns.createStandaloneAd({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
adAccountId: '1234567890',
name: 'Spring Performance Max',
campaignType: 'pmax',
budgetAmount: 40,
budgetType: 'daily',
assetGroup: {
businessName: 'Acme',
finalUrl: 'https://example.com',
longHeadline: 'Everything your team needs in one place',
headlines: ['Acme for teams', 'Start in minutes', 'Built for scale'],
descriptions: [
'Set up in minutes and invite your whole team.',
'Trusted by thousands of growing companies.'
],
images: {
landscape: ['https://cdn.example.com/pmax-1200x628.jpg'],
square: ['https://cdn.example.com/pmax-1080x1080.jpg'],
logo: ['https://cdn.example.com/logo-1200x1200.jpg']
}
},
countries: ['US']
}
});The asset group needs:
businessName,finalUrlandlongHeadline- at least three
headlines - at least two
descriptions - images under all three of
landscape,squareandlogo
A YouTube video is optional via assetGroup.youtubeVideoId.
Read the result back with GET /v1/ads/campaigns/{campaignId}/asset-groups. Adding more asset groups, swapping assets and product targeting are on Performance Max.
Performance Max differs from Search and Display in ways that will reject the request rather than degrade quietly:
| Rule | Detail |
|---|---|
| Always created paused | Google rejects creating a Performance Max campaign in an active state. Activate it after review. |
| Daily budget only | budgetType must be daily. |
| Bidding | Maximize Conversions or Maximize Conversion Value. Portfolio bidding and bid caps are rejected. |
| Targeting | Geo and language only. An omitted geo targets all locations. |
| No legacy creative fields | headline, body, linkUrl and attach shapes belong to the other campaign types. |
Send validateOnly: true to validate the whole request against Google without creating or persisting anything. It is the safe way to check a payload before committing budget.
Edit responsive search ad text
PUT /v1/ads/{adId} replaces a Search ad's RSA text with top-level headlines, descriptions and finalUrls.
Each array is a full replacement of that list, not a merge. Omit a field entirely to leave it unchanged, or include it to replace every entry, including ones you want to keep.
const { data: updated } = await zernio.adcampaigns.updateAd({
path: { adId: '66f0a1b2c3d4e5f6a7b8c9d0' },
body: {
headlines: [
{ text: 'Social Media API', pinnedField: 'HEADLINE_1' },
{ text: 'Schedule Your Posts' },
{ text: 'Build With Zernio' }
],
descriptions: [
{ text: 'Connect social accounts and schedule posts with the Zernio API.', pinnedField: 'DESCRIPTION_1' },
{ text: 'Build social publishing into your application.' }
],
finalUrls: ['https://zernio.com']
}
});Use 3 to 15 headlines (1 to 30 characters each) and 2 to 4 descriptions (1 to 90 characters each). Zernio does not pad or truncate on update the way it does on create.
pinnedField fixes an asset to one position (HEADLINE_1, HEADLINE_2, HEADLINE_3, DESCRIPTION_1 or DESCRIPTION_2).
- Omit an asset from a supplied list to remove it.
- Include it without
pinnedFieldto unpin it. - Omit the entire list to preserve its current text and pins.
These Google updates use the top-level fields above. The legacy creative fields are unsupported for Google.
Read the current text and pins back with GET /v1/ads/{adId} in ad.creative.headlines, ad.creative.descriptions and ad.creative.finalUrls.
Google Search enrichment preserves pinnedField and reports top-level cachedAt and stale. Google mutations invalidate this read.
Enrichment failure. The endpoint still returns 200 with stored details and no cache metadata. That response is not confirmation of a fresh Google read.
Account, campaign and ad-group assets
Sitelinks, structured snippets and callouts are Google's account extensions. They exist at 3 levels, each with its own attach lifecycle:
| Level | Attach with | Manage with |
|---|---|---|
| Account (reusable across campaigns) | Add account sitelinks, snippets, callouts | List, update, remove |
| Campaign | Attach campaign assets | List, update, remove |
| Ad group (ad set) | Attach ad-group assets | List, update, remove |
POST /v1/ads/campaigns/{campaignId}/assets and POST /v1/ads/ad-sets/{adSetId}/assets create and attach sitelinks, callouts and structuredSnippets in one mutation at the chosen level.
At all three levels, updates edit the underlying shared Google asset in place.
Send updates with assetResourceName and the fields to change. Omitted fields stay unchanged.
A change is visible on every attachment using that asset, even when you make it through a campaign or ad-group endpoint.
Updates count toward Zernio's per-minute burst limit (see Quotas and the ops budget) and invalidate affected cached lists.
Removing at any level only detaches the specified attachment. Google assets cannot be deleted, so the underlying asset and its other attachments remain.
Media requirements
| Type | Format | Max | Notes |
|---|---|---|---|
| Responsive Search Ad | Text | 15 headlines and 4 descriptions | Headlines are truncated to 30 characters and descriptions to 90; Zernio pads up to Google's minimum of 3 headlines and 2 descriptions |
| Responsive Display (landscape) | JPEG, PNG | 5120 KB | 1.91:1, 1200 x 628 recommended. Required. |
| Responsive Display (square) | JPEG, PNG | 5120 KB | 1:1, 1080 x 1080 recommended. Required. |
| Keyword | Text | 80 characters | broad, phrase or exact match |
If it fails
| Error | Cause | Fix |
|---|---|---|
400 with NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET | One of the 2 Display images is missing | Pass both images.landscape and images.square and repeat the call |
422 feature_not_available | goal: "video_views" on a create | Use engagement, traffic or awareness |
400 on additionalHeadlines or additionalDescriptions | After deduplicating, the lists hold more than 15 headlines or 4 descriptions | Trim the lists |
400 on locationTargetingType | The request joins an existing campaign | Change it on the campaign, see Targeting |
Google's own message is forwarded in platformError:
{
"error": "Google rejected the ad: NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET",
"type": "platform_error",
"code": "platform_api_error",
"platform": "google",
"platformError": {
"code": 3,
"message": "Request contains an invalid argument.",
"details": [ { "errors": [ { "errorCode": { "assetError": "NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET" }, "message": "Too few." } ] } ]
}
}Error handling covers the envelope.
Related
- Targeting: locations, languages, audiences and who a campaign reaches.
- Keywords: read, add and research keywords.
- Pause or resume: status, budget and delete once the campaign runs.
- Limits and errors: quotas, what the API cannot do, and the errors Google returns most often.
- Create standalone ad: every field, including
sitelinks,calloutsandstructuredSnippets.
Google Ads
Create Search, Display, Performance Max and Demand Gen campaigns, mine keywords, upload Customer Match lists, send conversions, manage goals, labels and MCC links, apply recommendations and query GAQL.
Performance Max
Manage a Google Performance Max campaign's asset groups: read them with ad strength, add groups, edit URLs and status, link and unlink assets, and replace the retail listing-group tree.