/*
* Hanzo Cloud API
*
* The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator's admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
*
* The version of the OpenAPI document: v1
*
* Generated by: https://openapi-generator.tech
*/
use reqwest;
use serde::{Deserialize, Serialize, de::Error as _};
use crate::{apis::ResponseContent, models};
use super::{Error, configuration, ContentType};
/// struct for typed errors of method [`seo_audit`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeoAuditError {
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`seo_backlink`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeoBacklinkError {
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`seo_competitor`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeoCompetitorError {
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`seo_idea`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeoIdeaError {
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`seo_keyword`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeoKeywordError {
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`seo_rank`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeoRankError {
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`seo_rate`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SeoRateError {
UnknownValue(serde_json::Value),
}
/// Fetches one page and reports what it gets wrong. It returns the page's on-page score, its title and description, how much readable text it carries, and the full set of named checks — is it https, does it have one h1, is the title duplicated, is it slow, is it a redirect, is anything on it broken. It is the technical half of search visibility, and it is the half a developer can act on this afternoon. ONE PAGE, LIVE, IN THIS REQUEST. It is deliberately not a site crawl: a crawl is a job with a lifecycle, and this answers the same questions about the page somebody is actually looking at, now, with no task id to poll. Point it at the pages that matter one at a time. It is priced per page fetched, which is one.
pub async fn seo_audit(configuration: &configuration::Configuration, seo_audit_in: models::SeoAuditIn) -> Result<models::SeoAuditOut, Error<SeoAuditError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_seo_audit_in = seo_audit_in;
let uri_str = format!("{}/v1/seo/audit", 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_seo_audit_in);
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::SeoAuditOut`"))),
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::SeoAuditOut`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<SeoAuditError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Summarises who links to a target. It returns the authority score, how many links point at it and from how many distinct sites, how many of those are broken, and how much of the profile reads as spam. Distinct sites is the number to read: a thousand links from one domain is one endorsement, and a profile that grew fast in links and not in domains is usually a profile somebody bought. The target can be a whole domain, a subdomain, or one page URL — the summary is scoped to whatever is named. It is priced per request, so a domain with ten million links costs the same as one with ten.
pub async fn seo_backlink(configuration: &configuration::Configuration, seo_backlink_in: models::SeoBacklinkIn) -> Result<models::SeoBacklinkOut, Error<SeoBacklinkError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_seo_backlink_in = seo_backlink_in;
let uri_str = format!("{}/v1/seo/backlinks", 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_seo_backlink_in);
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::SeoBacklinkOut`"))),
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::SeoBacklinkOut`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<SeoBacklinkError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Names the domains that place for the same phrases. Given a set of phrases it returns the sites that appear across them, with each one's average position, how many of the phrases it places for, its share of the available attention and the visits that earns. It answers \"who am I actually up against here\", which is a different question from \"who do I think my competitors are\" and frequently a different answer. Pair it with seoRank: this says who is in the race, seoRank says where any one of them finishes. It is priced per row, so Limit decides the cost.
pub async fn seo_competitor(configuration: &configuration::Configuration, seo_competitor_in: models::SeoCompetitorIn) -> Result<models::SeoCompetitorOut, Error<SeoCompetitorError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_seo_competitor_in = seo_competitor_in;
let uri_str = format!("{}/v1/seo/competitors", 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_seo_competitor_in);
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::SeoCompetitorOut`"))),
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::SeoCompetitorOut`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<SeoCompetitorError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Grows a seed phrase into the phrases nobody named yet. It takes phrases you have and returns phrases in the same category that you do not — relevant rather than merely containing the seed — each with its search volume, click cost, competition and how hard its first page is to reach. This is where a keyword list comes FROM; seoKeyword is where a list you already have gets measured. It is priced per row, so Limit is the knob that decides what the call costs. Total says how many more there were.
pub async fn seo_idea(configuration: &configuration::Configuration, seo_idea_in: models::SeoIdeaIn) -> Result<models::SeoIdeaOut, Error<SeoIdeaError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_seo_idea_in = seo_idea_in;
let uri_str = format!("{}/v1/seo/ideas", 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_seo_idea_in);
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::SeoIdeaOut`"))),
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::SeoIdeaOut`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<SeoIdeaError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Measures phrases the caller already has. It answers, for each phrase named, how many people search it in a month, what an advertising click on it costs, and how contested that advertising is. This is the ground fact of search: everything else on this surface is a question about phrases, and this is the one that says whether a phrase is worth having. Give it phrases you already suspect. To find phrases you have not thought of, use seoIdea; to find the ones a site already places for, use seoRank. The market defaults to the United States in English. It is priced per request rather than per phrase, so asking about fifty phrases costs what asking about one does.
pub async fn seo_keyword(configuration: &configuration::Configuration, seo_keyword_in: models::SeoKeywordIn) -> Result<models::SeoKeywordOut, Error<SeoKeywordError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_seo_keyword_in = seo_keyword_in;
let uri_str = format!("{}/v1/seo/keywords", 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_seo_keyword_in);
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::SeoKeywordOut`"))),
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::SeoKeywordOut`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<SeoKeywordError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Reports every phrase a domain already places for. For each one it gives the phrase, the position on the results page, the page of the site that placed, that result's headline, the phrase's monthly searches and the visits the placement is estimated to earn. It is the single most direct question about a site's search visibility — yours or a competitor's, since it takes any domain. Position is the ABSOLUTE rank, counting every element on the page — the ads, the answer boxes, the map — because that is what a person scrolling actually passes. An organic-only rank flatters a result that sits below half a screen of other things. It is priced per row, so Limit decides what the call costs, and Total says how many more there were.
pub async fn seo_rank(configuration: &configuration::Configuration, seo_rank_in: models::SeoRankIn) -> Result<models::SeoRankOut, Error<SeoRankError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_seo_rank_in = seo_rank_in;
let uri_str = format!("{}/v1/seo/rankings", 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_seo_rank_in);
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::SeoRankOut`"))),
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::SeoRankOut`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<SeoRankError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Publishes what every call on this surface costs. The numbers are read from the upstream's own published price list, not from a table kept here, so a price change on their side moves this card within the hour and moves what is debited with it. That is the whole of the pricing model: this surface resells at cost, and the cost is theirs to state. A row has two numbers because a call has two costs: a flat charge for asking, and a charge per row returned. An op priced per request reports zero for the second, and for one priced per row the total is `request + result x limit` — which is the amount your balance is authorized against before the call, and roughly what you will be debited after it. It is a read and it is free: asking what something costs must not require the balance that would pay for it. If the upstream cannot be reached the card comes back empty rather than stale — a price nobody can confirm is not a price.
pub async fn seo_rate(configuration: &configuration::Configuration, ) -> Result<models::SeoRateOut, Error<SeoRateError>> {
let uri_str = format!("{}/v1/seo/rates", configuration.base_path);
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::SeoRateOut`"))),
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::SeoRateOut`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<SeoRateError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}