Skip to main content

hanzo_client/apis/
seo_api.rs

1/*
2 * Hanzo Cloud API
3 *
4 * 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/.
5 *
6 * The version of the OpenAPI document: v1
7 * 
8 * Generated by: https://openapi-generator.tech
9 */
10
11
12use reqwest;
13use serde::{Deserialize, Serialize, de::Error as _};
14use crate::{apis::ResponseContent, models};
15use super::{Error, configuration, ContentType};
16
17
18/// struct for typed errors of method [`seo_audit`]
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(untagged)]
21pub enum SeoAuditError {
22    UnknownValue(serde_json::Value),
23}
24
25/// struct for typed errors of method [`seo_backlink`]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum SeoBacklinkError {
29    UnknownValue(serde_json::Value),
30}
31
32/// struct for typed errors of method [`seo_competitor`]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum SeoCompetitorError {
36    UnknownValue(serde_json::Value),
37}
38
39/// struct for typed errors of method [`seo_idea`]
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum SeoIdeaError {
43    UnknownValue(serde_json::Value),
44}
45
46/// struct for typed errors of method [`seo_keyword`]
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(untagged)]
49pub enum SeoKeywordError {
50    UnknownValue(serde_json::Value),
51}
52
53/// struct for typed errors of method [`seo_rank`]
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(untagged)]
56pub enum SeoRankError {
57    UnknownValue(serde_json::Value),
58}
59
60/// struct for typed errors of method [`seo_rate`]
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(untagged)]
63pub enum SeoRateError {
64    UnknownValue(serde_json::Value),
65}
66
67
68/// 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.
69pub async fn seo_audit(configuration: &configuration::Configuration, seo_audit_in: models::SeoAuditIn) -> Result<models::SeoAuditOut, Error<SeoAuditError>> {
70    // add a prefix to parameters to efficiently prevent name collisions
71    let p_seo_audit_in = seo_audit_in;
72
73    let uri_str = format!("{}/v1/seo/audit", configuration.base_path);
74    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
75
76    if let Some(ref user_agent) = configuration.user_agent {
77        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
78    }
79    if let Some(ref token) = configuration.bearer_access_token {
80        req_builder = req_builder.bearer_auth(token.to_owned());
81    };
82    req_builder = req_builder.json(&p_seo_audit_in);
83
84    let req = req_builder.build()?;
85    let resp = configuration.client.execute(req).await?;
86
87    let status = resp.status();
88    let content_type = resp
89        .headers()
90        .get("content-type")
91        .and_then(|v| v.to_str().ok())
92        .unwrap_or("application/octet-stream");
93    let content_type = super::ContentType::from(content_type);
94
95    if !status.is_client_error() && !status.is_server_error() {
96        let content = resp.text().await?;
97        match content_type {
98            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
99            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SeoAuditOut`"))),
100            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`")))),
101        }
102    } else {
103        let content = resp.text().await?;
104        let entity: Option<SeoAuditError> = serde_json::from_str(&content).ok();
105        Err(Error::ResponseError(ResponseContent { status, content, entity }))
106    }
107}
108
109/// 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.
110pub async fn seo_backlink(configuration: &configuration::Configuration, seo_backlink_in: models::SeoBacklinkIn) -> Result<models::SeoBacklinkOut, Error<SeoBacklinkError>> {
111    // add a prefix to parameters to efficiently prevent name collisions
112    let p_seo_backlink_in = seo_backlink_in;
113
114    let uri_str = format!("{}/v1/seo/backlinks", configuration.base_path);
115    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
116
117    if let Some(ref user_agent) = configuration.user_agent {
118        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
119    }
120    if let Some(ref token) = configuration.bearer_access_token {
121        req_builder = req_builder.bearer_auth(token.to_owned());
122    };
123    req_builder = req_builder.json(&p_seo_backlink_in);
124
125    let req = req_builder.build()?;
126    let resp = configuration.client.execute(req).await?;
127
128    let status = resp.status();
129    let content_type = resp
130        .headers()
131        .get("content-type")
132        .and_then(|v| v.to_str().ok())
133        .unwrap_or("application/octet-stream");
134    let content_type = super::ContentType::from(content_type);
135
136    if !status.is_client_error() && !status.is_server_error() {
137        let content = resp.text().await?;
138        match content_type {
139            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
140            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SeoBacklinkOut`"))),
141            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`")))),
142        }
143    } else {
144        let content = resp.text().await?;
145        let entity: Option<SeoBacklinkError> = serde_json::from_str(&content).ok();
146        Err(Error::ResponseError(ResponseContent { status, content, entity }))
147    }
148}
149
150/// 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.
151pub async fn seo_competitor(configuration: &configuration::Configuration, seo_competitor_in: models::SeoCompetitorIn) -> Result<models::SeoCompetitorOut, Error<SeoCompetitorError>> {
152    // add a prefix to parameters to efficiently prevent name collisions
153    let p_seo_competitor_in = seo_competitor_in;
154
155    let uri_str = format!("{}/v1/seo/competitors", configuration.base_path);
156    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
157
158    if let Some(ref user_agent) = configuration.user_agent {
159        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
160    }
161    if let Some(ref token) = configuration.bearer_access_token {
162        req_builder = req_builder.bearer_auth(token.to_owned());
163    };
164    req_builder = req_builder.json(&p_seo_competitor_in);
165
166    let req = req_builder.build()?;
167    let resp = configuration.client.execute(req).await?;
168
169    let status = resp.status();
170    let content_type = resp
171        .headers()
172        .get("content-type")
173        .and_then(|v| v.to_str().ok())
174        .unwrap_or("application/octet-stream");
175    let content_type = super::ContentType::from(content_type);
176
177    if !status.is_client_error() && !status.is_server_error() {
178        let content = resp.text().await?;
179        match content_type {
180            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
181            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SeoCompetitorOut`"))),
182            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`")))),
183        }
184    } else {
185        let content = resp.text().await?;
186        let entity: Option<SeoCompetitorError> = serde_json::from_str(&content).ok();
187        Err(Error::ResponseError(ResponseContent { status, content, entity }))
188    }
189}
190
191/// 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.
192pub async fn seo_idea(configuration: &configuration::Configuration, seo_idea_in: models::SeoIdeaIn) -> Result<models::SeoIdeaOut, Error<SeoIdeaError>> {
193    // add a prefix to parameters to efficiently prevent name collisions
194    let p_seo_idea_in = seo_idea_in;
195
196    let uri_str = format!("{}/v1/seo/ideas", configuration.base_path);
197    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
198
199    if let Some(ref user_agent) = configuration.user_agent {
200        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
201    }
202    if let Some(ref token) = configuration.bearer_access_token {
203        req_builder = req_builder.bearer_auth(token.to_owned());
204    };
205    req_builder = req_builder.json(&p_seo_idea_in);
206
207    let req = req_builder.build()?;
208    let resp = configuration.client.execute(req).await?;
209
210    let status = resp.status();
211    let content_type = resp
212        .headers()
213        .get("content-type")
214        .and_then(|v| v.to_str().ok())
215        .unwrap_or("application/octet-stream");
216    let content_type = super::ContentType::from(content_type);
217
218    if !status.is_client_error() && !status.is_server_error() {
219        let content = resp.text().await?;
220        match content_type {
221            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
222            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SeoIdeaOut`"))),
223            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`")))),
224        }
225    } else {
226        let content = resp.text().await?;
227        let entity: Option<SeoIdeaError> = serde_json::from_str(&content).ok();
228        Err(Error::ResponseError(ResponseContent { status, content, entity }))
229    }
230}
231
232/// 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.
233pub async fn seo_keyword(configuration: &configuration::Configuration, seo_keyword_in: models::SeoKeywordIn) -> Result<models::SeoKeywordOut, Error<SeoKeywordError>> {
234    // add a prefix to parameters to efficiently prevent name collisions
235    let p_seo_keyword_in = seo_keyword_in;
236
237    let uri_str = format!("{}/v1/seo/keywords", configuration.base_path);
238    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
239
240    if let Some(ref user_agent) = configuration.user_agent {
241        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
242    }
243    if let Some(ref token) = configuration.bearer_access_token {
244        req_builder = req_builder.bearer_auth(token.to_owned());
245    };
246    req_builder = req_builder.json(&p_seo_keyword_in);
247
248    let req = req_builder.build()?;
249    let resp = configuration.client.execute(req).await?;
250
251    let status = resp.status();
252    let content_type = resp
253        .headers()
254        .get("content-type")
255        .and_then(|v| v.to_str().ok())
256        .unwrap_or("application/octet-stream");
257    let content_type = super::ContentType::from(content_type);
258
259    if !status.is_client_error() && !status.is_server_error() {
260        let content = resp.text().await?;
261        match content_type {
262            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
263            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SeoKeywordOut`"))),
264            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`")))),
265        }
266    } else {
267        let content = resp.text().await?;
268        let entity: Option<SeoKeywordError> = serde_json::from_str(&content).ok();
269        Err(Error::ResponseError(ResponseContent { status, content, entity }))
270    }
271}
272
273/// 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.
274pub async fn seo_rank(configuration: &configuration::Configuration, seo_rank_in: models::SeoRankIn) -> Result<models::SeoRankOut, Error<SeoRankError>> {
275    // add a prefix to parameters to efficiently prevent name collisions
276    let p_seo_rank_in = seo_rank_in;
277
278    let uri_str = format!("{}/v1/seo/rankings", configuration.base_path);
279    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
280
281    if let Some(ref user_agent) = configuration.user_agent {
282        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
283    }
284    if let Some(ref token) = configuration.bearer_access_token {
285        req_builder = req_builder.bearer_auth(token.to_owned());
286    };
287    req_builder = req_builder.json(&p_seo_rank_in);
288
289    let req = req_builder.build()?;
290    let resp = configuration.client.execute(req).await?;
291
292    let status = resp.status();
293    let content_type = resp
294        .headers()
295        .get("content-type")
296        .and_then(|v| v.to_str().ok())
297        .unwrap_or("application/octet-stream");
298    let content_type = super::ContentType::from(content_type);
299
300    if !status.is_client_error() && !status.is_server_error() {
301        let content = resp.text().await?;
302        match content_type {
303            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
304            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SeoRankOut`"))),
305            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`")))),
306        }
307    } else {
308        let content = resp.text().await?;
309        let entity: Option<SeoRankError> = serde_json::from_str(&content).ok();
310        Err(Error::ResponseError(ResponseContent { status, content, entity }))
311    }
312}
313
314/// 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.
315pub async fn seo_rate(configuration: &configuration::Configuration, ) -> Result<models::SeoRateOut, Error<SeoRateError>> {
316
317    let uri_str = format!("{}/v1/seo/rates", configuration.base_path);
318    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
319
320    if let Some(ref user_agent) = configuration.user_agent {
321        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
322    }
323    if let Some(ref token) = configuration.bearer_access_token {
324        req_builder = req_builder.bearer_auth(token.to_owned());
325    };
326
327    let req = req_builder.build()?;
328    let resp = configuration.client.execute(req).await?;
329
330    let status = resp.status();
331    let content_type = resp
332        .headers()
333        .get("content-type")
334        .and_then(|v| v.to_str().ok())
335        .unwrap_or("application/octet-stream");
336    let content_type = super::ContentType::from(content_type);
337
338    if !status.is_client_error() && !status.is_server_error() {
339        let content = resp.text().await?;
340        match content_type {
341            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
342            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SeoRateOut`"))),
343            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`")))),
344        }
345    } else {
346        let content = resp.text().await?;
347        let entity: Option<SeoRateError> = serde_json::from_str(&content).ok();
348        Err(Error::ResponseError(ResponseContent { status, content, entity }))
349    }
350}
351