/*
* 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 super::{configuration, ContentType, Error};
use crate::{apis::ResponseContent, models};
use reqwest;
use serde::{de::Error as _, Deserialize, Serialize};
/// struct for typed errors of method [`add_users_to_ad_audience`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AddUsersToAdAudienceError {
Status400(),
Status401(models::InlineObject),
Status403(),
Status404(models::InlineObject1),
Status422(),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`create_ad_audience`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CreateAdAudienceError {
Status400(),
Status401(models::InlineObject),
Status403(),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`delete_ad_audience`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DeleteAdAudienceError {
Status401(models::InlineObject),
Status403(),
Status404(models::InlineObject1),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`get_ad_audience`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GetAdAudienceError {
Status401(models::InlineObject),
Status403(),
Status404(models::InlineObject1),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`list_ad_audiences`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ListAdAudiencesError {
Status401(models::InlineObject),
Status403(),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`replace_ad_audience_companies`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ReplaceAdAudienceCompaniesError {
Status400(),
Status401(models::InlineObject),
Status403(),
Status404(models::InlineObject1),
Status422(),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`update_ad_audience`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UpdateAdAudienceError {
Status400(),
Status401(models::InlineObject),
Status403(),
Status404(models::InlineObject1),
Status422(),
Status501(),
UnknownValue(serde_json::Value),
}
/// Upload user data to a customer_list audience. Data is SHA256-hashed server-side before sending to the platform. Email is used on every platform; phone is used on Meta only (other platforms ignore it). On TikTok and Pinterest, the first upload also provisions the audience (deferred create). LinkedIn uploads are full-replace. Max 10,000 users per request. customer_list only. A LinkedIn `company_list` audience takes company rows, not people: send those to `POST /v1/ads/audiences/{audienceId}/companies`. This endpoint 422s for every other audience type.
pub async fn add_users_to_ad_audience(
configuration: &configuration::Configuration,
audience_id: &str,
add_users_to_ad_audience_request: models::AddUsersToAdAudienceRequest,
) -> Result<models::AddUsersToAdAudience200Response, Error<AddUsersToAdAudienceError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_path_audience_id = audience_id;
let p_body_add_users_to_ad_audience_request = add_users_to_ad_audience_request;
let uri_str = format!(
"{}/v1/ads/audiences/{audienceId}/users",
configuration.base_path,
audienceId = crate::apis::urlencode(p_path_audience_id)
);
let mut req_builder = configuration
.client
.request(reqwest::Method::POST, &uri_str);
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
req_builder = req_builder.json(&p_body_add_users_to_ad_audience_request);
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::AddUsersToAdAudience200Response`"))),
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::AddUsersToAdAudience200Response`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<AddUsersToAdAudienceError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent {
status,
content,
entity,
}))
}
}
/// Create a custom audience. `customer_list` is supported on Meta, Google, X, LinkedIn, TikTok, and Pinterest; `website` and `lookalike` are Meta-only; `company_list`, `engagement` and `website_retargeting` are LinkedIn-only. `saved_targeting` stores a reusable TargetingSpec (no member upload, no adAccountId) that you reference later via `savedTargetingId` on `POST /v1/ads/create`. How the audience gets filled depends on the type: - `customer_list` is created empty. Add members with `POST /v1/ads/audiences/{audienceId}/users`. On TikTok and Pinterest the audience is provisioned lazily on that first upload (until then its status is `pending`). - `company_list` is filled AT CREATION from the `companies` array below, which is required. To change the list afterwards send the new full list to `POST /v1/ads/audiences/{audienceId}/companies` (a replace, not a merge). The `/users` endpoint rejects these audiences with a 422. - `website`, `website_retargeting`, `engagement`, `meta_engagement` and `lookalike` fill themselves from the pixel, engagement source or seed audience you point them at. They take no member upload at all. Create is not idempotent, never auto-retry.
pub async fn create_ad_audience(
configuration: &configuration::Configuration,
create_ad_audience_request: models::CreateAdAudienceRequest,
) -> Result<models::CreateAdAudience201Response, Error<CreateAdAudienceError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_body_create_ad_audience_request = create_ad_audience_request;
let uri_str = format!("{}/v1/ads/audiences", configuration.base_path);
let mut req_builder = configuration
.client
.request(reqwest::Method::POST, &uri_str);
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
req_builder = req_builder.json(&p_body_create_ad_audience_request);
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::CreateAdAudience201Response`"))),
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::CreateAdAudience201Response`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<CreateAdAudienceError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent {
status,
content,
entity,
}))
}
}
/// Deletes the audience from both the platform and the local database. `saved_targeting` audiences exist only on Zernio, so only the local record is removed.
pub async fn delete_ad_audience(
configuration: &configuration::Configuration,
audience_id: &str,
) -> Result<models::DeleteAccountGroup200Response, Error<DeleteAdAudienceError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_path_audience_id = audience_id;
let uri_str = format!(
"{}/v1/ads/audiences/{audienceId}",
configuration.base_path,
audienceId = crate::apis::urlencode(p_path_audience_id)
);
let mut req_builder = configuration
.client
.request(reqwest::Method::DELETE, &uri_str);
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::DeleteAccountGroup200Response`"))),
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`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<DeleteAdAudienceError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent {
status,
content,
entity,
}))
}
}
/// Returns the local audience record and fresh data from Meta (if available).
pub async fn get_ad_audience(
configuration: &configuration::Configuration,
audience_id: &str,
) -> Result<models::GetAdAudience200Response, Error<GetAdAudienceError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_path_audience_id = audience_id;
let uri_str = format!(
"{}/v1/ads/audiences/{audienceId}",
configuration.base_path,
audienceId = crate::apis::urlencode(p_path_audience_id)
);
let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::GetAdAudience200Response`"))),
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::GetAdAudience200Response`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<GetAdAudienceError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent {
status,
content,
entity,
}))
}
}
/// Returns custom audiences for the given ad account. Supports Meta, Google, TikTok, Pinterest, LinkedIn, and X.
pub async fn list_ad_audiences(
configuration: &configuration::Configuration,
account_id: &str,
ad_account_id: &str,
platform: Option<&str>,
r#type: Option<&str>,
) -> Result<models::ListAdAudiences200Response, Error<ListAdAudiencesError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_query_account_id = account_id;
let p_query_ad_account_id = ad_account_id;
let p_query_platform = platform;
let p_query_type = r#type;
let uri_str = format!("{}/v1/ads/audiences", configuration.base_path);
let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
req_builder = req_builder.query(&[("accountId", &p_query_account_id.to_string())]);
req_builder = req_builder.query(&[("adAccountId", &p_query_ad_account_id.to_string())]);
if let Some(ref param_value) = p_query_platform {
req_builder = req_builder.query(&[("platform", ¶m_value.to_string())]);
}
if let Some(ref param_value) = p_query_type {
req_builder = req_builder.query(&[("type", ¶m_value.to_string())]);
}
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ListAdAudiences200Response`"))),
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::ListAdAudiences200Response`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<ListAdAudiencesError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent {
status,
content,
entity,
}))
}
}
/// Upload the company rows of a LinkedIn `company_list` audience (account-based marketing). LinkedIn-only, every other platform returns 422. A LinkedIn audience segment holds exactly one uploaded list, so the list you send here REPLACES the segment's list instead of being appended to it: always send the full set of companies. LinkedIn returns only the identifier of the uploaded file, never its rows, so the merge cannot be done for you, keep the source list on your side. How the matching behaves: - Rows are plain text (not hashed), matched against LinkedIn's own company graph. - Matching is asynchronous: LinkedIn takes up to 48h for a new audience and up to 24h for a later update, and the audience stays `processing` meanwhile. - LinkedIn does not document how quickly companies dropped from the list stop being targeted, so treat removals as eventual rather than immediate. - LinkedIn recommends at least 1,000 companies for a usable match rate, and caps a list at 300,000. The initial list is sent with `companies` on `POST /v1/ads/audiences`; this endpoint is for every change after that.
pub async fn replace_ad_audience_companies(
configuration: &configuration::Configuration,
audience_id: &str,
replace_ad_audience_companies_request: models::ReplaceAdAudienceCompaniesRequest,
) -> Result<models::ReplaceAdAudienceCompanies200Response, Error<ReplaceAdAudienceCompaniesError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_path_audience_id = audience_id;
let p_body_replace_ad_audience_companies_request = replace_ad_audience_companies_request;
let uri_str = format!(
"{}/v1/ads/audiences/{audienceId}/companies",
configuration.base_path,
audienceId = crate::apis::urlencode(p_path_audience_id)
);
let mut req_builder = configuration
.client
.request(reqwest::Method::POST, &uri_str);
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
req_builder = req_builder.json(&p_body_replace_ad_audience_companies_request);
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ReplaceAdAudienceCompanies200Response`"))),
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::ReplaceAdAudienceCompanies200Response`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<ReplaceAdAudienceCompaniesError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent {
status,
content,
entity,
}))
}
}
/// Update an audience. `saved_targeting` audiences accept `name`, `description`, and `spec` (full replacement, no merge, Zernio-only, no platform call). Platform audiences (uploaded/website/lookalike) accept `name` and `description` only, updated on the platform first and then mirrored locally; their rules are immutable, so `spec` returns 400 for them. Platform audience updates are Meta-only for now (other platforms return 501). Ads already created from a saved_targeting audience are unaffected, they snapshot the targeting at creation.
pub async fn update_ad_audience(
configuration: &configuration::Configuration,
audience_id: &str,
update_ad_audience_request: models::UpdateAdAudienceRequest,
) -> Result<models::CreateAdAudience201Response, Error<UpdateAdAudienceError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_path_audience_id = audience_id;
let p_body_update_ad_audience_request = update_ad_audience_request;
let uri_str = format!(
"{}/v1/ads/audiences/{audienceId}",
configuration.base_path,
audienceId = crate::apis::urlencode(p_path_audience_id)
);
let mut req_builder = configuration.client.request(reqwest::Method::PUT, &uri_str);
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
req_builder = req_builder.json(&p_body_update_ad_audience_request);
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::CreateAdAudience201Response`"))),
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::CreateAdAudience201Response`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<UpdateAdAudienceError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent {
status,
content,
entity,
}))
}
}