late-sdk 0.0.544 → 0.0.545

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +52 -3
  3. data/docs/AdAudiencesApi.md +5 -5
  4. data/docs/AdCampaignsApi.md +142 -0
  5. data/docs/AdsApi.md +795 -37
  6. data/docs/CreateAdCampaign201Response.md +24 -0
  7. data/docs/CreateAdCampaignRequest.md +32 -0
  8. data/docs/CreateAdCreative201Response.md +20 -0
  9. data/docs/CreateAdCreativeRequest.md +40 -0
  10. data/docs/CreateAdCreativeRequestCarouselCardsInner.md +26 -0
  11. data/docs/CreateSmsSenderId200Response.md +22 -0
  12. data/docs/CreateSmsSenderIdRequest.md +18 -0
  13. data/docs/CreateStandaloneAd200Response.md +22 -0
  14. data/docs/CreateStandaloneAd200ResponseResultsInner.md +22 -0
  15. data/docs/CreateStandaloneAdRequest.md +4 -0
  16. data/docs/DeleteAdCreative200Response.md +20 -0
  17. data/docs/DeleteSmsSenderId200Response.md +18 -0
  18. data/docs/DiscordApi.md +153 -1
  19. data/docs/{ListDiscordGuildMembers200ResponseDataInner.md → DiscordGuildMember.md} +3 -3
  20. data/docs/{ListDiscordGuildMembers200ResponseDataInnerUser.md → DiscordGuildMemberUser.md} +2 -2
  21. data/docs/DuplicateAd200Response.md +22 -0
  22. data/docs/DuplicateAdRequest.md +28 -0
  23. data/docs/DuplicateAdSet200Response.md +22 -0
  24. data/docs/DuplicateAdSetRequest.md +36 -0
  25. data/docs/GetAdCreative200Response.md +18 -0
  26. data/docs/GetDiscordGuildMember200Response.md +18 -0
  27. data/docs/ListAdCreatives200Response.md +22 -0
  28. data/docs/ListAdImages200Response.md +22 -0
  29. data/docs/ListAdLabels200Response.md +22 -0
  30. data/docs/ListDiscordGuildMembers200Response.md +1 -1
  31. data/docs/ListHighDemandPeriods200Response.md +22 -0
  32. data/docs/ListMetaBusinesses200Response.md +20 -0
  33. data/docs/ListSmsSenderIds200Response.md +20 -0
  34. data/docs/ListSmsSenderIds200ResponseBudget.md +24 -0
  35. data/docs/ListSmsSenderIds200ResponseBudgetPendingRequest.md +22 -0
  36. data/docs/ListSmsSenderIds200ResponseSenderIdsInner.md +24 -0
  37. data/docs/RequestSmsSenderIdLimitIncrease200Response.md +20 -0
  38. data/docs/RequestSmsSenderIdLimitIncreaseRequest.md +20 -0
  39. data/docs/SMSApi.md +275 -0
  40. data/docs/SearchDiscordGuildMembers200Response.md +18 -0
  41. data/docs/UpdateAdCreative200Response.md +22 -0
  42. data/docs/UpdateAdCreativeRequest.md +20 -0
  43. data/lib/zernio-sdk/api/ad_audiences_api.rb +4 -4
  44. data/lib/zernio-sdk/api/ad_campaigns_api.rb +142 -0
  45. data/lib/zernio-sdk/api/ads_api.rb +829 -51
  46. data/lib/zernio-sdk/api/discord_api.rb +174 -2
  47. data/lib/zernio-sdk/api/sms_api.rb +254 -0
  48. data/lib/zernio-sdk/models/create_ad_campaign201_response.rb +210 -0
  49. data/lib/zernio-sdk/models/create_ad_campaign_request.rb +343 -0
  50. data/lib/zernio-sdk/models/create_ad_creative201_response.rb +157 -0
  51. data/lib/zernio-sdk/models/create_ad_creative_request.rb +424 -0
  52. data/lib/zernio-sdk/models/create_ad_creative_request_carousel_cards_inner.rb +255 -0
  53. data/lib/zernio-sdk/models/create_sms_sender_id200_response.rb +166 -0
  54. data/lib/zernio-sdk/models/create_sms_sender_id_request.rb +183 -0
  55. data/lib/zernio-sdk/models/create_standalone_ad200_response.rb +201 -0
  56. data/lib/zernio-sdk/models/create_standalone_ad200_response_results_inner.rb +212 -0
  57. data/lib/zernio-sdk/models/create_standalone_ad_request.rb +23 -1
  58. data/lib/zernio-sdk/models/delete_ad_creative200_response.rb +156 -0
  59. data/lib/zernio-sdk/models/delete_sms_sender_id200_response.rb +147 -0
  60. data/lib/zernio-sdk/models/{list_discord_guild_members200_response_data_inner.rb → discord_guild_member.rb} +5 -4
  61. data/lib/zernio-sdk/models/{list_discord_guild_members200_response_data_inner_user.rb → discord_guild_member_user.rb} +3 -3
  62. data/lib/zernio-sdk/models/duplicate_ad200_response.rb +200 -0
  63. data/lib/zernio-sdk/models/duplicate_ad_request.rb +243 -0
  64. data/lib/zernio-sdk/models/duplicate_ad_set200_response.rb +201 -0
  65. data/lib/zernio-sdk/models/duplicate_ad_set_request.rb +302 -0
  66. data/lib/zernio-sdk/models/get_ad_creative200_response.rb +148 -0
  67. data/lib/zernio-sdk/models/get_discord_guild_member200_response.rb +147 -0
  68. data/lib/zernio-sdk/models/list_ad_creatives200_response.rb +167 -0
  69. data/lib/zernio-sdk/models/list_ad_images200_response.rb +167 -0
  70. data/lib/zernio-sdk/models/list_ad_labels200_response.rb +167 -0
  71. data/lib/zernio-sdk/models/list_discord_guild_members200_response.rb +1 -1
  72. data/lib/zernio-sdk/models/list_high_demand_periods200_response.rb +168 -0
  73. data/lib/zernio-sdk/models/list_meta_businesses200_response.rb +158 -0
  74. data/lib/zernio-sdk/models/list_sms_sender_ids200_response.rb +158 -0
  75. data/lib/zernio-sdk/models/list_sms_sender_ids200_response_budget.rb +178 -0
  76. data/lib/zernio-sdk/models/list_sms_sender_ids200_response_budget_pending_request.rb +167 -0
  77. data/lib/zernio-sdk/models/list_sms_sender_ids200_response_sender_ids_inner.rb +175 -0
  78. data/lib/zernio-sdk/models/request_sms_sender_id_limit_increase200_response.rb +156 -0
  79. data/lib/zernio-sdk/models/request_sms_sender_id_limit_increase_request.rb +210 -0
  80. data/lib/zernio-sdk/models/search_discord_guild_members200_response.rb +149 -0
  81. data/lib/zernio-sdk/models/update_ad_creative200_response.rb +165 -0
  82. data/lib/zernio-sdk/models/update_ad_creative_request.rb +200 -0
  83. data/lib/zernio-sdk/version.rb +1 -1
  84. data/lib/zernio-sdk.rb +33 -2
  85. data/openapi.yaml +740 -27
  86. data/spec/api/ad_audiences_api_spec.rb +2 -2
  87. data/spec/api/ad_campaigns_api_spec.rb +25 -0
  88. data/spec/api/ads_api_spec.rb +143 -1
  89. data/spec/api/discord_api_spec.rb +30 -1
  90. data/spec/api/sms_api_spec.rb +46 -0
  91. data/spec/models/create_ad_campaign201_response_spec.rb +58 -0
  92. data/spec/models/create_ad_campaign_request_spec.rb +94 -0
  93. data/spec/models/create_ad_creative201_response_spec.rb +42 -0
  94. data/spec/models/create_ad_creative_request_carousel_cards_inner_spec.rb +60 -0
  95. data/spec/models/create_ad_creative_request_spec.rb +106 -0
  96. data/spec/models/create_sms_sender_id200_response_spec.rb +48 -0
  97. data/spec/models/create_sms_sender_id_request_spec.rb +36 -0
  98. data/spec/models/create_standalone_ad200_response_results_inner_spec.rb +56 -0
  99. data/spec/models/create_standalone_ad200_response_spec.rb +52 -0
  100. data/spec/models/create_standalone_ad_request_spec.rb +16 -0
  101. data/spec/models/delete_ad_creative200_response_spec.rb +42 -0
  102. data/spec/models/delete_sms_sender_id200_response_spec.rb +36 -0
  103. data/spec/models/{list_discord_guild_members200_response_data_inner_spec.rb → discord_guild_member_spec.rb} +6 -6
  104. data/spec/models/{list_discord_guild_members200_response_data_inner_user_spec.rb → discord_guild_member_user_spec.rb} +6 -6
  105. data/spec/models/duplicate_ad200_response_spec.rb +52 -0
  106. data/spec/models/duplicate_ad_request_spec.rb +74 -0
  107. data/spec/models/duplicate_ad_set200_response_spec.rb +52 -0
  108. data/spec/models/duplicate_ad_set_request_spec.rb +102 -0
  109. data/spec/models/get_ad_creative200_response_spec.rb +36 -0
  110. data/spec/models/get_discord_guild_member200_response_spec.rb +36 -0
  111. data/spec/models/list_ad_creatives200_response_spec.rb +48 -0
  112. data/spec/models/list_ad_images200_response_spec.rb +48 -0
  113. data/spec/models/list_ad_labels200_response_spec.rb +48 -0
  114. data/spec/models/list_high_demand_periods200_response_spec.rb +48 -0
  115. data/spec/models/list_meta_businesses200_response_spec.rb +42 -0
  116. data/spec/models/list_sms_sender_ids200_response_budget_pending_request_spec.rb +48 -0
  117. data/spec/models/list_sms_sender_ids200_response_budget_spec.rb +54 -0
  118. data/spec/models/list_sms_sender_ids200_response_sender_ids_inner_spec.rb +54 -0
  119. data/spec/models/list_sms_sender_ids200_response_spec.rb +42 -0
  120. data/spec/models/request_sms_sender_id_limit_increase200_response_spec.rb +42 -0
  121. data/spec/models/request_sms_sender_id_limit_increase_request_spec.rb +42 -0
  122. data/spec/models/search_discord_guild_members200_response_spec.rb +36 -0
  123. data/spec/models/update_ad_creative200_response_spec.rb +48 -0
  124. data/spec/models/update_ad_creative_request_spec.rb +42 -0
  125. data/zernio-sdk-0.0.545.gem +0 -0
  126. metadata +134 -10
  127. data/zernio-sdk-0.0.544.gem +0 -0
data/openapi.yaml CHANGED
@@ -3791,6 +3791,22 @@ components:
3791
3791
  timestamp:
3792
3792
  type: string
3793
3793
  format: date-time
3794
+ DiscordGuildMember:
3795
+ type: object
3796
+ description: A Discord guild member, returned verbatim from Discord's API.
3797
+ properties:
3798
+ user:
3799
+ type: object
3800
+ properties:
3801
+ id: { type: string, description: User snowflake }
3802
+ username: { type: string }
3803
+ discriminator: { type: string }
3804
+ avatar: { type: [string, "null"] }
3805
+ global_name: { type: [string, "null"], description: "User's display name (post-2023 Discord rebrand)" }
3806
+ nick: { type: [string, "null"], description: Guild-specific nickname }
3807
+ roles: { type: array, items: { type: string }, description: Snowflake IDs of roles assigned to this member }
3808
+ joined_at: { type: string, format: date-time }
3809
+ premium_since: { type: [string, "null"], format: date-time, description: When the user started boosting the server }
3794
3810
  DiscordRole:
3795
3811
  type: object
3796
3812
  description: A Discord guild role, returned verbatim from Discord's API.
@@ -20002,10 +20018,10 @@ paths:
20002
20018
  to all members joined in the last 7 days") on the actual platform shape.
20003
20019
 
20004
20020
  **Important:** this endpoint requires the privileged "Server Members
20005
- Intent" enabled on the Discord app (Developer Portal Bot tab → toggle
20006
- "Server Members Intent" ON, then Save). Without it, Discord returns an
20007
- empty array with no error. Verify the intent is enabled before relying
20008
- on this endpoint.
20021
+ Intent" on the Discord application. If the intent is not enabled,
20022
+ Discord rejects the call and this endpoint returns **403**. Single
20023
+ member lookup and prefix search (see the sibling endpoints) do not
20024
+ need the intent.
20009
20025
 
20010
20026
  Pagination: pass `after` = the last `user.id` from the previous page.
20011
20027
  Omit on the first call. Response includes a `nextCursor` and `hasMore`
@@ -20025,21 +20041,7 @@ paths:
20025
20041
  properties:
20026
20042
  data:
20027
20043
  type: array
20028
- items:
20029
- type: object
20030
- properties:
20031
- user:
20032
- type: object
20033
- properties:
20034
- id: { type: string, description: User snowflake }
20035
- username: { type: string }
20036
- discriminator: { type: string }
20037
- avatar: { type: [string, "null"] }
20038
- global_name: { type: [string, "null"], description: User's display name (post-2023 Discord rebrand) }
20039
- nick: { type: [string, "null"], description: Guild-specific nickname }
20040
- roles: { type: array, items: { type: string }, description: Snowflake IDs of roles assigned to this member }
20041
- joined_at: { type: string, format: date-time }
20042
- premium_since: { type: [string, "null"], format: date-time, description: When the user started boosting the server }
20044
+ items: { $ref: '#/components/schemas/DiscordGuildMember' }
20043
20045
  pagination:
20044
20046
  type: object
20045
20047
  properties:
@@ -20047,8 +20049,67 @@ paths:
20047
20049
  hasMore: { type: boolean }
20048
20050
  '400': { description: Invalid query params. }
20049
20051
  '401': { $ref: '#/components/responses/Unauthorized' }
20052
+ '403': { description: The Server Members Intent is not enabled on the Discord application. }
20050
20053
  '404': { description: Discord account not found or not in this guild. }
20051
20054
 
20055
+ /v1/discord/guilds/{guildId}/members/search:
20056
+ get:
20057
+ operationId: searchDiscordGuildMembers
20058
+ tags: [Discord]
20059
+ summary: Search Discord guild members
20060
+ description: |
20061
+ Search guild members whose username or nickname **starts with** the
20062
+ query (Discord matches prefixes only, not substrings).
20063
+
20064
+ Does not require the privileged Server Members Intent, so this works
20065
+ even where the full member listing returns 403.
20066
+ parameters:
20067
+ - { name: guildId, in: path, required: true, schema: { type: string } }
20068
+ - { name: accountId, in: query, required: true, schema: { type: string } }
20069
+ - { name: query, in: query, required: true, schema: { type: string, minLength: 1, maxLength: 100 }, description: Username or nickname prefix to match. }
20070
+ - { name: limit, in: query, required: false, schema: { type: integer, minimum: 1, maximum: 1000, default: 25 } }
20071
+ responses:
20072
+ '200':
20073
+ description: Matching guild members.
20074
+ content:
20075
+ application/json:
20076
+ schema:
20077
+ type: object
20078
+ properties:
20079
+ data:
20080
+ type: array
20081
+ items: { $ref: '#/components/schemas/DiscordGuildMember' }
20082
+ '400': { description: Invalid query params. }
20083
+ '401': { $ref: '#/components/responses/Unauthorized' }
20084
+ '404': { description: Discord account not found or not in this guild. }
20085
+
20086
+ /v1/discord/guilds/{guildId}/members/{userId}:
20087
+ get:
20088
+ operationId: getDiscordGuildMember
20089
+ tags: [Discord]
20090
+ summary: Get a Discord guild member
20091
+ description: |
20092
+ Fetch a single guild member by Discord user id.
20093
+
20094
+ Does not require the privileged Server Members Intent, so this works
20095
+ even where the full member listing returns 403.
20096
+ parameters:
20097
+ - { name: guildId, in: path, required: true, schema: { type: string } }
20098
+ - { name: userId, in: path, required: true, schema: { type: string }, description: Discord user snowflake. }
20099
+ - { name: accountId, in: query, required: true, schema: { type: string } }
20100
+ responses:
20101
+ '200':
20102
+ description: The guild member.
20103
+ content:
20104
+ application/json:
20105
+ schema:
20106
+ type: object
20107
+ properties:
20108
+ data: { $ref: '#/components/schemas/DiscordGuildMember' }
20109
+ '400': { $ref: '#/components/responses/BadRequest' }
20110
+ '401': { $ref: '#/components/responses/Unauthorized' }
20111
+ '404': { description: 'Discord account not found, or the user is not a member of this guild.' }
20112
+
20052
20113
  /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}:
20053
20114
  put:
20054
20115
  operationId: addDiscordMemberRole
@@ -25956,6 +26017,155 @@ paths:
25956
26017
  schema: { type: string, description: "`format=csv` download. Columns: phone_number, opted_out_at, keyword, our_number" }
25957
26018
  '401': { $ref: '#/components/responses/Unauthorized' }
25958
26019
 
26020
+ /v1/sms/sender-ids:
26021
+ post:
26022
+ operationId: createSmsSenderId
26023
+ tags: [SMS]
26024
+ summary: Create an alphanumeric sender ID
26025
+ description: |
26026
+ Registers an alphanumeric sender ID (e.g. `ZERNIO`) — a branded `from`
26027
+ for one-way international SMS. No phone number purchase or carrier
26028
+ registration is needed; once created, pass it as `from` on
26029
+ `POST /v1/sms/messages`.
26030
+
26031
+ Constraints: 3-11 characters (letters, digits, spaces; at least one
26032
+ letter). Sends cannot reach the US, Canada, or Puerto Rico, are
26033
+ text-only, and recipients cannot reply. Sender IDs that impersonate
26034
+ well-known brands or institutions are rejected, and an ID already
26035
+ registered by another workspace returns 409 (active sender IDs are
26036
+ globally unique, first-come-first-served). Creating the same sender ID
26037
+ again is a no-op (re-activates it after a delete).
26038
+ security:
26039
+ - bearerAuth: []
26040
+ requestBody:
26041
+ required: true
26042
+ content:
26043
+ application/json:
26044
+ schema:
26045
+ type: object
26046
+ required: [senderId]
26047
+ properties:
26048
+ senderId:
26049
+ type: string
26050
+ minLength: 3
26051
+ maxLength: 11
26052
+ description: 'The sender ID recipients will see (3-11 letters/digits/spaces, at least one letter, no leading/trailing space).'
26053
+ responses:
26054
+ '200':
26055
+ description: Sender ID created (or re-activated).
26056
+ content:
26057
+ application/json:
26058
+ schema:
26059
+ type: object
26060
+ properties:
26061
+ id: { type: string, description: Sender ID resource id. }
26062
+ senderId: { type: string }
26063
+ isActive: { type: boolean }
26064
+ '400': { $ref: '#/components/responses/BadRequest' }
26065
+ '401': { $ref: '#/components/responses/Unauthorized' }
26066
+ '403': { description: 'Workspace is not on usage-based billing, or already holds the maximum of 5 active sender IDs (code `sender_id_limit_reached`; raisable via support).' }
26067
+ '409': { description: 'Sender ID already registered by another workspace (code `sender_id_taken`).' }
26068
+ '422': { description: 'Sender ID rejected: it appears to impersonate a protected brand or institution.' }
26069
+ get:
26070
+ operationId: listSmsSenderIds
26071
+ tags: [SMS]
26072
+ summary: List alphanumeric sender IDs
26073
+ security:
26074
+ - bearerAuth: []
26075
+ responses:
26076
+ '200':
26077
+ description: The workspace's sender IDs, newest first.
26078
+ content:
26079
+ application/json:
26080
+ schema:
26081
+ type: object
26082
+ properties:
26083
+ senderIds:
26084
+ type: array
26085
+ items:
26086
+ type: object
26087
+ properties:
26088
+ id: { type: string }
26089
+ senderId: { type: string }
26090
+ isActive: { type: boolean }
26091
+ createdAt: { type: [string, "null"], format: date-time }
26092
+ budget:
26093
+ type: object
26094
+ description: 'Workspace-wide daily sending budget, shared by every sender ID (resets midnight UTC).'
26095
+ properties:
26096
+ cap: { type: integer, description: 'Daily message cap (raisable via `/v1/sms/sender-ids/limit-request`).' }
26097
+ usedToday: { type: integer, description: Messages already counted against today's cap. }
26098
+ level: { type: integer, description: 'Cap tier (Level 1 = 500/day).' }
26099
+ pendingRequest:
26100
+ type: [object, "null"]
26101
+ description: 'The in-flight cap-raise request awaiting review, or null. While set, further requests return 409.'
26102
+ properties:
26103
+ requestedCap: { type: integer }
26104
+ level: { type: integer }
26105
+ requestedAt: { type: [string, "null"], format: date-time }
26106
+ '401': { $ref: '#/components/responses/Unauthorized' }
26107
+
26108
+ /v1/sms/sender-ids/limit-request:
26109
+ post:
26110
+ operationId: requestSmsSenderIdLimitIncrease
26111
+ tags: [SMS]
26112
+ summary: Request a higher sender ID daily limit
26113
+ description: |
26114
+ Asks support to raise the workspace's daily sender-ID message cap.
26115
+ There is no self-serve raise: the request (desired cap + use case) is
26116
+ reviewed manually, usually within a business day.
26117
+ security:
26118
+ - bearerAuth: []
26119
+ requestBody:
26120
+ required: true
26121
+ content:
26122
+ application/json:
26123
+ schema:
26124
+ type: object
26125
+ required: [requestedCap, reason]
26126
+ properties:
26127
+ requestedCap: { type: integer, description: 'Desired daily message cap. Must exceed the current cap.' }
26128
+ reason: { type: string, minLength: 10, maxLength: 2000, description: 'Use case and audience (what you send, to whom, opt-in status).' }
26129
+ responses:
26130
+ '200':
26131
+ description: Request submitted for review.
26132
+ content:
26133
+ application/json:
26134
+ schema:
26135
+ type: object
26136
+ properties:
26137
+ requested: { type: boolean }
26138
+ requestedCap: { type: integer }
26139
+ '400': { $ref: '#/components/responses/BadRequest' }
26140
+ '401': { $ref: '#/components/responses/Unauthorized' }
26141
+ '409': { description: 'A cap-raise request is already awaiting review (code `sender_id_raise_pending`); one at a time.' }
26142
+ '503': { description: Request could not be submitted; retry or contact support. }
26143
+
26144
+ /v1/sms/sender-ids/{id}:
26145
+ delete:
26146
+ operationId: deleteSmsSenderId
26147
+ tags: [SMS]
26148
+ summary: Delete an alphanumeric sender ID
26149
+ description: |
26150
+ Deactivates the sender ID so it can no longer send. Re-creating the
26151
+ same sender ID via `POST /v1/sms/sender-ids` re-activates it.
26152
+ security:
26153
+ - bearerAuth: []
26154
+ parameters:
26155
+ - { name: id, in: path, required: true, schema: { type: string }, description: Sender ID resource id. }
26156
+ responses:
26157
+ '200':
26158
+ description: Sender ID deactivated.
26159
+ content:
26160
+ application/json:
26161
+ schema:
26162
+ type: object
26163
+ properties:
26164
+ deleted: { type: boolean }
26165
+ '400': { $ref: '#/components/responses/BadRequest' }
26166
+ '401': { $ref: '#/components/responses/Unauthorized' }
26167
+ '404': { description: Sender ID not found. }
26168
+
25959
26169
  /v1/sms/registrations:
25960
26170
  post:
25961
26171
  operationId: startSmsRegistration
@@ -33046,6 +33256,55 @@ paths:
33046
33256
  '403':
33047
33257
  description: Ads access required. Legacy plans need the Ads add-on; included by default on usage-based plans.
33048
33258
 
33259
+ post:
33260
+ operationId: createAdCampaign
33261
+ tags: [Ad Campaigns]
33262
+ summary: Create a standalone campaign (Meta)
33263
+ description: |-
33264
+ Creates a campaign WITHOUT its first ad set / ad (the ODAX shell only). Ad sets join it
33265
+ later via `existingCampaignId` on the create endpoints. A budget here is campaign-level
33266
+ (CBO) by definition; omit it for ABO (each ad set carries its own budget). Created
33267
+ `PAUSED` unless `status: ACTIVE`. The campaign materializes in `/v1/ads/tree` via the
33268
+ next sync discovery pass. Meta only.
33269
+ security:
33270
+ - bearerAuth: []
33271
+ requestBody:
33272
+ required: true
33273
+ content:
33274
+ application/json:
33275
+ schema:
33276
+ type: object
33277
+ required: [accountId, adAccountId, name, goal]
33278
+ properties:
33279
+ accountId: { type: string, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
33280
+ adAccountId: { type: string, description: "Meta ad account id (act_<n>)." }
33281
+ name: { type: string, maxLength: 255 }
33282
+ goal:
33283
+ type: string
33284
+ enum: [engagement, traffic, awareness, video_views, lead_generation, lead_conversion, job_applicants, conversions, app_promotion, catalog_sales]
33285
+ description: Mapped to the ODAX objective (same mapping as POST /v1/ads/create).
33286
+ specialAdCategories:
33287
+ type: array
33288
+ items: { type: string, enum: [HOUSING, EMPLOYMENT, CREDIT, ISSUES_ELECTIONS_POLITICS, FINANCIAL_PRODUCTS_SERVICES, ONLINE_GAMBLING_AND_GAMING] }
33289
+ budgetAmount: { type: number, description: "Campaign-level (CBO) budget in whole currency units. Requires budgetType." }
33290
+ budgetType: { type: string, enum: [daily, lifetime] }
33291
+ status: { type: string, enum: [ACTIVE, PAUSED], default: PAUSED }
33292
+ responses:
33293
+ '201':
33294
+ description: Campaign created
33295
+ content:
33296
+ application/json:
33297
+ schema:
33298
+ type: object
33299
+ properties:
33300
+ adAccountId: { type: string }
33301
+ campaignId: { type: string, description: Platform id of the new campaign }
33302
+ objective: { type: string, description: "Resolved ODAX objective (e.g. OUTCOME_SALES)." }
33303
+ status: { type: string, enum: [ACTIVE, PAUSED] }
33304
+ '400': { description: "Invalid input, or Meta rejected the create" }
33305
+ '401': { $ref: '#/components/responses/Unauthorized' }
33306
+ '501': { description: Only supported on Meta (facebook/instagram) }
33307
+
33049
33308
  /v1/ads/campaigns/{campaignId}/status:
33050
33309
  put:
33051
33310
  operationId: updateAdCampaignStatus
@@ -33333,6 +33592,98 @@ paths:
33333
33592
  '404': { description: Source campaign not found }
33334
33593
  '501': { description: Operation not supported on this platform }
33335
33594
 
33595
+ /v1/ads/ad-sets/{adSetId}/duplicate:
33596
+ post:
33597
+ operationId: duplicateAdSet
33598
+ tags: [Ad Campaigns]
33599
+ summary: Duplicate an ad set (Meta)
33600
+ description: |-
33601
+ Duplicates an ad set, including its ads and creatives by default (`deepCopy: true`),
33602
+ via Meta's native `POST /{adset-id}/copies`. The copy is created paused so callers can
33603
+ review before launching. `campaignId` retargets the copy into another campaign; omitted
33604
+ = the source's own campaign. The new hierarchy materializes asynchronously — sync
33605
+ discovery is triggered automatically (`syncAfter: false` to skip). Meta only.
33606
+ security:
33607
+ - bearerAuth: []
33608
+ parameters:
33609
+ - { name: adSetId, in: path, required: true, schema: { type: string }, description: Source platform ad set ID }
33610
+ requestBody:
33611
+ required: true
33612
+ content:
33613
+ application/json:
33614
+ schema:
33615
+ type: object
33616
+ required: [platform]
33617
+ properties:
33618
+ platform: { type: string, enum: [facebook, instagram] }
33619
+ campaignId: { type: string, description: Destination platform campaign id (defaults to the source's campaign) }
33620
+ deepCopy: { type: boolean, default: true, description: Copy child ads + creatives }
33621
+ statusOption: { type: string, enum: [ACTIVE, PAUSED, INHERITED_FROM_SOURCE], default: PAUSED }
33622
+ startTime: { type: string, format: date-time, description: Reschedule the copy's start time }
33623
+ endTime: { type: string, format: date-time }
33624
+ renameStrategy: { type: string, enum: [DEEP_RENAME, ONLY_TOP_LEVEL_RENAME, NO_RENAME] }
33625
+ renamePrefix: { type: string }
33626
+ renameSuffix: { type: string }
33627
+ syncAfter: { type: boolean, default: true }
33628
+ responses:
33629
+ '200':
33630
+ description: Ad set duplicated
33631
+ content:
33632
+ application/json:
33633
+ schema:
33634
+ type: object
33635
+ properties:
33636
+ copiedAdSetId: { type: string, description: Platform ID of the new ad set }
33637
+ discovery: { type: string, enum: [triggered, skipped, failed] }
33638
+ raw: { type: object, description: Meta's native copy response (includes ad_object_ids for child copies) }
33639
+ '400': { description: Invalid input }
33640
+ '401': { $ref: '#/components/responses/Unauthorized' }
33641
+ '404': { description: Source ad set not found }
33642
+ '501': { description: Only supported on Meta (facebook/instagram) }
33643
+
33644
+ /v1/ads/{adId}/duplicate:
33645
+ post:
33646
+ operationId: duplicateAd
33647
+ tags: [Ads]
33648
+ summary: Duplicate an ad (Meta)
33649
+ description: |-
33650
+ Duplicates a single ad via Meta's native `POST /{ad-id}/copies`. The copy is created
33651
+ paused. `adSetId` retargets the copy into another ad set; omitted = the source's own ad
33652
+ set. Accepts the Zernio ad id or the platform ad id. Sync discovery is triggered
33653
+ automatically (`syncAfter: false` to skip). Meta only.
33654
+ security:
33655
+ - bearerAuth: []
33656
+ parameters:
33657
+ - { name: adId, in: path, required: true, schema: { type: string }, description: Zernio ad ID or platform ad ID }
33658
+ requestBody:
33659
+ required: false
33660
+ content:
33661
+ application/json:
33662
+ schema:
33663
+ type: object
33664
+ properties:
33665
+ adSetId: { type: string, description: Destination platform ad set id (defaults to the source's ad set) }
33666
+ statusOption: { type: string, enum: [ACTIVE, PAUSED, INHERITED_FROM_SOURCE], default: PAUSED }
33667
+ renameStrategy: { type: string, enum: [DEEP_RENAME, ONLY_TOP_LEVEL_RENAME, NO_RENAME] }
33668
+ renamePrefix: { type: string }
33669
+ renameSuffix: { type: string }
33670
+ syncAfter: { type: boolean, default: true }
33671
+ responses:
33672
+ '200':
33673
+ description: Ad duplicated
33674
+ content:
33675
+ application/json:
33676
+ schema:
33677
+ type: object
33678
+ properties:
33679
+ copiedAdId: { type: string, description: Platform ID of the new ad }
33680
+ discovery: { type: string, enum: [triggered, skipped, failed] }
33681
+ raw: { type: object }
33682
+ '400': { description: Invalid input }
33683
+ '401': { $ref: '#/components/responses/Unauthorized' }
33684
+ '404': { description: Ad not found }
33685
+ '501': { description: Only supported on Meta (facebook/instagram) }
33686
+
33336
33687
  /v1/ads/ad-sets/{adSetId}:
33337
33688
  get:
33338
33689
  operationId: getAdSetDetails
@@ -34655,6 +35006,302 @@ paths:
34655
35006
  '401': { $ref: '#/components/responses/Unauthorized' }
34656
35007
  '501': { description: Only supported on Meta (facebook/instagram) }
34657
35008
 
35009
+ /v1/ads/businesses:
35010
+ get:
35011
+ operationId: listMetaBusinesses
35012
+ tags: [Ads]
35013
+ summary: Businesses list (Meta)
35014
+ description: |-
35015
+ Business Manager portfolios the connected Meta user belongs to (Meta's `/me/businesses`),
35016
+ rows returned verbatim (id, name, verification_status, created_time). Token-scoped, so no
35017
+ `adAccountId` is needed. Meta only; for TikTok Business Centers use
35018
+ `GET /v1/ads/business-centers`.
35019
+ security:
35020
+ - bearerAuth: []
35021
+ parameters:
35022
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
35023
+ - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, description: Rows per page }
35024
+ - { name: after, in: query, schema: { type: string }, description: "Cursor from paging.after of the previous page." }
35025
+ responses:
35026
+ '200':
35027
+ description: Businesses (raw Meta shape)
35028
+ content:
35029
+ application/json:
35030
+ schema:
35031
+ type: object
35032
+ properties:
35033
+ data:
35034
+ type: array
35035
+ items: { type: object, description: "Raw Meta business row (id, name, verification_status, created_time)." }
35036
+ paging:
35037
+ type: object
35038
+ properties:
35039
+ after: { type: [string, "null"], description: "Cursor for the next page; null when exhausted." }
35040
+ '400': { description: "Invalid input, or Meta rejected the query" }
35041
+ '401': { $ref: '#/components/responses/Unauthorized' }
35042
+ '501': { description: Only supported on Meta (facebook/instagram) }
35043
+
35044
+ /v1/ads/labels:
35045
+ get:
35046
+ operationId: listAdLabels
35047
+ tags: [Ads]
35048
+ summary: Ad labels (Meta)
35049
+ description: |-
35050
+ Lists the ad account's organizational labels (Meta's `/act_X/adlabels`), rows returned
35051
+ verbatim (id, name, created/updated time). Meta only.
35052
+ security:
35053
+ - bearerAuth: []
35054
+ parameters:
35055
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
35056
+ - { name: adAccountId, in: query, required: true, schema: { type: string }, description: "Meta ad account id (act_<n>)." }
35057
+ - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, description: Rows per page }
35058
+ - { name: after, in: query, schema: { type: string }, description: "Cursor from paging.after of the previous page." }
35059
+ responses:
35060
+ '200':
35061
+ description: Ad labels (raw Meta shape)
35062
+ content:
35063
+ application/json:
35064
+ schema:
35065
+ type: object
35066
+ properties:
35067
+ adAccountId: { type: string }
35068
+ data:
35069
+ type: array
35070
+ items: { type: object, description: "Raw Meta ad label row (id, name, created_time, updated_time)." }
35071
+ paging:
35072
+ type: object
35073
+ properties:
35074
+ after: { type: [string, "null"], description: "Cursor for the next page; null when exhausted." }
35075
+ '400': { description: "Invalid input, or Meta rejected the query" }
35076
+ '401': { $ref: '#/components/responses/Unauthorized' }
35077
+ '501': { description: Only supported on Meta (facebook/instagram) }
35078
+
35079
+ /v1/ads/high-demand-periods:
35080
+ get:
35081
+ operationId: listHighDemandPeriods
35082
+ tags: [Ads]
35083
+ summary: High demand periods / budget schedules (Meta)
35084
+ description: |-
35085
+ Scheduled budget increases (Meta's budget-scheduling API). The Graph edge lives on the
35086
+ campaign and ad-set nodes only, so exactly one of `campaignId` / `adSetId` (platform
35087
+ ids) is required. Rows returned verbatim (budget_value, budget_value_type, time window,
35088
+ recurrence). Meta only.
35089
+ security:
35090
+ - bearerAuth: []
35091
+ parameters:
35092
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
35093
+ - { name: campaignId, in: query, schema: { type: string }, description: "Platform campaign id. Exactly one of campaignId / adSetId." }
35094
+ - { name: adSetId, in: query, schema: { type: string }, description: "Platform ad set id. Exactly one of campaignId / adSetId." }
35095
+ - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, description: Rows per page }
35096
+ - { name: after, in: query, schema: { type: string }, description: "Cursor from paging.after of the previous page." }
35097
+ responses:
35098
+ '200':
35099
+ description: Budget schedules (raw Meta shape)
35100
+ content:
35101
+ application/json:
35102
+ schema:
35103
+ type: object
35104
+ properties:
35105
+ objectId: { type: string, description: "The campaign / ad set id the schedules belong to." }
35106
+ data:
35107
+ type: array
35108
+ items: { type: object, description: "Raw Meta high-demand-period row." }
35109
+ paging:
35110
+ type: object
35111
+ properties:
35112
+ after: { type: [string, "null"], description: "Cursor for the next page; null when exhausted." }
35113
+ '400': { description: "Invalid input, or Meta rejected the query" }
35114
+ '401': { $ref: '#/components/responses/Unauthorized' }
35115
+ '501': { description: Only supported on Meta (facebook/instagram) }
35116
+
35117
+ /v1/ads/creatives:
35118
+ get:
35119
+ operationId: listAdCreatives
35120
+ tags: [Ads]
35121
+ summary: Creative library (Meta)
35122
+ description: |-
35123
+ Lists the ad account's creative library (Meta's `/act_X/adcreatives`), rows returned
35124
+ verbatim. The default projection covers id, name, status, object type, thumbnail,
35125
+ object_story_spec / asset_feed_spec and url_tags; `fields` is a raw-passthrough
35126
+ override. Any creative id here is reusable on the create endpoints via
35127
+ `existingCreativeId`. Meta only.
35128
+ security:
35129
+ - bearerAuth: []
35130
+ parameters:
35131
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
35132
+ - { name: adAccountId, in: query, required: true, schema: { type: string }, description: "Meta ad account id (act_<n>)." }
35133
+ - { name: fields, in: query, schema: { type: string }, description: "Comma-separated Graph field override (supports nested {} projections)." }
35134
+ - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, description: Rows per page }
35135
+ - { name: after, in: query, schema: { type: string }, description: "Cursor from paging.after of the previous page." }
35136
+ responses:
35137
+ '200':
35138
+ description: Creatives (raw Meta shape)
35139
+ content:
35140
+ application/json:
35141
+ schema:
35142
+ type: object
35143
+ properties:
35144
+ adAccountId: { type: string }
35145
+ data:
35146
+ type: array
35147
+ items: { type: object, description: "Raw Meta creative row." }
35148
+ paging:
35149
+ type: object
35150
+ properties:
35151
+ after: { type: [string, "null"], description: "Cursor for the next page; null when exhausted." }
35152
+ '400': { description: "Invalid input, or Meta rejected the query" }
35153
+ '401': { $ref: '#/components/responses/Unauthorized' }
35154
+ '501': { description: Only supported on Meta (facebook/instagram) }
35155
+ post:
35156
+ operationId: createAdCreative
35157
+ tags: [Ads]
35158
+ summary: Create a standalone creative (Meta)
35159
+ description: |-
35160
+ Creates a creative in the library WITHOUT an ad, reusable on the create endpoints via
35161
+ `existingCreativeId`. Provide exactly one of `imageUrl` (uploaded server-side),
35162
+ `imageHash` (from POST /v1/ads/images or the library list), or `carouselCards` (2-10
35163
+ hand-built cards). The Page (and linked Instagram account, when present) is resolved
35164
+ from `accountId` as the story actor. Meta only.
35165
+ security:
35166
+ - bearerAuth: []
35167
+ requestBody:
35168
+ required: true
35169
+ content:
35170
+ application/json:
35171
+ schema:
35172
+ type: object
35173
+ required: [accountId, adAccountId, headline, body, linkUrl]
35174
+ properties:
35175
+ accountId: { type: string, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token and Page." }
35176
+ adAccountId: { type: string, description: "Meta ad account id (act_<n>)." }
35177
+ headline: { type: string, maxLength: 255 }
35178
+ body: { type: string, description: Primary text }
35179
+ description: { type: string, maxLength: 255, description: "Link description below the headline; omitted = Meta scrapes the destination's OG description." }
35180
+ callToAction: { type: string, default: LEARN_MORE, description: "CTA type (same whitelist as POST /v1/ads/create)." }
35181
+ linkUrl: { type: string, format: uri }
35182
+ imageUrl: { type: string, format: uri, description: "Publicly reachable image; uploaded to the account's library server-side." }
35183
+ imageHash: { type: string, description: "Existing library image hash (POST /v1/ads/images or GET /v1/ads/images)." }
35184
+ carouselCards:
35185
+ type: array
35186
+ minItems: 2
35187
+ maxItems: 10
35188
+ items:
35189
+ type: object
35190
+ required: [imageUrl, linkUrl]
35191
+ properties:
35192
+ imageUrl: { type: string, format: uri }
35193
+ linkUrl: { type: string, format: uri }
35194
+ headline: { type: string, maxLength: 255 }
35195
+ description: { type: string, maxLength: 255 }
35196
+ callToAction: { type: string }
35197
+ urlTags: { type: string, description: "Appended to every outbound URL (e.g. utm_source=fb)." }
35198
+ creativeFeatures:
35199
+ type: object
35200
+ additionalProperties: { type: string, enum: [OPT_IN, OPT_OUT] }
35201
+ description: 'Advantage+ creative enhancements: partial map of Meta creative feature keys (snake_case) to enroll status, forwarded as degrees_of_freedom_spec.creative_features_spec. Unspecified features default to OPT_OUT.'
35202
+ responses:
35203
+ '201':
35204
+ description: Creative created
35205
+ content:
35206
+ application/json:
35207
+ schema:
35208
+ type: object
35209
+ properties:
35210
+ adAccountId: { type: string }
35211
+ creativeId: { type: string, description: "Platform creative id, reusable via existingCreativeId." }
35212
+ '400': { description: "Invalid input, or Meta rejected the create" }
35213
+ '401': { $ref: '#/components/responses/Unauthorized' }
35214
+ '422': { description: No Facebook Page found to act as the story actor }
35215
+ '501': { description: Only supported on Meta (facebook/instagram) }
35216
+
35217
+ /v1/ads/creatives/{creativeId}:
35218
+ get:
35219
+ operationId: getAdCreative
35220
+ tags: [Ads]
35221
+ summary: Creative details (Meta)
35222
+ description: |-
35223
+ One creative's details, verbatim from Meta. `fields` is a raw-passthrough override of
35224
+ the default projection. Meta only.
35225
+ security:
35226
+ - bearerAuth: []
35227
+ parameters:
35228
+ - { name: creativeId, in: path, required: true, schema: { type: string }, description: Platform creative id }
35229
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
35230
+ - { name: fields, in: query, schema: { type: string }, description: "Comma-separated Graph field override (supports nested {} projections)." }
35231
+ responses:
35232
+ '200':
35233
+ description: Creative details
35234
+ content:
35235
+ application/json:
35236
+ schema:
35237
+ type: object
35238
+ properties:
35239
+ creative: { type: object, description: Raw Meta creative node }
35240
+ '400': { description: "Invalid input, or Meta rejected the query" }
35241
+ '401': { $ref: '#/components/responses/Unauthorized' }
35242
+ '501': { description: Only supported on Meta (facebook/instagram) }
35243
+ put:
35244
+ operationId: updateAdCreative
35245
+ tags: [Ads]
35246
+ summary: Rename a creative (Meta)
35247
+ description: |-
35248
+ Renames a creative. Creatives are immutable on Meta beyond `name` — for content changes
35249
+ create a new creative (POST /v1/ads/creatives) and swap it onto the ad
35250
+ (PUT /v1/ads/{adId} with `creative`). Meta only.
35251
+ security:
35252
+ - bearerAuth: []
35253
+ parameters:
35254
+ - { name: creativeId, in: path, required: true, schema: { type: string }, description: Platform creative id }
35255
+ requestBody:
35256
+ required: true
35257
+ content:
35258
+ application/json:
35259
+ schema:
35260
+ type: object
35261
+ required: [accountId, name]
35262
+ properties:
35263
+ accountId: { type: string, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
35264
+ name: { type: string, maxLength: 255 }
35265
+ responses:
35266
+ '200':
35267
+ description: Creative renamed
35268
+ content:
35269
+ application/json:
35270
+ schema:
35271
+ type: object
35272
+ properties:
35273
+ creativeId: { type: string }
35274
+ name: { type: string }
35275
+ message: { type: string }
35276
+ '400': { description: "Invalid input, or Meta rejected the update" }
35277
+ '401': { $ref: '#/components/responses/Unauthorized' }
35278
+ '501': { description: Only supported on Meta (facebook/instagram) }
35279
+ delete:
35280
+ operationId: deleteAdCreative
35281
+ tags: [Ads]
35282
+ summary: Delete a creative (Meta)
35283
+ description: |-
35284
+ Deletes a creative from the library. Meta only allows deleting creatives not referenced
35285
+ by any ad — otherwise its 400 surfaces verbatim.
35286
+ security:
35287
+ - bearerAuth: []
35288
+ parameters:
35289
+ - { name: creativeId, in: path, required: true, schema: { type: string }, description: Platform creative id }
35290
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
35291
+ responses:
35292
+ '200':
35293
+ description: Creative deleted
35294
+ content:
35295
+ application/json:
35296
+ schema:
35297
+ type: object
35298
+ properties:
35299
+ creativeId: { type: string }
35300
+ message: { type: string }
35301
+ '400': { description: "Invalid input, the creative is in use, or Meta rejected the delete" }
35302
+ '401': { $ref: '#/components/responses/Unauthorized' }
35303
+ '501': { description: Only supported on Meta (facebook/instagram) }
35304
+
34658
35305
  /v1/ads/accounts/finance:
34659
35306
  get:
34660
35307
  operationId: getAdAccountFinance
@@ -35172,6 +35819,13 @@ paths:
35172
35819
  billingEvent: { type: string, description: "Meta only. Explicit ad-set `billing_event`. Defaults to `IMPRESSIONS`. Forwarded verbatim to Meta, which validates compatibility with the optimization goal." }
35173
35820
  buyingType: { type: string, enum: [AUCTION, RESERVED], description: "Meta only. RESERVED = Reach & Frequency: requires `rfPredictionId` (a RESERVED prediction from /v1/ads/rf-predictions + /reserve). Budget, schedule and pricing come from the reservation, so budgetAmount/budgetType are not required and bid fields are ignored. Only the plain single-ad shape (no creatives[], adSetId, existingCampaignId or dynamicCreative)." }
35174
35821
  rfPredictionId: { type: string, description: "Meta only. The RESERVED prediction id the R&F ad set runs on (reserving mints a new id — pass that one). Requires buyingType RESERVED." }
35822
+ creativeFeatures:
35823
+ type: object
35824
+ additionalProperties: { type: string, enum: [OPT_IN, OPT_OUT] }
35825
+ description: 'Meta only. Advantage+ creative enhancements: a partial map of Meta creative feature keys (snake_case, e.g. enhance_cta, image_brightness_and_contrast, text_optimizations) to enroll status, forwarded as degrees_of_freedom_spec.creative_features_spec. Meta validates the keys; unspecified features default to OPT_OUT. The legacy standard_enhancements bundle is deprecated by Meta and rejected.'
35826
+ validateOnly:
35827
+ type: boolean
35828
+ 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 — 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.'
35175
35829
  budgetAmount: { type: number, description: "Required on legacy + multi-creative shapes. Inherited on attach." }
35176
35830
  budgetType: { type: string, enum: [daily, lifetime], description: "Required on legacy + multi-creative shapes. Inherited on attach." }
35177
35831
  status:
@@ -35801,6 +36455,23 @@ paths:
35801
36455
  type: string
35802
36456
  description: Product Set ID inside the catalog.
35803
36457
  responses:
36458
+ '200':
36459
+ description: 'validateOnly dry-run passed — nothing was created'
36460
+ content:
36461
+ application/json:
36462
+ schema:
36463
+ type: object
36464
+ properties:
36465
+ validateOnly: { type: boolean, enum: [true] }
36466
+ results:
36467
+ type: array
36468
+ items:
36469
+ type: object
36470
+ properties:
36471
+ node: { type: string, enum: [campaign, adSet, creative, ad] }
36472
+ status: { type: string, enum: [validated, skipped] }
36473
+ reason: { type: string, description: "Why the node could not be validated (only on skipped)." }
36474
+ message: { type: string }
35804
36475
  '201':
35805
36476
  description: Ad(s) created
35806
36477
  content:
@@ -35822,7 +36493,7 @@ paths:
35822
36493
  platformAdSetId: { type: string }
35823
36494
  message: { type: string }
35824
36495
  '400':
35825
- description: Missing required fields, invalid values, or non-Meta platform used with creatives[] / adSetId
36496
+ description: 'Missing required fields, invalid values, non-Meta platform used with creatives[] / adSetId, or a Meta validateOnly validation failure (verbatim)'
35826
36497
  '401': { $ref: '#/components/responses/Unauthorized' }
35827
36498
  '403':
35828
36499
  description: Ads access required. Legacy plans need the Ads add-on; included by default on usage-based plans.
@@ -36102,6 +36773,43 @@ paths:
36102
36773
  '401': { $ref: '#/components/responses/Unauthorized' }
36103
36774
  '501': { description: Only supported on Meta (facebook/instagram) }
36104
36775
 
36776
+ get:
36777
+ operationId: listAdImages
36778
+ tags: [Ads]
36779
+ summary: Ad image library (Meta)
36780
+ description: |-
36781
+ Lists the ad account's image library (Meta's `/act_X/adimages`), rows returned verbatim.
36782
+ The default projection covers hash, url, name, dimensions and status; `fields` is a
36783
+ raw-passthrough override. Any `hash` here is reusable wherever Meta accepts
36784
+ `image_hash` (e.g. `imageHash` on POST /v1/ads/creatives). Meta only.
36785
+ security:
36786
+ - bearerAuth: []
36787
+ parameters:
36788
+ - { name: accountId, in: query, required: true, schema: { type: string }, description: "Zernio SocialAccount id (posting or ads variant) used to resolve the Meta token." }
36789
+ - { name: adAccountId, in: query, required: true, schema: { type: string }, description: "Meta ad account id (act_<n>)." }
36790
+ - { name: fields, in: query, schema: { type: string }, description: "Comma-separated Graph field override (supports nested {} projections)." }
36791
+ - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, description: Rows per page }
36792
+ - { name: after, in: query, schema: { type: string }, description: "Cursor from paging.after of the previous page." }
36793
+ responses:
36794
+ '200':
36795
+ description: Ad images (raw Meta shape)
36796
+ content:
36797
+ application/json:
36798
+ schema:
36799
+ type: object
36800
+ properties:
36801
+ adAccountId: { type: string }
36802
+ data:
36803
+ type: array
36804
+ items: { type: object, description: "Raw Meta ad image row (hash, url, name, width, height, status)." }
36805
+ paging:
36806
+ type: object
36807
+ properties:
36808
+ after: { type: [string, "null"], description: "Cursor for the next page; null when exhausted." }
36809
+ '400': { description: "Invalid input, or Meta rejected the query" }
36810
+ '401': { $ref: '#/components/responses/Unauthorized' }
36811
+ '501': { description: Only supported on Meta (facebook/instagram) }
36812
+
36105
36813
  /v1/ads/interests:
36106
36814
  get:
36107
36815
  operationId: searchAdInterests
@@ -36672,12 +37380,15 @@ paths:
36672
37380
  put:
36673
37381
  operationId: updateAdAudience
36674
37382
  tags: [Ad Audiences]
36675
- summary: Update saved targeting audience
37383
+ summary: Update an audience
36676
37384
  description: |
36677
- Update a `saved_targeting` audience's name, description, or spec. Only `saved_targeting` audiences are
36678
- updatable (they exist only on Zernio); uploaded/derived audiences return 422, delete and recreate those
36679
- instead. `spec` replaces the stored spec wholesale (no merge). Ads already created from this audience are
36680
- unaffected, they snapshot the targeting at creation.
37385
+ Update an audience. `saved_targeting` audiences accept `name`, `description`, and `spec`
37386
+ (full replacement, no merge, Zernio-only, no platform call). Platform audiences
37387
+ (uploaded/website/lookalike) accept `name` and `description` only, updated on the
37388
+ platform first and then mirrored locally; their rules are immutable, so `spec` returns
37389
+ 400 for them. Platform audience updates are Meta-only for now (other platforms return
37390
+ 501). Ads already created from a saved_targeting audience are unaffected, they snapshot
37391
+ the targeting at creation.
36681
37392
  security:
36682
37393
  - bearerAuth: []
36683
37394
  parameters:
@@ -36706,13 +37417,15 @@ paths:
36706
37417
  audience: { type: object }
36707
37418
  message: { type: string }
36708
37419
  '400':
36709
- description: Invalid body (no fields provided or malformed spec)
37420
+ description: 'Invalid body (no fields provided, malformed spec, or spec on a platform audience)'
36710
37421
  '401': { $ref: '#/components/responses/Unauthorized' }
36711
37422
  '403':
36712
37423
  description: Ads access required. Legacy plans need the Ads add-on; included by default on usage-based plans.
36713
37424
  '404': { $ref: '#/components/responses/NotFound' }
36714
37425
  '422':
36715
- description: The audience is not saved_targeting (uploaded/derived audiences are managed on the platform)
37426
+ description: The audience has no platform counterpart to update
37427
+ '501':
37428
+ description: Platform audience updates are only supported on Meta
36716
37429
  delete:
36717
37430
  operationId: deleteAdAudience
36718
37431
  tags: [Ad Audiences]