Skip to main content

late/apis/
ads_api.rs

1/*
2 * Zernio API
3 *
4 * API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api
5 *
6 * The version of the OpenAPI document: 1.0.1
7 * Contact: support@zernio.com
8 * Generated by: https://openapi-generator.tech
9 */
10
11use super::{configuration, ContentType, Error};
12use crate::{apis::ResponseContent, models};
13use reqwest;
14use serde::{de::Error as _, Deserialize, Serialize};
15
16/// struct for typed errors of method [`boost_post`]
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(untagged)]
19pub enum BoostPostError {
20    Status400(),
21    Status401(models::InlineObject),
22    Status403(),
23    Status422(),
24    UnknownValue(serde_json::Value),
25}
26
27/// struct for typed errors of method [`create_standalone_ad`]
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(untagged)]
30pub enum CreateStandaloneAdError {
31    Status400(),
32    Status401(models::InlineObject),
33    Status403(),
34    Status422(),
35    UnknownValue(serde_json::Value),
36}
37
38/// struct for typed errors of method [`delete_ad`]
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(untagged)]
41pub enum DeleteAdError {
42    Status401(models::InlineObject),
43    Status404(models::InlineObject1),
44    UnknownValue(serde_json::Value),
45}
46
47/// struct for typed errors of method [`get_ad`]
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(untagged)]
50pub enum GetAdError {
51    Status401(models::InlineObject),
52    Status404(models::InlineObject1),
53    UnknownValue(serde_json::Value),
54}
55
56/// struct for typed errors of method [`get_ad_analytics`]
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(untagged)]
59pub enum GetAdAnalyticsError {
60    Status401(models::InlineObject),
61    Status403(),
62    Status404(models::InlineObject1),
63    UnknownValue(serde_json::Value),
64}
65
66/// struct for typed errors of method [`list_ad_accounts`]
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(untagged)]
69pub enum ListAdAccountsError {
70    Status401(models::InlineObject),
71    Status422(),
72    UnknownValue(serde_json::Value),
73}
74
75/// struct for typed errors of method [`list_ads`]
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(untagged)]
78pub enum ListAdsError {
79    Status401(models::InlineObject),
80    Status403(),
81    UnknownValue(serde_json::Value),
82}
83
84/// struct for typed errors of method [`list_conversion_destinations`]
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(untagged)]
87pub enum ListConversionDestinationsError {
88    Status400(),
89    Status401(models::InlineObject),
90    Status403(),
91    Status404(),
92    UnknownValue(serde_json::Value),
93}
94
95/// struct for typed errors of method [`search_ad_interests`]
96#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(untagged)]
98pub enum SearchAdInterestsError {
99    Status401(models::InlineObject),
100    Status403(),
101    UnknownValue(serde_json::Value),
102}
103
104/// struct for typed errors of method [`send_conversions`]
105#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(untagged)]
107pub enum SendConversionsError {
108    Status400(),
109    Status401(models::InlineObject),
110    Status403(),
111    Status404(),
112    UnknownValue(serde_json::Value),
113}
114
115/// struct for typed errors of method [`update_ad`]
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(untagged)]
118pub enum UpdateAdError {
119    Status400(),
120    Status401(models::InlineObject),
121    Status404(models::InlineObject1),
122    UnknownValue(serde_json::Value),
123}
124
125/// Creates a paid ad campaign from an existing published post. Creates the full platform campaign hierarchy (campaign, ad set, ad).
126pub async fn boost_post(
127    configuration: &configuration::Configuration,
128    boost_post_request: models::BoostPostRequest,
129) -> Result<models::UpdateAd200Response, Error<BoostPostError>> {
130    // add a prefix to parameters to efficiently prevent name collisions
131    let p_body_boost_post_request = boost_post_request;
132
133    let uri_str = format!("{}/v1/ads/boost", configuration.base_path);
134    let mut req_builder = configuration
135        .client
136        .request(reqwest::Method::POST, &uri_str);
137
138    if let Some(ref user_agent) = configuration.user_agent {
139        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
140    }
141    if let Some(ref token) = configuration.bearer_access_token {
142        req_builder = req_builder.bearer_auth(token.to_owned());
143    };
144    req_builder = req_builder.json(&p_body_boost_post_request);
145
146    let req = req_builder.build()?;
147    let resp = configuration.client.execute(req).await?;
148
149    let status = resp.status();
150    let content_type = resp
151        .headers()
152        .get("content-type")
153        .and_then(|v| v.to_str().ok())
154        .unwrap_or("application/octet-stream");
155    let content_type = super::ContentType::from(content_type);
156
157    if !status.is_client_error() && !status.is_server_error() {
158        let content = resp.text().await?;
159        match content_type {
160            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
161            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::UpdateAd200Response`"))),
162            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::UpdateAd200Response`")))),
163        }
164    } else {
165        let content = resp.text().await?;
166        let entity: Option<BoostPostError> = serde_json::from_str(&content).ok();
167        Err(Error::ResponseError(ResponseContent {
168            status,
169            content,
170            entity,
171        }))
172    }
173}
174
175/// Creates a paid ad with custom creative. The request body supports three mutually-exclusive shapes:  1. **Legacy single-creative** (all platforms). Top-level `headline` + `body` + `imageUrl` + `linkUrl` + `callToAction` create 1 campaign + 1 ad set + 1 ad. 2. **Multi-creative** (Meta only — use `creatives[]` array). Creates 1 campaign + 1 ad set + N ads sharing the same budget / targeting / schedule. This is the standard performance-marketing creative-testing flow — Meta's delivery algorithm A/B tests the creatives inside a single ad set so budget isn't fragmented across N parallel campaigns. 3. **Attach to existing ad set** (Meta only — pass `adSetId` + a single creative). Adds one new ad to an existing ad set without creating a new campaign. Budget, targeting, goal are inherited from the ad set on Meta.  `creatives[]` and `adSetId` are mutually exclusive; specifying both returns 400.  **Per-platform required fields** (the platform is inferred from `accountId`): - **Meta (facebook, instagram)**: `accountId`, `adAccountId`, `name`, `goal`, `budgetAmount`, `budgetType`, `headline`, `body`, `imageUrl`, `linkUrl`, `callToAction`. - **Google Ads (Display)**: `accountId`, `adAccountId`, `name`, `goal`, `budgetAmount`, `budgetType`, `headline`, `body`, `linkUrl`, `imageUrl`, `businessName`. `longHeadline` defaults to `headline` if omitted (max 90 chars). - **Google Ads (Search)**: `accountId`, `adAccountId`, `name`, `goal`, `budgetAmount`, `budgetType`, `headline`, `body`, `linkUrl`, `businessName`. `imageUrl` is NOT required for Search. Set `campaignType: \"search\"`. - **TikTok**: `accountId`, `adAccountId`, `name`, `goal`, `budgetAmount`, `budgetType`, `body`, `linkUrl`, `imageUrl` (this field **carries the VIDEO URL** for TikTok; the TikTok ads endpoint is video-only and the field is named `imageUrl` for cross-platform consistency). - **Pinterest**: `accountId`, `adAccountId`, `name`, `goal`, `budgetAmount`, `budgetType`, `headline`, `body`, `imageUrl`, `linkUrl`. Optional `boardId` (auto-creates \"Zernio Ads\" board if omitted). - **X / Twitter**: `accountId` (the posting account, internally resolved to the linked X Ads credential), `adAccountId`, `name`, `goal`, `budgetAmount`, `budgetType`, `body` (the tweet text, max 280 chars with `linkUrl` adding ~24). `headline`, `imageUrl`, `callToAction`, and targeting fields are ignored. Requires a connected X Ads account (`/v1/connect/twitter/ads`); otherwise 422.  **Budget minimums** are enforced per platform in USD: TikTok=$20, Pinterest=$5, all others=$1. If you pass `currency` other than USD, this minimum is currently still evaluated as USD. Pass the ad account's native currency on every request so Meta-side amount conversion is correct.  **Video ads (Meta only).** Meta (facebook, instagram) supports video creatives on all three shapes (legacy, multi-creative via `creatives[].video`, attach). Set `video: { url, thumbnailUrl }` at the request root (for legacy/attach) or per-entry inside `creatives[]` (for multi-creative). `video` and `imageUrl` are mutually exclusive per creative; supply exactly one. `video.thumbnailUrl` is required because Meta requires a thumbnail on every video creative. The video is uploaded to Meta via chunked transfer and the request blocks on Meta's transcoding (`status.video_status === 'ready'`). This path can take several minutes for longer videos; this endpoint is configured with `maxDuration = 800` on Vercel (13 min) to cover it, but your HTTP client should allow for the same. If transcoding hasn't finished within 10 minutes, the request fails with a `platform_error`. Video on non-Meta platforms is NOT supported here: TikTok uses its own flow (pass the video URL via `imageUrl`); other platforms are image-only.
176pub async fn create_standalone_ad(
177    configuration: &configuration::Configuration,
178    create_standalone_ad_request: models::CreateStandaloneAdRequest,
179) -> Result<models::CreateStandaloneAd201Response, Error<CreateStandaloneAdError>> {
180    // add a prefix to parameters to efficiently prevent name collisions
181    let p_body_create_standalone_ad_request = create_standalone_ad_request;
182
183    let uri_str = format!("{}/v1/ads/create", configuration.base_path);
184    let mut req_builder = configuration
185        .client
186        .request(reqwest::Method::POST, &uri_str);
187
188    if let Some(ref user_agent) = configuration.user_agent {
189        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
190    }
191    if let Some(ref token) = configuration.bearer_access_token {
192        req_builder = req_builder.bearer_auth(token.to_owned());
193    };
194    req_builder = req_builder.json(&p_body_create_standalone_ad_request);
195
196    let req = req_builder.build()?;
197    let resp = configuration.client.execute(req).await?;
198
199    let status = resp.status();
200    let content_type = resp
201        .headers()
202        .get("content-type")
203        .and_then(|v| v.to_str().ok())
204        .unwrap_or("application/octet-stream");
205    let content_type = super::ContentType::from(content_type);
206
207    if !status.is_client_error() && !status.is_server_error() {
208        let content = resp.text().await?;
209        match content_type {
210            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
211            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::CreateStandaloneAd201Response`"))),
212            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::CreateStandaloneAd201Response`")))),
213        }
214    } else {
215        let content = resp.text().await?;
216        let entity: Option<CreateStandaloneAdError> = serde_json::from_str(&content).ok();
217        Err(Error::ResponseError(ResponseContent {
218            status,
219            content,
220            entity,
221        }))
222    }
223}
224
225/// Cancels the ad on the platform and marks it as cancelled in the database. The ad is preserved for history.
226pub async fn delete_ad(
227    configuration: &configuration::Configuration,
228    ad_id: &str,
229) -> Result<models::DeleteAccountGroup200Response, Error<DeleteAdError>> {
230    // add a prefix to parameters to efficiently prevent name collisions
231    let p_path_ad_id = ad_id;
232
233    let uri_str = format!(
234        "{}/v1/ads/{adId}",
235        configuration.base_path,
236        adId = crate::apis::urlencode(p_path_ad_id)
237    );
238    let mut req_builder = configuration
239        .client
240        .request(reqwest::Method::DELETE, &uri_str);
241
242    if let Some(ref user_agent) = configuration.user_agent {
243        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
244    }
245    if let Some(ref token) = configuration.bearer_access_token {
246        req_builder = req_builder.bearer_auth(token.to_owned());
247    };
248
249    let req = req_builder.build()?;
250    let resp = configuration.client.execute(req).await?;
251
252    let status = resp.status();
253    let content_type = resp
254        .headers()
255        .get("content-type")
256        .and_then(|v| v.to_str().ok())
257        .unwrap_or("application/octet-stream");
258    let content_type = super::ContentType::from(content_type);
259
260    if !status.is_client_error() && !status.is_server_error() {
261        let content = resp.text().await?;
262        match content_type {
263            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
264            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::DeleteAccountGroup200Response`"))),
265            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::DeleteAccountGroup200Response`")))),
266        }
267    } else {
268        let content = resp.text().await?;
269        let entity: Option<DeleteAdError> = serde_json::from_str(&content).ok();
270        Err(Error::ResponseError(ResponseContent {
271            status,
272            content,
273            entity,
274        }))
275    }
276}
277
278/// Returns an ad with its creative, targeting, status, and performance metrics.
279pub async fn get_ad(
280    configuration: &configuration::Configuration,
281    ad_id: &str,
282) -> Result<models::GetAd200Response, Error<GetAdError>> {
283    // add a prefix to parameters to efficiently prevent name collisions
284    let p_path_ad_id = ad_id;
285
286    let uri_str = format!(
287        "{}/v1/ads/{adId}",
288        configuration.base_path,
289        adId = crate::apis::urlencode(p_path_ad_id)
290    );
291    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
292
293    if let Some(ref user_agent) = configuration.user_agent {
294        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
295    }
296    if let Some(ref token) = configuration.bearer_access_token {
297        req_builder = req_builder.bearer_auth(token.to_owned());
298    };
299
300    let req = req_builder.build()?;
301    let resp = configuration.client.execute(req).await?;
302
303    let status = resp.status();
304    let content_type = resp
305        .headers()
306        .get("content-type")
307        .and_then(|v| v.to_str().ok())
308        .unwrap_or("application/octet-stream");
309    let content_type = super::ContentType::from(content_type);
310
311    if !status.is_client_error() && !status.is_server_error() {
312        let content = resp.text().await?;
313        match content_type {
314            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
315            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::GetAd200Response`"))),
316            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::GetAd200Response`")))),
317        }
318    } else {
319        let content = resp.text().await?;
320        let entity: Option<GetAdError> = serde_json::from_str(&content).ok();
321        Err(Error::ResponseError(ResponseContent {
322            status,
323            content,
324            entity,
325        }))
326    }
327}
328
329/// Returns detailed performance analytics for an ad. Includes summary metrics, a daily timeline over the requested date range, and optional demographic breakdowns (Meta and TikTok only). If no date range is provided, defaults to the last 90 days. Date range is capped at 90 days max.
330pub async fn get_ad_analytics(
331    configuration: &configuration::Configuration,
332    ad_id: &str,
333    from_date: Option<String>,
334    to_date: Option<String>,
335    breakdowns: Option<&str>,
336) -> Result<models::GetAdAnalytics200Response, Error<GetAdAnalyticsError>> {
337    // add a prefix to parameters to efficiently prevent name collisions
338    let p_path_ad_id = ad_id;
339    let p_query_from_date = from_date;
340    let p_query_to_date = to_date;
341    let p_query_breakdowns = breakdowns;
342
343    let uri_str = format!(
344        "{}/v1/ads/{adId}/analytics",
345        configuration.base_path,
346        adId = crate::apis::urlencode(p_path_ad_id)
347    );
348    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
349
350    if let Some(ref param_value) = p_query_from_date {
351        req_builder = req_builder.query(&[("fromDate", &param_value.to_string())]);
352    }
353    if let Some(ref param_value) = p_query_to_date {
354        req_builder = req_builder.query(&[("toDate", &param_value.to_string())]);
355    }
356    if let Some(ref param_value) = p_query_breakdowns {
357        req_builder = req_builder.query(&[("breakdowns", &param_value.to_string())]);
358    }
359    if let Some(ref user_agent) = configuration.user_agent {
360        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
361    }
362    if let Some(ref token) = configuration.bearer_access_token {
363        req_builder = req_builder.bearer_auth(token.to_owned());
364    };
365
366    let req = req_builder.build()?;
367    let resp = configuration.client.execute(req).await?;
368
369    let status = resp.status();
370    let content_type = resp
371        .headers()
372        .get("content-type")
373        .and_then(|v| v.to_str().ok())
374        .unwrap_or("application/octet-stream");
375    let content_type = super::ContentType::from(content_type);
376
377    if !status.is_client_error() && !status.is_server_error() {
378        let content = resp.text().await?;
379        match content_type {
380            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
381            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::GetAdAnalytics200Response`"))),
382            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::GetAdAnalytics200Response`")))),
383        }
384    } else {
385        let content = resp.text().await?;
386        let entity: Option<GetAdAnalyticsError> = serde_json::from_str(&content).ok();
387        Err(Error::ResponseError(ResponseContent {
388            status,
389            content,
390            entity,
391        }))
392    }
393}
394
395/// Returns the platform ad accounts available for the given social account (e.g. Meta ad accounts, TikTok advertiser IDs, Google Ads customer IDs).
396pub async fn list_ad_accounts(
397    configuration: &configuration::Configuration,
398    account_id: &str,
399) -> Result<models::ListAdAccounts200Response, Error<ListAdAccountsError>> {
400    // add a prefix to parameters to efficiently prevent name collisions
401    let p_query_account_id = account_id;
402
403    let uri_str = format!("{}/v1/ads/accounts", configuration.base_path);
404    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
405
406    req_builder = req_builder.query(&[("accountId", &p_query_account_id.to_string())]);
407    if let Some(ref user_agent) = configuration.user_agent {
408        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
409    }
410    if let Some(ref token) = configuration.bearer_access_token {
411        req_builder = req_builder.bearer_auth(token.to_owned());
412    };
413
414    let req = req_builder.build()?;
415    let resp = configuration.client.execute(req).await?;
416
417    let status = resp.status();
418    let content_type = resp
419        .headers()
420        .get("content-type")
421        .and_then(|v| v.to_str().ok())
422        .unwrap_or("application/octet-stream");
423    let content_type = super::ContentType::from(content_type);
424
425    if !status.is_client_error() && !status.is_server_error() {
426        let content = resp.text().await?;
427        match content_type {
428            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
429            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ListAdAccounts200Response`"))),
430            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ListAdAccounts200Response`")))),
431        }
432    } else {
433        let content = resp.text().await?;
434        let entity: Option<ListAdAccountsError> = serde_json::from_str(&content).ok();
435        Err(Error::ResponseError(ResponseContent {
436            status,
437            content,
438            entity,
439        }))
440    }
441}
442
443/// Returns a paginated list of ads with metrics computed over an optional date range. Use source=all to include externally-synced ads from platform ad managers. If no date range is provided, defaults to the last 90 days. Date range is capped at 90 days max.
444pub async fn list_ads(
445    configuration: &configuration::Configuration,
446    page: Option<i32>,
447    limit: Option<i32>,
448    source: Option<&str>,
449    status: Option<models::AdStatus>,
450    platform: Option<&str>,
451    account_id: Option<&str>,
452    ad_account_id: Option<&str>,
453    profile_id: Option<&str>,
454    campaign_id: Option<&str>,
455    from_date: Option<String>,
456    to_date: Option<String>,
457) -> Result<models::ListAds200Response, Error<ListAdsError>> {
458    // add a prefix to parameters to efficiently prevent name collisions
459    let p_query_page = page;
460    let p_query_limit = limit;
461    let p_query_source = source;
462    let p_query_status = status;
463    let p_query_platform = platform;
464    let p_query_account_id = account_id;
465    let p_query_ad_account_id = ad_account_id;
466    let p_query_profile_id = profile_id;
467    let p_query_campaign_id = campaign_id;
468    let p_query_from_date = from_date;
469    let p_query_to_date = to_date;
470
471    let uri_str = format!("{}/v1/ads", configuration.base_path);
472    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
473
474    if let Some(ref param_value) = p_query_page {
475        req_builder = req_builder.query(&[("page", &param_value.to_string())]);
476    }
477    if let Some(ref param_value) = p_query_limit {
478        req_builder = req_builder.query(&[("limit", &param_value.to_string())]);
479    }
480    if let Some(ref param_value) = p_query_source {
481        req_builder = req_builder.query(&[("source", &param_value.to_string())]);
482    }
483    if let Some(ref param_value) = p_query_status {
484        req_builder = req_builder.query(&[("status", &param_value.to_string())]);
485    }
486    if let Some(ref param_value) = p_query_platform {
487        req_builder = req_builder.query(&[("platform", &param_value.to_string())]);
488    }
489    if let Some(ref param_value) = p_query_account_id {
490        req_builder = req_builder.query(&[("accountId", &param_value.to_string())]);
491    }
492    if let Some(ref param_value) = p_query_ad_account_id {
493        req_builder = req_builder.query(&[("adAccountId", &param_value.to_string())]);
494    }
495    if let Some(ref param_value) = p_query_profile_id {
496        req_builder = req_builder.query(&[("profileId", &param_value.to_string())]);
497    }
498    if let Some(ref param_value) = p_query_campaign_id {
499        req_builder = req_builder.query(&[("campaignId", &param_value.to_string())]);
500    }
501    if let Some(ref param_value) = p_query_from_date {
502        req_builder = req_builder.query(&[("fromDate", &param_value.to_string())]);
503    }
504    if let Some(ref param_value) = p_query_to_date {
505        req_builder = req_builder.query(&[("toDate", &param_value.to_string())]);
506    }
507    if let Some(ref user_agent) = configuration.user_agent {
508        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
509    }
510    if let Some(ref token) = configuration.bearer_access_token {
511        req_builder = req_builder.bearer_auth(token.to_owned());
512    };
513
514    let req = req_builder.build()?;
515    let resp = configuration.client.execute(req).await?;
516
517    let status = resp.status();
518    let content_type = resp
519        .headers()
520        .get("content-type")
521        .and_then(|v| v.to_str().ok())
522        .unwrap_or("application/octet-stream");
523    let content_type = super::ContentType::from(content_type);
524
525    if !status.is_client_error() && !status.is_server_error() {
526        let content = resp.text().await?;
527        match content_type {
528            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
529            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ListAds200Response`"))),
530            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ListAds200Response`")))),
531        }
532    } else {
533        let content = resp.text().await?;
534        let entity: Option<ListAdsError> = serde_json::from_str(&content).ok();
535        Err(Error::ResponseError(ResponseContent {
536            status,
537            content,
538            entity,
539        }))
540    }
541}
542
543/// Returns the list of pixels (Meta) or conversion actions (Google) accessible to the connected ads account. Use the returned `id` as `destinationId` when posting to `POST /v1/ads/conversions`.  For Google, each destination's `type` reflects the conversion action's category (PURCHASE, LEAD, SIGN_UP, etc.) — the event type is locked to the destination. For Meta, `type` is absent: pixels accept any event name per request.
544pub async fn list_conversion_destinations(
545    configuration: &configuration::Configuration,
546    account_id: &str,
547) -> Result<models::ListConversionDestinations200Response, Error<ListConversionDestinationsError>> {
548    // add a prefix to parameters to efficiently prevent name collisions
549    let p_path_account_id = account_id;
550
551    let uri_str = format!(
552        "{}/v1/accounts/{accountId}/conversion-destinations",
553        configuration.base_path,
554        accountId = crate::apis::urlencode(p_path_account_id)
555    );
556    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
557
558    if let Some(ref user_agent) = configuration.user_agent {
559        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
560    }
561    if let Some(ref token) = configuration.bearer_access_token {
562        req_builder = req_builder.bearer_auth(token.to_owned());
563    };
564
565    let req = req_builder.build()?;
566    let resp = configuration.client.execute(req).await?;
567
568    let status = resp.status();
569    let content_type = resp
570        .headers()
571        .get("content-type")
572        .and_then(|v| v.to_str().ok())
573        .unwrap_or("application/octet-stream");
574    let content_type = super::ContentType::from(content_type);
575
576    if !status.is_client_error() && !status.is_server_error() {
577        let content = resp.text().await?;
578        match content_type {
579            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
580            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ListConversionDestinations200Response`"))),
581            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ListConversionDestinations200Response`")))),
582        }
583    } else {
584        let content = resp.text().await?;
585        let entity: Option<ListConversionDestinationsError> = serde_json::from_str(&content).ok();
586        Err(Error::ResponseError(ResponseContent {
587            status,
588            content,
589            entity,
590        }))
591    }
592}
593
594/// Search for interest-based targeting options available on the platform.
595pub async fn search_ad_interests(
596    configuration: &configuration::Configuration,
597    q: &str,
598    account_id: &str,
599) -> Result<models::SearchAdInterests200Response, Error<SearchAdInterestsError>> {
600    // add a prefix to parameters to efficiently prevent name collisions
601    let p_query_q = q;
602    let p_query_account_id = account_id;
603
604    let uri_str = format!("{}/v1/ads/interests", configuration.base_path);
605    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
606
607    req_builder = req_builder.query(&[("q", &p_query_q.to_string())]);
608    req_builder = req_builder.query(&[("accountId", &p_query_account_id.to_string())]);
609    if let Some(ref user_agent) = configuration.user_agent {
610        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
611    }
612    if let Some(ref token) = configuration.bearer_access_token {
613        req_builder = req_builder.bearer_auth(token.to_owned());
614    };
615
616    let req = req_builder.build()?;
617    let resp = configuration.client.execute(req).await?;
618
619    let status = resp.status();
620    let content_type = resp
621        .headers()
622        .get("content-type")
623        .and_then(|v| v.to_str().ok())
624        .unwrap_or("application/octet-stream");
625    let content_type = super::ContentType::from(content_type);
626
627    if !status.is_client_error() && !status.is_server_error() {
628        let content = resp.text().await?;
629        match content_type {
630            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
631            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SearchAdInterests200Response`"))),
632            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::SearchAdInterests200Response`")))),
633        }
634    } else {
635        let content = resp.text().await?;
636        let entity: Option<SearchAdInterestsError> = serde_json::from_str(&content).ok();
637        Err(Error::ResponseError(ResponseContent {
638            status,
639            content,
640            entity,
641        }))
642    }
643}
644
645/// Relay one or more conversion events to the target ad platform's native Conversions API. Supported platforms: Meta (metaads) via Graph API, Google Ads (googleads) via Data Manager API `ingestEvents`.  Platform is inferred from the provided `accountId`. `destinationId` semantics differ per platform: - Meta: pixel (dataset) ID, e.g. \"123456789012345\" - Google: conversion action resource name, e.g.   \"customers/1234567890/conversionActions/987654321\"  Callers can list valid destinations via `GET /v1/accounts/{accountId}/conversion-destinations`.  All PII (email, phone, names, external IDs) is hashed with SHA-256 server-side per each platform's normalization spec (including Google's Gmail-specific dot/plus-suffix stripping). Send plaintext.  Requires the Ads add-on.  Batching: Meta caps at 1000 events per request and rejects the entire batch if any event is malformed. Google caps at 2000. Both are handled automatically by chunking.  Dedup: pass a stable `eventId` on every event. Meta uses it to dedupe against pixel events; Google maps it to transactionId.
646pub async fn send_conversions(
647    configuration: &configuration::Configuration,
648    send_conversions_request: models::SendConversionsRequest,
649) -> Result<models::SendConversions200Response, Error<SendConversionsError>> {
650    // add a prefix to parameters to efficiently prevent name collisions
651    let p_body_send_conversions_request = send_conversions_request;
652
653    let uri_str = format!("{}/v1/ads/conversions", configuration.base_path);
654    let mut req_builder = configuration
655        .client
656        .request(reqwest::Method::POST, &uri_str);
657
658    if let Some(ref user_agent) = configuration.user_agent {
659        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
660    }
661    if let Some(ref token) = configuration.bearer_access_token {
662        req_builder = req_builder.bearer_auth(token.to_owned());
663    };
664    req_builder = req_builder.json(&p_body_send_conversions_request);
665
666    let req = req_builder.build()?;
667    let resp = configuration.client.execute(req).await?;
668
669    let status = resp.status();
670    let content_type = resp
671        .headers()
672        .get("content-type")
673        .and_then(|v| v.to_str().ok())
674        .unwrap_or("application/octet-stream");
675    let content_type = super::ContentType::from(content_type);
676
677    if !status.is_client_error() && !status.is_server_error() {
678        let content = resp.text().await?;
679        match content_type {
680            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
681            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SendConversions200Response`"))),
682            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::SendConversions200Response`")))),
683        }
684    } else {
685        let content = resp.text().await?;
686        let entity: Option<SendConversionsError> = serde_json::from_str(&content).ok();
687        Err(Error::ResponseError(ResponseContent {
688            status,
689            content,
690            entity,
691        }))
692    }
693}
694
695/// Update one or more fields on an ad. Status changes and budget updates are propagated to the platform. Targeting updates are Meta-only.
696pub async fn update_ad(
697    configuration: &configuration::Configuration,
698    ad_id: &str,
699    update_ad_request: models::UpdateAdRequest,
700) -> Result<models::UpdateAd200Response, Error<UpdateAdError>> {
701    // add a prefix to parameters to efficiently prevent name collisions
702    let p_path_ad_id = ad_id;
703    let p_body_update_ad_request = update_ad_request;
704
705    let uri_str = format!(
706        "{}/v1/ads/{adId}",
707        configuration.base_path,
708        adId = crate::apis::urlencode(p_path_ad_id)
709    );
710    let mut req_builder = configuration.client.request(reqwest::Method::PUT, &uri_str);
711
712    if let Some(ref user_agent) = configuration.user_agent {
713        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
714    }
715    if let Some(ref token) = configuration.bearer_access_token {
716        req_builder = req_builder.bearer_auth(token.to_owned());
717    };
718    req_builder = req_builder.json(&p_body_update_ad_request);
719
720    let req = req_builder.build()?;
721    let resp = configuration.client.execute(req).await?;
722
723    let status = resp.status();
724    let content_type = resp
725        .headers()
726        .get("content-type")
727        .and_then(|v| v.to_str().ok())
728        .unwrap_or("application/octet-stream");
729    let content_type = super::ContentType::from(content_type);
730
731    if !status.is_client_error() && !status.is_server_error() {
732        let content = resp.text().await?;
733        match content_type {
734            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
735            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::UpdateAd200Response`"))),
736            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::UpdateAd200Response`")))),
737        }
738    } else {
739        let content = resp.text().await?;
740        let entity: Option<UpdateAdError> = serde_json::from_str(&content).ok();
741        Err(Error::ResponseError(ResponseContent {
742            status,
743            content,
744            entity,
745        }))
746    }
747}