late 0.0.1097

API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api Versioning and deprecation: all endpoints are versioned in the URL path (current version: /v1). Breaking changes only ship in a new path version; existing versions keep working. Deprecated operations are marked 'deprecated: true' in this spec and announced in the changelog (https://zernio.com/changelog) before removal. Errors: every 4xx/5xx response is application/json with a machine-readable 'code' and a human-readable 'error' message (see the ErrorResponse schema). Request ids: responses carry an X-Request-Id header with the id we log the request under. Quote it when reporting a problem. A valid x-request-id you send is reused as that id.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
/*
 * Zernio API
 *
 * API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api  Versioning and deprecation: all endpoints are versioned in the URL path (current version: /v1). Breaking changes only ship in a new path version; existing versions keep working. Deprecated operations are marked 'deprecated: true' in this spec and announced in the changelog (https://zernio.com/changelog) before removal.  Errors: every 4xx/5xx response is application/json with a machine-readable 'code' and a human-readable 'error' message (see the ErrorResponse schema).  Request ids: responses carry an X-Request-Id header with the id we log the request under. Quote it when reporting a problem. A valid x-request-id you send is reused as that id.
 *
 * The version of the OpenAPI document: 1.168.0
 * Contact: support@zernio.com
 * Generated by: https://openapi-generator.tech
 */

use crate::models;
use serde::{Deserialize, Serialize};

/// CtwaAdRequestBody : In addition to the `required` list, the request must use EXACTLY ONE of the two shapes:  - Single-creative: `headline`, `body`, and one of `imageUrl` / `video`,   OR `platformPostId` / `objectStoryId` to reuse an organic post.   On POST /v1/ads/messaging, `placementAssets` can replace `imageUrl` / `video`. - Multi-creative: a non-empty `creatives[]` array. Top-level   creative fields must NOT be set on this shape.  Existing post references work on messaging and CTWA only (not call ads). They cannot be combined with each other or with headline, body, imageUrl or video. `welcomeMessage` works on them (it lives on the ad, not the post). No media is uploaded and the organic post is retained. Fresh creatives still require headline, body, and image or video.  The route enforces this at the Zod boundary; OpenAPI's `required` cannot express the OR cleanly.  Campaign, ad set and targeting fields match POST /v1/ads/create on Meta and run through the same builders: the full targeting set (including `rawTargeting`, `languages`, `gender`, exclusions and `savedTargetingId`), `budgetLevel` (campaign budget), `startDate` / `endDate` in the ad account timezone, and `existingCampaignId` to add a new ad set to a campaign you already have.
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct CtwaAdRequestBody {
    /// Meta enhancement settings for single or attached ads, and defaults for creatives[]. An item replaces the entire map, including with an empty object.
    #[serde(rename = "creativeFeatures", skip_serializing_if = "Option::is_none")]
    pub creative_features: Option<CreativeFeatures>,
    #[serde(rename = "tracking", skip_serializing_if = "Option::is_none")]
    pub tracking: Option<Box<models::AdTracking>>,
    /// Facebook or Instagram SocialAccount ID.
    #[serde(rename = "accountId")]
    pub account_id: String,
    /// Meta ad account ID, e.g. `act_123456789`.
    #[serde(rename = "adAccountId")]
    pub ad_account_id: String,
    /// Ad display name. Used to derive campaign / ad set names. On the multi-creative shape, each ad's Meta name gets a \" #N\" suffix (1-indexed) so Ads Manager shows them as a numbered batch.
    #[serde(rename = "name")]
    pub name: String,
    /// Exact name for the campaign this request provisions. Omitted keeps `<name> - Campaign`. Ignored with `adSetId` (the ad set already has a campaign).
    #[serde(rename = "campaignName", skip_serializing_if = "Option::is_none")]
    pub campaign_name: Option<String>,
    /// Exact name for the ad set this request provisions. Omitted keeps `<name> - Ad Set`. Ignored with `adSetId`.
    #[serde(rename = "adSetName", skip_serializing_if = "Option::is_none")]
    pub ad_set_name: Option<String>,
    /// Messaging and CTWA only. Platform post or reel ID, the same input boostPost takes as platformPostId. Facebook IDs become object_story_id; Instagram IDs become source_instagram_media_id run as the media owner (resolved from the media on a Meta ads business-login connection, so no Instagram connection is needed). Mutually exclusive with objectStoryId and fresh creative fields.
    #[serde(rename = "platformPostId", skip_serializing_if = "Option::is_none")]
    pub platform_post_id: Option<String>,
    /// Alias of platformPostId, kept for existing callers. Sending both with different values is a 400.
    #[serde(rename = "existingPostId", skip_serializing_if = "Option::is_none")]
    pub existing_post_id: Option<String>,
    /// Messaging and CTWA only. Raw Facebook pageId_postId reference, used as object_story_id even with an Instagram account. Mutually exclusive with platformPostId and fresh creative fields.
    #[serde(rename = "objectStoryId", skip_serializing_if = "Option::is_none")]
    pub object_story_id: Option<String>,
    /// Facebook Page the ad runs as, when the connection was granted several Pages. Defaults to the Page bound to the connection. Any Page granted to the connection is accepted; other ids answer 400 listing the granted Pages. Same semantics as `pageId` on POST /v1/ads/create.
    #[serde(rename = "pageId", skip_serializing_if = "Option::is_none")]
    pub page_id: Option<String>,
    /// WhatsApp only. Optional E.164 number already paired with the Facebook Page. Omit to let Meta select the paired number. Sent to the creative CTA and, when creating a new ad set, its promoted_object. Attach requests do not change the existing ad set. Stored as creative.whatsappPhoneNumber on every created ad.
    #[serde(
        rename = "whatsappPhoneNumber",
        skip_serializing_if = "Option::is_none"
    )]
    pub whatsapp_phone_number: Option<String>,
    /// Single-creative shape only. Mutually exclusive with `creatives[]`.
    #[serde(rename = "headline", skip_serializing_if = "Option::is_none")]
    pub headline: Option<String>,
    /// Primary text shown above the image / video. Single-creative shape only. Mutually exclusive with `creatives[]`.
    #[serde(rename = "body", skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
    /// Link description, independent of `headline` and `body` (Meta's `link_data.description`, `video_data.link_description` on video, and the shared description of a `placementAssets` feed). Meta shows it mainly on Facebook Feed placements, under the headline, when there is room; Instagram, Stories, Reels and Messenger placements do not display it. Also accepted per entry in `creatives[]`. Not allowed with an existing post creative.
    #[serde(rename = "description", skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// Image asset for single-creative shape. Mutually exclusive with `video` and with `creatives[]`. Required on the single-creative shape if neither `video` nor an existing post reference is supplied.
    #[serde(rename = "imageUrl", skip_serializing_if = "Option::is_none")]
    pub image_url: Option<String>,
    #[serde(rename = "video", skip_serializing_if = "Option::is_none")]
    pub video: Option<Box<models::CtwaAdRequestBodyVideo>>,
    #[serde(rename = "welcomeMessage", skip_serializing_if = "Option::is_none")]
    pub welcome_message: Option<Box<models::CtwaAdRequestBodyWelcomeMessage>>,
    /// Multi-creative shape: N CTWA ads under one campaign + one ad set, sharing budget and targeting. Mutually exclusive with the top-level single-creative fields (`headline` / `body` / `imageUrl` / `video`): setting both is a 400, unlike `POST /v1/ads/create` where the top-level fields are silently ignored in multi-creative mode. Each entry supplies headline, body, and image/video, or a platformPostId or objectStoryId reference. Fresh and existing creatives can be mixed.
    #[serde(rename = "creatives", skip_serializing_if = "Option::is_none")]
    pub creatives: Option<Vec<models::CtwaAdRequestBodyCreativesInner>>,
    /// Attach the creatives to this EXISTING messaging ad set instead of building a campaign, so the ad set keeps its learning phase. It then owns budget, targeting and schedule, so `budgetAmount`, `budgetType`, `budgetLevel`, `startDate`, `endDate`, `objective`, `campaignStatus`, `existingCampaignId`, the special ad category fields and every targeting field except `ageMin`, `ageMax`, `placements` and `advantageAudience` are rejected with a 400 alongside it. Its `destination_type` must match the ad's destination.
    #[serde(rename = "adSetId", skip_serializing_if = "Option::is_none")]
    pub ad_set_id: Option<String>,
    /// Create the new messaging ad set (and its ads) under this EXISTING Meta campaign instead of a new one, e.g. several audience ad sets under one campaign. The campaign's objective must be OUTCOME_ENGAGEMENT, OUTCOME_SALES or OUTCOME_LEADS (400 otherwise). If the campaign has a campaign budget, omit `budgetAmount` and `budgetType` (400 if sent); otherwise they are required and land on the new ad set. `objective`, `campaignName`, `campaignStatus`, `budgetLevel`, `specialAdCategories`, `specialAdCategoryCountry` and `adSetId` are rejected alongside it. To add ads to an existing ad set instead, use `adSetId`.
    #[serde(rename = "existingCampaignId", skip_serializing_if = "Option::is_none")]
    pub existing_campaign_id: Option<String>,
    /// Where the budget lives. `adset` (default) puts it on the new ad set. `campaign` creates an Advantage campaign budget (CBO): the budget and bid strategy sit on the campaign and the ad set inherits them, same as POST /v1/ads/create. Not allowed with `adSetId` or `existingCampaignId`.
    #[serde(rename = "budgetLevel", skip_serializing_if = "Option::is_none")]
    pub budget_level: Option<BudgetLevel>,
    /// Budget amount in the ad account's currency major units (e.g. dollars for USD, not cents). Must be > 0. Required unless `adSetId` is set (the ad set owns it) or `existingCampaignId` names a campaign with a campaign budget.
    #[serde(rename = "budgetAmount", skip_serializing_if = "Option::is_none")]
    pub budget_amount: Option<f64>,
    /// Required unless `adSetId` is set or `existingCampaignId` names a campaign with a campaign budget. `lifetime` requires `endDate`.
    #[serde(rename = "budgetType", skip_serializing_if = "Option::is_none")]
    pub budget_type: Option<BudgetType>,
    /// ISO 4217 currency code matching the ad account's currency (e.g. `USD`). Optional: Zernio resolves it from the ad account when omitted. The value selects the minor-unit exponent Zernio converts budget/bid amounts by before calling Meta (most currencies are cents; zero-decimal currencies like JPY/KRW are sent as-is).
    #[serde(rename = "currency", skip_serializing_if = "Option::is_none")]
    pub currency: Option<String>,
    /// When the ad set starts delivering. ISO 8601 date or date-time. A value with an offset (`2027-01-15T10:00:00+01:00`, `...Z`) is used as is; one without an offset (`2027-01-15T10:00:00`) is read in the ad account's timezone, and a date-only value starts at 00:00 local. Defaults to now.
    #[serde(rename = "startDate", skip_serializing_if = "Option::is_none")]
    pub start_date: Option<String>,
    /// ISO 8601 date or date-time, read like `startDate`; a date-only value ends at 23:59:59 local. Required when `budgetType` is `lifetime`.
    #[serde(rename = "endDate", skip_serializing_if = "Option::is_none")]
    pub end_date: Option<String>,
    /// ISO 3166-1 alpha-2 country codes. Defaults to `[\"US\"]` only when no other geo (`cities`, `regions`, `zips`, `metros`, `customLocations`) is supplied.
    #[serde(rename = "countries", skip_serializing_if = "Option::is_none")]
    pub countries: Option<Vec<String>>,
    /// City-level geo targeting for local CTWA campaigns. Each entry maps to Meta's TargetingGeoLocationCity. `key` is Meta's city ID. `radius` and `distance_unit` are coupled: set both or neither. Meta enforces a minimum city radius (~17 km / 10 mi); smaller values resolve to a 0-size audience and the ad fails at launch. For a tighter catchment use customLocations (lat/lng).
    #[serde(rename = "cities", skip_serializing_if = "Option::is_none")]
    pub cities: Option<Vec<models::CtwaAdRequestBodyCitiesInner>>,
    /// Region / state-level geo targeting. `key` is Meta's region ID (lookupable via GET /v1/ads/targeting/search?type=region).
    #[serde(rename = "regions", skip_serializing_if = "Option::is_none")]
    pub regions: Option<Vec<models::CtwaAdRequestBodyRegionsInner>>,
    /// ZIP / postal-code geo targeting. `key` is the platform's postal id resolved via /v1/ads/targeting/search.
    #[serde(rename = "zips", skip_serializing_if = "Option::is_none")]
    pub zips: Option<Vec<models::CtwaAdRequestBodyZipsInner>>,
    /// DMA / metro-area geo targeting. `key` is Meta's metro id (e.g. `DMA:807`).
    #[serde(rename = "metros", skip_serializing_if = "Option::is_none")]
    pub metros: Option<Vec<models::CtwaAdRequestBodyZipsInner>>,
    /// Meta only. Continents and trade blocs (`geo_locations.country_groups`), for targeting a whole region without listing its countries. Combines with `countries` rather than replacing it, and is also accepted under `excludedLocations`. Discoverable via `GET /v1/ads/targeting/search?dimension=geo&geoType=country_group`.
    #[serde(rename = "countryGroups", skip_serializing_if = "Option::is_none")]
    pub country_groups: Option<Vec<CountryGroups>>,
    /// Point-radius geo (Meta `geo_locations.custom_locations`). Use for targeting a radius around a specific lat/long when no Meta city/region key fits. `distanceUnit` is required.
    #[serde(rename = "customLocations", skip_serializing_if = "Option::is_none")]
    pub custom_locations: Option<Vec<models::CreateStandaloneAdRequestCustomLocationsInner>>,
    #[serde(rename = "ageMin", skip_serializing_if = "Option::is_none")]
    pub age_min: Option<i32>,
    #[serde(rename = "ageMax", skip_serializing_if = "Option::is_none")]
    pub age_max: Option<i32>,
    #[serde(rename = "interests", skip_serializing_if = "Option::is_none")]
    pub interests: Option<Vec<models::CreateStandaloneAdRequestBehaviorsInner>>,
    /// Custom audience ID to target.
    #[serde(rename = "audienceId", skip_serializing_if = "Option::is_none")]
    pub audience_id: Option<String>,
    #[serde(rename = "placements", skip_serializing_if = "Option::is_none")]
    pub placements: Option<Box<models::CtwaAdRequestBodyPlacements>>,
    /// Restrict the audience by gender (Meta `genders`). Omit or send all for everyone; all is ignored in adSetId attach mode. Stored on the ad and read back in `targeting.gender`.
    #[serde(rename = "gender", skip_serializing_if = "Option::is_none")]
    pub gender: Option<Gender>,
    /// Audience languages (Meta `locales`). A bare ISO 639-1 code targets all regional variants (\"en\" = all English), a region-qualified code a specific one (\"en_GB\", \"pt_BR\"); unknown codes are rejected.
    #[serde(rename = "languages", skip_serializing_if = "Option::is_none")]
    pub languages: Option<Vec<String>>,
    /// Meta place keys (from GET /v1/ads/targeting/search).
    #[serde(rename = "places", skip_serializing_if = "Option::is_none")]
    pub places: Option<Vec<models::CtwaAdRequestBodyPlacesInner>>,
    /// Meta neighborhood keys (from GET /v1/ads/targeting/search).
    #[serde(rename = "neighborhoods", skip_serializing_if = "Option::is_none")]
    pub neighborhoods: Option<Vec<models::CtwaAdRequestBodyPlacesInner>>,
    /// Geo to exclude, same shape as POST /v1/ads/create (countries, countryGroups, regions, cities, zips, places, neighborhoods, customLocations).
    #[serde(rename = "excludedLocations", skip_serializing_if = "Option::is_none")]
    pub excluded_locations: Option<std::collections::HashMap<String, serde_json::Value>>,
    /// Meta behavior ids. Each dimension is its own flexible_spec entry: OR within, AND across.
    #[serde(rename = "behaviors", skip_serializing_if = "Option::is_none")]
    pub behaviors: Option<Vec<models::CreateStandaloneAdRequestBehaviorsInner>>,
    #[serde(rename = "workPositions", skip_serializing_if = "Option::is_none")]
    pub work_positions: Option<Vec<models::CreateStandaloneAdRequestBehaviorsInner>>,
    #[serde(rename = "workEmployers", skip_serializing_if = "Option::is_none")]
    pub work_employers: Option<Vec<models::CreateStandaloneAdRequestBehaviorsInner>>,
    #[serde(rename = "workIndustries", skip_serializing_if = "Option::is_none")]
    pub work_industries: Option<Vec<models::CreateStandaloneAdRequestBehaviorsInner>>,
    /// Normalized household-income tier, same as POST /v1/ads/create. Incompatible with housing, employment and credit specialAdCategories.
    #[serde(rename = "incomeTier", skip_serializing_if = "Option::is_none")]
    pub income_tier: Option<IncomeTier>,
    /// Meta `user_os`, e.g. [\"iOS_ver_14.0_and_above\"].
    #[serde(rename = "userOs", skip_serializing_if = "Option::is_none")]
    pub user_os: Option<Vec<String>>,
    /// Meta `user_device`.
    #[serde(rename = "userDevice", skip_serializing_if = "Option::is_none")]
    pub user_device: Option<Vec<String>>,
    /// Custom or lookalike audience ids to include.
    #[serde(rename = "audienceInclude", skip_serializing_if = "Option::is_none")]
    pub audience_include: Option<Vec<String>>,
    /// Custom or lookalike audience ids to exclude.
    #[serde(rename = "audienceExclude", skip_serializing_if = "Option::is_none")]
    pub audience_exclude: Option<Vec<String>>,
    /// ID of a saved_targeting audience (POST /v1/ads/audiences), expanded as the base targeting. Precedence: savedTargetingId, then `targeting`, then the flat fields.
    #[serde(rename = "savedTargetingId", skip_serializing_if = "Option::is_none")]
    pub saved_targeting_id: Option<String>,
    /// Nested targeting object, same contract as POST /v1/ads/create and boost. Flat fields win per key.
    #[serde(rename = "targeting", skip_serializing_if = "Option::is_none")]
    pub targeting: Option<Box<models::TargetingSpec>>,
    /// Meta targeting spec sent as the BASE layer of the ad set's `targeting`, exactly as POST /v1/ads/create does: use it for anything the flat fields cannot express, such as a layered `flexible_spec` (entries AND together, ids inside one entry OR). Flat fields you also send are layered on top and win per key. With rawTargeting present the US geo and `advantage_audience: 0` defaults are not injected, so include `targeting_automation` in it (or send `advantageAudience`), as Meta requires it on create.
    #[serde(rename = "rawTargeting", skip_serializing_if = "Option::is_none")]
    pub raw_targeting: Option<std::collections::HashMap<String, serde_json::Value>>,
    /// Meta special ad categories on the new campaign.
    #[serde(
        rename = "specialAdCategories",
        skip_serializing_if = "Option::is_none"
    )]
    pub special_ad_categories: Option<Vec<SpecialAdCategories>>,
    /// Countries the special ad category applies to. Requires specialAdCategories.
    #[serde(
        rename = "specialAdCategoryCountry",
        skip_serializing_if = "Option::is_none"
    )]
    pub special_ad_category_country: Option<Vec<String>>,
    /// Meta's Advantage+ audience expansion. `0` (default) keeps targeting strict; `1` lets Meta expand beyond the supplied targeting when its delivery system finds better matches. Always sent on CREATE (Meta requires it).
    #[serde(rename = "advantageAudience", skip_serializing_if = "Option::is_none")]
    pub advantage_audience: Option<AdvantageAudience>,
    /// Defaults to `OUTCOME_ENGAGEMENT`. `OUTCOME_SALES` and `OUTCOME_LEADS` require additional account configuration (Dataset linked to the WABA for sales) and may be rejected by Meta if missing.
    #[serde(rename = "objective", skip_serializing_if = "Option::is_none")]
    pub objective: Option<Objective>,
    /// Defaults to `ACTIVE`. `PAUSED` pauses only the top-most object this call creates: the new campaign (ad set and ads switched on), or, with `adSetId`, the new ads themselves.
    #[serde(rename = "status", skip_serializing_if = "Option::is_none")]
    pub status: Option<Status>,
    /// Campaign-level status, same semantics as `POST /v1/ads/create`. Defaults to `status`. `PAUSED` holds the new campaign off while the ad set and ads switch on (one resume call brings the whole hierarchy live); `ACTIVE` with `status: PAUSED` switches the campaign on and pauses the new ad set instead. Only meaningful when a new campaign is being created; rejected with a 400 alongside `adSetId` (the attach shape reuses an existing campaign).
    #[serde(rename = "campaignStatus", skip_serializing_if = "Option::is_none")]
    pub campaign_status: Option<CampaignStatus>,
    /// Meta bid strategy applied to the shared ad set. Defaults to `LOWEST_COST_WITHOUT_CAP` (auto-bid) when omitted. `LOWEST_COST_WITH_BID_CAP` and `COST_CAP` require `bidAmount`. `LOWEST_COST_WITH_MIN_ROAS` requires `roasAverageFloor`. CTWA's `optimization_goal` is fixed to `CONVERSATIONS`, but the bid strategy is independent.
    #[serde(rename = "bidStrategy", skip_serializing_if = "Option::is_none")]
    pub bid_strategy: Option<BidStrategy>,
    /// Whole currency units (e.g. `5` = $5.00 on a USD account). Required when `bidStrategy` is `LOWEST_COST_WITH_BID_CAP` or `COST_CAP`; rejected otherwise.
    #[serde(rename = "bidAmount", skip_serializing_if = "Option::is_none")]
    pub bid_amount: Option<f64>,
    /// Decimal ROAS multiplier (e.g. `2.0` = 2.0× ROAS floor). Required when `bidStrategy` is `LOWEST_COST_WITH_MIN_ROAS`; rejected otherwise. Meta enforces its own upper bound server-side.
    #[serde(rename = "roasAverageFloor", skip_serializing_if = "Option::is_none")]
    pub roas_average_floor: Option<f64>,
    /// Legal entity that benefits from the ad. Required when targeting EU users (EU DSA, Article 26). Optional if the ad account has a default beneficiary: set it once via `PATCH /v1/ads/accounts` or in Meta Ads Manager, and Meta fills it in whenever the field is omitted.
    #[serde(rename = "dsaBeneficiary", skip_serializing_if = "Option::is_none")]
    pub dsa_beneficiary: Option<String>,
    /// Legal entity that pays for the ad. Can differ from `dsaBeneficiary` (for example, an agency paying for a client's ads). Same rules as `dsaBeneficiary`: required for EU targeting unless the ad account has a default payor.
    #[serde(rename = "dsaPayor", skip_serializing_if = "Option::is_none")]
    pub dsa_payor: Option<String>,
    /// Meta only. Regional regulation categories required when the ad set targets certain countries (e.g. BRAZIL_REGULATION, SINGAPORE_UNIVERSAL, TAIWAN_UNIVERSAL, THAILAND_UNIVERSAL, AUSTRALIA_FINSERV, INDIA_FINSERV, TAIWAN_FINSERV). Forwarded to the ad set.
    #[serde(
        rename = "regionalRegulatedCategories",
        skip_serializing_if = "Option::is_none"
    )]
    pub regional_regulated_categories: Option<Vec<String>>,
    /// Meta only. Beneficiary/payer entity IDs required alongside regionalRegulatedCategories. Values are numeric IDs from the advertiser's Meta verification/authorization setup. Keys depend on the declared category: BRAZIL_REGULATION and THAILAND_UNIVERSAL use universal_beneficiary / universal_payer; SINGAPORE_UNIVERSAL uses singapore_universal_beneficiary / singapore_universal_payer; TAIWAN_UNIVERSAL uses taiwan_universal_beneficiary / taiwan_universal_payer; TAIWAN_FINSERV uses taiwan_finserv_beneficiary / taiwan_finserv_payer; AUSTRALIA_FINSERV uses australia_finserv_beneficiary / australia_finserv_payer; INDIA_FINSERV uses india_finserv_beneficiary / india_finserv_payer. Both beneficiary and payer must be included. If omitted and the advertiser has set defaults in Meta Ads Manager advertising settings, Meta auto-fills them.
    #[serde(
        rename = "regionalRegulationIdentities",
        skip_serializing_if = "Option::is_none"
    )]
    pub regional_regulation_identities: Option<std::collections::HashMap<String, i32>>,
}

impl CtwaAdRequestBody {
    /// In addition to the `required` list, the request must use EXACTLY ONE of the two shapes:  - Single-creative: `headline`, `body`, and one of `imageUrl` / `video`,   OR `platformPostId` / `objectStoryId` to reuse an organic post.   On POST /v1/ads/messaging, `placementAssets` can replace `imageUrl` / `video`. - Multi-creative: a non-empty `creatives[]` array. Top-level   creative fields must NOT be set on this shape.  Existing post references work on messaging and CTWA only (not call ads). They cannot be combined with each other or with headline, body, imageUrl or video. `welcomeMessage` works on them (it lives on the ad, not the post). No media is uploaded and the organic post is retained. Fresh creatives still require headline, body, and image or video.  The route enforces this at the Zod boundary; OpenAPI's `required` cannot express the OR cleanly.  Campaign, ad set and targeting fields match POST /v1/ads/create on Meta and run through the same builders: the full targeting set (including `rawTargeting`, `languages`, `gender`, exclusions and `savedTargetingId`), `budgetLevel` (campaign budget), `startDate` / `endDate` in the ad account timezone, and `existingCampaignId` to add a new ad set to a campaign you already have.
    pub fn new(account_id: String, ad_account_id: String, name: String) -> CtwaAdRequestBody {
        CtwaAdRequestBody {
            creative_features: None,
            tracking: None,
            account_id,
            ad_account_id,
            name,
            campaign_name: None,
            ad_set_name: None,
            platform_post_id: None,
            existing_post_id: None,
            object_story_id: None,
            page_id: None,
            whatsapp_phone_number: None,
            headline: None,
            body: None,
            description: None,
            image_url: None,
            video: None,
            welcome_message: None,
            creatives: None,
            ad_set_id: None,
            existing_campaign_id: None,
            budget_level: None,
            budget_amount: None,
            budget_type: None,
            currency: None,
            start_date: None,
            end_date: None,
            countries: None,
            cities: None,
            regions: None,
            zips: None,
            metros: None,
            country_groups: None,
            custom_locations: None,
            age_min: None,
            age_max: None,
            interests: None,
            audience_id: None,
            placements: None,
            gender: None,
            languages: None,
            places: None,
            neighborhoods: None,
            excluded_locations: None,
            behaviors: None,
            work_positions: None,
            work_employers: None,
            work_industries: None,
            income_tier: None,
            user_os: None,
            user_device: None,
            audience_include: None,
            audience_exclude: None,
            saved_targeting_id: None,
            targeting: None,
            raw_targeting: None,
            special_ad_categories: None,
            special_ad_category_country: None,
            advantage_audience: None,
            objective: None,
            status: None,
            campaign_status: None,
            bid_strategy: None,
            bid_amount: None,
            roas_average_floor: None,
            dsa_beneficiary: None,
            dsa_payor: None,
            regional_regulated_categories: None,
            regional_regulation_identities: None,
        }
    }
}
/// Meta enhancement settings for single or attached ads, and defaults for creatives[]. An item replaces the entire map, including with an empty object.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum CreativeFeatures {
    #[serde(rename = "OPT_IN")]
    OptIn,
    #[serde(rename = "OPT_OUT")]
    OptOut,
}

impl Default for CreativeFeatures {
    fn default() -> CreativeFeatures {
        Self::OptIn
    }
}
/// Where the budget lives. `adset` (default) puts it on the new ad set. `campaign` creates an Advantage campaign budget (CBO): the budget and bid strategy sit on the campaign and the ad set inherits them, same as POST /v1/ads/create. Not allowed with `adSetId` or `existingCampaignId`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum BudgetLevel {
    #[serde(rename = "adset")]
    Adset,
    #[serde(rename = "campaign")]
    Campaign,
}

impl Default for BudgetLevel {
    fn default() -> BudgetLevel {
        Self::Adset
    }
}
/// Required unless `adSetId` is set or `existingCampaignId` names a campaign with a campaign budget. `lifetime` requires `endDate`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum BudgetType {
    #[serde(rename = "daily")]
    Daily,
    #[serde(rename = "lifetime")]
    Lifetime,
}

impl Default for BudgetType {
    fn default() -> BudgetType {
        Self::Daily
    }
}
/// Meta only. Continents and trade blocs (`geo_locations.country_groups`), for targeting a whole region without listing its countries. Combines with `countries` rather than replacing it, and is also accepted under `excludedLocations`. Discoverable via `GET /v1/ads/targeting/search?dimension=geo&geoType=country_group`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum CountryGroups {
    #[serde(rename = "africa")]
    Africa,
    #[serde(rename = "asia")]
    Asia,
    #[serde(rename = "europe")]
    Europe,
    #[serde(rename = "north_america")]
    NorthAmerica,
    #[serde(rename = "south_america")]
    SouthAmerica,
    #[serde(rename = "oceania")]
    Oceania,
    #[serde(rename = "central_america")]
    CentralAmerica,
    #[serde(rename = "caribbean")]
    Caribbean,
    #[serde(rename = "eea")]
    Eea,
    #[serde(rename = "euro_area")]
    EuroArea,
    #[serde(rename = "nafta")]
    Nafta,
    #[serde(rename = "mercosur")]
    Mercosur,
    #[serde(rename = "afta")]
    Afta,
    #[serde(rename = "apec")]
    Apec,
    #[serde(rename = "gcc")]
    Gcc,
    #[serde(rename = "cisfta")]
    Cisfta,
    #[serde(rename = "emerging_markets")]
    EmergingMarkets,
    #[serde(rename = "itunes_app_store")]
    ItunesAppStore,
    #[serde(rename = "android_free_store")]
    AndroidFreeStore,
    #[serde(rename = "android_paid_store")]
    AndroidPaidStore,
}

impl Default for CountryGroups {
    fn default() -> CountryGroups {
        Self::Africa
    }
}
/// Restrict the audience by gender (Meta `genders`). Omit or send all for everyone; all is ignored in adSetId attach mode. Stored on the ad and read back in `targeting.gender`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Gender {
    #[serde(rename = "all")]
    All,
    #[serde(rename = "male")]
    Male,
    #[serde(rename = "female")]
    Female,
}

impl Default for Gender {
    fn default() -> Gender {
        Self::All
    }
}
/// Normalized household-income tier, same as POST /v1/ads/create. Incompatible with housing, employment and credit specialAdCategories.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum IncomeTier {
    #[serde(rename = "top_5")]
    Top5,
    #[serde(rename = "top_10")]
    Top10,
    #[serde(rename = "top_10_25")]
    Top1025,
    #[serde(rename = "top_25_50")]
    Top2550,
}

impl Default for IncomeTier {
    fn default() -> IncomeTier {
        Self::Top5
    }
}
/// Meta special ad categories on the new campaign.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum SpecialAdCategories {
    #[serde(rename = "HOUSING")]
    Housing,
    #[serde(rename = "EMPLOYMENT")]
    Employment,
    #[serde(rename = "CREDIT")]
    Credit,
    #[serde(rename = "ISSUES_ELECTIONS_POLITICS")]
    IssuesElectionsPolitics,
    #[serde(rename = "FINANCIAL_PRODUCTS_SERVICES")]
    FinancialProductsServices,
    #[serde(rename = "ONLINE_GAMBLING_AND_GAMING")]
    OnlineGamblingAndGaming,
}

impl Default for SpecialAdCategories {
    fn default() -> SpecialAdCategories {
        Self::Housing
    }
}
/// Meta's Advantage+ audience expansion. `0` (default) keeps targeting strict; `1` lets Meta expand beyond the supplied targeting when its delivery system finds better matches. Always sent on CREATE (Meta requires it).
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum AdvantageAudience {
    #[serde(rename = "0")]
    Variant0,
    #[serde(rename = "1")]
    Variant1,
}

impl Default for AdvantageAudience {
    fn default() -> AdvantageAudience {
        Self::Variant0
    }
}
/// Defaults to `OUTCOME_ENGAGEMENT`. `OUTCOME_SALES` and `OUTCOME_LEADS` require additional account configuration (Dataset linked to the WABA for sales) and may be rejected by Meta if missing.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Objective {
    #[serde(rename = "OUTCOME_ENGAGEMENT")]
    OutcomeEngagement,
    #[serde(rename = "OUTCOME_SALES")]
    OutcomeSales,
    #[serde(rename = "OUTCOME_LEADS")]
    OutcomeLeads,
}

impl Default for Objective {
    fn default() -> Objective {
        Self::OutcomeEngagement
    }
}
/// Defaults to `ACTIVE`. `PAUSED` pauses only the top-most object this call creates: the new campaign (ad set and ads switched on), or, with `adSetId`, the new ads themselves.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Status {
    #[serde(rename = "ACTIVE")]
    Active,
    #[serde(rename = "PAUSED")]
    Paused,
}

impl Default for Status {
    fn default() -> Status {
        Self::Active
    }
}
/// Campaign-level status, same semantics as `POST /v1/ads/create`. Defaults to `status`. `PAUSED` holds the new campaign off while the ad set and ads switch on (one resume call brings the whole hierarchy live); `ACTIVE` with `status: PAUSED` switches the campaign on and pauses the new ad set instead. Only meaningful when a new campaign is being created; rejected with a 400 alongside `adSetId` (the attach shape reuses an existing campaign).
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum CampaignStatus {
    #[serde(rename = "ACTIVE")]
    Active,
    #[serde(rename = "PAUSED")]
    Paused,
}

impl Default for CampaignStatus {
    fn default() -> CampaignStatus {
        Self::Active
    }
}
/// Meta bid strategy applied to the shared ad set. Defaults to `LOWEST_COST_WITHOUT_CAP` (auto-bid) when omitted. `LOWEST_COST_WITH_BID_CAP` and `COST_CAP` require `bidAmount`. `LOWEST_COST_WITH_MIN_ROAS` requires `roasAverageFloor`. CTWA's `optimization_goal` is fixed to `CONVERSATIONS`, but the bid strategy is independent.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum BidStrategy {
    #[serde(rename = "LOWEST_COST_WITHOUT_CAP")]
    LowestCostWithoutCap,
    #[serde(rename = "LOWEST_COST_WITH_BID_CAP")]
    LowestCostWithBidCap,
    #[serde(rename = "COST_CAP")]
    CostCap,
    #[serde(rename = "LOWEST_COST_WITH_MIN_ROAS")]
    LowestCostWithMinRoas,
}

impl Default for BidStrategy {
    fn default() -> BidStrategy {
        Self::LowestCostWithoutCap
    }
}