late-sdk 0.0.899 → 0.0.901

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -8
  3. data/docs/AdAccountsApi.md +1375 -277
  4. data/docs/AdCampaignsApi.md +525 -13
  5. data/docs/AdCreative.md +6 -0
  6. data/docs/AdTracking.md +20 -0
  7. data/docs/AddAccountCalloutsRequest.md +3 -3
  8. data/docs/AddAccountSitelinks201Response.md +20 -0
  9. data/docs/AddAccountSitelinks201ResponseSitelinksInner.md +26 -0
  10. data/docs/AddAccountSitelinksRequest.md +22 -0
  11. data/docs/AddAccountStructuredSnippets201Response.md +20 -0
  12. data/docs/AddAccountStructuredSnippets201ResponseStructuredSnippetsInner.md +22 -0
  13. data/docs/AddAccountStructuredSnippetsRequest.md +22 -0
  14. data/docs/AttachAdGroupAssets201Response.md +24 -0
  15. data/docs/AttachCampaignAssetsRequest.md +4 -4
  16. data/docs/CreateAdCampaign200Response.md +26 -0
  17. data/docs/CreateAdCampaignRequest.md +8 -0
  18. data/docs/CreateCallAdRequest.md +2 -0
  19. data/docs/CreateMessagingAdRequest.md +2 -0
  20. data/docs/CreateStandaloneAdRequest.md +15 -7
  21. data/docs/CreateStandaloneAdRequestAdditionalDescriptionsInner.md +49 -0
  22. data/docs/CreateStandaloneAdRequestAdditionalHeadlinesInner.md +49 -0
  23. data/docs/CtwaAdRequestBody.md +2 -0
  24. data/docs/GetAd200Response.md +5 -1
  25. data/docs/GetAdComments200ResponseMeta.md +10 -4
  26. data/docs/GetIosFourteenCampaignLimits200Response.md +18 -0
  27. data/docs/GetIosFourteenCampaignLimits200ResponseLimits.md +22 -0
  28. data/docs/GoogleAssetUpdate.md +26 -0
  29. data/docs/GoogleRsaDescription.md +20 -0
  30. data/docs/GoogleRsaHeadline.md +20 -0
  31. data/docs/{AttachCampaignAssetsRequestSitelinksInner.md → GoogleSitelink.md} +2 -2
  32. data/docs/{AttachCampaignAssetsRequestStructuredSnippetsInner.md → GoogleStructuredSnippet.md} +2 -2
  33. data/docs/HideAdComment200Response.md +22 -0
  34. data/docs/HideAdCommentRequest.md +18 -0
  35. data/docs/ListAccountCallouts200Response.md +2 -2
  36. data/docs/ListAccountCallouts200ResponseCalloutsInner.md +3 -3
  37. data/docs/ListAccountSitelinks200Response.md +24 -0
  38. data/docs/ListAccountSitelinks200ResponseSitelinksInner.md +32 -0
  39. data/docs/ListAccountStructuredSnippets200Response.md +24 -0
  40. data/docs/ListAccountStructuredSnippets200ResponseStructuredSnippetsInner.md +28 -0
  41. data/docs/ListAdGroupAssets200Response.md +28 -0
  42. data/docs/ListAdGroupAssets200ResponseCalloutsInner.md +22 -0
  43. data/docs/ListAdGroupAssets200ResponseSitelinksInner.md +28 -0
  44. data/docs/ListAdGroupAssets200ResponseStructuredSnippetsInner.md +24 -0
  45. data/docs/ListAdsInstagramAccounts200Response.md +22 -0
  46. data/docs/ListAdsInstagramAccounts200ResponseAccountsInner.md +26 -0
  47. data/docs/ListAdsInstagramAccounts200ResponsePagesInner.md +24 -0
  48. data/docs/ListAdsInstagramAccounts200ResponseResolved.md +22 -0
  49. data/docs/ListAdvertisableApplications200Response.md +18 -0
  50. data/docs/ListAdvertisableApplications200ResponseApplicationsInner.md +24 -0
  51. data/docs/ListCampaignAssets200Response.md +28 -0
  52. data/docs/ListCampaignAssets200ResponseCalloutsInner.md +22 -0
  53. data/docs/ListCampaignAssets200ResponseSitelinksInner.md +28 -0
  54. data/docs/ListCampaignAssets200ResponseStructuredSnippetsInner.md +24 -0
  55. data/docs/MetaInstagramIdentityRef.md +22 -0
  56. data/docs/RemoveAccountCalloutRequest.md +3 -3
  57. data/docs/RemoveAdGroupAssetsRequest.md +24 -0
  58. data/docs/RemoveCampaignAssets200Response.md +18 -0
  59. data/docs/RemoveCampaignAssetsRequest.md +24 -0
  60. data/docs/ReplyToAdComment200Response.md +20 -0
  61. data/docs/ReplyToAdCommentRequest.md +18 -0
  62. data/docs/TargetingSpec.md +4 -0
  63. data/docs/UpdateAccountCallouts200Response.md +20 -0
  64. data/docs/UpdateAccountCalloutsRequest.md +22 -0
  65. data/docs/UpdateAccountCalloutsRequestUpdatesInner.md +20 -0
  66. data/docs/UpdateAccountCalloutsRequestUpdatesInnerCalloutAsset.md +18 -0
  67. data/docs/UpdateAccountSitelinksRequest.md +22 -0
  68. data/docs/UpdateAccountSitelinksRequestUpdatesInner.md +22 -0
  69. data/docs/UpdateAccountSitelinksRequestUpdatesInnerSitelinkAsset.md +24 -0
  70. data/docs/UpdateAccountStructuredSnippetsRequest.md +22 -0
  71. data/docs/UpdateAccountStructuredSnippetsRequestUpdatesInner.md +20 -0
  72. data/docs/UpdateAdRequest.md +6 -0
  73. data/docs/UpdateCampaignAssets200Response.md +18 -0
  74. data/docs/UpdateCampaignAssetsRequest.md +22 -0
  75. data/lib/zernio-sdk/api/ad_accounts_api.rb +1511 -363
  76. data/lib/zernio-sdk/api/ad_campaigns_api.rb +589 -13
  77. data/lib/zernio-sdk/models/ad_creative.rb +112 -1
  78. data/lib/zernio-sdk/models/{create_standalone_ad_request_tracking.rb → ad_tracking.rb} +5 -5
  79. data/lib/zernio-sdk/models/add_account_callouts_request.rb +34 -3
  80. data/lib/zernio-sdk/models/add_account_sitelinks201_response.rb +158 -0
  81. data/lib/zernio-sdk/models/add_account_sitelinks201_response_sitelinks_inner.rb +267 -0
  82. data/lib/zernio-sdk/models/add_account_sitelinks_request.rb +253 -0
  83. data/lib/zernio-sdk/models/add_account_structured_snippets201_response.rb +158 -0
  84. data/lib/zernio-sdk/models/add_account_structured_snippets201_response_structured_snippets_inner.rb +229 -0
  85. data/lib/zernio-sdk/models/add_account_structured_snippets_request.rb +253 -0
  86. data/lib/zernio-sdk/models/attach_ad_group_assets201_response.rb +180 -0
  87. data/lib/zernio-sdk/models/attach_campaign_assets_request.rb +37 -5
  88. data/lib/zernio-sdk/models/create_ad_campaign200_response.rb +231 -0
  89. data/lib/zernio-sdk/models/create_ad_campaign_request.rb +52 -1
  90. data/lib/zernio-sdk/models/create_call_ad_request.rb +10 -1
  91. data/lib/zernio-sdk/models/create_messaging_ad_request.rb +10 -1
  92. data/lib/zernio-sdk/models/create_standalone_ad_request.rb +102 -8
  93. data/lib/zernio-sdk/models/create_standalone_ad_request_additional_descriptions_inner.rb +104 -0
  94. data/lib/zernio-sdk/models/create_standalone_ad_request_additional_headlines_inner.rb +104 -0
  95. data/lib/zernio-sdk/models/ctwa_ad_request_body.rb +10 -1
  96. data/lib/zernio-sdk/models/get_ad200_response.rb +25 -4
  97. data/lib/zernio-sdk/models/get_ad_comments200_response_meta.rb +36 -46
  98. data/lib/zernio-sdk/models/get_ios_fourteen_campaign_limits200_response.rb +164 -0
  99. data/lib/zernio-sdk/models/get_ios_fourteen_campaign_limits200_response_limits.rb +172 -0
  100. data/lib/zernio-sdk/models/google_asset_update.rb +234 -0
  101. data/lib/zernio-sdk/models/google_rsa_description.rb +226 -0
  102. data/lib/zernio-sdk/models/google_rsa_headline.rb +226 -0
  103. data/lib/zernio-sdk/models/{attach_campaign_assets_request_sitelinks_inner.rb → google_sitelink.rb} +3 -3
  104. data/lib/zernio-sdk/models/{attach_campaign_assets_request_structured_snippets_inner.rb → google_structured_snippet.rb} +3 -3
  105. data/lib/zernio-sdk/models/hide_ad_comment200_response.rb +225 -0
  106. data/lib/zernio-sdk/models/hide_ad_comment_request.rb +165 -0
  107. data/lib/zernio-sdk/models/list_account_callouts200_response.rb +2 -2
  108. data/lib/zernio-sdk/models/list_account_callouts200_response_callouts_inner.rb +13 -14
  109. data/lib/zernio-sdk/models/list_account_sitelinks200_response.rb +179 -0
  110. data/lib/zernio-sdk/models/list_account_sitelinks200_response_sitelinks_inner.rb +210 -0
  111. data/lib/zernio-sdk/models/list_account_structured_snippets200_response.rb +179 -0
  112. data/lib/zernio-sdk/models/list_account_structured_snippets200_response_structured_snippets_inner.rb +194 -0
  113. data/lib/zernio-sdk/models/list_ad_group_assets200_response.rb +201 -0
  114. data/lib/zernio-sdk/models/list_ad_group_assets200_response_callouts_inner.rb +165 -0
  115. data/lib/zernio-sdk/models/list_ad_group_assets200_response_sitelinks_inner.rb +192 -0
  116. data/lib/zernio-sdk/models/list_ad_group_assets200_response_structured_snippets_inner.rb +176 -0
  117. data/lib/zernio-sdk/models/list_ads_instagram_accounts200_response.rb +220 -0
  118. data/lib/zernio-sdk/models/list_ads_instagram_accounts200_response_accounts_inner.rb +287 -0
  119. data/lib/zernio-sdk/models/list_ads_instagram_accounts200_response_pages_inner.rb +210 -0
  120. data/lib/zernio-sdk/models/list_ads_instagram_accounts200_response_resolved.rb +211 -0
  121. data/lib/zernio-sdk/models/list_advertisable_applications200_response.rb +166 -0
  122. data/lib/zernio-sdk/models/list_advertisable_applications200_response_applications_inner.rb +250 -0
  123. data/lib/zernio-sdk/models/list_campaign_assets200_response.rb +201 -0
  124. data/lib/zernio-sdk/models/list_campaign_assets200_response_callouts_inner.rb +165 -0
  125. data/lib/zernio-sdk/models/list_campaign_assets200_response_sitelinks_inner.rb +192 -0
  126. data/lib/zernio-sdk/models/list_campaign_assets200_response_structured_snippets_inner.rb +176 -0
  127. data/lib/zernio-sdk/models/meta_instagram_identity_ref.rb +202 -0
  128. data/lib/zernio-sdk/models/remove_account_callout_request.rb +45 -3
  129. data/lib/zernio-sdk/models/remove_ad_group_assets_request.rb +281 -0
  130. data/lib/zernio-sdk/models/remove_campaign_assets200_response.rb +147 -0
  131. data/lib/zernio-sdk/models/remove_campaign_assets_request.rb +281 -0
  132. data/lib/zernio-sdk/models/reply_to_ad_comment200_response.rb +215 -0
  133. data/lib/zernio-sdk/models/reply_to_ad_comment_request.rb +174 -0
  134. data/lib/zernio-sdk/models/targeting_spec.rb +63 -1
  135. data/lib/zernio-sdk/models/update_account_callouts200_response.rb +156 -0
  136. data/lib/zernio-sdk/models/update_account_callouts_request.rb +253 -0
  137. data/lib/zernio-sdk/models/update_account_callouts_request_updates_inner.rb +186 -0
  138. data/lib/zernio-sdk/models/update_account_callouts_request_updates_inner_callout_asset.rb +182 -0
  139. data/lib/zernio-sdk/models/update_account_sitelinks_request.rb +253 -0
  140. data/lib/zernio-sdk/models/update_account_sitelinks_request_updates_inner.rb +216 -0
  141. data/lib/zernio-sdk/models/update_account_sitelinks_request_updates_inner_sitelink_asset.rb +241 -0
  142. data/lib/zernio-sdk/models/update_account_structured_snippets_request.rb +253 -0
  143. data/lib/zernio-sdk/models/update_account_structured_snippets_request_updates_inner.rb +186 -0
  144. data/lib/zernio-sdk/models/update_ad_request.rb +112 -1
  145. data/lib/zernio-sdk/models/update_campaign_assets200_response.rb +147 -0
  146. data/lib/zernio-sdk/models/update_campaign_assets_request.rb +253 -0
  147. data/lib/zernio-sdk/version.rb +1 -1
  148. data/lib/zernio-sdk.rb +55 -4
  149. data/openapi.yaml +2898 -733
  150. data/spec/api/ad_accounts_api_spec.rb +209 -10
  151. data/spec/api/ad_campaigns_api_spec.rb +99 -6
  152. data/spec/models/ad_creative_spec.rb +18 -0
  153. data/spec/models/{create_standalone_ad_request_tracking_spec.rb → ad_tracking_spec.rb} +6 -6
  154. data/spec/models/add_account_sitelinks201_response_sitelinks_inner_spec.rb +60 -0
  155. data/spec/models/add_account_sitelinks201_response_spec.rb +42 -0
  156. data/spec/models/add_account_sitelinks_request_spec.rb +48 -0
  157. data/spec/models/add_account_structured_snippets201_response_spec.rb +42 -0
  158. data/spec/models/add_account_structured_snippets201_response_structured_snippets_inner_spec.rb +52 -0
  159. data/spec/models/add_account_structured_snippets_request_spec.rb +48 -0
  160. data/spec/models/attach_ad_group_assets201_response_spec.rb +54 -0
  161. data/spec/models/create_ad_campaign200_response_spec.rb +68 -0
  162. data/spec/models/create_ad_campaign_request_spec.rb +28 -0
  163. data/spec/models/create_call_ad_request_spec.rb +6 -0
  164. data/spec/models/create_messaging_ad_request_spec.rb +6 -0
  165. data/spec/models/create_standalone_ad_request_additional_descriptions_inner_spec.rb +32 -0
  166. data/spec/models/create_standalone_ad_request_additional_headlines_inner_spec.rb +32 -0
  167. data/spec/models/create_standalone_ad_request_spec.rb +28 -0
  168. data/spec/models/ctwa_ad_request_body_spec.rb +6 -0
  169. data/spec/models/get_ad200_response_spec.rb +12 -0
  170. data/spec/models/get_ad_comments200_response_meta_spec.rb +19 -1
  171. data/spec/models/get_ios_fourteen_campaign_limits200_response_limits_spec.rb +48 -0
  172. data/spec/models/get_ios_fourteen_campaign_limits200_response_spec.rb +36 -0
  173. data/spec/models/google_asset_update_spec.rb +60 -0
  174. data/spec/models/google_rsa_description_spec.rb +46 -0
  175. data/spec/models/google_rsa_headline_spec.rb +46 -0
  176. data/spec/models/{attach_campaign_assets_request_sitelinks_inner_spec.rb → google_sitelink_spec.rb} +6 -6
  177. data/spec/models/{attach_campaign_assets_request_structured_snippets_inner_spec.rb → google_structured_snippet_spec.rb} +6 -6
  178. data/spec/models/hide_ad_comment200_response_spec.rb +52 -0
  179. data/spec/models/hide_ad_comment_request_spec.rb +36 -0
  180. data/spec/models/list_account_callouts200_response_callouts_inner_spec.rb +2 -2
  181. data/spec/models/{create_standalone_ad_request_promoted_object_spec.rb → list_account_sitelinks200_response_sitelinks_inner_spec.rb} +14 -32
  182. data/spec/models/list_account_sitelinks200_response_spec.rb +54 -0
  183. data/spec/models/list_account_structured_snippets200_response_spec.rb +54 -0
  184. data/spec/models/list_account_structured_snippets200_response_structured_snippets_inner_spec.rb +66 -0
  185. data/spec/models/list_ad_group_assets200_response_callouts_inner_spec.rb +48 -0
  186. data/spec/models/list_ad_group_assets200_response_sitelinks_inner_spec.rb +66 -0
  187. data/spec/models/list_ad_group_assets200_response_spec.rb +66 -0
  188. data/spec/models/list_ad_group_assets200_response_structured_snippets_inner_spec.rb +54 -0
  189. data/spec/models/list_ads_instagram_accounts200_response_accounts_inner_spec.rb +64 -0
  190. data/spec/models/list_ads_instagram_accounts200_response_pages_inner_spec.rb +54 -0
  191. data/spec/models/list_ads_instagram_accounts200_response_resolved_spec.rb +52 -0
  192. data/spec/models/list_ads_instagram_accounts200_response_spec.rb +48 -0
  193. data/spec/models/list_advertisable_applications200_response_applications_inner_spec.rb +54 -0
  194. data/spec/models/list_advertisable_applications200_response_spec.rb +36 -0
  195. data/spec/models/list_campaign_assets200_response_callouts_inner_spec.rb +48 -0
  196. data/spec/models/list_campaign_assets200_response_sitelinks_inner_spec.rb +66 -0
  197. data/spec/models/list_campaign_assets200_response_spec.rb +66 -0
  198. data/spec/models/list_campaign_assets200_response_structured_snippets_inner_spec.rb +54 -0
  199. data/spec/models/meta_instagram_identity_ref_spec.rb +48 -0
  200. data/spec/models/remove_ad_group_assets_request_spec.rb +54 -0
  201. data/spec/models/remove_campaign_assets200_response_spec.rb +36 -0
  202. data/spec/models/remove_campaign_assets_request_spec.rb +54 -0
  203. data/spec/models/reply_to_ad_comment200_response_spec.rb +46 -0
  204. data/spec/models/reply_to_ad_comment_request_spec.rb +36 -0
  205. data/spec/models/targeting_spec_spec.rb +12 -0
  206. data/spec/models/update_account_callouts200_response_spec.rb +42 -0
  207. data/spec/models/update_account_callouts_request_spec.rb +48 -0
  208. data/spec/models/update_account_callouts_request_updates_inner_callout_asset_spec.rb +36 -0
  209. data/spec/models/update_account_callouts_request_updates_inner_spec.rb +42 -0
  210. data/spec/models/update_account_sitelinks_request_spec.rb +48 -0
  211. data/spec/models/update_account_sitelinks_request_updates_inner_sitelink_asset_spec.rb +54 -0
  212. data/spec/models/update_account_sitelinks_request_updates_inner_spec.rb +48 -0
  213. data/spec/models/update_account_structured_snippets_request_spec.rb +48 -0
  214. data/spec/models/update_account_structured_snippets_request_updates_inner_spec.rb +42 -0
  215. data/spec/models/update_ad_request_spec.rb +18 -0
  216. data/spec/models/update_campaign_assets200_response_spec.rb +36 -0
  217. data/spec/models/update_campaign_assets_request_spec.rb +48 -0
  218. data/zernio-sdk-0.0.901.gem +0 -0
  219. metadata +222 -18
  220. data/docs/CreateStandaloneAdRequestPromotedObject.md +0 -38
  221. data/docs/CreateStandaloneAdRequestTracking.md +0 -20
  222. data/lib/zernio-sdk/models/create_standalone_ad_request_promoted_object.rb +0 -249
  223. data/zernio-sdk-0.0.899.gem +0 -0
data/openapi.yaml CHANGED
@@ -804,6 +804,27 @@ components:
804
804
  effective_account_limit: 2000
805
805
  current_account_count: 2000
806
806
  schemas:
807
+ AdTracking:
808
+ type: object
809
+ description: "Meta only. Attaches pixel measurement to the ad regardless of the optimization goal (the \"Website events\" tracking row in Ads Manager). `pixelId` becomes the ad's `tracking_specs` (offsite_conversion + fb_pixel); `urlTags` is stored on the new creative as `url_tags` and retained on the ad for compatibility. Applied on the legacy single-creative shape, every ad of the multi-creative shape, and the attach shape. NOTE: tracking lives on the AD object and is not inherited from the ad set, so pass it on EVERY attach call that should carry the pixel."
810
+ properties:
811
+ pixelId: { type: string, description: "Meta Pixel ID to attach for offsite-conversion measurement." }
812
+ urlTags:
813
+ type: array
814
+ description: "Click-URL params stored on the creative as `url_tags` and returned by GET /v1/ads/{adId}/tracking-tags. App-promotion linkUrl stays byte-identical to promotedObject.objectStoreUrl. Meta dynamic macros ({{ad.id}}, {{campaign.id}}, {{placement}}, ...) are sent through unescaped so Meta expands them; every other character is percent-encoded."
815
+ items:
816
+ type: object
817
+ required: [key, value]
818
+ properties:
819
+ key: { type: string }
820
+ value: { type: string }
821
+ MetaInstagramIdentityRef:
822
+ type: object
823
+ required: [igUserId, username]
824
+ properties:
825
+ igUserId: { type: string, description: "Instagram identity ID." }
826
+ username: { type: string, description: "Instagram username; empty when Meta does not expose it." }
827
+ profilePictureUrl: { type: string, description: "Profile picture URL when available." }
807
828
  GoogleBusinessReview:
808
829
  type: object
809
830
  description: A Google Business Profile review, as returned by every gmb-reviews read endpoint.
@@ -889,6 +910,7 @@ components:
889
910
  creativeFeatures:
890
911
  $ref: '#/components/schemas/MetaCreativeFeatures'
891
912
  description: 'Meta enhancement settings for single or attached ads, and defaults for creatives[]. An item replaces the entire map, including with an empty object.'
913
+ tracking: { $ref: '#/components/schemas/AdTracking' }
892
914
  accountId:
893
915
  type: string
894
916
  minLength: 1
@@ -8812,6 +8834,114 @@ components:
8812
8834
  properties:
8813
8835
  amount: { type: number }
8814
8836
  type: { type: string, enum: [daily, lifetime] }
8837
+ AdPromotedObject:
8838
+ type: object
8839
+ description: |
8840
+ What the ad optimises against. Behaviour depends on the platform.
8841
+
8842
+ **Meta**: forwarded to the ad set's `promoted_object` (snake-cased).
8843
+ For `goal: app_promotion`, it is also sent on the campaign only when
8844
+ `isSkadnetworkAttribution: true`. Plain Android app installs keep the
8845
+ existing campaign payload, with the promoted object only on the ad set.
8846
+ POST /v1/ads/campaigns forwards this object only for that explicit SKAN flag.
8847
+ Required for goals whose ad-set optimization_goal points at a specific
8848
+ event/page/app (without it Meta rejects the ad-set create with
8849
+ `error_subcode: 1815430` "Please select a promoted object for your ad set"):
8850
+ - `goal: conversions` / `lead_conversion` (OFFSITE_CONVERSIONS): requires `pixelId` + `customEventType`, or `customConversionId` when optimising against a Custom Conversion (the conversion carries its own event definition). For a pixel CUSTOM event (one you named yourself in CAPI/Events Manager), send `customEventType: OTHER` + `customEventStr` with the event name.
8851
+ - `goal: app_promotion` (APP_INSTALLS): requires `applicationId` + `objectStoreUrl`
8852
+ - `goal: lead_generation` (LEAD_GENERATION): `pageId` is auto-filled from the connected Page when omitted
8853
+
8854
+ Other Meta goals (engagement, traffic, awareness, video_views) ignore this field.
8855
+
8856
+ **TikTok**: used by `goal: conversions` and the Smart+ goals (`smartPlus: true`).
8857
+ - `pixelId` maps to the ad group's `pixel_id`. Required: a TikTok website-conversion
8858
+ ad group without a pixel is rejected with `40002: Please select a pixel`.
8859
+ - `customEventType` maps to the ad group's `optimization_event` (the pixel event to
8860
+ optimise for). Optional on the regular conversions flow, required on Smart+.
8861
+ See the `customEventType` field below for the valid TikTok codes.
8862
+ - `applicationId` (Smart+ `goal: app_promotion` only) maps to the ad group's `app_id`:
8863
+ the App ID of an app registered on the TikTok Ads account (Assets → Events →
8864
+ App Events). Install optimization needs the app's MMP tracking configured.
8865
+
8866
+ The remaining `promotedObject.*` fields are Meta-only. Platforms other than
8867
+ Meta and TikTok ignore `promotedObject` entirely.
8868
+ properties:
8869
+ pixelId:
8870
+ type: string
8871
+ description: |
8872
+ Pixel ID. **Meta:** Facebook Pixel ID, required for `goal: conversions`.
8873
+ Requires `customEventType` alongside it; Meta rejects any promoted_object
8874
+ carrying `pixel_id` without `custom_event_type` (error_subcode 1885014),
8875
+ even when `customConversionId` is also present.
8876
+ **TikTok:** TikTok Pixel ID, required for `goal: conversions`.
8877
+ To discover the pixels an ad account can use, call
8878
+ `GET /v1/accounts/{accountId}/tracking-tags?adAccountId=act_...` (each entry
8879
+ carries `kind` and `ownerAdAccountId`), or
8880
+ `GET /v1/accounts/{accountId}/conversion-destinations`. Note this is a
8881
+ different resource from `GET /v1/ads/{adId}/tracking-tags`, which reads an
8882
+ ad's click-URL params (`url_tags`), not pixels.
8883
+ customEventType:
8884
+ type: string
8885
+ description: |
8886
+ The event the campaign/ad group optimises against.
8887
+
8888
+ **Meta:** standard event like `PURCHASE`, `LEAD`, `COMPLETE_REGISTRATION`,
8889
+ `ADD_TO_CART`. Uppercased internally so callers can pass any case. Required
8890
+ for `goal: conversions`.
8891
+
8892
+ **TikTok:** an `optimization_event` code (UPPER_SNAKE, not Meta's vocabulary
8893
+ and not PascalCase), OR the exact event name shown in TikTok Events Manager
8894
+ (auto-resolved to its code). Must be one of the event types your TikTok
8895
+ Pixel tracks; custom events are not optimizable. Current taxonomy:
8896
+ `SHOPPING` (Purchase), `ON_WEB_CART` (Add to Cart), `INITIATE_ORDER`
8897
+ (Initiate Checkout), `FORM` (Lead), `ON_WEB_REGISTER` (Complete
8898
+ Registration), `ON_WEB_DETAIL` (View Content). `ON_WEB_ORDER` is
8899
+ deprecated. On rejection the error lists the event types your pixel
8900
+ actually tracks. Optional for `goal: conversions`.
8901
+ customEventStr:
8902
+ type: string
8903
+ description: |
8904
+ Meta only. Pixel custom-event name to optimise against (Meta's
8905
+ `custom_event_str`), exactly as it appears in Events Manager and in your
8906
+ CAPI payloads (case-sensitive, not uppercased). Requires
8907
+ `customEventType: OTHER`, and `OTHER` requires this field (400 either way).
8908
+ The same as picking a custom event in Ads Manager's conversion-event
8909
+ dropdown. For rule-based Custom Conversions use `customConversionId`
8910
+ instead.
8911
+ pageId:
8912
+ type: string
8913
+ description: |
8914
+ Facebook Page ID. Used by `goal: lead_generation`. Auto-filled from the
8915
+ connected Page when omitted.
8916
+ applicationId:
8917
+ type: string
8918
+ description: "App ID. Required for `goal: app_promotion`."
8919
+ objectStoreUrl:
8920
+ type: string
8921
+ format: uri
8922
+ description: "App Store / Play Store listing URL. Required for `goal: app_promotion`."
8923
+ customConversionId:
8924
+ type: string
8925
+ description: |
8926
+ Custom Conversion ID, when optimising against one instead of a standard
8927
+ event. Accepted alone by this API, without `pixelId` or `customEventType`.
8928
+ If `pixelId` is also sent, `customEventType` is still required on the
8929
+ promoted_object (Meta rejects `pixel_id` without `custom_event_type`,
8930
+ error_subcode 1885014).
8931
+ productCatalogId:
8932
+ type: string
8933
+ description: "Optional catalog ID. If supplied with productSetId, the set must belong to this catalog. A catalog ID cannot replace productSetId."
8934
+ productSetId:
8935
+ type: string
8936
+ description: "Meta product SET ID from GET /v1/ads/catalogs/{catalogId}/product-sets. Zernio checks that the token can read the set and its product_catalog before creation. A catalog ID or inaccessible set returns a precise 400 naming promotedObject.productSetId. A mismatch with productCatalogId names promotedObject.productCatalogId."
8937
+ offlineConversionDataSetId:
8938
+ type: string
8939
+ description: 'Meta only. Offline event set (dataset) to optimise toward. Post-merger these are datasets: the id is the dataset id (for pixel-backed datasets, the pixel id).'
8940
+ whatsappPhoneNumber:
8941
+ type: string
8942
+ description: 'Meta only. WhatsApp number on messaging-destination ad sets.'
8943
+ additionalProperties: false
8944
+
8815
8945
  TargetingSpec:
8816
8946
  type: object
8817
8947
  description: |
@@ -8829,6 +8959,16 @@ components:
8829
8959
  platforms. Fields a platform cannot honour are rejected at create time with
8830
8960
  `INVALID_FIELD_VALUE` naming the offending field (not silently dropped).
8831
8961
  properties:
8962
+ userOs:
8963
+ type: array
8964
+ minItems: 1
8965
+ items: { type: string, minLength: 1 }
8966
+ description: '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.'
8967
+ userDevice:
8968
+ type: array
8969
+ minItems: 1
8970
+ items: { type: string, minLength: 1 }
8971
+ description: 'Meta only. Device models such as iPhone. Emitted as user_device. May also be supplied inside targeting.'
8832
8972
  countries: { type: array, items: { type: string }, description: "ISO 3166-1 alpha-2 country codes (e.g. ['US'])." }
8833
8973
  regions:
8834
8974
  type: array
@@ -9035,6 +9175,135 @@ components:
9035
9175
  enum: [applied, not_returned, unavailable]
9036
9176
  description: 'Meta creative readback result. applied means Meta returned promotion metadata; not_returned means the read succeeded without promotion metadata; unavailable means the read failed. Only applied confirms the returned offer. Missing metadata is not proof that Ads Manager displays the requested Promotion.'
9037
9177
  example: not_returned
9178
+ GoogleSitelink:
9179
+ type: object
9180
+ required:
9181
+ - text
9182
+ - linkUrl
9183
+ properties:
9184
+ text:
9185
+ type: string
9186
+ minLength: 1
9187
+ maxLength: 25
9188
+ linkUrl:
9189
+ type: string
9190
+ format: uri
9191
+ description1:
9192
+ type: string
9193
+ minLength: 1
9194
+ maxLength: 35
9195
+ description2:
9196
+ type: string
9197
+ minLength: 1
9198
+ maxLength: 35
9199
+ GoogleStructuredSnippet:
9200
+ type: object
9201
+ required:
9202
+ - header
9203
+ - values
9204
+ properties:
9205
+ header:
9206
+ type: string
9207
+ enum:
9208
+ - Amenities
9209
+ - Brands
9210
+ - Courses
9211
+ - Degree programs
9212
+ - Destinations
9213
+ - Featured hotels
9214
+ - Insurance coverage
9215
+ - Models
9216
+ - Neighborhoods
9217
+ - Service catalog
9218
+ - Shows
9219
+ - Styles
9220
+ - Types
9221
+ values:
9222
+ type: array
9223
+ minItems: 3
9224
+ maxItems: 10
9225
+ items:
9226
+ type: string
9227
+ minLength: 1
9228
+ maxLength: 25
9229
+ GoogleAssetUpdate:
9230
+ type: object
9231
+ required:
9232
+ - assetResourceName
9233
+ properties:
9234
+ assetResourceName:
9235
+ type: string
9236
+ pattern: ^customers/\d+/assets/\d+$
9237
+ description: "Asset resource name returned by a list operation. Must belong to the selected customer."
9238
+ sitelinkAsset:
9239
+ type: object
9240
+ properties:
9241
+ linkText:
9242
+ type: string
9243
+ minLength: 1
9244
+ maxLength: 25
9245
+ description1:
9246
+ type: string
9247
+ maxLength: 35
9248
+ description2:
9249
+ type: string
9250
+ maxLength: 35
9251
+ linkUrl:
9252
+ type: string
9253
+ format: uri
9254
+ description: "Alias for finalUrls with one URL. Do not supply both."
9255
+ minProperties: 1
9256
+ finalUrls:
9257
+ type: array
9258
+ items:
9259
+ type: string
9260
+ format: uri
9261
+ minItems: 1
9262
+ calloutAsset:
9263
+ type: object
9264
+ required:
9265
+ - calloutText
9266
+ properties:
9267
+ calloutText:
9268
+ type: string
9269
+ minLength: 1
9270
+ maxLength: 25
9271
+ structuredSnippetAsset:
9272
+ $ref: '#/components/schemas/GoogleStructuredSnippet'
9273
+ description: "Supply fields for exactly one asset type per update. finalUrls may accompany sitelinkAsset. Shared asset edits affect every attachment using the asset."
9274
+
9275
+ GoogleRsaHeadline:
9276
+ type: object
9277
+ required:
9278
+ - text
9279
+ properties:
9280
+ text:
9281
+ type: string
9282
+ minLength: 1
9283
+ maxLength: 30
9284
+ pinnedField:
9285
+ type: string
9286
+ enum:
9287
+ - HEADLINE_1
9288
+ - HEADLINE_2
9289
+ - HEADLINE_3
9290
+ description: "Optional fixed headline position. Omit to leave the asset unpinned."
9291
+ GoogleRsaDescription:
9292
+ type: object
9293
+ required:
9294
+ - text
9295
+ properties:
9296
+ text:
9297
+ type: string
9298
+ minLength: 1
9299
+ maxLength: 90
9300
+ pinnedField:
9301
+ type: string
9302
+ enum:
9303
+ - DESCRIPTION_1
9304
+ - DESCRIPTION_2
9305
+ description: "Optional fixed description position. Omit to leave the asset unpinned."
9306
+
9038
9307
  Ad:
9039
9308
  type: object
9040
9309
  properties:
@@ -9191,6 +9460,27 @@ components:
9191
9460
  type: [object, "null"]
9192
9461
  description: Platform-specific creative data. Fields vary by platform.
9193
9462
  properties:
9463
+ headlines:
9464
+ type: array
9465
+ minItems: 3
9466
+ maxItems: 15
9467
+ items:
9468
+ $ref: '#/components/schemas/GoogleRsaHeadline'
9469
+ description: "Google RSA only. Replaces the complete headline list. No padding or truncation on update."
9470
+ descriptions:
9471
+ type: array
9472
+ minItems: 2
9473
+ maxItems: 4
9474
+ items:
9475
+ $ref: '#/components/schemas/GoogleRsaDescription'
9476
+ description: "Google RSA only. Replaces the complete description list. No padding or truncation on update."
9477
+ finalUrls:
9478
+ type: array
9479
+ minItems: 1
9480
+ items:
9481
+ type: string
9482
+ format: uri
9483
+ description: "Google RSA only. Replaces final URLs. Omitted lists stay unchanged."
9194
9484
  thumbnailUrl: { type: [string, "null"], description: Primary thumbnail/image URL }
9195
9485
  imageUrl: { type: string, description: Alternative image URL }
9196
9486
  videoId: { type: [string, "null"], description: "Meta video ID for VIDEO-type ads. Null for non-video ads. Callers that need an embeddable MP4 can call GET /{videoId}?fields=source with the page access token." }
@@ -42919,6 +43209,18 @@ paths:
42919
43209
  type: string
42920
43210
  enum: [engagement, traffic, awareness, video_views, lead_generation, lead_conversion, job_applicants, conversions, app_promotion, catalog_sales, page_likes]
42921
43211
  description: Mapped to the ODAX objective (same mapping as POST /v1/ads/create).
43212
+ isSkadnetworkAttribution:
43213
+ type: boolean
43214
+ description: '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.'
43215
+ promotedObject:
43216
+ $ref: '#/components/schemas/AdPromotedObject'
43217
+ buyingType:
43218
+ type: string
43219
+ enum: [AUCTION, RESERVED]
43220
+ description: 'Meta only. SKAdNetwork app promotion requires AUCTION.'
43221
+ validateOnly:
43222
+ type: boolean
43223
+ description: 'Meta only. Runs campaign validation without creating or persisting a campaign; Idempotency-Key storage is bypassed. Returns HTTP 200 with validateOnly true and status VALIDATED.'
42922
43224
  specialAdCategories:
42923
43225
  type: array
42924
43226
  items: { type: string, enum: [HOUSING, EMPLOYMENT, CREDIT, ISSUES_ELECTIONS_POLITICS, FINANCIAL_PRODUCTS_SERVICES, ONLINE_GAMBLING_AND_GAMING] }
@@ -42932,7 +43234,30 @@ paths:
42932
43234
  bidAmount: { type: number, description: "Whole currency units (USD: 5 = $5.00). Required for LOWEST_COST_WITH_BID_CAP and COST_CAP; ignored otherwise. On Meta, validated here but NOT stored: the campaign object has no bid_amount field, only bid_strategy lives on it, and the amount takes effect once an ad set joins this campaign (existingCampaignId on POST /v1/ads/create) and supplies its own bidAmount there. On Google, stored directly on the campaign's bidding strategy." }
42933
43235
  roasAverageFloor: { type: number, description: "Decimal ROAS multiplier (2.0 = 2.0x). Required for LOWEST_COST_WITH_MIN_ROAS." }
42934
43236
  portfolioBidStrategyId: { type: string, pattern: '^\d+$', description: "Google only. 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." }
43237
+ example:
43238
+ accountId: '69fc524892b3d8e85f893e73'
43239
+ adAccountId: 'act_757082720485182'
43240
+ name: 'iOS app campaign'
43241
+ goal: app_promotion
43242
+ isSkadnetworkAttribution: true
43243
+ promotedObject: { applicationId: '123456789', objectStoreUrl: 'https://apps.apple.com/us/app/id123456789' }
43244
+ buyingType: AUCTION
43245
+ status: PAUSED
43246
+ validateOnly: true
42935
43247
  responses:
43248
+ '200':
43249
+ description: 'Campaign validation passed without creating a campaign.'
43250
+ content:
43251
+ application/json:
43252
+ schema:
43253
+ type: object
43254
+ properties:
43255
+ validateOnly: { type: boolean, description: 'Always true.' }
43256
+ adAccountId: { type: string }
43257
+ campaignId: { type: string, const: '', description: 'Empty because no campaign was created.' }
43258
+ objective: { type: string }
43259
+ status: { type: string, const: VALIDATED }
43260
+ example: { validateOnly: true, adAccountId: 'act_757082720485182', campaignId: '', objective: OUTCOME_APP_PROMOTION, status: VALIDATED }
42936
43261
  '201':
42937
43262
  description: Campaign created
42938
43263
  content:
@@ -44316,6 +44641,11 @@ paths:
44316
44641
  summary: Get ad details
44317
44642
  description: |
44318
44643
  Returns an ad with its creative, targeting, status, and performance metrics.
44644
+ Google Search ads include current creative.headlines, creative.descriptions and creative.finalUrls,
44645
+ preserving pinnedField. Top-level cachedAt and stale report cache freshness. Google mutations invalidate this read.
44646
+ RSA enrichment requires a stored advertisingChannelType of SEARCH. Ads with an unknown or other channel
44647
+ return their stored details without a Google read. If RSA enrichment fails, the stored ad is returned
44648
+ with HTTP 200 and without cache metadata.
44319
44649
 
44320
44650
  The `{adId}` path segment accepts any identifier dialect Zernio indexes for the ad:
44321
44651
  - the Zernio internal `_id` (24-char hex)
@@ -44348,10 +44678,26 @@ paths:
44348
44678
  description: Ad details
44349
44679
  content:
44350
44680
  application/json:
44681
+ example:
44682
+ ad:
44683
+ platform: google
44684
+ creative:
44685
+ headlines:
44686
+ - { text: "Social Media API", pinnedField: HEADLINE_1 }
44687
+ - { text: "Schedule Your Posts" }
44688
+ - { text: "Build With Zernio" }
44689
+ descriptions:
44690
+ - { text: "Connect social accounts with one API.", pinnedField: DESCRIPTION_1 }
44691
+ - { text: "Build social publishing into your application." }
44692
+ finalUrls: ["https://zernio.com"]
44693
+ cachedAt: null
44694
+ stale: false
44351
44695
  schema:
44352
44696
  type: object
44353
44697
  properties:
44354
44698
  ad: { $ref: '#/components/schemas/Ad' }
44699
+ cachedAt: { type: [string, "null"], format: date-time, description: "Google RSA details cache timestamp." }
44700
+ stale: { type: boolean, description: "Whether Google RSA details use the last successful cached response." }
44355
44701
  '400': { $ref: '#/components/responses/BadRequest' }
44356
44702
  '401': { $ref: '#/components/responses/Unauthorized' }
44357
44703
  '404': { $ref: '#/components/responses/NotFound' }
@@ -44374,7 +44720,11 @@ paths:
44374
44720
  Each list you send becomes the FULL new set of its kind (criteria not in the
44375
44721
  list are removed); a kind left out is untouched. Any other `targeting` field
44376
44722
  returns 400: Google cannot mutate broad targeting post-create without recreating
44377
- the campaign. `creative` returns 501.
44723
+ the campaign. RSA text updates use top-level `headlines`, `descriptions` and `finalUrls`.
44724
+ Each supplied array replaces the full list; omit a field to preserve it. Use 3-15 headlines
44725
+ (1-30 characters) and 2-4 descriptions (1-90 characters). Omit an asset to remove it;
44726
+ omit pinnedField on an included asset to unpin it. Updates do not pad or truncate text.
44727
+ The legacy creative fields remain unsupported for Google.
44378
44728
  - **LinkedIn**: status, budget, targeting (countries or regions, excludedLocations (countries),
44379
44729
  the B2B facets, and audience segments; applied to the LinkedIn Campaign via
44380
44730
  PARTIAL_UPDATE, and REPLACES the campaign's entire targetingCriteria, not a merge),
@@ -44417,9 +44767,51 @@ paths:
44417
44767
  required: true
44418
44768
  content:
44419
44769
  application/json:
44770
+ examples:
44771
+ googleRsa:
44772
+ summary: "Replace and pin Google RSA text."
44773
+ value:
44774
+ headlines:
44775
+ - text: Social Media API
44776
+ pinnedField: HEADLINE_1
44777
+ - text: Schedule Your Posts
44778
+ - text: Build With Zernio
44779
+ descriptions:
44780
+ - text: Connect social accounts and schedule posts with the Zernio API.
44781
+ pinnedField: DESCRIPTION_1
44782
+ - text: Build social publishing into your application.
44783
+ finalUrls:
44784
+ - https://zernio.com
44785
+ metaPromotion:
44786
+ summary: "Remove a Meta promotion."
44787
+ value:
44788
+ creative:
44789
+ promotion: null
44790
+ creativeFeatures: { auto_promotion_tag: OPT_OUT }
44420
44791
  schema:
44421
44792
  type: object
44422
44793
  properties:
44794
+ headlines:
44795
+ type: array
44796
+ minItems: 3
44797
+ maxItems: 15
44798
+ items:
44799
+ $ref: '#/components/schemas/GoogleRsaHeadline'
44800
+ description: "Google RSA only. Replaces the complete headline list. No padding or truncation on update."
44801
+ descriptions:
44802
+ type: array
44803
+ minItems: 2
44804
+ maxItems: 4
44805
+ items:
44806
+ $ref: '#/components/schemas/GoogleRsaDescription'
44807
+ description: "Google RSA only. Replaces the complete description list. No padding or truncation on update."
44808
+ finalUrls:
44809
+ type: array
44810
+ minItems: 1
44811
+ items:
44812
+ type: string
44813
+ format: uri
44814
+ description: "Google RSA only. Replaces final URLs. Omitted lists stay unchanged."
44423
44815
  status: { type: string, enum: [active, paused] }
44424
44816
  budget:
44425
44817
  type: object
@@ -44527,10 +44919,6 @@ paths:
44527
44919
  videoId: { type: string, description: "Meta only. Reuse an already-uploaded ad video (from POST /v1/ads/videos or GET /v1/ads/videos) instead of re-uploading via videoUrl." }
44528
44920
  existingCreativeId: { type: string, description: "Meta only. Repoint the ad at an existing library creative (from GET /v1/ads/creatives); all other creative fields are ignored." }
44529
44921
  name: { type: string, maxLength: 255, description: "Rename the ad. Now propagated to Meta (POST /{ad-id}); non-Meta platforms return 501." }
44530
- example:
44531
- creative:
44532
- promotion: null
44533
- creativeFeatures: { auto_promotion_tag: OPT_OUT }
44534
44922
  responses:
44535
44923
  '200':
44536
44924
  description: Ad updated
@@ -44621,302 +45009,957 @@ paths:
44621
45009
  '404': { description: Ad not found }
44622
45010
 
44623
45011
  /v1/ads/campaigns/{campaignId}/assets:
45012
+ get:
45013
+ operationId: listCampaignAssets
45014
+ summary: List campaign assets
45015
+ x-resource-group: ads
45016
+ tags:
45017
+ - Ad Campaigns
45018
+ x-platforms:
45019
+ - google
45020
+ description: "Lists directly attached Google assets. Fresh reads are cached for 10 minutes; exhausted quota\
45021
+ \ may return the last successful read with stale=true. Inherited assets are not included."
45022
+ security:
45023
+ - bearerAuth: []
45024
+ parameters:
45025
+ - name: campaignId
45026
+ in: path
45027
+ required: true
45028
+ schema:
45029
+ type: string
45030
+ pattern: ^\d+$
45031
+ description: "Numeric Google platform id."
45032
+ - name: accountId
45033
+ in: query
45034
+ required: true
45035
+ schema:
45036
+ type: string
45037
+ pattern: ^[a-fA-F0-9]{24}$
45038
+ description: "Zernio Google Ads connection id."
45039
+ - name: customerId
45040
+ in: query
45041
+ schema:
45042
+ type: string
45043
+ pattern: ^\d+$
45044
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
45045
+ responses:
45046
+ '200':
45047
+ description: "Assets returned."
45048
+ content:
45049
+ application/json:
45050
+ schema:
45051
+ type: object
45052
+ properties:
45053
+ campaignId:
45054
+ type: string
45055
+ sitelinks:
45056
+ type: array
45057
+ items:
45058
+ type: object
45059
+ properties:
45060
+ assetResourceName:
45061
+ type: string
45062
+ campaignAssetResourceName:
45063
+ type: string
45064
+ text:
45065
+ type: string
45066
+ linkUrl:
45067
+ type: string
45068
+ format: uri
45069
+ description1:
45070
+ type: string
45071
+ description2:
45072
+ type: string
45073
+ callouts:
45074
+ type: array
45075
+ items:
45076
+ type: object
45077
+ properties:
45078
+ assetResourceName:
45079
+ type: string
45080
+ campaignAssetResourceName:
45081
+ type: string
45082
+ calloutText:
45083
+ type: string
45084
+ structuredSnippets:
45085
+ type: array
45086
+ items:
45087
+ type: object
45088
+ properties:
45089
+ assetResourceName:
45090
+ type: string
45091
+ campaignAssetResourceName:
45092
+ type: string
45093
+ header:
45094
+ type: string
45095
+ values:
45096
+ type: array
45097
+ items:
45098
+ type: string
45099
+ cachedAt:
45100
+ type:
45101
+ - string
45102
+ - 'null'
45103
+ format: date-time
45104
+ description: "Time of the cached Google read. Null when no cache was used."
45105
+ stale:
45106
+ type: boolean
45107
+ description: "True when exhausted quota required returning the last successful read."
45108
+ '400':
45109
+ $ref: '#/components/responses/BadRequest'
45110
+ '401':
45111
+ $ref: '#/components/responses/Unauthorized'
45112
+ '403':
45113
+ description: "Ads access is required."
45114
+ '404':
45115
+ $ref: '#/components/responses/NotFound'
45116
+ '429':
45117
+ description: "Google Ads operations budget or platform quota exhausted."
45118
+ '501':
45119
+ description: "Only supported on Google Ads."
44624
45120
  post:
44625
- x-resource-group: "ads"
44626
45121
  operationId: attachCampaignAssets
44627
- tags: ["Ad Campaigns"]
44628
- x-platforms: ["google"]
44629
- summary: Attach extension assets to a Google Search campaign
44630
- description: |-
44631
- Attach sitelinks, callouts and/or structured snippets to an already-existing Google
44632
- Search campaign. These are the same builders POST /v1/ads/create uses, but without rebuilding
44633
- the hierarchy. At least one of sitelinks, callouts or structuredSnippets is required.
44634
-
44635
- Google-only. Other platforms have no equivalent extension surface and return 501.
44636
-
44637
- Approval status is Google-async; poll `asset.policy_summary` after review. Assets
44638
- stay in the account library even if the campaign is later deleted.
45122
+ summary: Attach campaign assets
45123
+ x-resource-group: ads
45124
+ tags:
45125
+ - Ad Campaigns
45126
+ x-platforms:
45127
+ - google
45128
+ description: "Creates and attaches sitelinks, callouts and structured snippets in one Google mutation."
44639
45129
  security:
44640
- - bearerAuth: []
45130
+ - bearerAuth: []
44641
45131
  parameters:
44642
- - { name: campaignId, in: path, required: true, schema: { type: string }, description: "Numeric Google platform campaign id." }
45132
+ - name: campaignId
45133
+ in: path
45134
+ required: true
45135
+ schema:
45136
+ type: string
45137
+ pattern: ^\d+$
45138
+ description: "Numeric Google platform id."
44643
45139
  requestBody:
44644
45140
  required: true
44645
45141
  content:
44646
45142
  application/json:
44647
45143
  schema:
44648
45144
  type: object
44649
- required: [accountId]
45145
+ required:
45146
+ - accountId
44650
45147
  properties:
44651
- accountId: { type: string, description: "Zernio Google Ads SocialAccount id. Resolves the customer id + refresh token." }
44652
- customerId: { type: string, description: "Numeric Google Ads customer id. Required when the connection has multiple Google Ads accounts; optional (and inferred) when it has only one." }
45148
+ accountId:
45149
+ type: string
45150
+ pattern: ^[a-fA-F0-9]{24}$
45151
+ description: "Zernio Google Ads connection id."
45152
+ customerId:
45153
+ type: string
45154
+ pattern: ^\d+$
45155
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
44653
45156
  sitelinks:
44654
45157
  type: array
45158
+ items:
45159
+ $ref: '#/components/schemas/GoogleSitelink'
44655
45160
  minItems: 2
44656
45161
  maxItems: 20
44657
- description: "See POST /v1/ads/create sitelinks, same shape."
44658
- items:
44659
- type: object
44660
- required: [text, linkUrl]
44661
- properties:
44662
- text: { type: string, minLength: 1, maxLength: 25 }
44663
- linkUrl: { type: string, format: uri }
44664
- description1: { type: string, minLength: 1, maxLength: 35 }
44665
- description2: { type: string, minLength: 1, maxLength: 35 }
44666
45162
  callouts:
44667
45163
  type: array
45164
+ items:
45165
+ type: string
45166
+ minLength: 1
45167
+ maxLength: 25
44668
45168
  minItems: 1
44669
45169
  maxItems: 20
44670
- items: { type: string, minLength: 1, maxLength: 25 }
44671
45170
  structuredSnippets:
44672
45171
  type: array
45172
+ items:
45173
+ $ref: '#/components/schemas/GoogleStructuredSnippet'
44673
45174
  minItems: 1
44674
45175
  maxItems: 20
44675
- items:
44676
- type: object
44677
- required: [header, values]
44678
- properties:
44679
- header:
44680
- type: string
44681
- enum: [Amenities, Brands, Courses, Degree programs, Destinations, Featured hotels, Insurance coverage, Models, Neighborhoods, Service catalog, Shows, Styles, Types]
44682
- values:
44683
- type: array
44684
- minItems: 3
44685
- maxItems: 10
44686
- items: { type: string, minLength: 1, maxLength: 25 }
45176
+ description: "Provide at least one of sitelinks, callouts or structuredSnippets. Sitelink description1\
45177
+ \ and description2 must be supplied together."
45178
+ example:
45179
+ accountId: 64b1f0c8a1b2c3d4e5f60718
45180
+ customerId: '1234567890'
45181
+ sitelinks:
45182
+ - text: Pricing
45183
+ linkUrl: https://zernio.com/pricing
45184
+ - text: Documentation
45185
+ linkUrl: https://zernio.com/docs
45186
+ callouts:
45187
+ - Fast setup
45188
+ structuredSnippets:
45189
+ - header: Types
45190
+ values:
45191
+ - Scheduling
45192
+ - Analytics
45193
+ - Messaging
44687
45194
  responses:
44688
45195
  '201':
44689
- description: Assets attached
45196
+ description: "Assets created and attached."
44690
45197
  content:
44691
45198
  application/json:
44692
45199
  schema:
44693
45200
  type: object
44694
45201
  properties:
44695
- campaignId: { type: string }
44696
- sitelinkAssetResourceNames: { type: array, items: { type: string } }
44697
- calloutAssetResourceNames: { type: array, items: { type: string } }
44698
- structuredSnippetAssetResourceNames: { type: array, items: { type: string } }
44699
- '400': { description: "Invalid input, Google rejected the assets, an unknown customerId (not one of this connection's Google Ads accounts), or a required customerId missing when the connection has multiple Google Ads accounts" }
44700
- '401': { $ref: '#/components/responses/Unauthorized' }
44701
- '403':
44702
- description: 'Returned with code `ads_allowance_exceeded` when the team has no payment method on file and has reached the 500 free live ads: add a card to resume.'
44703
- '422': { description: "No Google Ads customer accounts on this connection. Reconnect Google Ads." }
44704
- '501': { description: Only supported on Google Ads }
44705
-
44706
- /v1/ads/campaigns/{campaignId}/analytics:
44707
- get:
44708
- x-resource-group: "ads"
44709
- operationId: getCampaignAnalytics
44710
- tags: ["Ad Insights"]
44711
- x-platforms: ["meta", "google", "tiktok", "linkedin", "pinterest", "x"]
44712
- summary: Get campaign analytics
44713
- description: |
44714
- Returns performance analytics for a whole campaign in one call: summary metrics, a daily
44715
- timeline over the requested date range (summed across the campaign's ads), and optional
44716
- demographic breakdowns. Breakdowns are fetched live from Meta at the campaign level (one call
44717
- per dimension, no per-ad fan-out), so an agency dashboard gets campaign-level age/gender/etc.
44718
- without summing thousands of per-ad reads. `campaignId` is the platform campaign id; pass
44719
- `platform` when a campaign id could be ambiguous across platforms. If no date range is provided,
44720
- defaults to the last 90 days. Date range is capped at 730 days max.
44721
- Google adds searchImpressionShare, searchBudgetLostImpressionShare,
44722
- searchRankLostImpressionShare, searchTopImpressionShare and searchAbsoluteTopImpressionShare
44723
- under analytics.summary for the requested inclusive range. These ratios are queried
44724
- together without daily segmentation and cached for 10 minutes. Unavailable values are
44725
- null. analytics.impressionShareCache reports cachedAt and stale independently of synced metrics.
44726
- security:
44727
- - bearerAuth: []
44728
- parameters:
44729
- - { name: campaignId, in: path, required: true, schema: { type: string }, description: "Platform campaign id (platformCampaignId)." }
44730
- - { name: platform, in: query, schema: { type: string }, description: "Disambiguate when the campaign id exists across platforms (e.g. facebook, instagram)." }
44731
- - { name: fromDate, in: query, schema: { type: string, format: date }, description: "Start of date range (YYYY-MM-DD). Defaults to 90 days ago." }
44732
- - { name: toDate, in: query, schema: { type: string, format: date }, description: "End of date range (YYYY-MM-DD). Defaults to today. Max 730-day range." }
44733
- - name: breakdowns
44734
- in: query
44735
- schema: { type: string }
44736
- description: |
44737
- Comma-separated breakdown dimensions.
44738
-
44739
- **Meta**: age, gender, country, publisher_platform, device_platform, region,
44740
- platform_position, impression_device, video_asset, image_asset, body_asset, title_asset.
44741
-
44742
- **LinkedIn** (firmographics): job_title, job_function, seniority, industry,
44743
- company, company_size, country, region. Rows carry the raw pivot `value`
44744
- plus a resolved `name`. LinkedIn serves these aggregated over the whole
44745
- range, delays the data 12-24h, and omits segments with fewer than 3 events.
44746
- responses:
44747
- '200':
44748
- description: Campaign analytics
44749
- content:
44750
- application/json:
44751
- schema: { $ref: '#/components/schemas/CampaignAnalyticsResponse' }
44752
- example:
44753
- campaign: { id: "123456789", platform: google }
44754
- analytics:
44755
- summary:
44756
- searchImpressionShare: 0.42
44757
- searchBudgetLostImpressionShare: 0.13
44758
- searchRankLostImpressionShare: 0.45
44759
- searchTopImpressionShare: 0.31
44760
- searchAbsoluteTopImpressionShare: null
44761
- impressionShareCache: { cachedAt: "2026-09-09T10:00:00Z", stale: false }
44762
- daily: []
44763
- '202':
44764
- description: Historical data is incomplete and backfill remains pending.
44765
- headers:
44766
- Retry-After:
44767
- $ref: '#/components/headers/BackfillRetryAfter'
44768
- content:
44769
- application/json:
44770
- schema:
44771
- allOf:
44772
- - { $ref: '#/components/schemas/CampaignAnalyticsResponse' }
44773
- - type: object
44774
- required: [backfillPending]
44775
- properties:
44776
- backfillPending: { type: boolean, description: 'Always true on this response. Part of the requested range is still being backfilled; retry until the request returns 200.' }
45202
+ campaignId:
45203
+ type: string
45204
+ sitelinkAssetResourceNames:
45205
+ type: array
45206
+ items:
45207
+ type: string
45208
+ calloutAssetResourceNames:
45209
+ type: array
45210
+ items:
45211
+ type: string
45212
+ structuredSnippetAssetResourceNames:
45213
+ type: array
45214
+ items:
45215
+ type: string
44777
45216
  '400':
44778
- description: "Invalid parameter (e.g. an unknown `breakdowns` dimension). The message lists the offending value(s) and the supported set."
44779
- content:
44780
- application/json:
44781
- schema: { $ref: '#/components/schemas/ErrorResponse' }
44782
- '401': { $ref: '#/components/responses/Unauthorized' }
45217
+ $ref: '#/components/responses/BadRequest'
45218
+ '401':
45219
+ $ref: '#/components/responses/Unauthorized'
44783
45220
  '403':
44784
- description: Ads access required. Legacy plans need the Ads add-on; included by default on usage-based plans.
44785
- '404': { $ref: '#/components/responses/NotFound' }
44786
- '429': { description: "Google operations budget or quota exhausted without a cached impression-share result." }
44787
-
44788
- /v1/ads/preview:
44789
- post:
44790
- x-resource-group: "ads"
44791
- operationId: generateAdPreviews
44792
- tags: ["Ad Creatives"]
44793
- x-platforms: ["meta"]
44794
- summary: Render pre-create ad previews
44795
- description: |
44796
- Renders how a creative would look per placement BEFORE any ad exists, via Meta's
44797
- `/generatepreviews`. Provide exactly one creative source: `existingCreativeId` or `creativeSpec`.
44798
- Each preview is an HTML `<iframe>` snippet embeddable directly. Unknown `formats` values
44799
- return Meta's 400 verbatim.
45221
+ description: "Ads access is required."
45222
+ '404':
45223
+ $ref: '#/components/responses/NotFound'
45224
+ '429':
45225
+ description: "Google Ads operations budget or platform quota exhausted."
45226
+ '501':
45227
+ description: "Only supported on Google Ads."
45228
+ put:
45229
+ operationId: updateCampaignAssets
45230
+ summary: Update campaign assets
45231
+ x-resource-group: ads
45232
+ tags:
45233
+ - Ad Campaigns
45234
+ x-platforms:
45235
+ - google
45236
+ description: "Edits existing Google assets in place. Send updates with assetResourceName and the fields to change.\
45237
+ \ An asset is shared: changes affect every attachment using it. Omitted fields stay unchanged. The operation\
45238
+ \ consumes the Google operations budget and invalidates affected cached lists."
44800
45239
  security:
44801
- - bearerAuth: []
45240
+ - bearerAuth: []
45241
+ parameters:
45242
+ - name: campaignId
45243
+ in: path
45244
+ required: true
45245
+ schema:
45246
+ type: string
45247
+ pattern: ^\d+$
45248
+ description: "Numeric Google platform id."
44802
45249
  requestBody:
44803
45250
  required: true
44804
45251
  content:
44805
45252
  application/json:
44806
45253
  schema:
44807
45254
  type: object
44808
- required: [accountId, adAccountId]
45255
+ required:
45256
+ - accountId
45257
+ - updates
44809
45258
  properties:
44810
- accountId: { type: string, description: "Zernio SocialAccount id used to resolve the Meta token." }
44811
- adAccountId: { type: string, description: "Platform ad account id (Meta act_<n>, Google customer id, LinkedIn account id, ...)." }
44812
- formats:
45259
+ accountId:
45260
+ type: string
45261
+ pattern: ^[a-fA-F0-9]{24}$
45262
+ description: "Zernio Google Ads connection id."
45263
+ customerId:
45264
+ type: string
45265
+ pattern: ^\d+$
45266
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
45267
+ updates:
44813
45268
  type: array
45269
+ items:
45270
+ $ref: '#/components/schemas/GoogleAssetUpdate'
44814
45271
  minItems: 1
44815
- maxItems: 10
44816
- items: { type: string }
44817
- description: "Meta ad_format values, one preview per format. Defaults to [DESKTOP_FEED_STANDARD]."
44818
- existingCreativeId: { type: string, description: "Preview an existing ad-account creative by id. Mutually exclusive with creativeSpec." }
44819
- creativeSpec:
44820
- type: object
44821
- additionalProperties: true
44822
- description: "Raw Meta creative spec forwarded verbatim to /generatepreviews. Mutually exclusive with existingCreativeId."
45272
+ maxItems: 20
45273
+ example:
45274
+ accountId: 64b1f0c8a1b2c3d4e5f60718
45275
+ customerId: '1234567890'
45276
+ updates:
45277
+ - assetResourceName: customers/1234567890/assets/123
45278
+ calloutAsset:
45279
+ calloutText: Simple integration
44823
45280
  responses:
44824
45281
  '200':
44825
- description: Rendered previews
45282
+ description: "Assets returned."
44826
45283
  content:
44827
45284
  application/json:
44828
45285
  schema:
44829
45286
  type: object
44830
45287
  properties:
44831
- previews:
44832
- type: array
44833
- items:
44834
- type: object
44835
- properties:
44836
- format: { type: string }
44837
- html: { type: [string, "null"], description: "Meta's <iframe> snippet; null when Meta returned no preview for the format." }
44838
- '400': { description: "Invalid input, or Meta rejected the creative spec / ad_format; the message carries Meta's error" }
44839
- '401': { $ref: '#/components/responses/Unauthorized' }
44840
- '429': { description: Meta rate limit reached }
44841
- '501': { description: Only supported on Meta (facebook/instagram) }
44842
-
44843
- /v1/ads/{adId}/preview:
44844
- get:
44845
- x-resource-group: "ads"
44846
- operationId: getAdPreviews
44847
- tags: ["Ad Creatives"]
44848
- x-platforms: ["meta"]
44849
- summary: Render previews of an existing ad
44850
- description: |
44851
- Renders an EXISTING ad per placement via Meta's `/{ad_id}/previews`. Each preview is an HTML
44852
- `<iframe>` snippet embeddable directly. Unknown `formats` values return Meta's 400 verbatim.
45288
+ updated:
45289
+ type: integer
45290
+ '400':
45291
+ $ref: '#/components/responses/BadRequest'
45292
+ '401':
45293
+ $ref: '#/components/responses/Unauthorized'
45294
+ '403':
45295
+ description: "Ads access is required."
45296
+ '404':
45297
+ $ref: '#/components/responses/NotFound'
45298
+ '429':
45299
+ description: "Google Ads operations budget or platform quota exhausted."
45300
+ '501':
45301
+ description: "Only supported on Google Ads."
45302
+ delete:
45303
+ operationId: removeCampaignAssets
45304
+ summary: Remove campaign assets
45305
+ x-resource-group: ads
45306
+ tags:
45307
+ - Ad Campaigns
45308
+ x-platforms:
45309
+ - google
45310
+ description: "Removes the specified attachments only. Google assets cannot be deleted. Other attachments remain. assetResourceNames is retained for compatibility."
44853
45311
  security:
44854
- - bearerAuth: []
45312
+ - bearerAuth: []
44855
45313
  parameters:
44856
- - { name: adId, in: path, required: true, schema: { type: string }, description: "Zernio ad id (24-char hex)." }
44857
- - { name: formats, in: query, schema: { type: string }, description: "Comma-separated Meta ad_format values (max 10), one preview per format. Defaults to DESKTOP_FEED_STANDARD." }
45314
+ - name: campaignId
45315
+ in: path
45316
+ required: true
45317
+ schema:
45318
+ type: string
45319
+ pattern: ^\d+$
45320
+ description: "Numeric Google platform id."
45321
+ requestBody:
45322
+ required: true
45323
+ content:
45324
+ application/json:
45325
+ schema:
45326
+ type: object
45327
+ required:
45328
+ - accountId
45329
+ - assetResourceNames
45330
+ - campaignAssetResourceNames
45331
+ properties:
45332
+ accountId:
45333
+ type: string
45334
+ pattern: ^[a-fA-F0-9]{24}$
45335
+ description: "Zernio Google Ads connection id."
45336
+ customerId:
45337
+ type: string
45338
+ pattern: ^\d+$
45339
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
45340
+ assetResourceNames:
45341
+ type: array
45342
+ items:
45343
+ type: string
45344
+ minItems: 1
45345
+ campaignAssetResourceNames:
45346
+ type: array
45347
+ items:
45348
+ type: string
45349
+ minItems: 1
45350
+ example:
45351
+ accountId: 64b1f0c8a1b2c3d4e5f60718
45352
+ customerId: '1234567890'
45353
+ assetResourceNames:
45354
+ - customers/1234567890/assets/123
45355
+ campaignAssetResourceNames:
45356
+ - customers/1234567890/campaignAssets/456~123~CALLOUT
44858
45357
  responses:
44859
45358
  '200':
44860
- description: Rendered previews
45359
+ description: "Assets returned."
44861
45360
  content:
44862
45361
  application/json:
44863
45362
  schema:
44864
45363
  type: object
44865
45364
  properties:
44866
- adId: { type: string }
44867
- previews:
44868
- type: array
44869
- items:
44870
- type: object
44871
- properties:
44872
- format: { type: string }
44873
- html: { type: [string, "null"], description: "Meta's <iframe> snippet; null when Meta returned no preview for the format." }
44874
- '400': { description: "Invalid input, or Meta rejected the ad_format; the message carries Meta's error" }
44875
- '401': { $ref: '#/components/responses/Unauthorized' }
44876
- '404': { description: Ad not found }
44877
- '429': { description: Meta rate limit reached }
44878
- '501': { description: Only supported on Meta (facebook/instagram) }
45365
+ removed:
45366
+ type: boolean
45367
+ '400':
45368
+ $ref: '#/components/responses/BadRequest'
45369
+ '401':
45370
+ $ref: '#/components/responses/Unauthorized'
45371
+ '403':
45372
+ description: "Ads access is required."
45373
+ '404':
45374
+ $ref: '#/components/responses/NotFound'
45375
+ '429':
45376
+ description: "Google Ads operations budget or platform quota exhausted."
45377
+ '501':
45378
+ description: "Only supported on Google Ads."
44879
45379
 
44880
- /v1/ads/{adId}/media:
45380
+ /v1/ads/ad-sets/{adSetId}/assets:
44881
45381
  get:
44882
- x-resource-group: "ads"
44883
- operationId: getAdMedia
44884
- tags: ["Ad Creatives"]
44885
- x-platforms: ["meta"]
44886
- summary: Direct video and image URLs for an ad
44887
- description: |-
44888
- Returns the direct signed URLs for every video and image asset used by an ad's live
44889
- creative, normalised across shapes: single image/video, carousel,
44890
- Reels/Story (`object_story_spec.video_data`) and dynamic
44891
- creative (`asset_feed_spec`). Video items include Meta's poster thumbnail and the
44892
- video's Meta id when available.
44893
-
44894
- Reads Meta live rather than the stored creative blob because Meta's signed fbcdn
44895
- URLs carry an `oe=<hex>` expiration (image_url ~24 h, video source ~12 d). Treat
44896
- URLs as short-lived: re-fetch this endpoint before serving or downloading assets
44897
- instead of caching URLs beyond that window.
45382
+ operationId: listAdGroupAssets
45383
+ summary: List ad-group assets
45384
+ x-resource-group: ads
45385
+ tags:
45386
+ - Ad Campaigns
45387
+ x-platforms:
45388
+ - google
45389
+ description: "Lists directly attached Google assets. Fresh reads are cached for 10 minutes; exhausted quota\
45390
+ \ may return the last successful read with stale=true. Inherited assets are not included."
44898
45391
  security:
44899
- - bearerAuth: []
45392
+ - bearerAuth: []
44900
45393
  parameters:
44901
- - { name: adId, in: path, required: true, schema: { type: string }, description: "Zernio ad id (24-char hex) or platform ad id." }
45394
+ - name: adSetId
45395
+ in: path
45396
+ required: true
45397
+ schema:
45398
+ type: string
45399
+ pattern: ^\d+$
45400
+ description: "Numeric Google platform id."
45401
+ - name: accountId
45402
+ in: query
45403
+ required: true
45404
+ schema:
45405
+ type: string
45406
+ pattern: ^[a-fA-F0-9]{24}$
45407
+ description: "Zernio Google Ads connection id."
45408
+ - name: customerId
45409
+ in: query
45410
+ schema:
45411
+ type: string
45412
+ pattern: ^\d+$
45413
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
44902
45414
  responses:
44903
45415
  '200':
44904
- description: Media assets
45416
+ description: "Assets returned."
44905
45417
  content:
44906
45418
  application/json:
44907
45419
  schema:
44908
45420
  type: object
44909
45421
  properties:
44910
- adId: { type: string }
44911
- platform: { type: string, description: "'facebook' or 'instagram'. Only Meta is supported for now." }
44912
- media:
45422
+ adGroupId:
45423
+ type: string
45424
+ sitelinks:
44913
45425
  type: array
44914
45426
  items:
44915
45427
  type: object
44916
45428
  properties:
44917
- type: { type: string, enum: [image, video] }
44918
- url: { type: string, description: "Direct file URL (signed; short-lived, see description)." }
44919
- thumbnailUrl: { type: string, description: "Video poster URL (videos only)." }
45429
+ assetResourceName:
45430
+ type: string
45431
+ adGroupAssetResourceName:
45432
+ type: string
45433
+ text:
45434
+ type: string
45435
+ linkUrl:
45436
+ type: string
45437
+ format: uri
45438
+ description1:
45439
+ type: string
45440
+ description2:
45441
+ type: string
45442
+ callouts:
45443
+ type: array
45444
+ items:
45445
+ type: object
45446
+ properties:
45447
+ assetResourceName:
45448
+ type: string
45449
+ adGroupAssetResourceName:
45450
+ type: string
45451
+ calloutText:
45452
+ type: string
45453
+ structuredSnippets:
45454
+ type: array
45455
+ items:
45456
+ type: object
45457
+ properties:
45458
+ assetResourceName:
45459
+ type: string
45460
+ adGroupAssetResourceName:
45461
+ type: string
45462
+ header:
45463
+ type: string
45464
+ values:
45465
+ type: array
45466
+ items:
45467
+ type: string
45468
+ cachedAt:
45469
+ type:
45470
+ - string
45471
+ - 'null'
45472
+ format: date-time
45473
+ description: "Time of the cached Google read. Null when no cache was used."
45474
+ stale:
45475
+ type: boolean
45476
+ description: "True when exhausted quota required returning the last successful read."
45477
+ '400':
45478
+ $ref: '#/components/responses/BadRequest'
45479
+ '401':
45480
+ $ref: '#/components/responses/Unauthorized'
45481
+ '403':
45482
+ description: "Ads access is required."
45483
+ '404':
45484
+ $ref: '#/components/responses/NotFound'
45485
+ '429':
45486
+ description: "Google Ads operations budget or platform quota exhausted."
45487
+ '501':
45488
+ description: "Only supported on Google Ads."
45489
+ post:
45490
+ operationId: attachAdGroupAssets
45491
+ summary: Attach ad-group assets
45492
+ x-resource-group: ads
45493
+ tags:
45494
+ - Ad Campaigns
45495
+ x-platforms:
45496
+ - google
45497
+ description: "Creates and attaches sitelinks, callouts and structured snippets in one Google mutation."
45498
+ security:
45499
+ - bearerAuth: []
45500
+ parameters:
45501
+ - name: adSetId
45502
+ in: path
45503
+ required: true
45504
+ schema:
45505
+ type: string
45506
+ pattern: ^\d+$
45507
+ description: "Numeric Google platform id."
45508
+ requestBody:
45509
+ required: true
45510
+ content:
45511
+ application/json:
45512
+ schema:
45513
+ type: object
45514
+ required:
45515
+ - accountId
45516
+ properties:
45517
+ accountId:
45518
+ type: string
45519
+ pattern: ^[a-fA-F0-9]{24}$
45520
+ description: "Zernio Google Ads connection id."
45521
+ customerId:
45522
+ type: string
45523
+ pattern: ^\d+$
45524
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
45525
+ sitelinks:
45526
+ type: array
45527
+ items:
45528
+ $ref: '#/components/schemas/GoogleSitelink'
45529
+ minItems: 2
45530
+ maxItems: 20
45531
+ callouts:
45532
+ type: array
45533
+ items:
45534
+ type: string
45535
+ minLength: 1
45536
+ maxLength: 25
45537
+ minItems: 1
45538
+ maxItems: 20
45539
+ structuredSnippets:
45540
+ type: array
45541
+ items:
45542
+ $ref: '#/components/schemas/GoogleStructuredSnippet'
45543
+ minItems: 1
45544
+ maxItems: 20
45545
+ description: "Provide at least one of sitelinks, callouts or structuredSnippets. Sitelink description1\
45546
+ \ and description2 must be supplied together."
45547
+ example:
45548
+ accountId: 64b1f0c8a1b2c3d4e5f60718
45549
+ customerId: '1234567890'
45550
+ sitelinks:
45551
+ - text: Pricing
45552
+ linkUrl: https://zernio.com/pricing
45553
+ - text: Documentation
45554
+ linkUrl: https://zernio.com/docs
45555
+ callouts:
45556
+ - Fast setup
45557
+ structuredSnippets:
45558
+ - header: Types
45559
+ values:
45560
+ - Scheduling
45561
+ - Analytics
45562
+ - Messaging
45563
+ responses:
45564
+ '201':
45565
+ description: "Assets created and attached."
45566
+ content:
45567
+ application/json:
45568
+ schema:
45569
+ type: object
45570
+ properties:
45571
+ adGroupId:
45572
+ type: string
45573
+ sitelinkAssetResourceNames:
45574
+ type: array
45575
+ items:
45576
+ type: string
45577
+ calloutAssetResourceNames:
45578
+ type: array
45579
+ items:
45580
+ type: string
45581
+ structuredSnippetAssetResourceNames:
45582
+ type: array
45583
+ items:
45584
+ type: string
45585
+ '400':
45586
+ $ref: '#/components/responses/BadRequest'
45587
+ '401':
45588
+ $ref: '#/components/responses/Unauthorized'
45589
+ '403':
45590
+ description: "Ads access is required."
45591
+ '404':
45592
+ $ref: '#/components/responses/NotFound'
45593
+ '429':
45594
+ description: "Google Ads operations budget or platform quota exhausted."
45595
+ '501':
45596
+ description: "Only supported on Google Ads."
45597
+ put:
45598
+ operationId: updateAdGroupAssets
45599
+ summary: Update ad-group assets
45600
+ x-resource-group: ads
45601
+ tags:
45602
+ - Ad Campaigns
45603
+ x-platforms:
45604
+ - google
45605
+ description: "Edits existing Google assets in place. Send updates with assetResourceName and the fields to change.\
45606
+ \ An asset is shared: changes affect every attachment using it. Omitted fields stay unchanged. The operation\
45607
+ \ consumes the Google operations budget and invalidates affected cached lists."
45608
+ security:
45609
+ - bearerAuth: []
45610
+ parameters:
45611
+ - name: adSetId
45612
+ in: path
45613
+ required: true
45614
+ schema:
45615
+ type: string
45616
+ pattern: ^\d+$
45617
+ description: "Numeric Google platform id."
45618
+ requestBody:
45619
+ required: true
45620
+ content:
45621
+ application/json:
45622
+ schema:
45623
+ type: object
45624
+ required:
45625
+ - accountId
45626
+ - updates
45627
+ properties:
45628
+ accountId:
45629
+ type: string
45630
+ pattern: ^[a-fA-F0-9]{24}$
45631
+ description: "Zernio Google Ads connection id."
45632
+ customerId:
45633
+ type: string
45634
+ pattern: ^\d+$
45635
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
45636
+ updates:
45637
+ type: array
45638
+ items:
45639
+ $ref: '#/components/schemas/GoogleAssetUpdate'
45640
+ minItems: 1
45641
+ maxItems: 20
45642
+ example:
45643
+ accountId: 64b1f0c8a1b2c3d4e5f60718
45644
+ customerId: '1234567890'
45645
+ updates:
45646
+ - assetResourceName: customers/1234567890/assets/123
45647
+ calloutAsset:
45648
+ calloutText: Simple integration
45649
+ responses:
45650
+ '200':
45651
+ description: "Assets returned."
45652
+ content:
45653
+ application/json:
45654
+ schema:
45655
+ type: object
45656
+ properties:
45657
+ updated:
45658
+ type: integer
45659
+ '400':
45660
+ $ref: '#/components/responses/BadRequest'
45661
+ '401':
45662
+ $ref: '#/components/responses/Unauthorized'
45663
+ '403':
45664
+ description: "Ads access is required."
45665
+ '404':
45666
+ $ref: '#/components/responses/NotFound'
45667
+ '429':
45668
+ description: "Google Ads operations budget or platform quota exhausted."
45669
+ '501':
45670
+ description: "Only supported on Google Ads."
45671
+ delete:
45672
+ operationId: removeAdGroupAssets
45673
+ summary: Remove ad-group assets
45674
+ x-resource-group: ads
45675
+ tags:
45676
+ - Ad Campaigns
45677
+ x-platforms:
45678
+ - google
45679
+ description: "Removes the specified attachments only. Google assets cannot be deleted. Other attachments remain. assetResourceNames is retained for compatibility."
45680
+ security:
45681
+ - bearerAuth: []
45682
+ parameters:
45683
+ - name: adSetId
45684
+ in: path
45685
+ required: true
45686
+ schema:
45687
+ type: string
45688
+ pattern: ^\d+$
45689
+ description: "Numeric Google platform id."
45690
+ requestBody:
45691
+ required: true
45692
+ content:
45693
+ application/json:
45694
+ schema:
45695
+ type: object
45696
+ required:
45697
+ - accountId
45698
+ - assetResourceNames
45699
+ - adGroupAssetResourceNames
45700
+ properties:
45701
+ accountId:
45702
+ type: string
45703
+ pattern: ^[a-fA-F0-9]{24}$
45704
+ description: "Zernio Google Ads connection id."
45705
+ customerId:
45706
+ type: string
45707
+ pattern: ^\d+$
45708
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
45709
+ assetResourceNames:
45710
+ type: array
45711
+ items:
45712
+ type: string
45713
+ minItems: 1
45714
+ adGroupAssetResourceNames:
45715
+ type: array
45716
+ items:
45717
+ type: string
45718
+ minItems: 1
45719
+ example:
45720
+ accountId: 64b1f0c8a1b2c3d4e5f60718
45721
+ customerId: '1234567890'
45722
+ assetResourceNames:
45723
+ - customers/1234567890/assets/123
45724
+ adGroupAssetResourceNames:
45725
+ - customers/1234567890/adGroupAssets/456~123~CALLOUT
45726
+ responses:
45727
+ '200':
45728
+ description: "Assets returned."
45729
+ content:
45730
+ application/json:
45731
+ schema:
45732
+ type: object
45733
+ properties:
45734
+ removed:
45735
+ type: boolean
45736
+ '400':
45737
+ $ref: '#/components/responses/BadRequest'
45738
+ '401':
45739
+ $ref: '#/components/responses/Unauthorized'
45740
+ '403':
45741
+ description: "Ads access is required."
45742
+ '404':
45743
+ $ref: '#/components/responses/NotFound'
45744
+ '429':
45745
+ description: "Google Ads operations budget or platform quota exhausted."
45746
+ '501':
45747
+ description: "Only supported on Google Ads."
45748
+
45749
+ /v1/ads/campaigns/{campaignId}/analytics:
45750
+ get:
45751
+ x-resource-group: "ads"
45752
+ operationId: getCampaignAnalytics
45753
+ tags: ["Ad Insights"]
45754
+ x-platforms: ["meta", "google", "tiktok", "linkedin", "pinterest", "x"]
45755
+ summary: Get campaign analytics
45756
+ description: |
45757
+ Returns performance analytics for a whole campaign in one call: summary metrics, a daily
45758
+ timeline over the requested date range (summed across the campaign's ads), and optional
45759
+ demographic breakdowns. Breakdowns are fetched live from Meta at the campaign level (one call
45760
+ per dimension, no per-ad fan-out), so an agency dashboard gets campaign-level age/gender/etc.
45761
+ without summing thousands of per-ad reads. `campaignId` is the platform campaign id; pass
45762
+ `platform` when a campaign id could be ambiguous across platforms. If no date range is provided,
45763
+ defaults to the last 90 days. Date range is capped at 730 days max.
45764
+ Google adds searchImpressionShare, searchBudgetLostImpressionShare,
45765
+ searchRankLostImpressionShare, searchTopImpressionShare and searchAbsoluteTopImpressionShare
45766
+ under analytics.summary for the requested inclusive range. These ratios are queried
45767
+ together without daily segmentation and cached for 10 minutes. Unavailable values are
45768
+ null. analytics.impressionShareCache reports cachedAt and stale independently of synced metrics.
45769
+ security:
45770
+ - bearerAuth: []
45771
+ parameters:
45772
+ - { name: campaignId, in: path, required: true, schema: { type: string }, description: "Platform campaign id (platformCampaignId)." }
45773
+ - { name: platform, in: query, schema: { type: string }, description: "Disambiguate when the campaign id exists across platforms (e.g. facebook, instagram)." }
45774
+ - { name: fromDate, in: query, schema: { type: string, format: date }, description: "Start of date range (YYYY-MM-DD). Defaults to 90 days ago." }
45775
+ - { name: toDate, in: query, schema: { type: string, format: date }, description: "End of date range (YYYY-MM-DD). Defaults to today. Max 730-day range." }
45776
+ - name: breakdowns
45777
+ in: query
45778
+ schema: { type: string }
45779
+ description: |
45780
+ Comma-separated breakdown dimensions.
45781
+
45782
+ **Meta**: age, gender, country, publisher_platform, device_platform, region,
45783
+ platform_position, impression_device, video_asset, image_asset, body_asset, title_asset.
45784
+
45785
+ **LinkedIn** (firmographics): job_title, job_function, seniority, industry,
45786
+ company, company_size, country, region. Rows carry the raw pivot `value`
45787
+ plus a resolved `name`. LinkedIn serves these aggregated over the whole
45788
+ range, delays the data 12-24h, and omits segments with fewer than 3 events.
45789
+ responses:
45790
+ '200':
45791
+ description: Campaign analytics
45792
+ content:
45793
+ application/json:
45794
+ schema: { $ref: '#/components/schemas/CampaignAnalyticsResponse' }
45795
+ example:
45796
+ campaign: { id: "123456789", platform: google }
45797
+ analytics:
45798
+ summary:
45799
+ searchImpressionShare: 0.42
45800
+ searchBudgetLostImpressionShare: 0.13
45801
+ searchRankLostImpressionShare: 0.45
45802
+ searchTopImpressionShare: 0.31
45803
+ searchAbsoluteTopImpressionShare: null
45804
+ impressionShareCache: { cachedAt: "2026-09-09T10:00:00Z", stale: false }
45805
+ daily: []
45806
+ '202':
45807
+ description: Historical data is incomplete and backfill remains pending.
45808
+ headers:
45809
+ Retry-After:
45810
+ $ref: '#/components/headers/BackfillRetryAfter'
45811
+ content:
45812
+ application/json:
45813
+ schema:
45814
+ allOf:
45815
+ - { $ref: '#/components/schemas/CampaignAnalyticsResponse' }
45816
+ - type: object
45817
+ required: [backfillPending]
45818
+ properties:
45819
+ backfillPending: { type: boolean, description: 'Always true on this response. Part of the requested range is still being backfilled; retry until the request returns 200.' }
45820
+ '400':
45821
+ description: "Invalid parameter (e.g. an unknown `breakdowns` dimension). The message lists the offending value(s) and the supported set."
45822
+ content:
45823
+ application/json:
45824
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
45825
+ '401': { $ref: '#/components/responses/Unauthorized' }
45826
+ '403':
45827
+ description: Ads access required. Legacy plans need the Ads add-on; included by default on usage-based plans.
45828
+ '404': { $ref: '#/components/responses/NotFound' }
45829
+ '429': { description: "Google operations budget or quota exhausted without a cached impression-share result." }
45830
+
45831
+ /v1/ads/preview:
45832
+ post:
45833
+ x-resource-group: "ads"
45834
+ operationId: generateAdPreviews
45835
+ tags: ["Ad Creatives"]
45836
+ x-platforms: ["meta"]
45837
+ summary: Render pre-create ad previews
45838
+ description: |
45839
+ Renders how a creative would look per placement BEFORE any ad exists, via Meta's
45840
+ `/generatepreviews`. Provide exactly one creative source: `existingCreativeId` or `creativeSpec`.
45841
+ Each preview is an HTML `<iframe>` snippet embeddable directly. Unknown `formats` values
45842
+ return Meta's 400 verbatim.
45843
+ security:
45844
+ - bearerAuth: []
45845
+ requestBody:
45846
+ required: true
45847
+ content:
45848
+ application/json:
45849
+ schema:
45850
+ type: object
45851
+ required: [accountId, adAccountId]
45852
+ properties:
45853
+ accountId: { type: string, description: "Zernio SocialAccount id used to resolve the Meta token." }
45854
+ adAccountId: { type: string, description: "Platform ad account id (Meta act_<n>, Google customer id, LinkedIn account id, ...)." }
45855
+ formats:
45856
+ type: array
45857
+ minItems: 1
45858
+ maxItems: 10
45859
+ items: { type: string }
45860
+ description: "Meta ad_format values, one preview per format. Defaults to [DESKTOP_FEED_STANDARD]."
45861
+ existingCreativeId: { type: string, description: "Preview an existing ad-account creative by id. Mutually exclusive with creativeSpec." }
45862
+ creativeSpec:
45863
+ type: object
45864
+ additionalProperties: true
45865
+ description: "Raw Meta creative spec forwarded verbatim to /generatepreviews. Mutually exclusive with existingCreativeId."
45866
+ responses:
45867
+ '200':
45868
+ description: Rendered previews
45869
+ content:
45870
+ application/json:
45871
+ schema:
45872
+ type: object
45873
+ properties:
45874
+ previews:
45875
+ type: array
45876
+ items:
45877
+ type: object
45878
+ properties:
45879
+ format: { type: string }
45880
+ html: { type: [string, "null"], description: "Meta's <iframe> snippet; null when Meta returned no preview for the format." }
45881
+ '400': { description: "Invalid input, or Meta rejected the creative spec / ad_format; the message carries Meta's error" }
45882
+ '401': { $ref: '#/components/responses/Unauthorized' }
45883
+ '429': { description: Meta rate limit reached }
45884
+ '501': { description: Only supported on Meta (facebook/instagram) }
45885
+
45886
+ /v1/ads/{adId}/preview:
45887
+ get:
45888
+ x-resource-group: "ads"
45889
+ operationId: getAdPreviews
45890
+ tags: ["Ad Creatives"]
45891
+ x-platforms: ["meta"]
45892
+ summary: Render previews of an existing ad
45893
+ description: |
45894
+ Renders an EXISTING ad per placement via Meta's `/{ad_id}/previews`. Each preview is an HTML
45895
+ `<iframe>` snippet embeddable directly. Unknown `formats` values return Meta's 400 verbatim.
45896
+ security:
45897
+ - bearerAuth: []
45898
+ parameters:
45899
+ - { name: adId, in: path, required: true, schema: { type: string }, description: "Zernio ad id (24-char hex)." }
45900
+ - { name: formats, in: query, schema: { type: string }, description: "Comma-separated Meta ad_format values (max 10), one preview per format. Defaults to DESKTOP_FEED_STANDARD." }
45901
+ responses:
45902
+ '200':
45903
+ description: Rendered previews
45904
+ content:
45905
+ application/json:
45906
+ schema:
45907
+ type: object
45908
+ properties:
45909
+ adId: { type: string }
45910
+ previews:
45911
+ type: array
45912
+ items:
45913
+ type: object
45914
+ properties:
45915
+ format: { type: string }
45916
+ html: { type: [string, "null"], description: "Meta's <iframe> snippet; null when Meta returned no preview for the format." }
45917
+ '400': { description: "Invalid input, or Meta rejected the ad_format; the message carries Meta's error" }
45918
+ '401': { $ref: '#/components/responses/Unauthorized' }
45919
+ '404': { description: Ad not found }
45920
+ '429': { description: Meta rate limit reached }
45921
+ '501': { description: Only supported on Meta (facebook/instagram) }
45922
+
45923
+ /v1/ads/{adId}/media:
45924
+ get:
45925
+ x-resource-group: "ads"
45926
+ operationId: getAdMedia
45927
+ tags: ["Ad Creatives"]
45928
+ x-platforms: ["meta"]
45929
+ summary: Direct video and image URLs for an ad
45930
+ description: |-
45931
+ Returns the direct signed URLs for every video and image asset used by an ad's live
45932
+ creative, normalised across shapes: single image/video, carousel,
45933
+ Reels/Story (`object_story_spec.video_data`) and dynamic
45934
+ creative (`asset_feed_spec`). Video items include Meta's poster thumbnail and the
45935
+ video's Meta id when available.
45936
+
45937
+ Reads Meta live rather than the stored creative blob because Meta's signed fbcdn
45938
+ URLs carry an `oe=<hex>` expiration (image_url ~24 h, video source ~12 d). Treat
45939
+ URLs as short-lived: re-fetch this endpoint before serving or downloading assets
45940
+ instead of caching URLs beyond that window.
45941
+ security:
45942
+ - bearerAuth: []
45943
+ parameters:
45944
+ - { name: adId, in: path, required: true, schema: { type: string }, description: "Zernio ad id (24-char hex) or platform ad id." }
45945
+ responses:
45946
+ '200':
45947
+ description: Media assets
45948
+ content:
45949
+ application/json:
45950
+ schema:
45951
+ type: object
45952
+ properties:
45953
+ adId: { type: string }
45954
+ platform: { type: string, description: "'facebook' or 'instagram'. Only Meta is supported for now." }
45955
+ media:
45956
+ type: array
45957
+ items:
45958
+ type: object
45959
+ properties:
45960
+ type: { type: string, enum: [image, video] }
45961
+ url: { type: string, description: "Direct file URL (signed; short-lived, see description)." }
45962
+ thumbnailUrl: { type: string, description: "Video poster URL (videos only)." }
44920
45963
  videoId: { type: string, description: "Meta video id (videos only), reusable as video.id on the create endpoints." }
44921
45964
  length: { type: number, description: "Video length in seconds (videos only)." }
44922
45965
  index: { type: integer, description: "0-based position for carousel children or asset_feed_spec entries." }
@@ -45386,7 +46429,7 @@ paths:
45386
46429
  x-resource-group: "engagement"
45387
46430
  operationId: getAdComments
45388
46431
  tags: ["Ad Accounts"]
45389
- x-platforms: ["meta"]
46432
+ x-platforms: ["meta", "tiktok"]
45390
46433
  summary: List comments on an ad
45391
46434
  description: |
45392
46435
  Returns comments on an ad's underlying creative post. Useful for moderating or analyzing
@@ -45406,26 +46449,39 @@ paths:
45406
46449
  Instagram account on the profile can read the ad's media, the call returns
45407
46450
  ads_connection_required (the Facebook side, if any, is still readable via ?placement=facebook).
45408
46451
 
45409
- Meta-only for now. Other ad platforms (TikTok, LinkedIn, Pinterest, Google, X)
45410
- are not wired to this endpoint and return feature_not_available.
46452
+ TikTok uses the connected TikTok Ads advertiser token and supports both paid video
46453
+ ads and Spark Ads. `since` and `until` select a date window of at most 30 days;
46454
+ the default is the last 30 days. TikTok searches by ad group, so Zernio filters
46455
+ each page to this ad. A page can be empty while `pagination.hasMore` is true.
46456
+ Reuse `pagination.cursor` with the same `limit`; the cursor retains the date window.
46457
+ `placement` is Meta-only and returns a 400 for TikTok.
46458
+
46459
+ TikTok returns replies as separate comments with `parentId`; nested reply fetching
46460
+ is not supported. `canReply` requires a first-level comment and an identity with
46461
+ comment-management permission. `canDelete` reflects TikTok's own-comment deletion
46462
+ capability. `canHide` is supported and `canLike` is false. Use the ad comment
46463
+ reply, hide and delete operations below to moderate TikTok comments.
46464
+ Other platforms return feature_not_available.
45411
46465
 
45412
46466
  Requires the Ads add-on. Response shape matches GET /v1/inbox/comments/{postId}.
45413
46467
 
45414
46468
  The `{adId}` path segment accepts any identifier dialect Zernio indexes for the ad:
45415
- Zernio internal `_id` (24-char hex), Meta's numeric `platformAdId` (the value shipped in
46469
+ Zernio internal `_id` (24-char hex), the numeric `platformAdId` (the value shipped in
45416
46470
  `comment.received` webhooks as `comment.ad.id`), or the creative's
45417
46471
  `effective_object_story_id` / `effective_instagram_media_id`. Caller doesn't need a
45418
46472
  translation step.
45419
46473
  security:
45420
46474
  - bearerAuth: []
45421
46475
  parameters:
45422
- - { name: adId, in: path, required: true, schema: { type: string }, description: "Internal Zernio ad ID (ObjectId)." }
46476
+ - { name: adId, in: path, required: true, schema: { type: string }, description: "Internal Zernio ad ID or indexed platform ad/post ID." }
45423
46477
  - { name: placement, in: query, schema: { type: string, enum: [facebook, instagram] }, description: "Which side of the ad to return comments for. Omit to default to the Instagram side when present, else Facebook. Returns ad_not_commentable if the ad has no such placement." }
45424
46478
  - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 } }
46479
+ - { name: since, in: query, schema: { type: string, format: date }, description: "TikTok-only start date. Defaults to 30 days before until. Maximum window is 30 days." }
46480
+ - { name: until, in: query, schema: { type: string, format: date }, description: "TikTok-only end date. Defaults to today in UTC." }
45425
46481
  - { name: cursor, in: query, schema: { type: string }, description: "Pagination cursor from a previous response." }
45426
46482
  responses:
45427
46483
  '200':
45428
- description: Comments on the ad
46484
+ description: "Comments on the ad."
45429
46485
  content:
45430
46486
  application/json:
45431
46487
  schema:
@@ -45437,7 +46493,7 @@ paths:
45437
46493
  type: array
45438
46494
  items:
45439
46495
  type: object
45440
- description: Normalized comment. Same shape as /v1/inbox/comments/{postId} responses.
46496
+ description: "Normalized comment. Same shape as /v1/inbox/comments/{postId} responses."
45441
46497
  pagination:
45442
46498
  type: object
45443
46499
  properties:
@@ -45445,15 +46501,20 @@ paths:
45445
46501
  cursor: { type: string }
45446
46502
  meta:
45447
46503
  type: object
45448
- required: [platform, placement, adId, platformAdId, effectiveStoryId, accountId, lastUpdated]
46504
+ required: [platform, adId, accountId, lastUpdated]
45449
46505
  properties:
45450
- platform: { type: string, enum: [facebook, instagram], description: "Which side these comments are on (same as `placement`)." }
46506
+ platform: { type: string, enum: [facebook, instagram, tiktok], description: "Platform of the comments." }
45451
46507
  placement: { type: string, enum: [facebook, instagram], description: "The placement these comments are for, useful when you didn't pass ?placement= and want to know which one you got." }
45452
46508
  adId: { type: string, description: "Internal Zernio ad ID." }
45453
- platformAdId: { type: string, description: "Meta ad ID." }
46509
+ platformAdId: { type: string, description: "Platform ad ID." }
45454
46510
  effectiveStoryId:
45455
46511
  type: string
45456
46512
  description: "Underlying post ID the comments belong to. effective_object_story_id for the Facebook side, effective_instagram_media_id for the Instagram side."
46513
+ tiktokItemId:
46514
+ type: [string, "null"]
46515
+ description: "TikTok-only video item ID. Null when the ad and comments do not expose it."
46516
+ since: { type: string, format: date, description: "TikTok-only resolved start date." }
46517
+ until: { type: string, format: date, description: "TikTok-only resolved end date." }
45457
46518
  facebookAccountId:
45458
46519
  type: [string, "null"]
45459
46520
  description: "Facebook-only. The connected Facebook Page SocialAccount these comments were read through. Pass it as `accountId` (with `effectiveStoryId` as the postId) to /v1/inbox/comments to reply/hide/delete. Null when no connected Page was used (then moderation isn't possible)."
@@ -45468,19 +46529,215 @@ paths:
45468
46529
  description: "Instagram-only. The connected Instagram SocialAccount these comments were read through. Pass it as `accountId` (with `effectiveStoryId` as the postId) to /v1/inbox/comments to reply/hide/delete."
45469
46530
  accountId: { type: string, description: "Account ID (ads SocialAccount)." }
45470
46531
  lastUpdated: { type: string, format: date-time }
46532
+ example:
46533
+ status: success
46534
+ comments:
46535
+ - id: "7512345678901234567"
46536
+ message: "Can you share more details?"
46537
+ createdTime: "2026-09-08T10:00:00.000Z"
46538
+ from: { id: "6123456789123456789", name: "reader", username: "reader", picture: "https://example.com/avatar.jpg", isOwner: false }
46539
+ likeCount: 4
46540
+ replyCount: 0
46541
+ platform: tiktok
46542
+ url: null
46543
+ replies: []
46544
+ isHidden: false
46545
+ canReply: true
46546
+ canDelete: false
46547
+ canHide: true
46548
+ canLike: false
46549
+ isLiked: false
46550
+ pagination: { hasMore: false }
46551
+ meta:
46552
+ platform: tiktok
46553
+ adId: "507f1f77bcf86cd799439011"
46554
+ platformAdId: "1790166588666881"
46555
+ accountId: "507f1f77bcf86cd799439012"
46556
+ tiktokItemId: "7512345678901234500"
46557
+ since: "2026-08-10"
46558
+ until: "2026-09-09"
46559
+ lastUpdated: "2026-09-09T12:00:00.000Z"
45471
46560
  '400':
45472
46561
  description: |
45473
46562
  Invalid ad ID format, or the ad's creative format does not expose a commentable
45474
46563
  underlying post (code ad_not_commentable).
45475
46564
  '401': { $ref: '#/components/responses/Unauthorized' }
45476
46565
  '403':
45477
- description: Ads access required (legacy plans need the Ads add-on; included by default on usage-based plans), or ad platform is not Meta (code feature_not_available).
46566
+ description: Ads access required (legacy plans need the Ads add-on; included by default on usage-based plans), or ad platform is not Meta or TikTok (code feature_not_available).
45478
46567
  '404': { $ref: '#/components/responses/NotFound' }
45479
46568
  '422':
45480
46569
  description: |
45481
46570
  Ads account token unavailable, or (for Instagram-placed ads) no connected
45482
46571
  Instagram account on the profile can read the ad's media (code ads_connection_required).
45483
46572
 
46573
+ /v1/ads/{adId}/comments/{commentId}/reply:
46574
+ post:
46575
+ operationId: replyToAdComment
46576
+ summary: Reply to an ad comment
46577
+ tags: ["Ad Accounts"]
46578
+ x-resource-group: "engagement"
46579
+ x-platforms: ["tiktok"]
46580
+ description: |
46581
+ Reply to a first-level TikTok ad comment. Requires a TT_USER or CUSTOMIZED_USER identity with comment-management permission. Replies to replies are rejected. The response commentId identifies the new reply. This operation is not idempotent; do not blindly retry an uncertain response.
46582
+
46583
+ Requires Ads access. The ad is resolved within the caller's accessible profiles.
46584
+ Before moderation, Zernio verifies that the comment belongs to this ad using
46585
+ TikTok's ad-group comment listing. The default search window is the last 30 days.
46586
+ Use since/until for older comments, with at most 30 days between the dates.
46587
+ Lookups scan at most 2,000 ad-group comments; narrow the date window if exceeded.
46588
+ Meta returns 501 feature_not_available with guidance to use the existing inbox
46589
+ comment endpoints and the account/post IDs from GET /v1/ads/{adId}/comments.
46590
+ security:
46591
+ - bearerAuth: []
46592
+ parameters:
46593
+ - { name: adId, in: path, required: true, schema: { type: string }, description: "Internal Zernio ad ID or indexed platform ad ID." }
46594
+ - { name: commentId, in: path, required: true, schema: { type: string, pattern: '^\d+$' }, description: "TikTok comment ID from the ad comment listing." }
46595
+ - { name: since, in: query, schema: { type: string, format: date }, description: "Start date of the comment lookup window. Defaults to 30 days before until." }
46596
+ - { name: until, in: query, schema: { type: string, format: date }, description: "End date of the comment lookup window. Defaults to today in UTC." }
46597
+ requestBody:
46598
+ required: true
46599
+ content:
46600
+ application/json:
46601
+ schema:
46602
+ type: object
46603
+ required: [text]
46604
+ properties:
46605
+ text: { type: string, minLength: 1, description: "Non-empty reply text." }
46606
+ example: { text: "Thanks for your question!" }
46607
+ responses:
46608
+ '200':
46609
+ description: "Comment action completed."
46610
+ content:
46611
+ application/json:
46612
+ schema:
46613
+ type: object
46614
+ required: [status, commentId]
46615
+ properties:
46616
+ status: { type: string, enum: [success] }
46617
+ commentId: { type: string, description: "ID of the created reply or moderated comment." }
46618
+ example: { status: success, commentId: "7512345678901234567" }
46619
+ '400': { $ref: '#/components/responses/BadRequest' }
46620
+ '401': { $ref: '#/components/responses/Unauthorized' }
46621
+ '403':
46622
+ description: "Ads access or the required TikTok comment capability is unavailable."
46623
+ '404':
46624
+ description: "Ad is inaccessible or the comment was not found on this ad in the selected date window."
46625
+ '422':
46626
+ description: "TikTok Ads connection is unavailable."
46627
+ '501':
46628
+ description: "Moderation on this route supports TikTok. Use the inbox comment routes for Meta."
46629
+ '502':
46630
+ description: "TikTok rejected the request or was unavailable. Inspect platformError for its code and message."
46631
+
46632
+ /v1/ads/{adId}/comments/{commentId}/hide:
46633
+ post:
46634
+ operationId: hideAdComment
46635
+ summary: Hide or unhide an ad comment
46636
+ tags: ["Ad Accounts"]
46637
+ x-resource-group: "engagement"
46638
+ x-platforms: ["tiktok"]
46639
+ description: |
46640
+ Hide or restore a TikTok ad comment. Send hidden=true to hide it or hidden=false to make it public again.
46641
+
46642
+ Requires Ads access. The ad is resolved within the caller's accessible profiles.
46643
+ Before moderation, Zernio verifies that the comment belongs to this ad using
46644
+ TikTok's ad-group comment listing. The default search window is the last 30 days.
46645
+ Use since/until for older comments, with at most 30 days between the dates.
46646
+ Lookups scan at most 2,000 ad-group comments; narrow the date window if exceeded.
46647
+ Meta returns 501 feature_not_available with guidance to use the existing inbox
46648
+ comment endpoints and the account/post IDs from GET /v1/ads/{adId}/comments.
46649
+ security:
46650
+ - bearerAuth: []
46651
+ parameters:
46652
+ - { name: adId, in: path, required: true, schema: { type: string }, description: "Internal Zernio ad ID or indexed platform ad ID." }
46653
+ - { name: commentId, in: path, required: true, schema: { type: string, pattern: '^\d+$' }, description: "TikTok comment ID from the ad comment listing." }
46654
+ - { name: since, in: query, schema: { type: string, format: date }, description: "Start date of the comment lookup window. Defaults to 30 days before until." }
46655
+ - { name: until, in: query, schema: { type: string, format: date }, description: "End date of the comment lookup window. Defaults to today in UTC." }
46656
+ requestBody:
46657
+ required: true
46658
+ content:
46659
+ application/json:
46660
+ schema:
46661
+ type: object
46662
+ required: [hidden]
46663
+ properties:
46664
+ hidden: { type: boolean, description: "True to hide the comment; false to restore it." }
46665
+ example: { hidden: true }
46666
+ responses:
46667
+ '200':
46668
+ description: "Comment action completed."
46669
+ content:
46670
+ application/json:
46671
+ schema:
46672
+ type: object
46673
+ required: [status, commentId]
46674
+ properties:
46675
+ status: { type: string, enum: [success] }
46676
+ commentId: { type: string, description: "ID of the created reply or moderated comment." }
46677
+ hidden: { type: boolean, description: "The requested visibility state." }
46678
+ example: { status: success, commentId: "7512345678901234567", hidden: true }
46679
+ '400': { $ref: '#/components/responses/BadRequest' }
46680
+ '401': { $ref: '#/components/responses/Unauthorized' }
46681
+ '403':
46682
+ description: "Ads access or the required TikTok comment capability is unavailable."
46683
+ '404':
46684
+ description: "Ad is inaccessible or the comment was not found on this ad in the selected date window."
46685
+ '422':
46686
+ description: "TikTok Ads connection is unavailable."
46687
+ '501':
46688
+ description: "Moderation on this route supports TikTok. Use the inbox comment routes for Meta."
46689
+ '502':
46690
+ description: "TikTok rejected the request or was unavailable. Inspect platformError for its code and message."
46691
+
46692
+ /v1/ads/{adId}/comments/{commentId}:
46693
+ delete:
46694
+ operationId: deleteAdComment
46695
+ summary: Delete an ad comment
46696
+ tags: ["Ad Accounts"]
46697
+ x-resource-group: "engagement"
46698
+ x-platforms: ["tiktok"]
46699
+ description: |
46700
+ Delete your own TikTok ad comment or reply. TikTok must return can_delete=true for the comment. Other users' comments can be hidden instead.
46701
+
46702
+ Requires Ads access. The ad is resolved within the caller's accessible profiles.
46703
+ Before moderation, Zernio verifies that the comment belongs to this ad using
46704
+ TikTok's ad-group comment listing. The default search window is the last 30 days.
46705
+ Use since/until for older comments, with at most 30 days between the dates.
46706
+ Lookups scan at most 2,000 ad-group comments; narrow the date window if exceeded.
46707
+ Meta returns 501 feature_not_available with guidance to use the existing inbox
46708
+ comment endpoints and the account/post IDs from GET /v1/ads/{adId}/comments.
46709
+ security:
46710
+ - bearerAuth: []
46711
+ parameters:
46712
+ - { name: adId, in: path, required: true, schema: { type: string }, description: "Internal Zernio ad ID or indexed platform ad ID." }
46713
+ - { name: commentId, in: path, required: true, schema: { type: string, pattern: '^\d+$' }, description: "TikTok comment ID from the ad comment listing." }
46714
+ - { name: since, in: query, schema: { type: string, format: date }, description: "Start date of the comment lookup window. Defaults to 30 days before until." }
46715
+ - { name: until, in: query, schema: { type: string, format: date }, description: "End date of the comment lookup window. Defaults to today in UTC." }
46716
+ responses:
46717
+ '200':
46718
+ description: "Comment action completed."
46719
+ content:
46720
+ application/json:
46721
+ schema:
46722
+ type: object
46723
+ required: [status, commentId]
46724
+ properties:
46725
+ status: { type: string, enum: [success] }
46726
+ commentId: { type: string, description: "ID of the created reply or moderated comment." }
46727
+ example: { status: success, commentId: "7512345678901234567" }
46728
+ '400': { $ref: '#/components/responses/BadRequest' }
46729
+ '401': { $ref: '#/components/responses/Unauthorized' }
46730
+ '403':
46731
+ description: "Ads access or the required TikTok comment capability is unavailable."
46732
+ '404':
46733
+ description: "Ad is inaccessible or the comment was not found on this ad in the selected date window."
46734
+ '422':
46735
+ description: "TikTok Ads connection is unavailable."
46736
+ '501':
46737
+ description: "Moderation on this route supports TikTok. Use the inbox comment routes for Meta."
46738
+ '502':
46739
+ description: "TikTok rejected the request or was unavailable. Inspect platformError for its code and message."
46740
+
45484
46741
  /v1/ads/business-centers:
45485
46742
  get:
45486
46743
  x-resource-group: "ads"
@@ -45736,27 +46993,180 @@ paths:
45736
46993
  '401': { $ref: '#/components/responses/Unauthorized' }
45737
46994
  '501': { description: Only supported on Meta (facebook/instagram) }
45738
46995
 
45739
- /v1/ads/businesses:
46996
+ /v1/ads/instagram-accounts:
45740
46997
  get:
45741
- x-resource-group: "ads"
45742
- operationId: listMetaBusinesses
46998
+ operationId: listAdsInstagramAccounts
46999
+ summary: List Instagram ad identities
47000
+ description: "Discovers identities through connected_instagram_accounts, Page linkage and Page-backed identities, with a best-effort business fallback. Business permission errors do not fail discovery. The resolved object uses the same profile-scoped resolver as ad creation; null means no identity was resolved. Format-specific observed-actor fallbacks at creative creation are not predicted."
45743
47001
  tags: ["Ad Accounts"]
47002
+ x-resource-group: "ads"
45744
47003
  x-platforms: ["meta"]
45745
- summary: Businesses list
45746
- description: |-
45747
- Business Manager portfolios the connected Meta user belongs to (Meta's `/me/businesses`),
45748
- rows returned verbatim (id, name, verification_status, created_time). Token-scoped, so no
45749
- `adAccountId` is needed. For TikTok Business Centers use
45750
- `GET /v1/ads/business-centers`.
45751
47004
  security:
45752
47005
  - bearerAuth: []
45753
47006
  parameters:
45754
- - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
45755
- - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, description: Rows per page }
45756
- - { name: after, in: query, schema: { type: string }, description: "Cursor from paging.after of the previous page." }
47007
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio Meta Ads or Facebook SocialAccount ID." }
47008
+ - { name: adAccountId, in: query, required: true, schema: { type: string, pattern: '^act_[0-9]+$' }, description: "Meta ad account ID including the act_ prefix." }
45757
47009
  responses:
45758
47010
  '200':
45759
- description: Businesses (raw Meta shape)
47011
+ description: "Instagram identities and Page linkage."
47012
+ content:
47013
+ application/json:
47014
+ schema:
47015
+ type: object
47016
+ required: [accounts, pages, resolved]
47017
+ properties:
47018
+ accounts:
47019
+ type: array
47020
+ items:
47021
+ allOf:
47022
+ - $ref: '#/components/schemas/MetaInstagramIdentityRef'
47023
+ - type: object
47024
+ required: [isPageBacked, source]
47025
+ properties:
47026
+ isPageBacked: { type: boolean, description: "Whether this is a Page-backed Instagram identity." }
47027
+ source: { type: string, enum: [ad_account, page_backed, business], description: "Discovery source; Page linkage also uses page_backed." }
47028
+ pages:
47029
+ type: array
47030
+ items:
47031
+ type: object
47032
+ required: [pageId, name]
47033
+ properties:
47034
+ pageId: { type: string, description: "Facebook Page ID." }
47035
+ name: { type: string, description: "Facebook Page name." }
47036
+ instagramBusinessAccount: { $ref: '#/components/schemas/MetaInstagramIdentityRef' }
47037
+ connectedInstagramAccount: { $ref: '#/components/schemas/MetaInstagramIdentityRef' }
47038
+ resolved:
47039
+ type: object
47040
+ required: [pageId, igUserId, source]
47041
+ properties:
47042
+ pageId: { type: [string, "null"], description: "Page selected by the shared ad-creation resolver." }
47043
+ igUserId: { type: [string, "null"], description: "Instagram identity selected by the shared ad-creation resolver." }
47044
+ source: { type: [string, "null"], enum: [ad_account, page_backed, business, null], description: "Discovery source of the resolved identity; null when absent from discovery." }
47045
+ example:
47046
+ accounts:
47047
+ - { igUserId: "17841400000000000", username: "example", isPageBacked: false, source: page_backed }
47048
+ pages:
47049
+ - pageId: "123456789"
47050
+ name: "Example Page"
47051
+ instagramBusinessAccount: { igUserId: "17841400000000000", username: "example" }
47052
+ resolved: { pageId: "123456789", igUserId: "17841400000000000", source: page_backed }
47053
+ '400': { $ref: '#/components/responses/BadRequest' }
47054
+ '401': { $ref: '#/components/responses/Unauthorized' }
47055
+ '403': { description: "The account or Meta asset is not accessible." }
47056
+ '404': { $ref: '#/components/responses/NotFound' }
47057
+ '501': { description: "Only supported on Meta Ads and Facebook accounts." }
47058
+
47059
+ /v1/ads/advertisable-applications:
47060
+ get:
47061
+ operationId: listAdvertisableApplications
47062
+ summary: List advertisable apps
47063
+ description: "Lists applications available to a Meta ad account, their supported platforms and unmodified object store URLs. A listed app still needs a configured mobile platform and store URL to run install promotion."
47064
+ tags: ["Ad Accounts"]
47065
+ x-resource-group: "ads"
47066
+ x-platforms: ["meta"]
47067
+ security:
47068
+ - bearerAuth: []
47069
+ parameters:
47070
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio Meta Ads or Facebook SocialAccount ID." }
47071
+ - { name: adAccountId, in: query, required: true, schema: { type: string, pattern: '^act_[0-9]+$' }, description: "Meta ad account ID including the act_ prefix." }
47072
+ responses:
47073
+ '200':
47074
+ description: "Applications available for promotion."
47075
+ content:
47076
+ application/json:
47077
+ schema:
47078
+ type: object
47079
+ required: [applications]
47080
+ properties:
47081
+ applications:
47082
+ type: array
47083
+ items:
47084
+ type: object
47085
+ required: [id, name, supportedPlatforms, storeUrls]
47086
+ properties:
47087
+ id: { type: string, description: "Meta application ID." }
47088
+ name: { type: string, description: "Application name." }
47089
+ supportedPlatforms:
47090
+ type: array
47091
+ items: { type: string }
47092
+ description: "Platform identifiers reported by Meta."
47093
+ storeUrls:
47094
+ type: object
47095
+ additionalProperties: { type: string }
47096
+ description: "Platform-keyed store URLs returned unchanged by Meta."
47097
+ example:
47098
+ applications:
47099
+ - id: "123456789"
47100
+ name: "Example App"
47101
+ supportedPlatforms: [IOS, ANDROID]
47102
+ storeUrls: { iphone: "https://apps.apple.com/app/id123456789", google_play: "https://play.google.com/store/apps/details?id=com.example.app" }
47103
+ '400': { $ref: '#/components/responses/BadRequest' }
47104
+ '401': { $ref: '#/components/responses/Unauthorized' }
47105
+ '403': { description: "The account or Meta asset is not accessible." }
47106
+ '404': { $ref: '#/components/responses/NotFound' }
47107
+ '501': { description: "Only supported on Meta Ads and Facebook accounts." }
47108
+
47109
+ /v1/ads/ios-fourteen-campaign-limits:
47110
+ get:
47111
+ operationId: getIosFourteenCampaignLimits
47112
+ summary: Get iOS 14 campaign limits
47113
+ description: "Reads Meta iOS 14 campaign limits for an application on an ad account. applicationId is sent as Meta app_id. This read does not establish that the application is configured for iOS promotion."
47114
+ tags: ["Ad Accounts"]
47115
+ x-resource-group: "ads"
47116
+ x-platforms: ["meta"]
47117
+ security:
47118
+ - bearerAuth: []
47119
+ parameters:
47120
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio Meta Ads or Facebook SocialAccount ID." }
47121
+ - { name: adAccountId, in: query, required: true, schema: { type: string, pattern: '^act_[0-9]+$' }, description: "Meta ad account ID including the act_ prefix." }
47122
+ - { name: applicationId, in: query, required: true, schema: { type: string, pattern: '^[0-9]+$' }, description: "Meta application ID from advertisable-applications." }
47123
+ responses:
47124
+ '200':
47125
+ description: "Application campaign limits."
47126
+ content:
47127
+ application/json:
47128
+ schema:
47129
+ type: object
47130
+ required: [limits]
47131
+ properties:
47132
+ limits:
47133
+ type: [object, "null"]
47134
+ properties:
47135
+ campaignGroupLimit: { type: [number, "null"], description: "Campaign group limit reported by Meta." }
47136
+ campaignLimit: { type: [number, "null"], description: "Campaign limit reported by Meta." }
47137
+ campaignGroupLimitsDetails:
47138
+ type: array
47139
+ items: {}
47140
+ description: "Campaign group limit details returned by Meta."
47141
+ example:
47142
+ limits: { campaignGroupLimit: 9, campaignLimit: 5, campaignGroupLimitsDetails: [] }
47143
+ '400': { $ref: '#/components/responses/BadRequest' }
47144
+ '401': { $ref: '#/components/responses/Unauthorized' }
47145
+ '403': { description: "The account or Meta asset is not accessible." }
47146
+ '404': { $ref: '#/components/responses/NotFound' }
47147
+ '501': { description: "Only supported on Meta Ads and Facebook accounts." }
47148
+
47149
+ /v1/ads/businesses:
47150
+ get:
47151
+ x-resource-group: "ads"
47152
+ operationId: listMetaBusinesses
47153
+ tags: ["Ad Accounts"]
47154
+ x-platforms: ["meta"]
47155
+ summary: Businesses list
47156
+ description: |-
47157
+ Business Manager portfolios the connected Meta user belongs to (Meta's `/me/businesses`),
47158
+ rows returned verbatim (id, name, verification_status, created_time). Token-scoped, so no
47159
+ `adAccountId` is needed. For TikTok Business Centers use
47160
+ `GET /v1/ads/business-centers`.
47161
+ security:
47162
+ - bearerAuth: []
47163
+ parameters:
47164
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
47165
+ - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, description: Rows per page }
47166
+ - { name: after, in: query, schema: { type: string }, description: "Cursor from paging.after of the previous page." }
47167
+ responses:
47168
+ '200':
47169
+ description: Businesses (raw Meta shape)
45760
47170
  content:
45761
47171
  application/json:
45762
47172
  schema:
@@ -46817,453 +48227,1255 @@ paths:
46817
48227
  - "openai"
46818
48228
  description: "Optional courtesy field. The resolved account or campaign determines support; other platforms return 501."
46819
48229
  responses:
46820
- "200":
46821
- description: "Successful response."
48230
+ "200":
48231
+ description: "Successful response."
48232
+ content:
48233
+ application/json:
48234
+ schema:
48235
+ type: "object"
48236
+ properties:
48237
+ removed:
48238
+ type: "boolean"
48239
+ customerId:
48240
+ type: "string"
48241
+ pattern: "^\\d+$"
48242
+ description: "Resolved Google Ads customer id."
48243
+ example:
48244
+ removed: true
48245
+ customerId: "9122445560"
48246
+ "400":
48247
+ $ref: "#/components/responses/BadRequest"
48248
+ "401":
48249
+ $ref: "#/components/responses/Unauthorized"
48250
+ "403":
48251
+ description: "Ads access and permission to the selected account are required."
48252
+ "404":
48253
+ $ref: "#/components/responses/NotFound"
48254
+ "409":
48255
+ description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
48256
+ "422":
48257
+ description: "Google Ads connection is missing or unavailable."
48258
+ "429":
48259
+ description: "Google Ads operations budget or platform quota exhausted."
48260
+ "501":
48261
+ description: "Available only on Google Ads."
48262
+ /v1/ads/accounts/negative-keyword-lists/{listId}/keywords:
48263
+ put:
48264
+ x-resource-group: "ads"
48265
+ operationId: "replaceAdNegativeKeywordListKeywords"
48266
+ tags:
48267
+ - "Ad Accounts"
48268
+ x-platforms:
48269
+ - "google"
48270
+ summary: "Replace negative list keywords"
48271
+ description: "Replaces the full desired keyword set. Existing keywords are diffed by normalized text and match type; creates and removals are applied atomically in one mutation. Unchanged criteria retain their ids. Send an empty keywords array to clear the list. Changes affect every campaign using this list. Each create or removal consumes one daily operation; the entire batch must fit the remaining quota."
48272
+ security:
48273
+ - bearerAuth: []
48274
+ parameters:
48275
+ - name: "listId"
48276
+ in: "path"
48277
+ required: true
48278
+ schema:
48279
+ type: "string"
48280
+ pattern: "^\\d+$"
48281
+ description: "Google shared set id."
48282
+ requestBody:
48283
+ required: true
48284
+ content:
48285
+ application/json:
48286
+ schema:
48287
+ type: "object"
48288
+ additionalProperties: false
48289
+ required:
48290
+ - "accountId"
48291
+ - "keywords"
48292
+ properties:
48293
+ accountId:
48294
+ type: "string"
48295
+ pattern: "^[a-fA-F0-9]{24}$"
48296
+ description: "Zernio SocialAccount id."
48297
+ customerId:
48298
+ type: "string"
48299
+ pattern: "^\\d+$"
48300
+ description: "Connected Google Ads customer id, without dashes. Required when the connection has multiple customers."
48301
+ platform:
48302
+ type: "string"
48303
+ enum:
48304
+ - "facebook"
48305
+ - "instagram"
48306
+ - "tiktok"
48307
+ - "linkedin"
48308
+ - "pinterest"
48309
+ - "google"
48310
+ - "twitter"
48311
+ - "openai"
48312
+ description: "Optional courtesy field. The resolved account or campaign determines support; other platforms return 501."
48313
+ keywords:
48314
+ type: "array"
48315
+ maxItems: 5000
48316
+ items:
48317
+ $ref: "#/components/schemas/KeywordEntry"
48318
+ description: "Full desired keyword set. Bare strings use broad match. Send [] to clear the list."
48319
+ example:
48320
+ accountId: "69ce75d483e990e1c01ccfe4"
48321
+ customerId: "9122445560"
48322
+ keywords:
48323
+ - "free"
48324
+ - text: "jobs"
48325
+ matchType: "phrase"
48326
+ responses:
48327
+ "200":
48328
+ description: "Successful response."
48329
+ content:
48330
+ application/json:
48331
+ schema:
48332
+ type: "object"
48333
+ properties:
48334
+ created:
48335
+ type: "integer"
48336
+ description: "New criteria or campaign links created."
48337
+ removed:
48338
+ type: "integer"
48339
+ description: "Existing criteria or campaign links removed."
48340
+ customerId:
48341
+ type: "string"
48342
+ pattern: "^\\d+$"
48343
+ description: "Resolved Google Ads customer id."
48344
+ example:
48345
+ created: 1
48346
+ removed: 1
48347
+ customerId: "9122445560"
48348
+ "400":
48349
+ $ref: "#/components/responses/BadRequest"
48350
+ "401":
48351
+ $ref: "#/components/responses/Unauthorized"
48352
+ "403":
48353
+ description: "Ads access and permission to the selected account are required."
48354
+ "404":
48355
+ $ref: "#/components/responses/NotFound"
48356
+ "409":
48357
+ description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
48358
+ "422":
48359
+ description: "Google Ads connection is missing or unavailable."
48360
+ "429":
48361
+ description: "Google Ads operations budget or platform quota exhausted."
48362
+ "501":
48363
+ description: "Available only on Google Ads."
48364
+ /v1/ads/campaigns/{campaignId}/negative-keyword-lists:
48365
+ get:
48366
+ x-resource-group: "ads"
48367
+ operationId: "listCampaignNegativeKeywordLists"
48368
+ tags:
48369
+ - "Ad Campaigns"
48370
+ x-platforms:
48371
+ - "google"
48372
+ summary: "List campaign negative lists"
48373
+ description: "Returns shared negative keyword lists attached to the campaign, separate from campaign-level negative keywords. Google Ads shared negative keyword lists (shared_set type NEGATIVE_KEYWORDS). Reads are cached for 10 minutes; quota exhaustion may return the last successful result for up to 7 days with stale=true. Customer selection is limited to this connection and its account scope."
48374
+ security:
48375
+ - bearerAuth: []
48376
+ parameters:
48377
+ - name: "campaignId"
48378
+ in: "path"
48379
+ required: true
48380
+ schema:
48381
+ type: "string"
48382
+ pattern: "^\\d+$"
48383
+ description: "Google campaign id."
48384
+ - name: "platform"
48385
+ in: "query"
48386
+ schema:
48387
+ type: "string"
48388
+ enum:
48389
+ - "facebook"
48390
+ - "instagram"
48391
+ - "tiktok"
48392
+ - "linkedin"
48393
+ - "pinterest"
48394
+ - "google"
48395
+ - "twitter"
48396
+ - "openai"
48397
+ description: "Optional courtesy field. The resolved account or campaign determines support; other platforms return 501."
48398
+ responses:
48399
+ "200":
48400
+ description: "Successful response."
48401
+ content:
48402
+ application/json:
48403
+ schema:
48404
+ type: "object"
48405
+ properties:
48406
+ lists:
48407
+ type: "array"
48408
+ items:
48409
+ $ref: "#/components/schemas/AdNegativeKeywordList"
48410
+ customerId:
48411
+ type: "string"
48412
+ pattern: "^\\d+$"
48413
+ description: "Resolved Google Ads customer id."
48414
+ cachedAt:
48415
+ type:
48416
+ - "string"
48417
+ - "null"
48418
+ format: "date-time"
48419
+ description: "Last successful fetch time, or null without cache storage."
48420
+ stale:
48421
+ type: "boolean"
48422
+ description: "True when quota exhaustion caused the last successful cached result to be served."
48423
+ example:
48424
+ lists:
48425
+ - id: "1234567890"
48426
+ resourceName: "customers/9122445560/sharedSets/1234567890"
48427
+ name: "Excluded searches"
48428
+ memberCount: 2
48429
+ referenceCount: 1
48430
+ customerId: "9122445560"
48431
+ cachedAt: "2026-09-09T10:00:00Z"
48432
+ stale: false
48433
+ "400":
48434
+ $ref: "#/components/responses/BadRequest"
48435
+ "401":
48436
+ $ref: "#/components/responses/Unauthorized"
48437
+ "403":
48438
+ description: "Ads access and permission to the selected account are required."
48439
+ "404":
48440
+ $ref: "#/components/responses/NotFound"
48441
+ "409":
48442
+ description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
48443
+ "422":
48444
+ description: "Google Ads connection is missing or unavailable."
48445
+ "429":
48446
+ description: "Google Ads operations budget or platform quota exhausted."
48447
+ "501":
48448
+ description: "Available only on Google Ads."
48449
+ put:
48450
+ x-resource-group: "ads"
48451
+ operationId: "replaceCampaignNegativeKeywordLists"
48452
+ tags:
48453
+ - "Ad Campaigns"
48454
+ x-platforms:
48455
+ - "google"
48456
+ summary: "Replace campaign negative lists"
48457
+ description: "Sets the full desired set of shared negative keyword list associations on this campaign. Send listIds=[] to detach all negative keyword lists. Only campaign_shared_set links are changed; the lists and their keywords are preserved. Every list must belong to the campaign customer and have type NEGATIVE_KEYWORDS."
48458
+ security:
48459
+ - bearerAuth: []
48460
+ parameters:
48461
+ - name: "campaignId"
48462
+ in: "path"
48463
+ required: true
48464
+ schema:
48465
+ type: "string"
48466
+ pattern: "^\\d+$"
48467
+ description: "Google campaign id."
48468
+ requestBody:
48469
+ required: true
48470
+ content:
48471
+ application/json:
48472
+ schema:
48473
+ type: "object"
48474
+ additionalProperties: false
48475
+ required:
48476
+ - "listIds"
48477
+ properties:
48478
+ platform:
48479
+ type: "string"
48480
+ enum:
48481
+ - "facebook"
48482
+ - "instagram"
48483
+ - "tiktok"
48484
+ - "linkedin"
48485
+ - "pinterest"
48486
+ - "google"
48487
+ - "twitter"
48488
+ - "openai"
48489
+ description: "Optional courtesy field. The resolved account or campaign determines support; other platforms return 501."
48490
+ listIds:
48491
+ type: "array"
48492
+ maxItems: 20
48493
+ items:
48494
+ type: "string"
48495
+ pattern: "^\\d+$"
48496
+ description: "Shared negative keyword list id."
48497
+ example:
48498
+ platform: "google"
48499
+ listIds:
48500
+ - "1234567890"
48501
+ responses:
48502
+ "200":
48503
+ description: "Successful response."
48504
+ content:
48505
+ application/json:
48506
+ schema:
48507
+ type: "object"
48508
+ properties:
48509
+ created:
48510
+ type: "integer"
48511
+ description: "New criteria or campaign links created."
48512
+ removed:
48513
+ type: "integer"
48514
+ description: "Existing criteria or campaign links removed."
48515
+ customerId:
48516
+ type: "string"
48517
+ pattern: "^\\d+$"
48518
+ description: "Resolved Google Ads customer id."
48519
+ example:
48520
+ created: 1
48521
+ removed: 0
48522
+ customerId: "9122445560"
48523
+ "400":
48524
+ $ref: "#/components/responses/BadRequest"
48525
+ "401":
48526
+ $ref: "#/components/responses/Unauthorized"
48527
+ "403":
48528
+ description: "Ads access and permission to the selected account are required."
48529
+ "404":
48530
+ $ref: "#/components/responses/NotFound"
48531
+ "409":
48532
+ description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
48533
+ "422":
48534
+ description: "Google Ads connection is missing or unavailable."
48535
+ "429":
48536
+ description: "Google Ads operations budget or platform quota exhausted."
48537
+ "501":
48538
+ description: "Available only on Google Ads."
48539
+
48540
+ /v1/ads/accounts/callouts:
48541
+ get:
48542
+ operationId: listAccountCallouts
48543
+ summary: List account callouts
48544
+ x-resource-group: ads
48545
+ tags:
48546
+ - Ad Accounts
48547
+ x-platforms:
48548
+ - google
48549
+ description: "Lists directly attached Google assets. Fresh reads are cached for 10 minutes; exhausted quota\
48550
+ \ may return the last successful read with stale=true. Inherited assets are not included. Preserves Google\
48551
+ \ RMF C.75 account-level callouts."
48552
+ security:
48553
+ - bearerAuth: []
48554
+ parameters:
48555
+ - name: accountId
48556
+ in: query
48557
+ required: true
48558
+ schema:
48559
+ type: string
48560
+ pattern: ^[a-fA-F0-9]{24}$
48561
+ description: "Zernio Google Ads connection id."
48562
+ - name: customerId
48563
+ in: query
48564
+ schema:
48565
+ type: string
48566
+ pattern: ^\d+$
48567
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
48568
+ responses:
48569
+ '200':
48570
+ description: "Assets returned."
48571
+ content:
48572
+ application/json:
48573
+ schema:
48574
+ type: object
48575
+ properties:
48576
+ customerId:
48577
+ type: string
48578
+ callouts:
48579
+ type: array
48580
+ items:
48581
+ type: object
48582
+ properties:
48583
+ assetId:
48584
+ type: string
48585
+ status:
48586
+ type: string
48587
+ text:
48588
+ type: string
48589
+ cachedAt:
48590
+ type:
48591
+ - string
48592
+ - 'null'
48593
+ format: date-time
48594
+ description: "Time of the cached Google read. Null when no cache was used."
48595
+ stale:
48596
+ type: boolean
48597
+ description: "True when exhausted quota required returning the last successful read."
48598
+ '400':
48599
+ $ref: '#/components/responses/BadRequest'
48600
+ '401':
48601
+ $ref: '#/components/responses/Unauthorized'
48602
+ '403':
48603
+ description: "Ads access is required."
48604
+ '404':
48605
+ $ref: '#/components/responses/NotFound'
48606
+ '429':
48607
+ description: "Google Ads operations budget or platform quota exhausted."
48608
+ '501':
48609
+ description: "Only supported on Google Ads."
48610
+ post:
48611
+ operationId: addAccountCallouts
48612
+ summary: Add account callouts
48613
+ x-resource-group: ads
48614
+ tags:
48615
+ - Ad Accounts
48616
+ x-platforms:
48617
+ - google
48618
+ description: "Creates assets and customer_asset links for this Google customer. Links apply at account level."
48619
+ security:
48620
+ - bearerAuth: []
48621
+ requestBody:
48622
+ required: true
48623
+ content:
48624
+ application/json:
48625
+ schema:
48626
+ type: object
48627
+ required:
48628
+ - accountId
48629
+ - callouts
48630
+ properties:
48631
+ accountId:
48632
+ type: string
48633
+ pattern: ^[a-fA-F0-9]{24}$
48634
+ description: "Zernio Google Ads connection id."
48635
+ customerId:
48636
+ type: string
48637
+ pattern: ^\d+$
48638
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
48639
+ callouts:
48640
+ type: array
48641
+ items:
48642
+ type: string
48643
+ minLength: 1
48644
+ maxLength: 25
48645
+ minItems: 1
48646
+ maxItems: 20
48647
+ example:
48648
+ accountId: 64b1f0c8a1b2c3d4e5f60718
48649
+ customerId: '1234567890'
48650
+ callouts:
48651
+ - Fast setup
48652
+ responses:
48653
+ '201':
48654
+ description: "Assets created and attached."
48655
+ content:
48656
+ application/json:
48657
+ schema:
48658
+ type: object
48659
+ properties:
48660
+ customerId:
48661
+ type: string
48662
+ callouts:
48663
+ type: array
48664
+ items:
48665
+ type: object
48666
+ properties:
48667
+ assetId:
48668
+ type: string
48669
+ text:
48670
+ type: string
48671
+ '400':
48672
+ $ref: '#/components/responses/BadRequest'
48673
+ '401':
48674
+ $ref: '#/components/responses/Unauthorized'
48675
+ '403':
48676
+ description: "Ads access is required."
48677
+ '404':
48678
+ $ref: '#/components/responses/NotFound'
48679
+ '429':
48680
+ description: "Google Ads operations budget or platform quota exhausted."
48681
+ '501':
48682
+ description: "Only supported on Google Ads."
48683
+ put:
48684
+ operationId: updateAccountCallouts
48685
+ summary: Update account callouts
48686
+ x-resource-group: ads
48687
+ tags:
48688
+ - Ad Accounts
48689
+ x-platforms:
48690
+ - google
48691
+ description: "Edits existing Google assets in place. Send updates with assetResourceName and the fields to change.\
48692
+ \ An asset is shared: changes affect every attachment using it. Omitted fields stay unchanged. The operation\
48693
+ \ consumes the Google operations budget and invalidates affected cached lists."
48694
+ security:
48695
+ - bearerAuth: []
48696
+ requestBody:
48697
+ required: true
48698
+ content:
48699
+ application/json:
48700
+ schema:
48701
+ type: object
48702
+ required:
48703
+ - accountId
48704
+ - updates
48705
+ properties:
48706
+ accountId:
48707
+ type: string
48708
+ pattern: ^[a-fA-F0-9]{24}$
48709
+ description: "Zernio Google Ads connection id."
48710
+ customerId:
48711
+ type: string
48712
+ pattern: ^\d+$
48713
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
48714
+ updates:
48715
+ type: array
48716
+ items:
48717
+ type: object
48718
+ required:
48719
+ - assetResourceName
48720
+ properties:
48721
+ assetResourceName:
48722
+ type: string
48723
+ pattern: ^customers/\d+/assets/\d+$
48724
+ description: "Asset resource name returned by a list operation. Must belong to the selected\
48725
+ \ customer."
48726
+ calloutAsset:
48727
+ type: object
48728
+ required:
48729
+ - calloutText
48730
+ properties:
48731
+ calloutText:
48732
+ type: string
48733
+ minLength: 1
48734
+ maxLength: 25
48735
+ description: "Provide at least one field belonging to this asset type."
48736
+ minItems: 1
48737
+ maxItems: 20
48738
+ example:
48739
+ accountId: 64b1f0c8a1b2c3d4e5f60718
48740
+ customerId: '1234567890'
48741
+ updates:
48742
+ - assetResourceName: customers/1234567890/assets/123
48743
+ calloutAsset:
48744
+ calloutText: Simple integration
48745
+ responses:
48746
+ '200':
48747
+ description: "Assets returned."
48748
+ content:
48749
+ application/json:
48750
+ schema:
48751
+ type: object
48752
+ properties:
48753
+ updated:
48754
+ type: integer
48755
+ customerId:
48756
+ type: string
48757
+ '400':
48758
+ $ref: '#/components/responses/BadRequest'
48759
+ '401':
48760
+ $ref: '#/components/responses/Unauthorized'
48761
+ '403':
48762
+ description: "Ads access is required."
48763
+ '404':
48764
+ $ref: '#/components/responses/NotFound'
48765
+ '429':
48766
+ description: "Google Ads operations budget or platform quota exhausted."
48767
+ '501':
48768
+ description: "Only supported on Google Ads."
48769
+ delete:
48770
+ operationId: removeAccountCallout
48771
+ summary: Remove account callout
48772
+ x-resource-group: ads
48773
+ tags:
48774
+ - Ad Accounts
48775
+ x-platforms:
48776
+ - google
48777
+ description: "Removes the customer_asset attachment only. The underlying shared asset and its campaign or ad-group\
48778
+ \ attachments remain."
48779
+ security:
48780
+ - bearerAuth: []
48781
+ requestBody:
48782
+ required: true
48783
+ content:
48784
+ application/json:
48785
+ schema:
48786
+ type: object
48787
+ required:
48788
+ - accountId
48789
+ - assetId
48790
+ properties:
48791
+ accountId:
48792
+ type: string
48793
+ pattern: ^[a-fA-F0-9]{24}$
48794
+ description: "Zernio Google Ads connection id."
48795
+ customerId:
48796
+ type: string
48797
+ pattern: ^\d+$
48798
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
48799
+ assetId:
48800
+ type: string
48801
+ pattern: ^\d+$
48802
+ example:
48803
+ accountId: 64b1f0c8a1b2c3d4e5f60718
48804
+ customerId: '1234567890'
48805
+ assetId: '123'
48806
+ responses:
48807
+ '200':
48808
+ description: "Assets returned."
48809
+ content:
48810
+ application/json:
48811
+ schema:
48812
+ type: object
48813
+ properties:
48814
+ removed:
48815
+ type: boolean
48816
+ customerId:
48817
+ type: string
48818
+ '400':
48819
+ $ref: '#/components/responses/BadRequest'
48820
+ '401':
48821
+ $ref: '#/components/responses/Unauthorized'
48822
+ '403':
48823
+ description: "Ads access is required."
48824
+ '404':
48825
+ $ref: '#/components/responses/NotFound'
48826
+ '429':
48827
+ description: "Google Ads operations budget or platform quota exhausted."
48828
+ '501':
48829
+ description: "Only supported on Google Ads."
48830
+
48831
+ /v1/ads/accounts/sitelinks:
48832
+ get:
48833
+ operationId: listAccountSitelinks
48834
+ summary: List account sitelinks
48835
+ x-resource-group: ads
48836
+ tags:
48837
+ - Ad Accounts
48838
+ x-platforms:
48839
+ - google
48840
+ description: "Lists directly attached Google assets. Fresh reads are cached for 10 minutes; exhausted quota\
48841
+ \ may return the last successful read with stale=true. Inherited assets are not included."
48842
+ security:
48843
+ - bearerAuth: []
48844
+ parameters:
48845
+ - name: accountId
48846
+ in: query
48847
+ required: true
48848
+ schema:
48849
+ type: string
48850
+ pattern: ^[a-fA-F0-9]{24}$
48851
+ description: "Zernio Google Ads connection id."
48852
+ - name: customerId
48853
+ in: query
48854
+ schema:
48855
+ type: string
48856
+ pattern: ^\d+$
48857
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
48858
+ responses:
48859
+ '200':
48860
+ description: "Assets returned."
48861
+ content:
48862
+ application/json:
48863
+ schema:
48864
+ type: object
48865
+ properties:
48866
+ customerId:
48867
+ type: string
48868
+ sitelinks:
48869
+ type: array
48870
+ items:
48871
+ type: object
48872
+ properties:
48873
+ assetId:
48874
+ type: string
48875
+ status:
48876
+ type: string
48877
+ assetResourceName:
48878
+ type: string
48879
+ customerAssetResourceName:
48880
+ type: string
48881
+ text:
48882
+ type: string
48883
+ linkUrl:
48884
+ type: string
48885
+ format: uri
48886
+ description1:
48887
+ type: string
48888
+ description2:
48889
+ type: string
48890
+ cachedAt:
48891
+ type:
48892
+ - string
48893
+ - 'null'
48894
+ format: date-time
48895
+ description: "Time of the cached Google read. Null when no cache was used."
48896
+ stale:
48897
+ type: boolean
48898
+ description: "True when exhausted quota required returning the last successful read."
48899
+ '400':
48900
+ $ref: '#/components/responses/BadRequest'
48901
+ '401':
48902
+ $ref: '#/components/responses/Unauthorized'
48903
+ '403':
48904
+ description: "Ads access is required."
48905
+ '404':
48906
+ $ref: '#/components/responses/NotFound'
48907
+ '429':
48908
+ description: "Google Ads operations budget or platform quota exhausted."
48909
+ '501':
48910
+ description: "Only supported on Google Ads."
48911
+ post:
48912
+ operationId: addAccountSitelinks
48913
+ summary: Add account sitelinks
48914
+ x-resource-group: ads
48915
+ tags:
48916
+ - Ad Accounts
48917
+ x-platforms:
48918
+ - google
48919
+ description: "Creates assets and customer_asset links for this Google customer. Links apply at account level."
48920
+ security:
48921
+ - bearerAuth: []
48922
+ requestBody:
48923
+ required: true
48924
+ content:
48925
+ application/json:
48926
+ schema:
48927
+ type: object
48928
+ required:
48929
+ - accountId
48930
+ - sitelinks
48931
+ properties:
48932
+ accountId:
48933
+ type: string
48934
+ pattern: ^[a-fA-F0-9]{24}$
48935
+ description: "Zernio Google Ads connection id."
48936
+ customerId:
48937
+ type: string
48938
+ pattern: ^\d+$
48939
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
48940
+ sitelinks:
48941
+ type: array
48942
+ items:
48943
+ $ref: '#/components/schemas/GoogleSitelink'
48944
+ minItems: 1
48945
+ maxItems: 20
48946
+ example:
48947
+ accountId: 64b1f0c8a1b2c3d4e5f60718
48948
+ customerId: '1234567890'
48949
+ sitelinks:
48950
+ - text: Pricing
48951
+ linkUrl: https://zernio.com/pricing
48952
+ responses:
48953
+ '201':
48954
+ description: "Assets created and attached."
48955
+ content:
48956
+ application/json:
48957
+ schema:
48958
+ type: object
48959
+ properties:
48960
+ customerId:
48961
+ type: string
48962
+ sitelinks:
48963
+ type: array
48964
+ items:
48965
+ type: object
48966
+ properties:
48967
+ assetId:
48968
+ type: string
48969
+ text:
48970
+ type: string
48971
+ minLength: 1
48972
+ maxLength: 25
48973
+ linkUrl:
48974
+ type: string
48975
+ format: uri
48976
+ description1:
48977
+ type: string
48978
+ minLength: 1
48979
+ maxLength: 35
48980
+ description2:
48981
+ type: string
48982
+ minLength: 1
48983
+ maxLength: 35
48984
+ '400':
48985
+ $ref: '#/components/responses/BadRequest'
48986
+ '401':
48987
+ $ref: '#/components/responses/Unauthorized'
48988
+ '403':
48989
+ description: "Ads access is required."
48990
+ '404':
48991
+ $ref: '#/components/responses/NotFound'
48992
+ '429':
48993
+ description: "Google Ads operations budget or platform quota exhausted."
48994
+ '501':
48995
+ description: "Only supported on Google Ads."
48996
+ put:
48997
+ operationId: updateAccountSitelinks
48998
+ summary: Update account sitelinks
48999
+ x-resource-group: ads
49000
+ tags:
49001
+ - Ad Accounts
49002
+ x-platforms:
49003
+ - google
49004
+ description: "Edits existing Google assets in place. Send updates with assetResourceName and the fields to change.\
49005
+ \ An asset is shared: changes affect every attachment using it. Omitted fields stay unchanged. The operation\
49006
+ \ consumes the Google operations budget and invalidates affected cached lists."
49007
+ security:
49008
+ - bearerAuth: []
49009
+ requestBody:
49010
+ required: true
49011
+ content:
49012
+ application/json:
49013
+ schema:
49014
+ type: object
49015
+ required:
49016
+ - accountId
49017
+ - updates
49018
+ properties:
49019
+ accountId:
49020
+ type: string
49021
+ pattern: ^[a-fA-F0-9]{24}$
49022
+ description: "Zernio Google Ads connection id."
49023
+ customerId:
49024
+ type: string
49025
+ pattern: ^\d+$
49026
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
49027
+ updates:
49028
+ type: array
49029
+ items:
49030
+ type: object
49031
+ required:
49032
+ - assetResourceName
49033
+ properties:
49034
+ assetResourceName:
49035
+ type: string
49036
+ pattern: ^customers/\d+/assets/\d+$
49037
+ description: "Asset resource name returned by a list operation. Must belong to the selected\
49038
+ \ customer."
49039
+ sitelinkAsset:
49040
+ type: object
49041
+ properties:
49042
+ linkText:
49043
+ type: string
49044
+ minLength: 1
49045
+ maxLength: 25
49046
+ description1:
49047
+ type: string
49048
+ maxLength: 35
49049
+ description2:
49050
+ type: string
49051
+ maxLength: 35
49052
+ linkUrl:
49053
+ type: string
49054
+ format: uri
49055
+ description: "Alias for finalUrls with one URL. Do not supply both."
49056
+ minProperties: 1
49057
+ finalUrls:
49058
+ type: array
49059
+ items:
49060
+ type: string
49061
+ format: uri
49062
+ minItems: 1
49063
+ description: "Provide at least one field belonging to this asset type."
49064
+ minItems: 1
49065
+ maxItems: 20
49066
+ example:
49067
+ accountId: 64b1f0c8a1b2c3d4e5f60718
49068
+ customerId: '1234567890'
49069
+ updates:
49070
+ - assetResourceName: customers/1234567890/assets/123
49071
+ sitelinkAsset:
49072
+ linkText: Explore pricing
49073
+ finalUrls:
49074
+ - https://zernio.com/pricing
49075
+ responses:
49076
+ '200':
49077
+ description: "Assets returned."
46822
49078
  content:
46823
49079
  application/json:
46824
49080
  schema:
46825
- type: "object"
49081
+ type: object
46826
49082
  properties:
46827
- removed:
46828
- type: "boolean"
49083
+ updated:
49084
+ type: integer
46829
49085
  customerId:
46830
- type: "string"
46831
- pattern: "^\\d+$"
46832
- description: "Resolved Google Ads customer id."
46833
- example:
46834
- removed: true
46835
- customerId: "9122445560"
46836
- "400":
46837
- $ref: "#/components/responses/BadRequest"
46838
- "401":
46839
- $ref: "#/components/responses/Unauthorized"
46840
- "403":
46841
- description: "Ads access and permission to the selected account are required."
46842
- "404":
46843
- $ref: "#/components/responses/NotFound"
46844
- "409":
46845
- description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
46846
- "422":
46847
- description: "Google Ads connection is missing or unavailable."
46848
- "429":
49086
+ type: string
49087
+ '400':
49088
+ $ref: '#/components/responses/BadRequest'
49089
+ '401':
49090
+ $ref: '#/components/responses/Unauthorized'
49091
+ '403':
49092
+ description: "Ads access is required."
49093
+ '404':
49094
+ $ref: '#/components/responses/NotFound'
49095
+ '429':
46849
49096
  description: "Google Ads operations budget or platform quota exhausted."
46850
- "501":
46851
- description: "Available only on Google Ads."
46852
- /v1/ads/accounts/negative-keyword-lists/{listId}/keywords:
46853
- put:
46854
- x-resource-group: "ads"
46855
- operationId: "replaceAdNegativeKeywordListKeywords"
49097
+ '501':
49098
+ description: "Only supported on Google Ads."
49099
+ delete:
49100
+ operationId: removeAccountSitelink
49101
+ summary: Remove account sitelink
49102
+ x-resource-group: ads
46856
49103
  tags:
46857
- - "Ad Accounts"
49104
+ - Ad Accounts
46858
49105
  x-platforms:
46859
- - "google"
46860
- summary: "Replace negative list keywords"
46861
- description: "Replaces the full desired keyword set. Existing keywords are diffed by normalized text and match type; creates and removals are applied atomically in one mutation. Unchanged criteria retain their ids. Send an empty keywords array to clear the list. Changes affect every campaign using this list. Each create or removal consumes one daily operation; the entire batch must fit the remaining quota."
49106
+ - google
49107
+ description: "Removes the customer_asset attachment only. The underlying shared asset and its campaign or ad-group\
49108
+ \ attachments remain."
46862
49109
  security:
46863
- - bearerAuth: []
46864
- parameters:
46865
- - name: "listId"
46866
- in: "path"
46867
- required: true
46868
- schema:
46869
- type: "string"
46870
- pattern: "^\\d+$"
46871
- description: "Google shared set id."
49110
+ - bearerAuth: []
46872
49111
  requestBody:
46873
49112
  required: true
46874
49113
  content:
46875
49114
  application/json:
46876
49115
  schema:
46877
- type: "object"
46878
- additionalProperties: false
49116
+ type: object
46879
49117
  required:
46880
- - "accountId"
46881
- - "keywords"
49118
+ - accountId
49119
+ - assetId
46882
49120
  properties:
46883
49121
  accountId:
46884
- type: "string"
46885
- pattern: "^[a-fA-F0-9]{24}$"
46886
- description: "Zernio SocialAccount id."
49122
+ type: string
49123
+ pattern: ^[a-fA-F0-9]{24}$
49124
+ description: "Zernio Google Ads connection id."
46887
49125
  customerId:
46888
- type: "string"
46889
- pattern: "^\\d+$"
46890
- description: "Connected Google Ads customer id, without dashes. Required when the connection has multiple customers."
46891
- platform:
46892
- type: "string"
46893
- enum:
46894
- - "facebook"
46895
- - "instagram"
46896
- - "tiktok"
46897
- - "linkedin"
46898
- - "pinterest"
46899
- - "google"
46900
- - "twitter"
46901
- - "openai"
46902
- description: "Optional courtesy field. The resolved account or campaign determines support; other platforms return 501."
46903
- keywords:
46904
- type: "array"
46905
- maxItems: 5000
46906
- items:
46907
- $ref: "#/components/schemas/KeywordEntry"
46908
- description: "Full desired keyword set. Bare strings use broad match. Send [] to clear the list."
49126
+ type: string
49127
+ pattern: ^\d+$
49128
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
49129
+ assetId:
49130
+ type: string
49131
+ pattern: ^\d+$
46909
49132
  example:
46910
- accountId: "69ce75d483e990e1c01ccfe4"
46911
- customerId: "9122445560"
46912
- keywords:
46913
- - "free"
46914
- - text: "jobs"
46915
- matchType: "phrase"
49133
+ accountId: 64b1f0c8a1b2c3d4e5f60718
49134
+ customerId: '1234567890'
49135
+ assetId: '123'
46916
49136
  responses:
46917
- "200":
46918
- description: "Successful response."
49137
+ '200':
49138
+ description: "Assets returned."
46919
49139
  content:
46920
49140
  application/json:
46921
49141
  schema:
46922
- type: "object"
49142
+ type: object
46923
49143
  properties:
46924
- created:
46925
- type: "integer"
46926
- description: "New criteria or campaign links created."
46927
49144
  removed:
46928
- type: "integer"
46929
- description: "Existing criteria or campaign links removed."
49145
+ type: boolean
46930
49146
  customerId:
46931
- type: "string"
46932
- pattern: "^\\d+$"
46933
- description: "Resolved Google Ads customer id."
46934
- example:
46935
- created: 1
46936
- removed: 1
46937
- customerId: "9122445560"
46938
- "400":
46939
- $ref: "#/components/responses/BadRequest"
46940
- "401":
46941
- $ref: "#/components/responses/Unauthorized"
46942
- "403":
46943
- description: "Ads access and permission to the selected account are required."
46944
- "404":
46945
- $ref: "#/components/responses/NotFound"
46946
- "409":
46947
- description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
46948
- "422":
46949
- description: "Google Ads connection is missing or unavailable."
46950
- "429":
49147
+ type: string
49148
+ '400':
49149
+ $ref: '#/components/responses/BadRequest'
49150
+ '401':
49151
+ $ref: '#/components/responses/Unauthorized'
49152
+ '403':
49153
+ description: "Ads access is required."
49154
+ '404':
49155
+ $ref: '#/components/responses/NotFound'
49156
+ '429':
46951
49157
  description: "Google Ads operations budget or platform quota exhausted."
46952
- "501":
46953
- description: "Available only on Google Ads."
46954
- /v1/ads/campaigns/{campaignId}/negative-keyword-lists:
49158
+ '501':
49159
+ description: "Only supported on Google Ads."
49160
+
49161
+ /v1/ads/accounts/structured-snippets:
46955
49162
  get:
46956
- x-resource-group: "ads"
46957
- operationId: "listCampaignNegativeKeywordLists"
49163
+ operationId: listAccountStructuredSnippets
49164
+ summary: List account snippets
49165
+ x-resource-group: ads
46958
49166
  tags:
46959
- - "Ad Campaigns"
49167
+ - Ad Accounts
46960
49168
  x-platforms:
46961
- - "google"
46962
- summary: "List campaign negative lists"
46963
- description: "Returns shared negative keyword lists attached to the campaign, separate from campaign-level negative keywords. Google Ads shared negative keyword lists (shared_set type NEGATIVE_KEYWORDS). Reads are cached for 10 minutes; quota exhaustion may return the last successful result for up to 7 days with stale=true. Customer selection is limited to this connection and its account scope."
49169
+ - google
49170
+ description: "Lists directly attached Google assets. Fresh reads are cached for 10 minutes; exhausted quota\
49171
+ \ may return the last successful read with stale=true. Inherited assets are not included."
46964
49172
  security:
46965
- - bearerAuth: []
49173
+ - bearerAuth: []
46966
49174
  parameters:
46967
- - name: "campaignId"
46968
- in: "path"
46969
- required: true
46970
- schema:
46971
- type: "string"
46972
- pattern: "^\\d+$"
46973
- description: "Google campaign id."
46974
- - name: "platform"
46975
- in: "query"
46976
- schema:
46977
- type: "string"
46978
- enum:
46979
- - "facebook"
46980
- - "instagram"
46981
- - "tiktok"
46982
- - "linkedin"
46983
- - "pinterest"
46984
- - "google"
46985
- - "twitter"
46986
- - "openai"
46987
- description: "Optional courtesy field. The resolved account or campaign determines support; other platforms return 501."
49175
+ - name: accountId
49176
+ in: query
49177
+ required: true
49178
+ schema:
49179
+ type: string
49180
+ pattern: ^[a-fA-F0-9]{24}$
49181
+ description: "Zernio Google Ads connection id."
49182
+ - name: customerId
49183
+ in: query
49184
+ schema:
49185
+ type: string
49186
+ pattern: ^\d+$
49187
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
46988
49188
  responses:
46989
- "200":
46990
- description: "Successful response."
49189
+ '200':
49190
+ description: "Assets returned."
46991
49191
  content:
46992
49192
  application/json:
46993
49193
  schema:
46994
- type: "object"
49194
+ type: object
46995
49195
  properties:
46996
- lists:
46997
- type: "array"
46998
- items:
46999
- $ref: "#/components/schemas/AdNegativeKeywordList"
47000
49196
  customerId:
47001
- type: "string"
47002
- pattern: "^\\d+$"
47003
- description: "Resolved Google Ads customer id."
49197
+ type: string
49198
+ structuredSnippets:
49199
+ type: array
49200
+ items:
49201
+ type: object
49202
+ properties:
49203
+ assetId:
49204
+ type: string
49205
+ status:
49206
+ type: string
49207
+ assetResourceName:
49208
+ type: string
49209
+ customerAssetResourceName:
49210
+ type: string
49211
+ header:
49212
+ type: string
49213
+ values:
49214
+ type: array
49215
+ items:
49216
+ type: string
47004
49217
  cachedAt:
47005
49218
  type:
47006
- - "string"
47007
- - "null"
47008
- format: "date-time"
47009
- description: "Last successful fetch time, or null without cache storage."
49219
+ - string
49220
+ - 'null'
49221
+ format: date-time
49222
+ description: "Time of the cached Google read. Null when no cache was used."
47010
49223
  stale:
47011
- type: "boolean"
47012
- description: "True when quota exhaustion caused the last successful cached result to be served."
47013
- example:
47014
- lists:
47015
- - id: "1234567890"
47016
- resourceName: "customers/9122445560/sharedSets/1234567890"
47017
- name: "Excluded searches"
47018
- memberCount: 2
47019
- referenceCount: 1
47020
- customerId: "9122445560"
47021
- cachedAt: "2026-09-09T10:00:00Z"
47022
- stale: false
47023
- "400":
47024
- $ref: "#/components/responses/BadRequest"
47025
- "401":
47026
- $ref: "#/components/responses/Unauthorized"
47027
- "403":
47028
- description: "Ads access and permission to the selected account are required."
47029
- "404":
47030
- $ref: "#/components/responses/NotFound"
47031
- "409":
47032
- description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
47033
- "422":
47034
- description: "Google Ads connection is missing or unavailable."
47035
- "429":
49224
+ type: boolean
49225
+ description: "True when exhausted quota required returning the last successful read."
49226
+ '400':
49227
+ $ref: '#/components/responses/BadRequest'
49228
+ '401':
49229
+ $ref: '#/components/responses/Unauthorized'
49230
+ '403':
49231
+ description: "Ads access is required."
49232
+ '404':
49233
+ $ref: '#/components/responses/NotFound'
49234
+ '429':
47036
49235
  description: "Google Ads operations budget or platform quota exhausted."
47037
- "501":
47038
- description: "Available only on Google Ads."
47039
- put:
47040
- x-resource-group: "ads"
47041
- operationId: "replaceCampaignNegativeKeywordLists"
49236
+ '501':
49237
+ description: "Only supported on Google Ads."
49238
+ post:
49239
+ operationId: addAccountStructuredSnippets
49240
+ summary: Add account snippets
49241
+ x-resource-group: ads
47042
49242
  tags:
47043
- - "Ad Campaigns"
49243
+ - Ad Accounts
47044
49244
  x-platforms:
47045
- - "google"
47046
- summary: "Replace campaign negative lists"
47047
- description: "Sets the full desired set of shared negative keyword list associations on this campaign. Send listIds=[] to detach all negative keyword lists. Only campaign_shared_set links are changed; the lists and their keywords are preserved. Every list must belong to the campaign customer and have type NEGATIVE_KEYWORDS."
49245
+ - google
49246
+ description: "Creates assets and customer_asset links for this Google customer. Links apply at account level."
47048
49247
  security:
47049
- - bearerAuth: []
47050
- parameters:
47051
- - name: "campaignId"
47052
- in: "path"
47053
- required: true
47054
- schema:
47055
- type: "string"
47056
- pattern: "^\\d+$"
47057
- description: "Google campaign id."
49248
+ - bearerAuth: []
47058
49249
  requestBody:
47059
49250
  required: true
47060
49251
  content:
47061
49252
  application/json:
47062
49253
  schema:
47063
- type: "object"
47064
- additionalProperties: false
49254
+ type: object
47065
49255
  required:
47066
- - "listIds"
49256
+ - accountId
49257
+ - structuredSnippets
47067
49258
  properties:
47068
- platform:
47069
- type: "string"
47070
- enum:
47071
- - "facebook"
47072
- - "instagram"
47073
- - "tiktok"
47074
- - "linkedin"
47075
- - "pinterest"
47076
- - "google"
47077
- - "twitter"
47078
- - "openai"
47079
- description: "Optional courtesy field. The resolved account or campaign determines support; other platforms return 501."
47080
- listIds:
47081
- type: "array"
47082
- maxItems: 20
49259
+ accountId:
49260
+ type: string
49261
+ pattern: ^[a-fA-F0-9]{24}$
49262
+ description: "Zernio Google Ads connection id."
49263
+ customerId:
49264
+ type: string
49265
+ pattern: ^\d+$
49266
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
49267
+ structuredSnippets:
49268
+ type: array
47083
49269
  items:
47084
- type: "string"
47085
- pattern: "^\\d+$"
47086
- description: "Shared negative keyword list id."
49270
+ $ref: '#/components/schemas/GoogleStructuredSnippet'
49271
+ minItems: 1
49272
+ maxItems: 20
47087
49273
  example:
47088
- platform: "google"
47089
- listIds:
47090
- - "1234567890"
47091
- responses:
47092
- "200":
47093
- description: "Successful response."
47094
- content:
47095
- application/json:
47096
- schema:
47097
- type: "object"
47098
- properties:
47099
- created:
47100
- type: "integer"
47101
- description: "New criteria or campaign links created."
47102
- removed:
47103
- type: "integer"
47104
- description: "Existing criteria or campaign links removed."
47105
- customerId:
47106
- type: "string"
47107
- pattern: "^\\d+$"
47108
- description: "Resolved Google Ads customer id."
47109
- example:
47110
- created: 1
47111
- removed: 0
47112
- customerId: "9122445560"
47113
- "400":
47114
- $ref: "#/components/responses/BadRequest"
47115
- "401":
47116
- $ref: "#/components/responses/Unauthorized"
47117
- "403":
47118
- description: "Ads access and permission to the selected account are required."
47119
- "404":
47120
- $ref: "#/components/responses/NotFound"
47121
- "409":
47122
- description: "Ambiguous campaign or account selection. Use a profile-scoped key. A list still attached to a campaign may also be rejected by Google."
47123
- "422":
47124
- description: "Google Ads connection is missing or unavailable."
47125
- "429":
47126
- description: "Google Ads operations budget or platform quota exhausted."
47127
- "501":
47128
- description: "Available only on Google Ads."
47129
-
47130
- /v1/ads/accounts/callouts:
47131
- get:
47132
- x-resource-group: "ads"
47133
- operationId: listAccountCallouts
47134
- tags: ["Ad Accounts"]
47135
- x-platforms: ["google"]
47136
- summary: List account-level callout extensions
47137
- description: |-
47138
- Google Ads compliance row C.75: callout assets linked at the CUSTOMER
47139
- level via `customer_asset` (not a campaign or ad group), so they serve
47140
- fleet-wide across the account. Google only; every other platform
47141
- returns 501. Cached for the quota window (10 minutes fresh, up to 7
47142
- days last-good), and gated by the shared Google Ads operations budget
47143
- on a cache miss. The response carries `cachedAt` and `stale`, set when
47144
- a quota-exhausted call falls back to the last-good copy instead of a
47145
- live read.
47146
- security:
47147
- - bearerAuth: []
47148
- parameters:
47149
- - { name: accountId, in: query, required: true, schema: { type: string }, description: "Google ads SocialAccount id." }
47150
- - { name: customerId, in: query, schema: { type: string }, description: "Numeric Google Ads customer id (no dashes). Defaults to the account's connected customer." }
49274
+ accountId: 64b1f0c8a1b2c3d4e5f60718
49275
+ customerId: '1234567890'
49276
+ structuredSnippets:
49277
+ - header: Types
49278
+ values:
49279
+ - Scheduling
49280
+ - Analytics
49281
+ - Messaging
47151
49282
  responses:
47152
- '200':
47153
- description: Account-level callouts
49283
+ '201':
49284
+ description: "Assets created and attached."
47154
49285
  content:
47155
49286
  application/json:
47156
49287
  schema:
47157
49288
  type: object
47158
49289
  properties:
47159
- customerId: { type: string }
47160
- callouts:
49290
+ customerId:
49291
+ type: string
49292
+ structuredSnippets:
47161
49293
  type: array
47162
49294
  items:
47163
49295
  type: object
47164
49296
  properties:
47165
- assetId: { type: string }
47166
- text: { type: string }
47167
- status: { type: string, description: "customer_asset.status, e.g. ENABLED, REMOVED, PAUSED." }
47168
- cachedAt: { type: [string, "null"], format: date-time, description: "When this list was fetched from Google. Null when it was never served from cache." }
47169
- stale: { type: boolean, description: "True when Google's daily API quota was exhausted and this is the last successful fetch, not a live read." }
47170
- '400': { $ref: '#/components/responses/BadRequest' }
47171
- '401': { $ref: '#/components/responses/Unauthorized' }
49297
+ assetId:
49298
+ type: string
49299
+ header:
49300
+ type: string
49301
+ enum:
49302
+ - Amenities
49303
+ - Brands
49304
+ - Courses
49305
+ - Degree programs
49306
+ - Destinations
49307
+ - Featured hotels
49308
+ - Insurance coverage
49309
+ - Models
49310
+ - Neighborhoods
49311
+ - Service catalog
49312
+ - Shows
49313
+ - Styles
49314
+ - Types
49315
+ values:
49316
+ type: array
49317
+ minItems: 3
49318
+ maxItems: 10
49319
+ items:
49320
+ type: string
49321
+ minLength: 1
49322
+ maxLength: 25
49323
+ '400':
49324
+ $ref: '#/components/responses/BadRequest'
49325
+ '401':
49326
+ $ref: '#/components/responses/Unauthorized'
47172
49327
  '403':
47173
- description: Ads access required (Ads add-on on legacy plans, included on usage-based plans).
47174
- '404': { $ref: '#/components/responses/NotFound' }
47175
- '429': { description: Google Ads operations budget exhausted; retry later }
47176
- '501': { description: Only available on Google Ads accounts }
47177
-
47178
- post:
47179
- x-resource-group: "ads"
47180
- operationId: addAccountCallouts
47181
- tags: ["Ad Accounts"]
47182
- x-platforms: ["google"]
47183
- summary: Add account-level callout extensions
47184
- description: |-
47185
- Creates one asset plus one `customerAsset` link (field type CALLOUT)
47186
- per callout text, in a single mutate. Google only; every other
47187
- platform returns 501.
49328
+ description: "Ads access is required."
49329
+ '404':
49330
+ $ref: '#/components/responses/NotFound'
49331
+ '429':
49332
+ description: "Google Ads operations budget or platform quota exhausted."
49333
+ '501':
49334
+ description: "Only supported on Google Ads."
49335
+ put:
49336
+ operationId: updateAccountStructuredSnippets
49337
+ summary: Update account snippets
49338
+ x-resource-group: ads
49339
+ tags:
49340
+ - Ad Accounts
49341
+ x-platforms:
49342
+ - google
49343
+ description: "Edits existing Google assets in place. Send updates with assetResourceName and the fields to change.\
49344
+ \ An asset is shared: changes affect every attachment using it. Omitted fields stay unchanged. The operation\
49345
+ \ consumes the Google operations budget and invalidates affected cached lists."
47188
49346
  security:
47189
- - bearerAuth: []
49347
+ - bearerAuth: []
47190
49348
  requestBody:
47191
49349
  required: true
47192
49350
  content:
47193
49351
  application/json:
47194
49352
  schema:
47195
49353
  type: object
47196
- required: [accountId, callouts]
49354
+ required:
49355
+ - accountId
49356
+ - updates
47197
49357
  properties:
47198
- accountId: { type: string, description: "Zernio SocialAccount id owning the Google Ads connection." }
47199
- customerId: { type: string, description: "Numeric Google Ads customer id. Only required when the connection has more than one." }
47200
- callouts:
49358
+ accountId:
49359
+ type: string
49360
+ pattern: ^[a-fA-F0-9]{24}$
49361
+ description: "Zernio Google Ads connection id."
49362
+ customerId:
49363
+ type: string
49364
+ pattern: ^\d+$
49365
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
49366
+ updates:
47201
49367
  type: array
49368
+ items:
49369
+ type: object
49370
+ required:
49371
+ - assetResourceName
49372
+ properties:
49373
+ assetResourceName:
49374
+ type: string
49375
+ pattern: ^customers/\d+/assets/\d+$
49376
+ description: "Asset resource name returned by a list operation. Must belong to the selected\
49377
+ \ customer."
49378
+ structuredSnippetAsset:
49379
+ $ref: '#/components/schemas/GoogleStructuredSnippet'
49380
+ description: "Provide at least one field belonging to this asset type."
47202
49381
  minItems: 1
47203
49382
  maxItems: 20
47204
- items: { type: string, minLength: 1, maxLength: 25 }
47205
- description: 'Callout text, 1-25 characters each; up to 20 per request (Google''s CalloutAsset limits).'
49383
+ example:
49384
+ accountId: 64b1f0c8a1b2c3d4e5f60718
49385
+ customerId: '1234567890'
49386
+ updates:
49387
+ - assetResourceName: customers/1234567890/assets/123
49388
+ structuredSnippetAsset:
49389
+ header: Types
49390
+ values:
49391
+ - Scheduling
49392
+ - Reporting
49393
+ - Messaging
47206
49394
  responses:
47207
- '201':
47208
- description: Callouts created
49395
+ '200':
49396
+ description: "Assets returned."
47209
49397
  content:
47210
49398
  application/json:
47211
49399
  schema:
47212
49400
  type: object
47213
49401
  properties:
47214
- customerId: { type: string }
47215
- callouts:
47216
- type: array
47217
- items:
47218
- type: object
47219
- properties:
47220
- assetId: { type: string }
47221
- text: { type: string }
47222
- '400': { $ref: '#/components/responses/BadRequest' }
47223
- '401': { $ref: '#/components/responses/Unauthorized' }
49402
+ updated:
49403
+ type: integer
49404
+ customerId:
49405
+ type: string
49406
+ '400':
49407
+ $ref: '#/components/responses/BadRequest'
49408
+ '401':
49409
+ $ref: '#/components/responses/Unauthorized'
47224
49410
  '403':
47225
- description: Ads access required (Ads add-on on legacy plans, included on usage-based plans).
47226
- '404': { $ref: '#/components/responses/NotFound' }
47227
- '501': { description: Only supported on Google Ads }
47228
-
49411
+ description: "Ads access is required."
49412
+ '404':
49413
+ $ref: '#/components/responses/NotFound'
49414
+ '429':
49415
+ description: "Google Ads operations budget or platform quota exhausted."
49416
+ '501':
49417
+ description: "Only supported on Google Ads."
47229
49418
  delete:
47230
- x-resource-group: "ads"
47231
- operationId: removeAccountCallout
47232
- tags: ["Ad Accounts"]
47233
- x-platforms: ["google"]
47234
- summary: Remove an account-level callout extension
47235
- description: |-
47236
- Removes the `customerAsset` link (`customers/{cid}/customerAssets/{assetId}~CALLOUT`).
47237
- Google only; every other platform returns 501.
49419
+ operationId: removeAccountStructuredSnippet
49420
+ summary: Remove account snippet
49421
+ x-resource-group: ads
49422
+ tags:
49423
+ - Ad Accounts
49424
+ x-platforms:
49425
+ - google
49426
+ description: "Removes the customer_asset attachment only. The underlying shared asset and its campaign or ad-group\
49427
+ \ attachments remain."
47238
49428
  security:
47239
- - bearerAuth: []
49429
+ - bearerAuth: []
47240
49430
  requestBody:
47241
49431
  required: true
47242
49432
  content:
47243
49433
  application/json:
47244
49434
  schema:
47245
49435
  type: object
47246
- required: [accountId, assetId]
49436
+ required:
49437
+ - accountId
49438
+ - assetId
47247
49439
  properties:
47248
- accountId: { type: string, description: "Zernio SocialAccount id owning the Google Ads connection." }
47249
- customerId: { type: string, description: "Numeric Google Ads customer id. Only required when the connection has more than one." }
47250
- assetId: { type: string, description: "Numeric asset id from GET /v1/ads/accounts/callouts." }
49440
+ accountId:
49441
+ type: string
49442
+ pattern: ^[a-fA-F0-9]{24}$
49443
+ description: "Zernio Google Ads connection id."
49444
+ customerId:
49445
+ type: string
49446
+ pattern: ^\d+$
49447
+ description: "Google customer id without dashes. Required when the connection has multiple customers."
49448
+ assetId:
49449
+ type: string
49450
+ pattern: ^\d+$
49451
+ example:
49452
+ accountId: 64b1f0c8a1b2c3d4e5f60718
49453
+ customerId: '1234567890'
49454
+ assetId: '123'
47251
49455
  responses:
47252
49456
  '200':
47253
- description: Callout removed
49457
+ description: "Assets returned."
47254
49458
  content:
47255
49459
  application/json:
47256
49460
  schema:
47257
49461
  type: object
47258
49462
  properties:
47259
- removed: { type: boolean }
47260
- customerId: { type: string }
47261
- '400': { $ref: '#/components/responses/BadRequest' }
47262
- '401': { $ref: '#/components/responses/Unauthorized' }
49463
+ removed:
49464
+ type: boolean
49465
+ customerId:
49466
+ type: string
49467
+ '400':
49468
+ $ref: '#/components/responses/BadRequest'
49469
+ '401':
49470
+ $ref: '#/components/responses/Unauthorized'
47263
49471
  '403':
47264
- description: Ads access required (Ads add-on on legacy plans, included on usage-based plans).
47265
- '404': { $ref: '#/components/responses/NotFound' }
47266
- '501': { description: Only supported on Google Ads }
49472
+ description: "Ads access is required."
49473
+ '404':
49474
+ $ref: '#/components/responses/NotFound'
49475
+ '429':
49476
+ description: "Google Ads operations budget or platform quota exhausted."
49477
+ '501':
49478
+ description: "Only supported on Google Ads."
47267
49479
 
47268
49480
  /v1/ads/accounts/finance:
47269
49481
  get:
@@ -47922,20 +50134,7 @@ paths:
47922
50134
  campaignName: { type: string, maxLength: 255, description: "Meta only. Exact campaign name. Overrides the default `<name> - Campaign`." }
47923
50135
  adSetName: { type: string, maxLength: 255, description: "Meta only. 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.)" }
47924
50136
  adName: { type: string, maxLength: 255, description: "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.)" }
47925
- tracking:
47926
- type: object
47927
- description: "Meta only. Attaches pixel measurement to the ad regardless of the optimization goal (the \"Website events\" tracking row in Ads Manager). `pixelId` becomes the ad's `tracking_specs` (offsite_conversion + fb_pixel); `urlTags` becomes the ad's `url_tags` (click-tracking query params). Applied on the legacy single-creative shape, every ad of the multi-creative shape, and the attach shape. NOTE: tracking lives on the AD object and is not inherited from the ad set, so pass it on EVERY attach call that should carry the pixel."
47928
- properties:
47929
- pixelId: { type: string, description: "Meta Pixel ID to attach for offsite-conversion measurement." }
47930
- urlTags:
47931
- type: array
47932
- description: "Click-URL params appended to the ad's destination as `url_tags` (e.g. utm_source). Meta dynamic macros ({{ad.id}}, {{campaign.id}}, {{placement}}, ...) are sent through unescaped so Meta expands them; every other character is percent-encoded."
47933
- items:
47934
- type: object
47935
- required: [key, value]
47936
- properties:
47937
- key: { type: string }
47938
- value: { type: string }
50137
+ tracking: { $ref: '#/components/schemas/AdTracking' }
47939
50138
  goal:
47940
50139
  type: string
47941
50140
  enum: [engagement, traffic, awareness, video_views, lead_generation, lead_conversion, conversions, app_promotion, catalog_sales, page_likes, job_applicants]
@@ -47975,7 +50174,7 @@ paths:
47975
50174
  description: "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."
47976
50175
  validateOnly:
47977
50176
  type: boolean
47978
- description: 'Meta only, single standalone shape only (no creatives[], adSetId, or RESERVED). Dry-run: each node runs Meta''s execution_options validate_only and NOTHING is created or persisted. Children need real parents, so a fresh tree validates the campaign + creative (the ad set needs its campaign to exist, so pass existingCampaignId to validate it too; the ad itself is never validatable pre-create). A Meta validation failure returns the 400 verbatim; success returns 200 with per-node results instead of an ad.'
50177
+ description: 'Meta only. 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, existing video.id or existingCreativeId; media pools, new video uploads, creatives[], adSetId and RESERVED buying return 400. Existing campaign or creative nodes are marked skipped. Success returns 200 with per-node results; Meta rejection returns an error.'
47979
50178
  budgetAmount: { type: number, description: "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 shapes. Inherited on attach. OpenAI Ads requires a $1 minimum (its budget is lifetime-only, see budgetType)." }
47980
50179
  budgetType: { type: string, enum: [daily, lifetime], description: "Required on legacy + multi-creative shapes. Inherited on attach. OpenAI Ads accepts lifetime only (no daily-budget concept on the platform); sending daily returns 422. OpenAI Ads lifetime budgets require `endDate` to give the lifetime cap a spend window." }
47981
50180
  status:
@@ -48616,8 +50815,28 @@ paths:
48616
50815
  keywords: { type: array, maxItems: 1000, items: { $ref: '#/components/schemas/KeywordEntry' }, description: "Google Search only. Keywords on the new ad group; entries are strings (BROAD) or { text, matchType }. Editable later via PUT /v1/ads/{adId} targeting.keywords." }
48617
50816
  negativeKeywords: { type: array, maxItems: 1000, items: { $ref: '#/components/schemas/KeywordEntry' }, description: "Google Search only; other platforms return 400. Ad-group-level negative keywords on the new ad group. Editable later via PUT /v1/ads/{adId} targeting.negativeKeywords." }
48618
50817
  campaignNegativeKeywords: { type: array, maxItems: 1000, items: { $ref: '#/components/schemas/KeywordEntry' }, description: "Google Search only; other platforms return 400. Campaign-level negative keywords (campaign_criterion.negative), created alongside the ad group. Editable later via PUT /v1/ads/campaigns/{campaignId}/negative-keywords." }
48619
- additionalHeadlines: { type: array, items: { type: string }, description: "Google Search RSA only. Extra headlines." }
48620
- additionalDescriptions: { type: array, items: { type: string }, description: "Google Search RSA only. Extra descriptions." }
50818
+ additionalHeadlines:
50819
+ type: array
50820
+ items:
50821
+ oneOf:
50822
+ - type: string
50823
+ - $ref: '#/components/schemas/GoogleRsaHeadline'
50824
+ description: "Google Search RSA only. Extra text assets as strings or objects with text and optional pinnedField. Existing string input remains supported. The effective create lists, including primary text and deduplication, must contain 3-15 headlines and 2-4 descriptions; excess entries return 400."
50825
+ example:
50826
+ - Schedule Your Posts
50827
+ - text: Build With Zernio
50828
+ pinnedField: HEADLINE_2
50829
+ additionalDescriptions:
50830
+ type: array
50831
+ items:
50832
+ oneOf:
50833
+ - type: string
50834
+ - $ref: '#/components/schemas/GoogleRsaDescription'
50835
+ description: "Google Search RSA only. Extra text assets as strings or objects with text and optional pinnedField. Existing string input remains supported. The effective create lists, including primary text and deduplication, must contain 3-15 headlines and 2-4 descriptions; excess entries return 400."
50836
+ example:
50837
+ - Build social publishing into your application.
50838
+ - text: Connect social accounts with one API.
50839
+ pinnedField: DESCRIPTION_2
48621
50840
  sitelinks:
48622
50841
  type: array
48623
50842
  minItems: 2
@@ -48852,124 +51071,70 @@ paths:
48852
51071
  omitted); TikTok automates delivery within it.
48853
51072
  The budget lives on the Smart+ campaign (Campaign Budget Optimization); a `lifetime`
48854
51073
  budget additionally requires `endDate`. Cannot be combined with `adSetId`.
51074
+ userOs:
51075
+ type: array
51076
+ minItems: 1
51077
+ items: { type: string, minLength: 1 }
51078
+ description: '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.'
51079
+ userDevice:
51080
+ type: array
51081
+ minItems: 1
51082
+ items: { type: string, minLength: 1 }
51083
+ description: 'Meta only. Device models such as iPhone. Emitted as user_device. May also be supplied inside targeting.'
51084
+ isSkadnetworkAttribution:
51085
+ type: boolean
51086
+ description: '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.'
51087
+ campaignAttribution:
51088
+ type: string
51089
+ enum: [AEM, SKADNETWORK]
51090
+ description: '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.'
48855
51091
  promotedObject:
48856
- type: object
48857
- description: |
48858
- What the ad optimises against. Behaviour depends on the platform.
48859
-
48860
- **Meta**: forwarded to the ad set's `promoted_object` (snake-cased).
48861
- Required for goals whose ad-set optimization_goal points at a specific
48862
- event/page/app (without it Meta rejects the ad-set create with
48863
- `error_subcode: 1815430` "Please select a promoted object for your ad set"):
48864
- - `goal: conversions` / `lead_conversion` (OFFSITE_CONVERSIONS): requires `pixelId` + `customEventType`, or `customConversionId` when optimising against a Custom Conversion (the conversion carries its own event definition). For a pixel CUSTOM event (one you named yourself in CAPI/Events Manager), send `customEventType: OTHER` + `customEventStr` with the event name.
48865
- - `goal: app_promotion` (APP_INSTALLS): requires `applicationId` + `objectStoreUrl`
48866
- - `goal: lead_generation` (LEAD_GENERATION): `pageId` is auto-filled from the connected Page when omitted
48867
-
48868
- Other Meta goals (engagement, traffic, awareness, video_views) ignore this field.
48869
-
48870
- **TikTok**: used by `goal: conversions` and the Smart+ goals (`smartPlus: true`).
48871
- - `pixelId` maps to the ad group's `pixel_id`. Required: a TikTok website-conversion
48872
- ad group without a pixel is rejected with `40002: Please select a pixel`.
48873
- - `customEventType` maps to the ad group's `optimization_event` (the pixel event to
48874
- optimise for). Optional on the regular conversions flow, required on Smart+.
48875
- See the `customEventType` field below for the valid TikTok codes.
48876
- - `applicationId` (Smart+ `goal: app_promotion` only) maps to the ad group's `app_id`:
48877
- the App ID of an app registered on the TikTok Ads account (Assets → Events →
48878
- App Events). Install optimization needs the app's MMP tracking configured.
48879
-
48880
- The remaining `promotedObject.*` fields are Meta-only. Platforms other than
48881
- Meta and TikTok ignore `promotedObject` entirely.
48882
- properties:
48883
- pixelId:
48884
- type: string
48885
- description: |
48886
- Pixel ID. **Meta:** Facebook Pixel ID, required for `goal: conversions`.
48887
- Requires `customEventType` alongside it; Meta rejects any promoted_object
48888
- carrying `pixel_id` without `custom_event_type` (error_subcode 1885014),
48889
- even when `customConversionId` is also present.
48890
- **TikTok:** TikTok Pixel ID, required for `goal: conversions`.
48891
- To discover the pixels an ad account can use, call
48892
- `GET /v1/accounts/{accountId}/tracking-tags?adAccountId=act_...` (each entry
48893
- carries `kind` and `ownerAdAccountId`), or
48894
- `GET /v1/accounts/{accountId}/conversion-destinations`. Note this is a
48895
- different resource from `GET /v1/ads/{adId}/tracking-tags`, which reads an
48896
- ad's click-URL params (`url_tags`), not pixels.
48897
- customEventType:
48898
- type: string
48899
- description: |
48900
- The event the campaign/ad group optimises against.
48901
-
48902
- **Meta:** standard event like `PURCHASE`, `LEAD`, `COMPLETE_REGISTRATION`,
48903
- `ADD_TO_CART`. Uppercased internally so callers can pass any case. Required
48904
- for `goal: conversions`.
48905
-
48906
- **TikTok:** an `optimization_event` code (UPPER_SNAKE, not Meta's vocabulary
48907
- and not PascalCase), OR the exact event name shown in TikTok Events Manager
48908
- (auto-resolved to its code). Must be one of the event types your TikTok
48909
- Pixel tracks; custom events are not optimizable. Current taxonomy:
48910
- `SHOPPING` (Purchase), `ON_WEB_CART` (Add to Cart), `INITIATE_ORDER`
48911
- (Initiate Checkout), `FORM` (Lead), `ON_WEB_REGISTER` (Complete
48912
- Registration), `ON_WEB_DETAIL` (View Content). `ON_WEB_ORDER` is
48913
- deprecated. On rejection the error lists the event types your pixel
48914
- actually tracks. Optional for `goal: conversions`.
48915
- customEventStr:
48916
- type: string
48917
- description: |
48918
- Meta only. Pixel custom-event name to optimise against (Meta's
48919
- `custom_event_str`), exactly as it appears in Events Manager and in your
48920
- CAPI payloads (case-sensitive, not uppercased). Requires
48921
- `customEventType: OTHER`, and `OTHER` requires this field (400 either way).
48922
- The same as picking a custom event in Ads Manager's conversion-event
48923
- dropdown. For rule-based Custom Conversions use `customConversionId`
48924
- instead.
48925
- pageId:
48926
- type: string
48927
- description: |
48928
- Facebook Page ID. Used by `goal: lead_generation`. Auto-filled from the
48929
- connected Page when omitted.
48930
- applicationId:
48931
- type: string
48932
- description: "App ID. Required for `goal: app_promotion`."
48933
- objectStoreUrl:
48934
- type: string
48935
- format: uri
48936
- description: "App Store / Play Store listing URL. Required for `goal: app_promotion`."
48937
- customConversionId:
48938
- type: string
48939
- description: |
48940
- Custom Conversion ID, when optimising against one instead of a standard
48941
- event. Accepted alone by this API, without `pixelId` or `customEventType`.
48942
- If `pixelId` is also sent, `customEventType` is still required on the
48943
- promoted_object (Meta rejects `pixel_id` without `custom_event_type`,
48944
- error_subcode 1885014).
48945
- productCatalogId:
48946
- type: string
48947
- description: "Optional catalog ID. If supplied with productSetId, the set must belong to this catalog. A catalog ID cannot replace productSetId."
48948
- productSetId:
48949
- type: string
48950
- description: "Meta product SET ID from GET /v1/ads/catalogs/{catalogId}/product-sets. Zernio checks that the token can read the set and its product_catalog before creation. A catalog ID or inaccessible set returns a precise 400 naming promotedObject.productSetId. A mismatch with productCatalogId names promotedObject.productCatalogId."
48951
- offlineConversionDataSetId:
48952
- type: string
48953
- description: 'Meta only. Offline event set (dataset) to optimise toward. Post-merger these are datasets: the id is the dataset id (for pixel-backed datasets, the pixel id).'
48954
- whatsappPhoneNumber:
48955
- type: string
48956
- description: 'Meta only. WhatsApp number on messaging-destination ad sets.'
48957
- additionalProperties: false
48958
- example:
48959
- accountId: '69fc524892b3d8e85f893e73'
48960
- adAccountId: act_123456789
48961
- name: Autumn promotion
48962
- goal: traffic
48963
- budgetAmount: 5
48964
- budgetType: daily
48965
- status: PAUSED
48966
- headline: Save on your next order
48967
- body: Use SAVE20 at checkout.
48968
- callToAction: SHOP_NOW
48969
- linkUrl: https://example.com/shop
48970
- imageUrl: https://example.com/ad.jpg
48971
- promotion: { type: PERCENTAGE_OFF, value: 20, code: SAVE20 }
48972
- creativeFeatures: { auto_promotion_tag: OPT_OUT }
51092
+ $ref: '#/components/schemas/AdPromotedObject'
51093
+ examples:
51094
+ appPromotion:
51095
+ value:
51096
+ accountId: '69fc524892b3d8e85f893e73'
51097
+ adAccountId: 'act_757082720485182'
51098
+ name: 'iOS app installs'
51099
+ goal: app_promotion
51100
+ isSkadnetworkAttribution: true
51101
+ campaignAttribution: SKADNETWORK
51102
+ buyingType: AUCTION
51103
+ billingEvent: IMPRESSIONS
51104
+ optimizationGoal: APP_INSTALLS
51105
+ promotedObject:
51106
+ applicationId: '123456789'
51107
+ objectStoreUrl: 'https://apps.apple.com/us/app/id123456789'
51108
+ linkUrl: 'https://apps.apple.com/us/app/id123456789'
51109
+ headline: 'Install our app'
51110
+ body: 'Get started today.'
51111
+ callToAction: INSTALL_MOBILE_APP
51112
+ imageUrl: 'https://example.com/app.jpg'
51113
+ targeting:
51114
+ countries: [US]
51115
+ userOs: [iOS_ver_14.0_and_above]
51116
+ tracking:
51117
+ urlTags: [{ key: utm_content, value: '{{ad.id}}' }]
51118
+ budgetAmount: 1
51119
+ budgetType: daily
51120
+ status: PAUSED
51121
+ validateOnly: true
51122
+ retailPromotion:
51123
+ value:
51124
+ accountId: '69fc524892b3d8e85f893e73'
51125
+ adAccountId: act_123456789
51126
+ name: Autumn promotion
51127
+ goal: traffic
51128
+ budgetAmount: 5
51129
+ budgetType: daily
51130
+ status: PAUSED
51131
+ headline: Save on your next order
51132
+ body: Use SAVE20 at checkout.
51133
+ callToAction: SHOP_NOW
51134
+ linkUrl: https://example.com/shop
51135
+ imageUrl: https://example.com/ad.jpg
51136
+ promotion: { type: PERCENTAGE_OFF, value: 20, code: SAVE20 }
51137
+ creativeFeatures: { auto_promotion_tag: OPT_OUT }
48973
51138
  responses:
48974
51139
  '200':
48975
51140
  description: 'validateOnly dry-run passed, nothing was created'