/*
* 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).
*
* The version of the OpenAPI document: 1.0.4
* Contact: support@zernio.com
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct BoostPostRequest {
/// Zernio post ID (provide this or platformPostId)
#[serde(rename = "postId", skip_serializing_if = "Option::is_none")]
pub post_id: Option<String>,
/// Platform post ID (alternative to postId)
#[serde(rename = "platformPostId", skip_serializing_if = "Option::is_none")]
pub platform_post_id: Option<String>,
/// Account ID
#[serde(rename = "accountId")]
pub account_id: String,
/// Platform ad account ID
#[serde(rename = "adAccountId")]
pub ad_account_id: String,
#[serde(rename = "name")]
pub name: String,
/// Available goals vary by platform. Meta (Facebook/Instagram) and TikTok support all 7. LinkedIn supports all except app_promotion. X supports engagement, traffic, awareness, video_views, app_promotion. Pinterest and Google Ads support only engagement, traffic, awareness, video_views.
#[serde(rename = "goal")]
pub goal: Goal,
/// Meta only. Attach the boosted post to this existing ad set instead of creating a campaign. The ad set then owns budget, schedule and targeting; sending those too is a 400.
#[serde(rename = "adSetId", skip_serializing_if = "Option::is_none")]
pub ad_set_id: Option<String>,
#[serde(rename = "budget", skip_serializing_if = "Option::is_none")]
pub budget: Option<Box<models::BoostPostRequestBudget>>,
/// Meta only. Instagram identity the ad runs AS (creative.instagram_user_id), overriding the account linked to the Page. Live-verified against a Page-post creative.
#[serde(rename = "instagramAccountId", skip_serializing_if = "Option::is_none")]
pub instagram_account_id: Option<String>,
/// Meta only. Ad-set destination_type: where the click LANDS, as opposed to instagramAccountId which is who the ad runs as. Messaging destinations imply their matching CTA and require goal engagement. Lead ads use ON_AD; combining an instant form with a messaging destination is rejected.
#[serde(rename = "destinationType", skip_serializing_if = "Option::is_none")]
pub destination_type: Option<DestinationType>,
/// Meta WhatsApp only. E.164 number already paired with the Page. Omit to use the default pairing. Requires WHATSAPP destinationType or WHATSAPP_MESSAGE callToAction.
#[serde(
rename = "whatsappPhoneNumber",
skip_serializing_if = "Option::is_none"
)]
pub whatsapp_phone_number: Option<String>,
/// ISO 4217 currency code matching the ad account's currency. Meta only. Optional: Zernio resolves it from the ad account when omitted. The value selects the minor-unit exponent Zernio converts budget/bid amounts by before calling Meta (most currencies are cents; zero-decimal currencies like JPY/KRW are sent as-is).
#[serde(rename = "currency", skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
#[serde(rename = "schedule", skip_serializing_if = "Option::is_none")]
pub schedule: Option<Box<models::BoostPostRequestSchedule>>,
#[serde(rename = "targeting", skip_serializing_if = "Option::is_none")]
pub targeting: Option<Box<models::BoostPostRequestTargeting>>,
/// Meta only. A Meta-native targeting spec (e.g. `{ \"geo_locations\": { \"cities\": [{ \"key\": \"...\", \"radius\": 15, \"distance_unit\": \"kilometer\" }] } }`). Sent alone it is forwarded unchanged. Use for advanced fields the structured object does not expose (flexible_spec, excluded audiences, business places, user_os, wireless_carrier). Can be combined with `targeting`: rawTargeting is the BASE layer and the built camelCase spec is merged on top, key by key (camelCase wins on collision). The merge goes one level deep inside `geo_locations` and `excluded_geo_locations` (built sub-keys win; raw-only sub-keys such as `location_types` survive). Array values (`flexible_spec`, ...) are replaced as a whole key, never element-merged. When `rawTargeting` is present the `advantage_audience: 0` default that Zernio normally applies is no longer emitted, so it cannot clobber a `targeting_automation` sent in the raw spec. Meta requires `targeting_automation` on ad set creation, so include it in the raw spec, or send `targeting.advantage_audience` (0 or 1), which is merged over raw as `targeting_automation`.
#[serde(rename = "rawTargeting", skip_serializing_if = "Option::is_none")]
pub raw_targeting: Option<std::collections::HashMap<String, serde_json::Value>>,
/// Deprecated: send it inside `platformSpecificData` instead (Meta today; TikTok's nested shape is planned). The flat field keeps working during the deprecation window; sending both shapes returns a 400. Meta bid strategy applied to the ad set. On TikTok, mapped to `bid_type` / `bid_price` / `deep_bid_type` automatically.
#[serde(rename = "bidStrategy", skip_serializing_if = "Option::is_none")]
pub bid_strategy: Option<models::BidStrategy>,
/// Deprecated: send it inside `platformSpecificData` instead (Meta today; TikTok's nested shape is planned). The flat field keeps working during the deprecation window; sending both shapes returns a 400. Bid cap in WHOLE currency units (USD: 5 = $5.00; JPY: 100 = ¥100). Required when `bidStrategy` is `LOWEST_COST_WITH_BID_CAP` or `COST_CAP`. Backward-compat: providing `bidAmount` without `bidStrategy` is treated as `LOWEST_COST_WITH_BID_CAP`.
#[serde(rename = "bidAmount", skip_serializing_if = "Option::is_none")]
pub bid_amount: Option<f64>,
/// Deprecated: send it inside `platformSpecificData` instead (Meta today; TikTok's nested shape is planned). The flat field keeps working during the deprecation window; sending both shapes returns a 400. Minimum ROAS as a decimal multiplier (e.g. 2.0 = 2.0x ROAS). Required when `bidStrategy` is `LOWEST_COST_WITH_MIN_ROAS`. Sent to Meta as `bid_constraints.roas_average_floor` × 10000 (Meta uses fixed-point integers).
#[serde(rename = "roasAverageFloor", skip_serializing_if = "Option::is_none")]
pub roas_average_floor: Option<f64>,
#[serde(
rename = "platformSpecificData",
skip_serializing_if = "Option::is_none"
)]
pub platform_specific_data: Option<Box<models::BoostPostRequestPlatformSpecificData>>,
#[serde(rename = "tracking", skip_serializing_if = "Option::is_none")]
pub tracking: Option<Box<models::BoostPostRequestTracking>>,
/// Meta only. Required for housing, employment, credit, or political ads.
#[serde(
rename = "specialAdCategories",
skip_serializing_if = "Option::is_none"
)]
pub special_ad_categories: Option<Vec<SpecialAdCategories>>,
/// Meta (metaads) only. 2-letter ISO country codes the special ad category applies to. Requires specialAdCategories to be set (400 otherwise).
#[serde(
rename = "specialAdCategoryCountry",
skip_serializing_if = "Option::is_none"
)]
pub special_ad_category_country: Option<Vec<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 for regionalRegulatedCategories. Values are numeric IDs from Meta verification. Keys vary by category (e.g. universal_beneficiary / universal_payer for BRAZIL_REGULATION and THAILAND_UNIVERSAL). If omitted, Meta uses Ads Manager defaults when configured.
#[serde(
rename = "regionalRegulationIdentities",
skip_serializing_if = "Option::is_none"
)]
pub regional_regulation_identities: Option<std::collections::HashMap<String, i32>>,
/// Website URL for non-messaging CTA buttons. Send it with `callToAction`. Omit for messaging boosts. **Meta**: adds a top-level `call_to_action` to the post-reference creative. This is what gives a `traffic` boost a clickable destination without replacing the creative and losing the post's social proof. Ignored when `leadGenFormId` is set, which supplies its own destination. Live-verified against a Page-post creative. **TikTok**: maps to `landing_page_url` on the Spark Ad creative (`AdcreateCreatives.landing_page_url`); Spark Ads have no clickable destination without it. Ignored on LinkedIn / Pinterest / X / Google, which infer the destination from the boosted post.
#[serde(rename = "linkUrl", skip_serializing_if = "Option::is_none")]
pub link_url: Option<String>,
/// CTA button label. Non-messaging CTAs require `linkUrl`. WHATSAPP_MESSAGE, MESSAGE_PAGE, and INSTAGRAM_MESSAGE do not require a URL and reject linkUrl. **Meta**: the CTA enum of POST /v1/ads/create plus `VIEW_INSTAGRAM_PROFILE`, `WHATSAPP_MESSAGE`, `MESSAGE_PAGE`, and `INSTAGRAM_MESSAGE`. VIEW_INSTAGRAM_PROFILE requires linkUrl; the messaging CTAs select their destination automatically. **TikTok**: pass-through to `call_to_action` on the Spark Ad creative; the platform validates the value. See TikTok's \"Enumeration - Call-to-Action\".
#[serde(rename = "callToAction", skip_serializing_if = "Option::is_none")]
pub call_to_action: Option<String>,
/// TikTok-only. Spark Code (creator's `auth_code`) authorizing cross-creator Spark Ads: the advertiser can boost a video owned by a DIFFERENT TikTok account. Without this, boosts are limited to videos owned by the same account running the ads (same-BC creators only). The creator generates the code in their TikTok app's Promote settings and shares it with the advertiser. Maps to `auth_code` on the creative entry of /v2/ad/create/.
#[serde(rename = "sparkAuthCode", skip_serializing_if = "Option::is_none")]
pub spark_auth_code: Option<String>,
/// 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>,
/// Lead Gen form ID to attach to the boosted ad's creative. REQUIRED when `goal` is `lead_generation`. On Meta this is the leadgen_forms ID (create one via POST /v1/ads/lead-forms). On LinkedIn this is the adForm ID (create one via POST /v1/ads/lead-forms with a LinkedIn account); the creative's `leadgenCallToAction.destination` is set to `urn:li:adForm:{id}`. Ignored for other goals.
#[serde(rename = "leadGenFormId", skip_serializing_if = "Option::is_none")]
pub lead_gen_form_id: Option<String>,
/// Meta, TikTok, and LinkedIn. Publish state of the created entities. Omitted or ACTIVE publishes live (default); PAUSED creates them paused so you can review before they spend. On LinkedIn the whole campaign group, campaign, and creative hierarchy stays PAUSED (intendedStatus PAUSED on each).
#[serde(rename = "status", skip_serializing_if = "Option::is_none")]
pub status: Option<Status>,
/// Meta only. Explicit ad-set `optimization_goal` override. When omitted, defaults to the value derived from `goal`. Messaging boosts always use CONVERSATIONS and reject another optimizationGoal. Otherwise the value must be compatible with the objective Meta derives from `goal`, not with the objective used by `POST /v1/ads/create` for the same `goal` name: boost maps `goal: \"engagement\"` to objective `OUTCOME_AWARENESS`, which accepts `REACH`, `IMPRESSIONS`, `AD_RECALL_LIFT`, or THRUPLAY-class values, and rejects `POST_ENGAGEMENT` (that value is only valid under `OUTCOME_ENGAGEMENT`, which create uses for the same goal name).
#[serde(rename = "optimizationGoal", skip_serializing_if = "Option::is_none")]
pub optimization_goal: Option<String>,
}
impl BoostPostRequest {
pub fn new(
account_id: String,
ad_account_id: String,
name: String,
goal: Goal,
) -> BoostPostRequest {
BoostPostRequest {
post_id: None,
platform_post_id: None,
account_id,
ad_account_id,
name,
goal,
ad_set_id: None,
budget: None,
instagram_account_id: None,
destination_type: None,
whatsapp_phone_number: None,
currency: None,
schedule: None,
targeting: None,
raw_targeting: None,
bid_strategy: None,
bid_amount: None,
roas_average_floor: None,
platform_specific_data: None,
tracking: None,
special_ad_categories: None,
special_ad_category_country: None,
regional_regulated_categories: None,
regional_regulation_identities: None,
link_url: None,
call_to_action: None,
spark_auth_code: None,
dsa_beneficiary: None,
dsa_payor: None,
lead_gen_form_id: None,
status: None,
optimization_goal: None,
}
}
}
/// Available goals vary by platform. Meta (Facebook/Instagram) and TikTok support all 7. LinkedIn supports all except app_promotion. X supports engagement, traffic, awareness, video_views, app_promotion. Pinterest and Google Ads support only engagement, traffic, awareness, video_views.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Goal {
#[serde(rename = "engagement")]
Engagement,
#[serde(rename = "traffic")]
Traffic,
#[serde(rename = "awareness")]
Awareness,
#[serde(rename = "video_views")]
VideoViews,
#[serde(rename = "lead_generation")]
LeadGeneration,
#[serde(rename = "conversions")]
Conversions,
#[serde(rename = "app_promotion")]
AppPromotion,
}
impl Default for Goal {
fn default() -> Goal {
Self::Engagement
}
}
/// Meta only. Ad-set destination_type: where the click LANDS, as opposed to instagramAccountId which is who the ad runs as. Messaging destinations imply their matching CTA and require goal engagement. Lead ads use ON_AD; combining an instant form with a messaging destination is rejected.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum DestinationType {
#[serde(rename = "INSTAGRAM_PROFILE")]
InstagramProfile,
#[serde(rename = "WEBSITE")]
Website,
#[serde(rename = "ON_AD")]
OnAd,
#[serde(rename = "MESSENGER")]
Messenger,
#[serde(rename = "WHATSAPP")]
Whatsapp,
#[serde(rename = "INSTAGRAM_DIRECT")]
InstagramDirect,
}
impl Default for DestinationType {
fn default() -> DestinationType {
Self::InstagramProfile
}
}
/// Meta only. Required for housing, employment, credit, or political ads.
#[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 = "FINANCIAL_PRODUCTS_SERVICES")]
FinancialProductsServices,
#[serde(rename = "ISSUES_ELECTIONS_POLITICS")]
IssuesElectionsPolitics,
#[serde(rename = "ONLINE_GAMBLING_AND_GAMING")]
OnlineGamblingAndGaming,
}
impl Default for SpecialAdCategories {
fn default() -> SpecialAdCategories {
Self::Housing
}
}
/// Meta, TikTok, and LinkedIn. Publish state of the created entities. Omitted or ACTIVE publishes live (default); PAUSED creates them paused so you can review before they spend. On LinkedIn the whole campaign group, campaign, and creative hierarchy stays PAUSED (intendedStatus PAUSED on each).
#[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
}
}