Demand Gen
Create Google Demand Gen campaigns with image, video or carousel ads across YouTube, Discover and Gmail, grow them with new ad groups and ads, and edit creative, channels, audiences and targeting.
When you finish this page a Google Demand Gen campaign exists, paused, with one ad group and one ad, from one POST /v1/ads/create call, and you know how to add ad groups and ads to it and edit them later. You need a googleads account and the customer id from List ad accounts.
Demand Gen serves on YouTube (in-stream, in-feed and Shorts), Discover, Gmail and Google's display partners. Set campaignType: "demand_gen" and put the creative, channel and audience settings in demandGen. The request creates a daily budget, the campaign, one ad group and one ad in a single atomic Google request.
Create a campaign
curl -X POST "https://zernio.com/api/v1/ads/create" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"adAccountId": "1234567890",
"name": "Demand Gen launch",
"campaignType": "demand_gen",
"budgetAmount": 10,
"budgetType": "daily",
"countries": ["ES"],
"languages": ["es"],
"demandGen": {
"finalUrl": "https://example.com",
"businessName": "Acme",
"headlines": ["Schedule posts", "One social API"],
"descriptions": ["Publish and manage social content through one API."],
"callToAction": "Learn more",
"images": {
"landscape": ["https://cdn.example.com/landscape-1200x628.png"],
"square": ["https://cdn.example.com/square-1080x1080.png"],
"logo": ["https://cdn.example.com/logo-512.png"]
},
"channels": ["youtube_in_feed", "youtube_shorts", "discover", "gmail"],
"audience": {
"userInterests": ["92948"],
"ageRanges": [{ "min": 25, "max": 54 }]
}
}
}'The created ad carries Google's native platformCampaignId, platformAdSetId (the ad group) and platformAdId. The campaign is always created paused; enable it with PUT /v1/ads/campaigns/{campaignId}/status once you have reviewed it. Send validateOnly: true to have Google validate the whole request without creating anything.
budgetTypeisdaily, in whole units of the account currency like every other Google create.- Geo (
countries,regions,cities,zips,metros) andlanguagesare written on the ad group, because Demand Gen keeps them there rather than on the campaign. - Bidding: omit
bidStrategy(or sendLOWEST_COST_WITHOUT_CAP) for Maximize Conversions,COST_CAPplusbidAmountfor target CPA,LOWEST_COST_WITH_MIN_ROASplusroasAverageFloorfor target ROAS, orLOWEST_COST_WITH_BID_CAPplusbidAmountfor target CPC. locationTargetingType(presenceorpresence_or_interest) is set on the new campaign.
Choose the ad format
The fields you send in demandGen pick one of three ad formats:
| Format | Selected by | Creative fields |
|---|---|---|
| Multi-asset image ad | Default | headlines (1 to 5, 40 characters), descriptions (1 to 5, 90 characters), businessName, optional callToAction, and images.landscape or images.square (optionally portrait) plus images.logo |
| Video responsive ad | youtubeVideoIds (1 to 5 existing YouTube video ids) | headlines, longHeadlines (1 to 5, 90 characters, required here), descriptions, businessName, and exactly one images.logo |
| Carousel ad | carouselCards (2 to 10 cards) | Exactly one headline, one description, one logo, businessName and optional callToAction; each card has its own headline, images and optional finalUrl and callToAction |
finalUrl, businessName (25 characters), headlines, descriptions and images.logo are always required. Image sizes: landscape 1.91:1 (at least 600 x 314), square 1:1 (at least 300 x 300), portrait 4:5 (at least 480 x 600) and logo 1:1 (at least 128 x 128).
A carousel ad looks like this:
{
"campaignType": "demand_gen",
"demandGen": {
"finalUrl": "https://example.com",
"businessName": "Acme",
"headlines": ["One social API"],
"descriptions": ["Publish everywhere from one API."],
"images": { "logo": ["https://cdn.example.com/logo-512.png"] },
"carouselCards": [
{ "headline": "Schedule posts", "images": { "square": "https://cdn.example.com/card-1.png" } },
{ "headline": "Read analytics", "finalUrl": "https://example.com/pricing", "images": { "square": "https://cdn.example.com/card-2.png" } }
]
}
}Each card needs its own image (no two cards may share one), and every card should use the same image shape. Card images are uploaded to the account's asset library before the campaign is created, validateOnly included, because Google checks cards against existing images; an identical image is reused, not duplicated.
Channels and audiences
demandGen.channels sets the ad group's channel controls: only the listed channels serve, out of youtube_in_stream, youtube_in_feed, youtube_shorts, discover, gmail and display. Omit it to serve on all of them.
demandGen.audience builds a Google Audience and attaches it to the ad group. Send at least one dimension:
| Field | Values |
|---|---|
userLists | Numeric Google user list ids, such as a Customer Match list (up to 20) |
userInterests | Numeric Google interest category ids (up to 20) |
customAudiences | Numeric Google custom audience ids (up to 20) |
ageRanges | Objects with min (18, 25, 35, 45, 55 or 65) and optional max (24, 34, 44, 54 or 64; omit for no upper bound) |
genders | male, female, undetermined |
All ids must belong to the same Google customer. To reuse an Audience you already have, send its numeric id as demandGen.audienceId instead of audience.
Add ad groups and ads
Grow an existing Demand Gen campaign with the same endpoint and campaignType: "demand_gen":
- A new ad group: send
existingCampaignIdwith the campaign's Google id. The new ad group gets its own geo, languages,channelsandaudiencefrom the request, plus its ad, in one atomic request, and is created paused.demandGen.adGroupNamenames it (it defaults to the ad name). - A new ad in an ad group: send
adSetIdwith the ad group's Google id.demandGencarries only the ad's creative; ad group settings (geo, languages,channels,audience,audienceId,adGroupName) return400. The new ad is created paused.
{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"adAccountId": "1234567890",
"name": "Demand Gen, second audience",
"campaignType": "demand_gen",
"existingCampaignId": "23508518100",
"countries": ["US"],
"demandGen": {
"adGroupName": "Returning visitors",
"finalUrl": "https://example.com",
"businessName": "Acme",
"headlines": ["Schedule posts"],
"descriptions": ["Publish everywhere from one API."],
"images": {
"square": ["https://cdn.example.com/square-1080x1080.png"],
"logo": ["https://cdn.example.com/logo-512.png"]
},
"audience": { "userLists": ["123456"] }
}
}Neither shape takes budget, bidding or schedule fields: the campaign owns them, and sending them returns 400. locationTargetingType also belongs to the campaign and returns 400 here. The target must be a Demand Gen campaign or ad group, otherwise the request returns 400. Both shapes support validateOnly. A campaign migrated from Discovery that still targets locations and languages on the campaign refuses them on a new ad group, so geo and language fields return 400 there and the new ad group follows the campaign's targeting.
Edit creative, channels and audience
PUT /v1/ads/{adId} takes a top-level demandGen object and applies it in one atomic Google request. Every field you send replaces that whole field; fields you omit are kept.
curl -X PUT "https://zernio.com/api/v1/ads/66f0a1b2c3d4e5f6a7b8c9d2" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"demandGen": {
"headlines": ["Schedule posts", "One social API"],
"images": { "square": ["https://cdn.example.com/new-square.png"] },
"audience": { "userInterests": ["92948"], "genders": ["female"] }
}
}'- Which creative fields apply depends on the ad's format, read from Google: image ads take
headlines,descriptions,businessName,callToActionandimages; video ads takeheadlines,longHeadlines,descriptions,businessName,youtubeVideoIdsand exactly oneimages.logo; carousel ads take one headline, one description, one logo,businessNameandcallToAction. A field the ad does not take returns422. - A carousel's cards cannot be edited: create a new carousel ad instead.
- New images are uploaded as new Google assets; the previous ones stay in the account's asset library.
channels,audienceandaudienceIdchange the ad's ad group, so they apply to every ad in it.audiencebuilds a new Google Audience and attaches it in place of the current one (the previous audience is detached, not deleted). Ad groups of campaigns migrated from Discovery use ungrouped audience segments, and Google refuses an Audience on them.- The Search and Display creative fields (
headlinesat the top level,creative,assetGroup) return422on a Demand Gen ad, anddemandGenreturns422on any other channel.
Edit targeting
Demand Gen keeps locations and languages on the ad group, so there are two ways to change them:
targetingonPUT /v1/ads/{adId}takes locations, languages,locationTargetingTypeanddevices. Locations and languages go to that ad's ad group and apply to every ad in it.PUT /v1/ads/campaigns/{campaignId}/targetingwrites locations and languages to the campaign's ad group when it has exactly one, and the response carries thatadGroupId; the campaign-levellocationsandlanguagesread back then stay empty. With several ad groups the call returns400naming them, so edit each through an ad in that ad group.devicesandlocationTargetingTypestay campaign-level.
Campaigns migrated from Discovery that still target on the campaign keep being written there. Budget, bidding and name are edited with the regular campaign endpoints.
Related
- Create ads: Search, Display and Performance Max on the same endpoint.
- Customer Match: build the user lists
audience.userListstakes. - Create standalone ad and Update ad: every field of
demandGen.
Performance Max
Manage the asset groups of a Google Performance Max campaign: read them with ad strength, add new groups, edit URLs and status, link and unlink assets, and replace the retail listing-group tree.
Keywords
Read the Search keywords your Google campaigns run, add keywords to an ad group, and research new ones with the Keyword Planner.