Ad Campaigns

Create standalone ad

Changed on

Creates a paid ad with your own creative, building the campaign and ad set it needs or adding it to existing ones.

Create a paid ad with custom creative across Meta, Google Ads, Pinterest, TikTok, X, LinkedIn, OpenAI Ads (ChatGPT Ads) and Reddit Ads.

Google Performance Max: set campaignType: "pmax" and supply assetGroup with text, images by role, business name and finalUrl. Creates a daily budget, PAUSED campaign and asset group atomically. validateOnly: true validates the complete request with Google without creating or persisting resources. Read assets with GET /v1/ads/campaigns/{campaignId}/asset-groups. The logo is required; video is optional via assetGroup.youtubeVideoId. Brand guidelines are disabled at creation. All supplied asset links are validated together against Google's minimum asset requirements. PMax rejects ACTIVE creation, portfolio bidding, bid caps, legacy creative fields and attach shapes. Geo and language targeting are supported; omitted geo targets all locations. PMax does not require top-level goal, headline, body or linkUrl. Supported bidding: omitted or LOWEST_COST_WITHOUT_CAP for Maximize Conversions, COST_CAP plus bidAmount for target CPA, LOWEST_COST_WITH_MIN_ROAS plus roasAverageFloor for Maximize Conversion Value with target ROAS.

Google Demand Gen: set campaignType: "demand_gen" and supply demandGen. Creates a daily budget, PAUSED campaign, one ad group and one ad in a single atomic request: a multi-asset image ad, a video responsive ad when demandGen.youtubeVideoIds is sent, or a carousel ad (2 to 10 cards) when demandGen.carouselCards is sent. Geo (countries, regions, cities, zips, metros) and languages go on the ad group, as Demand Gen requires. demandGen.channels sets the ad group's channel controls and demandGen.audience builds a Google Audience (user lists, interests, custom audiences, age ranges, genders) attached to the ad group, or demandGen.audienceId attaches an existing one. validateOnly: true validates everything with Google without creating resources. Supported bidding: omitted or LOWEST_COST_WITHOUT_CAP for Maximize Conversions, COST_CAP plus bidAmount for target CPA, LOWEST_COST_WITH_MIN_ROAS plus roasAverageFloor for target ROAS, LOWEST_COST_WITH_BID_CAP plus bidAmount for target CPC. The created ad carries the native platformCampaignId, platformAdSetId (ad group) and platformAdId. Activate with PUT /v1/ads/campaigns/{campaignId}/status; budget, bidding and name are edited with the regular campaign endpoints; creative, channels, audience and ad group targeting with PUT /v1/ads/{adId} (demandGen, targeting). To grow an existing Demand Gen campaign, send existingCampaignId (adds a PAUSED ad group, with its own geo, languages, channels and audience, plus its ad) or adSetId (adds a PAUSED ad to that ad group); neither takes budget, bidding or schedule fields, and both support validateOnly.

Other mutually-exclusive request shapes are selected by the body:

  • Legacy single-creative shape (all platforms, the default).
  • Meta-only multi-creative shape via the creatives array: one ad set with N ads sharing budget and targeting.
  • Attach shape via adSetId: adds one new ad to an existing ad set, inheriting its budget, targeting, and schedule (Meta, Google Ads, TikTok, and LinkedIn). On LinkedIn adSetId is the existing Campaign id, and the budget, schedule, targeting and bidding fields must be omitted. On LinkedIn goal may be omitted too (taken from the Campaign's objective), and the created ad's targeting echoes the Campaign's audience.

Meta accepts creativeFeatures on the single and attach shapes and as defaults for creatives[]; an item replaces the whole feature map. promotion is not supported on any shape and any object is rejected with 400. Reusing existingCreativeId uses the existing creative settings instead of new settings. Requested settings are persisted for lists, exports, and default ad-detail reads.

Per-platform required fields, budget minimums, and video-ad rules are documented on each property below.

Meta required fields per shape (a missing one is a 400 naming it in param):

  • Every shape that builds a new ad set (no adSetId, existingCampaignId or buyingType: "RESERVED"): budgetAmount and budgetType, plus endDate for a lifetime budget.
  • Single image or video (the default), also with adSetId: headline (max 255), body, callToAction, exactly one of imageUrl / video (none for a catalog template creative, which ignores and does not upload any sent), and linkUrl unless the goal is lead_generation or page_visits or leadGenFormId is set.
  • bodies[] / headlines[] text variations: body, one list with 2+ items, callToAction, imageUrl or video, and linkUrl unless the goal is lead_generation or leadGenFormId is set. headline is not required.
  • placementAssets: headline, body, callToAction, and linkUrl with the same exemptions as the single shape; the media comes from placementAssets.
  • carouselCards: body, callToAction, and linkUrl unless the goal is lead_generation or leadGenFormId is set. headline is not required.
  • creatives[]: per entry headline, body, callToAction, imageUrl or video, and linkUrl unless the goal is lead_generation. Top-level body is not required.
  • dynamicCreative: imageUrls or videoUrls, callToActionTypes, and linkUrls unless the goal is lead_generation. Top-level body is not required.
  • existingCreativeId: no creative fields. Goal page_likes: body only.
  • Goal lead_generation always needs leadGenFormId, except with existingCreativeId.

LinkedIn creates a Single Image or Single Video Ad backed by a Direct Sponsored Content "dark post" authored by a Company Page (see organizationId). Supported goals are engagement, traffic, awareness, and video_views (video ads use the video field; video_views requires a video), and traffic ads require linkUrl.

Reddit Ads (accountId of a redditads connection, adAccountId like a2_abc123) creates a campaign, an ad group carrying the budget, schedule, targeting and pixel, and an ad running a new promoted post authored by the ad account's first profile (422 when it has none). The creative format follows the body: carouselCards builds a carousel, video a video ad, imageUrl an image ad, and none of them a text ad, which cannot link out (linkUrl is a 400 there; body is required). Only the legacy single-creative shape is available: creatives[], adSetId, existingCampaignId and validateOnly are refused. Reddit throttling surfaces as 429 rate_limited with platform: reddit; retry after a delay. To promote an existing Reddit post use POST /v1/ads/boost.

Idempotency: this endpoint is not idempotent at the platform level (a blind retry creates a second campaign/ad set/ad). Send an Idempotency-Key header to make retries safe: the first request with a given key creates the ad and we store the response; a retry with the same key replays that exact response (with Idempotent-Replayed: true) instead of creating duplicates. Reusing a key with a different body returns 422; a key whose first request is still in flight returns 409 (retry after a short backoff). If the first request is cut off mid-create (for example by a function timeout), its key can stay in progress and keep returning 409 until it expires; use a new key to retry sooner, after checking whether the ad was created. Keys are scoped to your credential and expire after 24h.

post/v1/ads/create

Request

  • Base URL: https://zernio.com/api
  • URL: https://zernio.com/api/v1/ads/create
  • Auth: HTTP bearer

Headers

Idempotency-Keystring

Optional client-generated unique key (e.g. a UUID) that makes retries safe. Same key + same body replays the original response; same key + different body → 422; key still processing → 409. A request cut off mid-handler (for example by a function timeout) can leave its key in progress, answering 409 until the key expires after 24 hours.

Request body

accountIdstring required
adAccountIdstring required
namestring required
campaignNamestring

Meta and Reddit. Exact campaign name. Overrides the default <name> - Campaign.

adSetNamestring

Meta and Reddit (the ad group). Exact ad set name. Overrides the default <name> - Ad Set. (For per-ad names on the multi-creative shape, set name on each creatives[] entry.)

adNamestring

Meta only. Exact ad name (the single-creative ad object's name). Overrides the default, which is name. (For per-ad names on the multi-creative shape, set name on each creatives[] entry instead.)

goal'engagement' | 'traffic' | 'awareness' | 'video_views' | 'lead_generation' | 'lead_conversion' | 'conversions' | 'app_promotion' | 'catalog_sales' | 'page_likes' | 'page_visits' | 'job_applicants'

Required on legacy and multi-creative shapes; the attach shape inherits it from the ad set. Available goals vary by platform.

Meta

  • conversions: OUTCOME_SALES. Requires promotedObject.pixelId and promotedObject.customEventType with a commerce event such as PURCHASE or START_TRIAL, or promotedObject.customConversionId to optimise against a Custom Conversion, or customEventType: OTHER + customEventStr to optimise against a pixel custom event.
  • lead_conversion: OUTCOME_LEADS optimizing website pixel leads. Same pixel and event fields, but with a leads-class event such as LEAD, SUBMIT_APPLICATION, SCHEDULE or CONTACT (or promotedObject.customConversionId to optimise against a Custom Conversion instead). Meta gates conversion events by objective, so leads-class events are rejected under conversions.
  • lead_generation: OUTCOME_LEADS with instant forms. Requires leadGenFormId. promotedObject.pageId is optional and auto-filled from the connected Page.
  • app_promotion: requires promotedObject.applicationId and promotedObject.objectStoreUrl.
  • catalog_sales: Advantage+ catalog ads, for example vehicle inventory. Requires promotedObject.productSetId, promotedObject.pixelId and promotedObject.customEventType. Builds a catalog TEMPLATE creative from the copy fields, which may carry template tags like {{product.name}} or {{vehicle.make}}. No imageUrl or video is sent; Meta renders the visuals per catalog item. Discover catalogs via GET /v1/ads/catalogs and product sets via GET /v1/ads/catalogs/{catalogId}/product-sets. Single shape only, no creatives[], dynamicCreative, placementAssets, existingCreativeId or carouselCards; an imageUrl or video sent with it is ignored and not uploaded. Works with existingCampaignId, and with adSetId to add a catalog ad to an existing ad set (then only promotedObject.productSetId is required; pixel and event stay on the ad set). Pick the format with catalogFormat.
  • traffic with promotedObject.productSetId: a catalog ad without a pixel (OUTCOME_TRAFFIC, optimizationGoal LINK_CLICKS by default or LANDING_PAGE_VIEWS). The product set goes on the template creative only, never on the ad set's promoted_object, and no pixelId or customEventType is needed. Same creative rules as catalog_sales.
  • page_likes: Page Likes conversion location under OUTCOME_ENGAGEMENT (destination_type ON_PAGE, optimization PAGE_LIKES). promotedObject.pageId is optional and auto-filled from the connected Page. The creative CTA is fixed to LIKE_PAGE targeting that Page; headline / body / linkUrl / callToAction / imageUrl / video are all optional (Meta derives the link and the Like button from the Page).
  • page_visits: Meta's "Page visits and followers" under OUTCOME_TRAFFIC (destination_type FACEBOOK_PAGE, optimization PROFILE_VISIT). promotedObject.pageId is optional and auto-filled from the connected Page, and linkUrl defaults to the Page. Meta enables Profile Visit ads per ad account: one it has not enabled is refused with Meta's "This account isn't eligible to use Profile Visit ads yet".

TikTok

  • conversions: website-conversion ad group. Requires promotedObject.pixelId, your TikTok Pixel ID. Accepts an optional promotedObject.customEventType with a TikTok optimization_event code your pixel tracks (newer pixels use e.g. SHOPPING for purchase events; legacy pixels use ON_WEB_ORDER, INITIATE_ORDER, ON_WEB_REGISTER or FORM). To inherit pixel and event from an existing ad group, pass adSetId instead.

LinkedIn

  • engagement, traffic, awareness and video_views create standalone Direct Sponsored Content ads. traffic requires linkUrl; video_views requires video.
  • lead_generation: requires leadGenFormId (an adForm ID from POST /v1/ads/lead-forms). The campaign objective is set to MAX_LEAD and the creative's leadgenCallToAction destination is set to urn:li:adForm:{id}.
  • job_applicants requires a platformSpecificData.jobs creative.
  • For conversions on LinkedIn, or to promote an existing post, use POST /v1/ads/boost.

OpenAI Ads

  • Only traffic, awareness, and conversions are supported (other goals return 400). Maps to OpenAI's bidding_type (clicks, impressions, conversions respectively). conversions requires an active conversion event setting on the account; create a tracking tag with defaultEventType via the tracking-tags API (POST /v1/accounts/{accountId}/tracking-tags), or configure a conversion event in OpenAI Ads Manager, or the request returns 400. Pick the event with promotedObject.customEventType (see AdPromotedObject); without it the most recently created optimizable event is used.

Reddit Ads

  • traffic (CLICKS), awareness (BRAND_AWARENESS), conversions, video_views (VIDEO_VIEWABLE_IMPRESSIONS) and lead_generation (LEAD_GENERATION); any other goal returns 400.
  • conversions requires promotedObject.customEventType; the pixel must already have received that event (see AdPromotedObject).
  • lead_generation sends people to linkUrl and optimizes toward the pixel's Lead event, which the pixel must already have received. Reddit retired its native lead forms on September 30, 2026, so leadGenFormId returns 400.
  • video_views needs a video (422 otherwise); optimizationGoal picks VIDEO_VIEW_6S (default) or VIDEO_VIEW_15S.
optimizationGoalstring

Meta, TikTok with goal video_views (ENGAGED_VIEW, the 6-second default, or ENGAGED_VIEW_FIFTEEN; both bill per view), or Reddit with goal video_views (VIDEO_VIEW_6S, the default, or VIDEO_VIEW_15S; other values return 400). Meta: Explicit ad-set optimization_goal (e.g. LANDING_PAGE_VIEWS, LINK_CLICKS, REACH, IMPRESSIONS, OFFSITE_CONVERSIONS, THRUPLAY, LEAD_GENERATION). Overrides the default derived from goal (e.g. traffic defaults to LINK_CLICKS). Forwarded verbatim to Meta, which validates compatibility with the campaign objective and rejects incompatible combinations.

billingEventstring

Meta only. Explicit ad-set billing_event. Defaults to IMPRESSIONS. Forwarded verbatim to Meta, which validates compatibility with the optimization goal.

buyingType'AUCTION' | 'RESERVED'

Meta only. Defaults to AUCTION and is explicitly sent on new campaigns, including validateOnly. Reusing existingCampaignId does not change the campaign. RESERVED = Reach & Frequency: requires rfPredictionId (a RESERVED prediction from /v1/ads/rf-predictions + /reserve). Budget, schedule and pricing come from the reservation, so budgetAmount/budgetType are not required and bid fields are ignored. Only the plain single-ad shape (no creatives[], adSetId, existingCampaignId or dynamicCreative).

rfPredictionIdstring

Meta only. The RESERVED prediction id the R&F ad set runs on (reserving mints a new id, so pass that one). Requires buyingType RESERVED.

promotionMetaPromotion nullable

Not supported. Meta validates creative_sourcing_spec.promotion_metadata_spec on the create call and then discards it, so a Promotion set through the Marketing API never reaches the creative. Any object is rejected with 400 invalid_field_value. Send null or omit the field, and set the Promotion on the ad in Ads Manager. Verified on 2026-09-11 across Graph v19.0 to v25.0 and every write path.

creativeFeaturesMetaCreativeFeatures

Meta Advantage+ creative enhancements. Map snake_case feature names to OPT_IN or OPT_OUT; Meta validates supported keys and unspecified features default to OPT_OUT. auto_promotion_tag is an Advantage+ enhancement, not the Ads Manager Promotion setting. The deprecated standard_enhancements bundle is rejected by Meta.

catalogFormat'single_image' | 'carousel_images_multi_items' | 'carousel_images_single_item' | 'carousel_slideshows'

Meta catalog template creatives only (goal catalog_sales, goal traffic with promotedObject.productSetId, or adSetId with promotedObject.productSetId); a 400 otherwise. Sent as object_story_spec.template_data.format_option. force_single_link is never sent with it (Meta rejects the pair, subcode 1443051). single_image also sends format_transformation_spec with carousel and da_collection set to data_source: ["none"], so Meta keeps the single image instead of turning it into a carousel or collection. carousel_images_single_item also sends template_data.multi_share_end_card: false, which Meta requires for that format (subcode 1443167). Omit it to let Meta pick the format.

multiAdvertiser'OPT_IN' | 'OPT_OUT'

Meta only. Multi-advertiser ads: whether Meta may show this ad alongside other advertisers' in one unit. Meta auto-enrols since Aug 2024, so send OPT_OUT to leave. It is a top-level creative field, NOT a creativeFeatures key, and Meta rejects it there.

aiDisclosure'OPT_IN' | 'OPT_OUT'

Meta only. Meta's "Ad includes media created or edited with AI" disclosure, the checkbox in Ads Manager, stored on the creative as generative_asset_spec.transparency_metadata.self_disclosure. OPT_IN checks it, OPT_OUT explicitly declares no AI media, omitted leaves Meta's default. Applied to each new creative, including standalone, creatives[] and attach shapes, and preserved when a creative is rebuilt. This sets the disclosure on the ad; whether and when the viewer-facing label renders is Meta's decision.

validateOnlyboolean

Google Performance Max validates the complete atomic campaign and asset group with no resource creation or local persistence. Google validation still downloads image URLs and consumes quota. On Meta, validates the complete inline campaign, ad set, creative and ad with execution_options validate_only. Nothing is uploaded or created, and validation bypasses Idempotency-Key storage. Supports a single image or existing video.id (each also with bodies[] / headlines[] Multiple Text Options), all-image placementAssets with per-rule copy and text pools, or existingCreativeId; dynamicCreative, translations, carouselCards, placement videos, new video uploads, creatives[], adSetId and RESERVED buying return 400. Placement validation uses existing Instagram identities only. Existing campaign or creative nodes are marked skipped. When Meta cannot dry-run the nested tree (code 2, or an unknown-error code 1 on an OFFSITE_CONVERSIONS ad set nested in a new campaign, which Meta returns for some website-conversion ad sets that it then creates normally), the campaign and creative are validated on their own and the adSet and ad nodes come back unverified with the reason. Success returns 200 with per-node results; Meta rejection returns an error. ChatGPT (OpenAI) has no platform dry-run: Zernio runs every check it knows (creative lengths, budget, bid strategy, targeting) plus live lookups of the conversion event and target countries, and uploads or creates nothing. OpenAI's own write-time checks (image fetch, currency-specific minimums, ad review) still run only on a real create. Any other platform, or a Google campaignType other than pmax or demand_gen, returns 501 feature_not_available.

budgetAmountnumber

Budget in WHOLE currency units (USD: 50 = $50.00), NOT cents. Meta's own Marketing API takes this same number in minor units, so it is an easy and expensive mix-up. Required on legacy, multi-creative and Performance Max shapes. Inherited on attach. OpenAI Ads: in the ad account currency, minimum 1; OpenAI can require a higher daily minimum for some currencies and names it in the error.

budgetType'daily' | 'lifetime'

Required on legacy, multi-creative and Performance Max shapes. Inherited on attach. OpenAI Ads accepts both as the campaign's single spend cap. A lifetime cap can later switch to daily with PUT /v1/ads/campaigns/{campaignId}, but OpenAI never switches a daily cap back to lifetime (422). Automatic bidding (Maximize Results) needs a daily budget. Reddit Ads: the ad group's daily or lifetime spend goal; lifetime needs endDate.

status'ACTIVE' | 'PAUSED'

Publish state of the created entities, on every platform. Omitted or ACTIVE publishes live (default, back-compat); PAUSED pauses only the TOP-MOST object this call creates and switches everything below it on, so one resume of that object brings the new tree live and nothing that already existed is touched: a new campaign is held paused with its ad set and ad on; with existingCampaignId the new ad set is held paused with its ad on; with adSetId the new ad itself is paused. campaignStatus: ACTIVE with status: PAUSED moves the pause down to the new ad set. campaignStatus, adSetStatus and adStatus set one level each and always win for that level; status: PAUSED adds a hold of its own only when none of them is PAUSED. To create every object paused, send all three as PAUSED. LinkedIn: a held campaign group is PAUSED with its campaign and creative ACTIVE; a new campaign in an existing group stays DRAFT; a held creative (with adSetId) is created DRAFT, as LinkedIn refuses PAUSED until its review is APPROVED. X has no per-ad switch, so its lowest level is the line item. Google Performance Max and Demand Gen accept PAUSED only (the campaign is created paused).

campaignStatus'ACTIVE' | 'PAUSED'

Every platform. Sets the switch of the new campaign alone (LinkedIn: the campaign group) and overrides status for it. PAUSED holds the campaign off with its ad set and ad on unless adSetStatus or adStatus say otherwise; ACTIVE with status: PAUSED switches the campaign on and holds the new ad set paused (its ad on). Omitted, it follows status. Ignored when the request creates no campaign (existingCampaignId or adSetId).

adSetStatus'ACTIVE' | 'PAUSED'

Every platform. Sets the switch of the new ad set alone (Google, TikTok, Pinterest and ChatGPT: the ad group; LinkedIn: the campaign, which stays DRAFT when held; X: the line item) and overrides status for it. Omitted, it follows status.

Precedence: a level status (campaignStatus, adSetStatus, adStatus) always wins for its level. status: PAUSED then holds the top-most new object that has no level status, and only when no level status is PAUSED; every other new object is switched on. So campaignStatus: ACTIVE + adSetStatus: PAUSED + adStatus: PAUSED keeps the campaign on with the new ad set and ad off, and all three PAUSED creates the whole tree paused.

Rejected with a 400 alongside adSetId (that ad set already exists; change it with PUT /v1/ads/ad-sets/{adSetId}/status). Performance Max and Demand Gen accept PAUSED only.

adStatus'ACTIVE' | 'PAUSED'

Request-side publish switch for adStatus on ad creation endpoints only (status, campaignStatus and adSetStatus carry their own inline ACTIVE/PAUSED enum there; the status-update endpoints are a separate lowercase active/paused enum). Distinct from AdStatus (the platform-reported delivery status returned on reads).

budgetLevel'adset' | 'campaign'

Meta only. Where the budget lives, which selects the Meta budget model:

  • adset (default): ABO (Ad-set Budget Optimization). The budget is set on the ad set. This is the back-compatible behaviour; omit this field to keep it.
  • campaign: CBO (Campaign Budget Optimization / Advantage Campaign Budget). The budget AND bidStrategy are set on the CAMPAIGN, and Meta distributes spend across ad sets automatically. The returned ad stores the applied budgetLevel and budget in campaignBudget for CBO or adSetBudget for ABO. Edit CBO budgets with PUT /v1/ads/campaigns/{campaignId} and ABO budgets with PUT /v1/ads/ad-sets/{adSetId}. Meta requires the budget at exactly one level, never both. Non-Meta platforms ignore this field. Ignored on the attach shape (adSetId), which inherits the existing budget.
currencystring

ISO 4217 currency code matching the ad account's currency (e.g. USD). Meta only. Optional: Zernio resolves it from the ad account when omitted. The value selects the minor-unit exponent Zernio converts budget/bid amounts by before calling Meta (most currencies are cents; zero-decimal currencies like JPY/KRW are sent as-is).

headlinestring

Required for Meta, Google, Pinterest, LinkedIn, OpenAI Ads and Reddit Ads on legacy + attach shapes (skip for multi-creative; use creatives[].headline). Ignored for TikTok. On X, the website card title (needs imageUrl and linkUrl, max 70). Max: Meta=255, Google=30, Pinterest=100, LinkedIn=400, OpenAI=50 (min 3), Reddit=300 (the promoted post's title). On LinkedIn this is the ad's headline (the bold text on the creative); for traffic ads it's the link card title. On OpenAI Ads this is the chat card's title.

longHeadlinestring

Google Display only. Defaults to headline if omitted. On LinkedIn, reused as the optional secondary description text on traffic (link) ads; omitted if not provided.

bodystring

Required on legacy + attach shapes. For X this is the tweet text (max 280 chars including a ~24-char URL when linkUrl is set). On LinkedIn this is the post commentary (the intro text shown above the ad). On OpenAI Ads this is the chat card's body text. On Reddit it is the promoted post's body: optional on image, video and carousel ads, required on text ads. Max: Google=90, Pinterest=500, OpenAI=100.

descriptionstring

Meta only (facebook/instagram). Link description: the secondary text shown below the headline (Meta's link_data.description; on video creatives mapped to video_data.link_description). When omitted, Meta auto-pulls the destination URL's OpenGraph description. Applies on legacy, attach, and placementAssets shapes; for multi-creative use creatives[].description (this field is the shared fallback). For multi-text variations use descriptions (array) instead.

bodiesstring[]

Meta only. Multiple Text Options (Advantage+ Flexible Format): supply 1-5 primary-text variations and Meta optimises delivery across them, WITHOUT enabling full Dynamic Creative (dynamicCreative). Uses optimization_type: DEGREES_OF_FREEDOM on the asset feed, so multiple ads per ad set are allowed (unlike dynamicCreative which is limited to one). Requires imageUrl or video, linkUrl, and callToAction. When set, the top-level body field is used as the object_story_spec.link_data.message (the preview text) and headlines must also be present. On a video creative the copy lands in video_data.message / video_data.title instead of link_data. Mutually exclusive with dynamicCreative, carouselCards, and creatives[]. With placementAssets the pool is applied to the placement-customized creative instead (optimization_type: PLACEMENT, every body carries the label of each rule that does not pin its own body, and the same for headlines), so Meta rotates the options per placement while media stays pinned; a rule's own body / headline stays pinned for that placement. descriptions[] cannot join a placement-customized creative (Meta refuses a second description per rule, subcode 1885878).

headlinesstring[]

Meta only. Headline variations for Multiple Text Options. Must be sent alongside bodies. The top-level headline field is used as the object_story_spec.link_data.name (video_data.title on a video creative).

descriptionsstring[]

Meta only. Optional description variations for Multiple Text Options. Sent alongside bodies and headlines.

callToAction'LEARN_MORE' | 'SHOP_NOW' | 'SIGN_UP' | 'BOOK_TRAVEL' | 'CONTACT_US' | 'DOWNLOAD' | 'GET_OFFER' | 'GET_QUOTE' | 'SUBSCRIBE' | 'WATCH_MORE' | 'ADD_TO_CART' | 'APPLY_NOW' | 'BOOK_NOW' | 'BUY_TICKETS' | 'DONATE' | 'DONATE_NOW' | 'GET_DIRECTIONS' | 'GET_SHOWTIMES' | 'LISTEN_NOW' | 'ORDER_NOW' | 'PLAY_GAME' | 'REQUEST_TIME' | 'SEE_MENU' | 'START_ORDER' | 'INSTALL_MOBILE_APP' | 'USE_APP' | 'REGISTER' | 'JOIN' | 'ATTEND' | 'REQUEST_DEMO' | 'VIEW_QUOTE' | 'APPLY' | 'SEE_MORE' | 'BUY_NOW'

Required on legacy + attach shapes for Meta. Honoured on TikTok (passes through to the Spark Ad creative's call_to_action) and on LinkedIn (the CTA button on the ad; defaults to LEARN_MORE when linkUrl is set). LinkedIn accepts: LEARN_MORE, SIGN_UP, DOWNLOAD, SUBSCRIBE, REGISTER, JOIN, ATTEND, REQUEST_DEMO, VIEW_QUOTE, APPLY, SEE_MORE, SHOP_NOW, BUY_NOW. Reddit (image, video and carousel ads) accepts: LEARN_MORE, SHOP_NOW, SIGN_UP, CONTACT_US, DOWNLOAD, GET_QUOTE, SUBSCRIBE, APPLY_NOW, BOOK_NOW, BUY_TICKETS, DONATE_NOW, GET_DIRECTIONS, GET_SHOWTIMES, LISTEN_NOW, ORDER_NOW, SEE_MENU, INSTALL_MOBILE_APP (other values return 400). Ignored by Google, Pinterest, and X.

linkUrlstring uri

Required on legacy + attach shapes (skip for multi-creative). On LinkedIn it's the ad's destination URL; required for traffic ads, optional for engagement / awareness. NOT required when goal is lead_generation (the ad opens a Lead Gen form instead of a destination). On LinkedIn, imageUrl + linkUrl publishes an ARTICLE-content creative; this is LinkedIn's article ad format, with the image as thumbnail and longHeadline as description. Required for OpenAI Ads (the chat card's target_url). Reddit: required on image and video ads (the post's destination and the ad's click URL), the fallback for carousel cards without their own, and refused with 400 on text ads.

leadGenFormIdstring

Lead Gen form ID to attach to the ad's creative. REQUIRED when goal is lead_generation. Create one via POST /v1/ads/lead-forms. On Meta (facebook/instagram) this is the leadgen_forms ID; the ad set's promoted_object.page_id + LEAD_GENERATION optimization + destination_type ON_AD are derived automatically from the goal. On LinkedIn this is the adForm ID; the creative's leadgenCallToAction.destination is set to urn:li:adForm:{id} and the campaign objective is set to MAX_LEAD. Forms must be owned by the sponsoredAccount (not the organization) for the URN to resolve. Also required on every Meta ATTACH (adSetId) call that targets a lead ad set (the form attaches per-ad; Meta rejects a formless ad in a lead ad set). placementAssets, dynamicCreative and carouselCards (Meta multi-card Instant-Form lead ad; linkUrl and per-card linkUrl are optional and forwarded as real destinations when sent, falling back to Meta's lead-form link when omitted) ARE supported on Meta instant-form lead ads. Reddit: rejected with 400 on every goal (Reddit retired its native lead forms).

imageUrlstring uri

Image creative for Meta/Google/Pinterest/LinkedIn on legacy + attach shapes (mutually exclusive with video). Required for LinkedIn ads unless video is set. Not required for Google Search campaigns. For TikTok, this field carries the VIDEO URL (the TikTok ads endpoint is video-only; the field retains the imageUrl name for cross-platform consistency). On X, imageUrl + headline + linkUrl together build a website card (image, title and destination under the post; the URL is not added to the post text); sending only some of them is a 400. description, video and callToAction are rejected with 400 on X (promote a post that carries a video with POST /v1/ads/boost). For Google Display, treated as the landscape image (alias of images.landscape); supply images.square alongside or the request is rejected. For LinkedIn the image is uploaded to LinkedIn under the authoring Company Page (see organizationId); recommended ratio 1.91:1 (e.g. 1200×627). Required for OpenAI Ads (uploaded as the chat card's image; OpenAI has no video ad format). Reddit: builds an image ad (requires linkUrl); on a Reddit video ad it is the fallback for video.thumbnailUrl.

adSetIdstring

When present, switches to the attach shape: adds one new ad to this existing ad set without creating a new campaign. Budget, targeting, goal, schedule, AND bid strategy are inherited from the ad set on Meta, and passing bidStrategy in attach mode returns 400. To change an existing ad set's bid, use PUT /v1/ads/ad-sets/{adSetId}. Mutually exclusive with creatives[]. dynamicCreative returns 400 in attach mode: create a new dynamic ad set by omitting adSetId instead.

The attached ad takes the full single-creative surface: headline/body/description/callToAction plus either imageUrl/video OR placementAssets (its own per-placement Feed/Story assets) OR translations/defaultLocale (its own per-locale asset feed, Meta only), and leadGenFormId when the target is a lead ad set (the parent must be ON_AD, true for ad sets created via goal lead_generation; Meta rejects a formless ad there, so pass the form on EVERY attached ad). This is the way to build N full ads sharing one ad set: create the first ad via the normal shape, then attach the rest one call each.

Supported on Meta (facebook, instagram), Google Ads, TikTok, and LinkedIn. On TikTok the adSetId is the ad group ID; the new ad inherits the ad group's bid + budget + targeting. On LinkedIn the adSetId is the LinkedIn Campaign ID (numeric); we attach a new Creative to that Campaign, so the Campaign's platformSpecificData bidding, targeting, budget and schedule are inherited (passing those fields returns 400).

On Google Ads the adSetId is the AD GROUP id (numeric). goal is not required: the ad group keeps its campaign's bidding, budget and targeting. campaignType is read from the ad group's campaign (Search, Display or Demand Gen), so it can be omitted; a campaignType that contradicts the ad group returns 400, an ad group Google does not know in that customer returns 404, and any other campaign type returns 422. A Search ad needs headline + body + linkUrl; a Display ad also needs images.landscape + images.square + businessName. Google allows 3 responsive search ads per ad group: a fourth returns 422 naming the limit (RESPONSIVE_SEARCH_ADS_PER_AD_GROUP) and the ad group. budgetAmount/budgetType and bidding fields (bidStrategy, bidAmount, portfolioBidStrategyId) return 400 on this shape; the ad group already owns them. With campaignType: "demand_gen" the ad group must belong to a Demand Gen campaign (otherwise 400); demandGen carries only the ad's creative, the new ad is created PAUSED, and ad group settings (geo, languages, demandGen.channels/audience/audienceId/ adGroupName) return 400. validateOnly is supported.

existingCampaignIdstring

Meta, Google Ads, LinkedIn and TikTok. On TikTok: creates the ad group and the ad under this existing campaign; the campaign is neither created nor activated and its objective must fit goal; not with smartPlus. On Meta: add the new ad set under this EXISTING campaign instead of creating a new one (multi-ad-set audience testing). The new ad set's budget is matched to the campaign's mode automatically: for a CBO campaign (campaign-level budget) omit budgetAmount/budgetType, since the campaign owns the budget; for an ABO campaign pass them (they go on the new ad set). On LinkedIn: create a new Campaign (and its Creative) under this EXISTING CampaignGroup. On Google Ads: create a new ad group under this EXISTING campaign; the new ad group inherits the campaign's budget, so omit budgetAmount/budgetType (and any bidding field), or the request returns 400. With campaignType: "demand_gen" the campaign must be a Demand Gen campaign (otherwise 400): the new ad group (with its geo, languages, channels and audience from the request) and its ad are created in one atomic request, the ad group PAUSED; schedule and locationTargetingType belong to the campaign and return 400. 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; the new ad group follows the campaign's targeting. On failure only the entities we authored are cleaned up; the pre-existing parent is left untouched and is never (re)activated. Mutually exclusive with adSetId and creatives[].

phoneNumberstring

Meta attach shape only (adSetId). The phone number (E.164, e.g. +4712345678) for an ad added to a "website and phone call" ad set (destination_type WEBSITE_AND_PHONE_CALL), sent as the creative's call configuration. Optional: when omitted, the number on the ad set's existing ads is reused, and if none of them carries one the request is a 400. Rejected with 400 on any other ad set, with existingCreativeId, without adSetId, or on other platforms (a new call campaign is POST /v1/ads/call).

existingCreativeIdstring

Meta only. Reuse an EXISTING ad creative by id instead of building a new one from the copy/media fields (which are then ignored). Combine with existingCampaignId to build a multi-ad-set campaign that shares one creative. Mutually exclusive with creatives[], dynamicCreative, and placementAssets. The creative id used is returned as creativeId on the create response.

businessNamestring

Google Display only

boardIdstring

Pinterest only. Board ID (auto-creates if not provided).

organizationIdstring

LinkedIn only. The Company Page that authors the Direct Sponsored Content ("dark") post backing the ad. Accepts a numeric organization ID or a full urn:li:organization:N URN. Required unless the resolved accountId is a connected LinkedIn Company-Page account (defaults to that page) or the LinkedIn ad account is org-owned (defaults to the account's owning organization). The authenticated member must be an ADMINISTRATOR or DIRECT_SPONSORED_CONTENT_POSTER of this page (and the page must be associated with the ad account), or LinkedIn returns 403. Ignored by every other platform.

countriesstring[]

ISO 3166-1 alpha-2 country codes (e.g. ['NL']). Defaults to ['US'] when no other geo targeting (flat or nested targeting) is provided. (LinkedIn and OpenAI Ads currently honour country-level targeting only; any other targeting field returns 400 for OpenAI Ads.)

countryGroupsstring[]

Meta only. Continents and trade blocs (geo_locations.country_groups), for targeting a whole region without listing its countries. Combines with countries rather than replacing it. Discoverable via GET /v1/ads/targeting/search?dimension=geo&geoType=country_group.

ageMininteger
ageMaxinteger
incomeTier'top_5' | 'top_10' | 'top_10_25' | 'top_25_50'

Normalized household-income tier. Meta and TikTok express all four; Google maps only top_10; rejected on LinkedIn, X, and Pinterest. On Meta, income targeting is incompatible with housing/employment/credit specialAdCategories.

languagesstring[]

e.g. ["en","es"]. Google: campaign language targeting (language_constant) using Google's language codes (ISO 639-1, plus variants such as zh_CN); unknown codes return 400. On Meta, a bare code targets all regional variants ("en" = all English), or use a region-qualified code for a specific one ("en_GB", "pt_BR", "zh_TW"); unknown codes are rejected. Other ad platforms use their own language-code systems.

savedTargetingIdstring

ID of a saved_targeting audience (created via POST /v1/ads/audiences). When set, its stored TargetingSpec is expanded as the base targeting; inline fields on this body merge on top. Lets you reuse a named targeting preset without re-sending every field.

rawTargetingobject

Meta only. A raw Meta-native targeting spec (snake_case: geo_locations, age_min, excluded_custom_audiences, flexible_spec, targeting_automation, user_os, wireless_carrier, business places, etc.), exactly the shape GET /v1/ads/{adId} returns for external ads. Sent alone it reaches the ad set VERBATIM (the clone-a-campaign's-targeting-exactly path). Meta validates and surfaces any errors.

Can be combined with the camelCase targeting fields (countries/regions/cities/interests/ageMin/..., targeting, savedTargetingId, audienceId): rawTargeting is the BASE layer and the built camelCase spec is merged on top, key by key, with the camelCase side winning on collision (the camelCase precedence chain stays savedTargetingId < targeting < flat fields). The merge goes one level deep inside geo_locations and excluded_geo_locations: built sub-keys win, raw-only sub-keys such as location_types survive alongside built countries. Array values (flexible_spec, ...) are replaced as a WHOLE key when the camelCase spec builds them, never element-merged. When rawTargeting is present the defaults the camelCase builder normally injects (US geo, targeting_automation.advantage_audience: 0) are suppressed, so raw's values are not clobbered. Include targeting_automation in the raw spec (or send advantageAudience) as Meta requires it on create. If cloning an EU campaign, also pass dsaBeneficiary / dsaPayor (those are separate fields, not part of targeting).

specialAdCategoriesstring[]

Meta only. Declares the ad's special category, required for housing, employment, credit, or political/social-issue ads (Meta enforces restricted targeting for these). Note: setting a special category disables income/zip targeting on Meta.

specialAdCategoryCountrystring[]

Meta (metaads) only. 2-letter ISO country codes the special ad category applies to. Requires specialAdCategories to be set (400 otherwise). Ignored when joining an existing campaign via existingCampaignId (the existing campaign's category/country already governs it).

regionalRegulatedCategoriesstring[]

Meta only. Regional regulation categories required when the ad set targets certain countries. Known values: AUSTRALIA_FINSERV, AUSTRALIA_UNIVERSAL, BRAZIL_REGULATION, INDIA_FINSERV, MALAYSIA_UNIVERSAL, SINGAPORE_UNIVERSAL, TAIWAN_FINSERV, TAIWAN_UNIVERSAL, THAILAND_UNIVERSAL. Meta rejects the ad set without this when the targeting geo includes the corresponding country.

regionalRegulationIdentitiesobject

Meta only. Verified identity IDs of the beneficiary and payer for each declared regionalRegulatedCategories value. Meta may require them on ad sets created through the API even when a default beneficiary and payer are set in Ads Manager advertising settings (seen on AUSTRALIA_FINSERV), so sending them explicitly is the reliable path. The value is the Verified identity ID (Meta Business Settings > Verified identities), not a Page or Business ID, and beneficiary and payer may be the same ID. Keys by category: AUSTRALIA_FINSERV uses australia_finserv_beneficiary / australia_finserv_payer; TAIWAN_FINSERV uses taiwan_finserv_beneficiary / taiwan_finserv_payer; INDIA_FINSERV uses india_finserv_beneficiary / india_finserv_payer; TAIWAN_UNIVERSAL uses taiwan_universal_beneficiary / taiwan_universal_payer; SINGAPORE_UNIVERSAL uses singapore_universal_beneficiary / singapore_universal_payer; AUSTRALIA_UNIVERSAL, BRAZIL_REGULATION, MALAYSIA_UNIVERSAL and THAILAND_UNIVERSAL use universal_beneficiary / universal_payer. When Meta rejects the ad set for a missing verified advertiser, the error carries param regionalRegulationIdentities, details.expectedIdentityKeys (the keys the declared categories expect), details.suggestedCategories (the markets Meta ties to that subcode) and Meta's metaCode, metaSubcode and fbtraceId. Ignored when adSetId adds the ad to an existing ad set.

endDatestring date-time

Required for lifetime budgets. Read like startDate (account timezone on Meta, TikTok, X, Pinterest, OpenAI and Reddit; whole account-local days on Google; UTC on LinkedIn), and a date-only end runs to 23:59:59 local.

startDatestring date-time

Ad-set start time (ISO 8601, e.g. "2026-06-10T09:00:00Z"), mapped to the ad set's start (Meta start_time, TikTok ad group schedule_start_time, LinkedIn / Pinterest / X / Google campaign start). When omitted the ad starts delivering immediately. For lifetime budgets Meta also requires endDate. On Meta, TikTok, X, Pinterest, OpenAI and Reddit a value without an offset (YYYY-MM-DD, YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS) is read in the ad account timezone (Meta itself would read it as UTC). Google runs whole days in the customer account timezone and takes the value's calendar date as written. LinkedIn ad accounts carry no timezone, so there a value without an offset is read as UTC. The created ad's schedule echoes the start and end the platform stored, as UTC instants; when attaching to an existing ad set (adSetId) it is that ad set's start and end. Same field as on POST /v1/ads/boost.

pageIdstring

Meta only. The Facebook Page the ad runs as (object_story_spec.page_id). Defaults to the Page bound to the connection. Pass another Page ID to run the ad as that Page: any Page granted to the connection is accepted (for a business-login connection, every Page granted in Meta's dialog; list them with GET /v1/ads/instagram-accounts). The Instagram identity is re-resolved for that Page unless instagramAccountId is set. A Page the connection cannot see is a 400 on pageId naming the granted Pages.

instagramAccountIdstring

Meta only. Override the Instagram account the ad is delivered as. Pass an Instagram Business Account ID (e.g. 17841...), mapped to the creative's instagram_user_id; it is sent as-is and never replaced. When omitted we use the Instagram account linked to the Page; formats that need an Instagram actor without one use the actor the Page's other ads already run as, then the Page's page-backed Instagram account. If Meta refuses that default because the ad account has no access to it (200/1815199), the creative is retried once with an Instagram account the ad account is authorized for; with none, the request fails with a 422 on instagramAccountId (list the usable accounts with GET /v1/ads/instagram-accounts). A creative replaced later through /v1/ads/{adId} follows the same rule. Useful when a Page has more than one eligible IG account.

optimizeCardOrderboolean

Meta only (a 400 on any other platform), and only with carouselCards (a 400 without them). Sets the creative's link_data.multi_share_optimized. false keeps the cards in the order given; Meta then takes at most 5 cards, so false with more than 5 carouselCards is a 400. true lets Meta pick and reorder the cards for performance (up to 10). Omitted, nothing is sent and Meta's default (true) applies.

defaultLocalestring

Meta only. Language the top-level copy is written in (e.g. en, pt_BR), used by the translations default rule. Defaults to en. Meta rejects a language asset feed whose default rule carries no locales of its own. Must NOT also appear as an entry in translations.

audienceIdstring

Custom audience ID for targeting

campaignType'display' | 'search' | 'pmax' | 'demand_gen'

Google only. Performance Max requires assetGroup and Demand Gen requires demandGen; both are always created PAUSED. With adSetId it defaults to the ad group's own campaign type instead of display.

locationTargetingType'presence' | 'presence_or_interest'

Google only. Who a campaign's location targeting reaches (Google's campaign.geo_target_type_setting.positive_geo_target_type). presence: people in, or regularly in, the targeted locations. presence_or_interest: also people searching for or showing interest in them. Omitted leaves Google's default, presence_or_interest. Accepted on Search, Display and Performance Max campaigns. Excluded locations always use presence (Google refuses presence_or_interest for exclusions on these campaign types).

calloutsstring[]

Google Search only. Short callout texts (max 25 chars each) that appear as non-clickable annotations under the ad, e.g. "Free shipping", "24/7 support". Each becomes one Asset (callout_asset) plus a CampaignAsset link with field_type CALLOUT. Response's creative.callouts[] echoes each input plus its Google resourceName.

advantageAudience0 | 1

Meta only. Controls the Advantage audience feature (targeting_automation). 0 = disabled (default), 1 = enabled. Meta Marketing API requires this field on all ad set creation requests.

gender'all' | 'male' | 'female'

Restrict the audience by gender. 'male' targets men only, 'female' targets women only, 'all' (default) targets everyone. Applied on Meta, TikTok, Pinterest and Reddit. Ignored on Google, LinkedIn and X.

bidStrategy'LOWEST_COST_WITHOUT_CAP' | 'LOWEST_COST_WITH_BID_CAP' | 'COST_CAP' | 'LOWEST_COST_WITH_MIN_ROAS'

Meta bid strategy. Same enum applies at campaign and ad-set level; ad-set value (when set) overrides campaign-level. Cross-field rules:

  • LOWEST_COST_WITHOUT_CAP (default): auto-bid, forbids bidAmount and roasAverageFloor.
  • LOWEST_COST_WITH_BID_CAP / COST_CAP: require bidAmount (whole currency units).
  • LOWEST_COST_WITH_MIN_ROAS: requires roasAverageFloor (decimal multiplier, 2.0 = 2.0x). Source: facebook-business-sdk-codegen api_specs/specs/enum_types.json (AdSet_bid_strategy, Campaign_bid_strategy).
bidAmountnumber

Deprecated: send it inside platformSpecificData instead (Meta today; TikTok's nested shape is planned). The flat field keeps working during the deprecation window; sending both shapes returns a 400.

Bid cap in WHOLE currency units (USD: 5 = $5.00; JPY: 100 = ¥100). Required when bidStrategy is LOWEST_COST_WITH_BID_CAP or COST_CAP. Meta only: sending bidAmount WITHOUT bidStrategy requires existingCampaignId (400 otherwise), and sets the new ad set's cap under the joined campaign's COST_CAP / LOWEST_COST_WITH_BID_CAP parent. The strategy itself is inherited from the campaign. Restating bidStrategy here is accepted but has no effect on the ad set.

Rejected with 400 in adSetId attach mode: that shape inherits its cap from the platform. Use PUT /v1/ads/ad-sets/{adSetId} there instead.

roasAverageFloornumber

Deprecated: send it inside platformSpecificData instead (Meta today; TikTok's nested shape is planned). The flat field keeps working during the deprecation window; sending both shapes returns a 400.

Minimum ROAS as a decimal multiplier (e.g. 2.0 = 2.0x ROAS). Required when bidStrategy is LOWEST_COST_WITH_MIN_ROAS. Sending it without bidStrategy is a 400. Sent to Meta as bid_constraints.roas_average_floor × 10000. Known gap: a CBO campaign's ROAS floor lives on the campaign only (set via POST /v1/ads/campaigns); there is no supported way to set it while joining a CBO campaign here.

portfolioBidStrategyIdstring

Google Search and Display only. Performance Max rejects portfolio bidding. Attach an existing portfolio bid strategy (numeric id from GET /v1/ads/bid-strategies) to the new campaign instead of a standard one. Exclusive with bidStrategy.

valueRuleSetIdstring

Meta only (facebook, instagram; other platforms return 400). Value rule set to attach to the new ad set, from /v1/ads/value-rule-sets. Attachment is driven by this id, so valueRulesApplied is optional alongside it.

Rejected with 400 in adSetId attach mode: that shape inherits the existing ad set's attachment, so the field would be silently ignored. Use PUT /v1/ads/ad-sets/{adSetId} there instead.

Ignored (stripped before the ad-set create) when buyingType is RESERVED: value rules only apply to auction ad sets on LOWEST_COST_WITHOUT_CAP or COST_CAP, and a Reach & Frequency reservation has no auction bid strategy.

Read back with GET /v1/ads/ad-sets/{adSetId}?fields=value_rule_set_id; the attachment is not mirrored onto Zernio's ad documents.

valueRulesAppliedboolean

Meta only (facebook, instagram; other platforms return 400). Optional when attaching, and requires valueRuleSetId. false is REJECTED here with 400: a newly created ad set has nothing to detach, so detaching lives on PUT /v1/ads/ad-sets/{adSetId}.

dsaBeneficiarystring

Legal entity that benefits from the ad. Required when targeting EU users (EU DSA, Article 26). Optional if the ad account has a default beneficiary: set it once via PATCH /v1/ads/accounts or in Meta Ads Manager, and Meta fills it in whenever the field is omitted.

dsaPayorstring

Legal entity that pays for the ad. Can differ from dsaBeneficiary (for example, an agency paying for a client's ads). Same rules as dsaBeneficiary: required for EU targeting unless the ad account has a default payor.

identityIdstring

TikTok: the identity the ad runs as, from GET /v1/ads/tiktok-identities. Overrides the connected account's own identity; must be authorized on the advertiser.

identityType'TT_USER' | 'CUSTOMIZED_USER' | 'BC_AUTH_TT'

TikTok only. Forces the identity attribution on the ad:

  • TT_USER: the posting account's open_id (real @username branding). Requires a connected TikTok posting account on the same profile.
  • CUSTOMIZED_USER: synthetic Brand Identity (display name + avatar). Requires a configured Brand Identity (cached on the tiktokads SocialAccount via PATCH /v1/connect/tiktok-ads) or an inline brandIdentity to create one on the fly.

When omitted, defaults to TT_USER if a posting account is connected on this profile, else CUSTOMIZED_USER. Spark Ads (POST /v1/ads/boost) always use TT_USER regardless of this field, because TikTok requires the original organic post's author identity for Spark.

smartPlusboolean

TikTok only. Creates the ad as a TikTok Upgraded Smart+ campaign: TikTok automates targeting, bidding and delivery. Supports goals conversions (Smart+ Web Conversions), lead_generation (Smart+ Lead Generation with a website form on linkUrl; TikTok Instant Forms not supported) and app_promotion (Smart+ App installs; the ad's destination is the app store, so linkUrl is not used). The web goals require promotedObject.pixelId AND promotedObject.customEventType; app_promotion requires promotedObject.applicationId instead. Targeting works like on any TikTok ad (defaults to countries: ["US"] when omitted); TikTok automates delivery within it. The budget lives on the Smart+ campaign (Campaign Budget Optimization); a lifetime budget additionally requires endDate. Cannot be combined with adSetId.

userOsstring[]

Meta only. Operating systems and version ranges, such as iOS_ver_14.0_and_above or Android. Emitted as user_os. May also be supplied inside targeting.

userDevicestring[]

Meta only. Device models such as iPhone. Emitted as user_device. May also be supplied inside targeting.

isSkadnetworkAttributionboolean

Meta app promotion only. Immutable campaign flag. Set true for iOS 14+ SKAdNetwork campaigns and supply promotedObject.applicationId plus promotedObject.objectStoreUrl. The campaign receives promotedObject only when this flag is true. Cannot be changed on an existing campaign.

campaignAttribution'AEM' | 'SKADNETWORK'

Meta ad-set attribution. Required as SKADNETWORK for iOS 14+ app promotion or a SKAdNetwork campaign. Requires AUCTION buying. Standalone Meta ad-set creation is not supported; use this field on /v1/ads/create.

Example request

{
  "creativeFeatures": {
    "auto_promotion_tag": "OPT_IN"
  },
  "creatives": [
    {
      "creativeFeatures": {
        "auto_promotion_tag": "OPT_IN"
      }
    }
  ],
  "placementAssets": {
    "defaultImageUrl": "https://example.com/feed-4x5.jpg",
    "rules": [
      {
        "imageUrl": "https://example.com/feed-4x5.jpg",
        "headline": "Build with Zernio",
        "body": "Schedule social posts from your app.",
        "description": "One API for your social content.",
        "placements": {
          "publisherPlatforms": [
            "facebook"
          ],
          "facebookPositions": [
            "feed"
          ]
        }
      },
      {
        "imageUrl": "https://example.com/story-9x16.jpg",
        "headline": "Your next story",
        "body": "Bring your social workflow to Zernio.",
        "placements": {
          "publisherPlatforms": [
            "facebook"
          ],
          "facebookPositions": [
            "story",
            "facebook_reels"
          ]
        }
      }
    ]
  },
  "locationTargetingType": "presence",
  "assetGroup": {
    "finalUrl": "https://zernio.com",
    "headlines": [
      "Schedule posts",
      "One social API",
      "Build with Zernio"
    ],
    "longHeadline": "Schedule social content from your app with Zernio",
    "descriptions": [
      "Connect your social accounts.",
      "Publish and manage social content through one API."
    ],
    "businessName": "Zernio",
    "images": {
      "landscape": [
        "https://example.com/landscape.png"
      ],
      "square": [
        "https://example.com/square.png"
      ],
      "logo": [
        "https://example.com/logo.png"
      ]
    }
  },
  "additionalHeadlines": [
    "Schedule Your Posts",
    {
      "text": "Build With Zernio",
      "pinnedField": "HEADLINE_2"
    }
  ],
  "additionalDescriptions": [
    "Build social publishing into your application.",
    {
      "text": "Connect social accounts with one API.",
      "pinnedField": "DESCRIPTION_2"
    }
  ]
}

Response

validateOnly dry-run passed, nothing was created

validateOnlyboolean

Always true in a validate-only response.

messagestring

Changes

    • breaking

      added subschema #1 subschema #2 to the response body oneOf list for the response status

    • info

      removed subschema #1 subschema #2 from the response body oneOf list for the response status

    • breaking

      added subschema #1 subschema #2 to the response body oneOf list for the response status

    • info

      removed subschema #1 subschema #2 from the response body oneOf list for the response status