Skip to main content

hanzo_client/apis/
content_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 [`get_content_board`]
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(untagged)]
21pub enum GetContentBoardError {
22    UnknownValue(serde_json::Value),
23}
24
25/// struct for typed errors of method [`get_content_channels`]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum GetContentChannelsError {
29    UnknownValue(serde_json::Value),
30}
31
32/// struct for typed errors of method [`get_content_lifecycle`]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum GetContentLifecycleError {
36    UnknownValue(serde_json::Value),
37}
38
39/// struct for typed errors of method [`post_content_by_doctype_by_name_transition`]
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum PostContentByDoctypeByNameTransitionError {
43    UnknownValue(serde_json::Value),
44}
45
46/// struct for typed errors of method [`post_content_generate`]
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(untagged)]
49pub enum PostContentGenerateError {
50    Status402(models::GenerateResult),
51    UnknownValue(serde_json::Value),
52}
53
54/// struct for typed errors of method [`post_content_publish`]
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(untagged)]
57pub enum PostContentPublishError {
58    UnknownValue(serde_json::Value),
59}
60
61
62/// Aggregates the caller org's marketing content across every publishable content type into ONE queue board — the cross-type read the framework's per-DocType list cannot give. It never fails on a partial outage: a content type the org has not installed, or one whose search errors, is skipped and logged rather than failing the whole board.
63pub async fn get_content_board(configuration: &configuration::Configuration, status: Option<&str>, project: Option<&str>, doctype: Option<&str>, limit: Option<i32>) -> Result<models::BoardPage, Error<GetContentBoardError>> {
64    // add a prefix to parameters to efficiently prevent name collisions
65    let p_status = status;
66    let p_project = project;
67    let p_doctype = doctype;
68    let p_limit = limit;
69
70    let uri_str = format!("{}/v1/content/board", configuration.base_path);
71    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
72
73    if let Some(ref param_value) = p_status {
74        req_builder = req_builder.query(&[("status", &param_value.to_string())]);
75    }
76    if let Some(ref param_value) = p_project {
77        req_builder = req_builder.query(&[("project", &param_value.to_string())]);
78    }
79    if let Some(ref param_value) = p_doctype {
80        req_builder = req_builder.query(&[("doctype", &param_value.to_string())]);
81    }
82    if let Some(ref param_value) = p_limit {
83        req_builder = req_builder.query(&[("limit", &param_value.to_string())]);
84    }
85    if let Some(ref user_agent) = configuration.user_agent {
86        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
87    }
88    if let Some(ref token) = configuration.bearer_access_token {
89        req_builder = req_builder.bearer_auth(token.to_owned());
90    };
91
92    let req = req_builder.build()?;
93    let resp = configuration.client.execute(req).await?;
94
95    let status = resp.status();
96    let content_type = resp
97        .headers()
98        .get("content-type")
99        .and_then(|v| v.to_str().ok())
100        .unwrap_or("application/octet-stream");
101    let content_type = super::ContentType::from(content_type);
102
103    if !status.is_client_error() && !status.is_server_error() {
104        let content = resp.text().await?;
105        match content_type {
106            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
107            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BoardPage`"))),
108            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::BoardPage`")))),
109        }
110    } else {
111        let content = resp.text().await?;
112        let entity: Option<GetContentBoardError> = serde_json::from_str(&content).ok();
113        Err(Error::ResponseError(ResponseContent { status, content, entity }))
114    }
115}
116
117/// Lists the distribution channels the caller's org has connected — the social integrations a publish can target. A deployment with no distribution edge wired answers 503 rather than an empty list that would read as \"no channels\".
118pub async fn get_content_channels(configuration: &configuration::Configuration, ) -> Result<models::ChannelList, Error<GetContentChannelsError>> {
119
120    let uri_str = format!("{}/v1/content/channels", configuration.base_path);
121    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
122
123    if let Some(ref user_agent) = configuration.user_agent {
124        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
125    }
126    if let Some(ref token) = configuration.bearer_access_token {
127        req_builder = req_builder.bearer_auth(token.to_owned());
128    };
129
130    let req = req_builder.build()?;
131    let resp = configuration.client.execute(req).await?;
132
133    let status = resp.status();
134    let content_type = resp
135        .headers()
136        .get("content-type")
137        .and_then(|v| v.to_str().ok())
138        .unwrap_or("application/octet-stream");
139    let content_type = super::ContentType::from(content_type);
140
141    if !status.is_client_error() && !status.is_server_error() {
142        let content = resp.text().await?;
143        match content_type {
144            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
145            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ChannelList`"))),
146            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::ChannelList`")))),
147        }
148    } else {
149        let content = resp.text().await?;
150        let entity: Option<GetContentChannelsError> = serde_json::from_str(&content).ok();
151        Err(Error::ResponseError(ResponseContent { status, content, entity }))
152    }
153}
154
155/// Returns the ONE marketing-content state machine: the ordered lifecycle states, which state a fresh document starts in, which one is publicly live, and the legal successors of every state. The console builds its board columns and its per-item action buttons from this single answer, so the UI and the write-time enforcement hook can never disagree about what is legal.
156pub async fn get_content_lifecycle(configuration: &configuration::Configuration, ) -> Result<models::StateGraph, Error<GetContentLifecycleError>> {
157
158    let uri_str = format!("{}/v1/content/lifecycle", configuration.base_path);
159    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
160
161    if let Some(ref user_agent) = configuration.user_agent {
162        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
163    }
164    if let Some(ref token) = configuration.bearer_access_token {
165        req_builder = req_builder.bearer_auth(token.to_owned());
166    };
167
168    let req = req_builder.build()?;
169    let resp = configuration.client.execute(req).await?;
170
171    let status = resp.status();
172    let content_type = resp
173        .headers()
174        .get("content-type")
175        .and_then(|v| v.to_str().ok())
176        .unwrap_or("application/octet-stream");
177    let content_type = super::ContentType::from(content_type);
178
179    if !status.is_client_error() && !status.is_server_error() {
180        let content = resp.text().await?;
181        match content_type {
182            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
183            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::StateGraph`"))),
184            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::StateGraph`")))),
185        }
186    } else {
187        let content = resp.text().await?;
188        let entity: Option<GetContentLifecycleError> = serde_json::from_str(&content).ok();
189        Err(Error::ResponseError(ResponseContent { status, content, entity }))
190    }
191}
192
193/// Moves one content item to a new lifecycle state and, on the move to published, fans it out to the item's channels. The edge must be legal for the item's current state — an illegal move is refused with 409 — and the status write re-validates it at the storage boundary. Distribution is best effort: its honest state is reported on the result and a distribution failure never rolls the status change back.
194pub async fn post_content_by_doctype_by_name_transition(configuration: &configuration::Configuration, doctype: &str, name: &str, transition_in: models::TransitionIn) -> Result<models::TransitionResult, Error<PostContentByDoctypeByNameTransitionError>> {
195    // add a prefix to parameters to efficiently prevent name collisions
196    let p_doctype = doctype;
197    let p_name = name;
198    let p_transition_in = transition_in;
199
200    let uri_str = format!("{}/v1/content/{doctype}/{name}/transition", configuration.base_path, doctype=crate::apis::urlencode(p_doctype), name=crate::apis::urlencode(p_name));
201    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
202
203    if let Some(ref user_agent) = configuration.user_agent {
204        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
205    }
206    if let Some(ref token) = configuration.bearer_access_token {
207        req_builder = req_builder.bearer_auth(token.to_owned());
208    };
209    req_builder = req_builder.json(&p_transition_in);
210
211    let req = req_builder.build()?;
212    let resp = configuration.client.execute(req).await?;
213
214    let status = resp.status();
215    let content_type = resp
216        .headers()
217        .get("content-type")
218        .and_then(|v| v.to_str().ok())
219        .unwrap_or("application/octet-stream");
220    let content_type = super::ContentType::from(content_type);
221
222    if !status.is_client_error() && !status.is_server_error() {
223        let content = resp.text().await?;
224        match content_type {
225            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
226            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::TransitionResult`"))),
227            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::TransitionResult`")))),
228        }
229    } else {
230        let content = resp.text().await?;
231        let entity: Option<PostContentByDoctypeByNameTransitionError> = serde_json::from_str(&content).ok();
232        Err(Error::ResponseError(ResponseContent { status, content, entity }))
233    }
234}
235
236/// Draft a piece of marketing content and file it in the CMS as a draft.  Answers 201 with the created draft's identity — {doctype, name, status} — and the document itself lands in the CMS through the SAME validate and lifecycle-hook pipeline an ordinary create runs. This is a WRITE, not a preview: there is no dry-run, and every call that succeeds leaves a document behind.  `doctype` picks which of two generation planes runs, and they are the only two. Campaign and SocialPost are drafted as brand COPY on the platform AI plane (zen5 by default, overridable per request with `model` or per deployment); Asset is a studio image render the AI plane never sees. Everything else about the call is identical.  MONEY, metered in exactly one place per mode and never both. Copy rides the platform's own inference meter — the org's balance is authorised before the model call and debited at the exact token cost after — so content never re-bills it. A studio render is invisible to that meter, so content is the sole meter for it: the org is gated BEFORE the GPU compute and refused 402 when out of funds or over its spend cap, and the debit is recorded only once the render actually returns, because the billable event is the consumed compute and not the CMS row. `project` rides the BODY rather than a server-minted identity claim, so it attributes spend but a project-scoped cap stays soft on it — the org is the value that is enforced.  The org is the caller's own, resolved once from the validated principal and never read from the body; a caller without one is refused 403. Status is not the generator's to choose: a generated item is ALWAYS a draft, and the storage-boundary hook enforces that a second time.  It fails closed rather than inventing anything. An unknown content type is 404 and a deployment whose marketing module is not installed is 409 naming the install call. An AI plane or studio that is unconfigured or unreachable, a graph the studio rejects, and a render that does not return in time all degrade to 503 — never fabricated copy, never a fake render. A `source_media` that fails the SSRF and traversal validator is 400 raised before the billing gate and before the studio is contacted, so a hostile source never costs the caller anything.
237pub async fn post_content_generate(configuration: &configuration::Configuration, generate_input: models::GenerateInput) -> Result<models::GenerateResult, Error<PostContentGenerateError>> {
238    // add a prefix to parameters to efficiently prevent name collisions
239    let p_generate_input = generate_input;
240
241    let uri_str = format!("{}/v1/content/generate", configuration.base_path);
242    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
243
244    if let Some(ref user_agent) = configuration.user_agent {
245        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
246    }
247    if let Some(ref token) = configuration.bearer_access_token {
248        req_builder = req_builder.bearer_auth(token.to_owned());
249    };
250    req_builder = req_builder.json(&p_generate_input);
251
252    let req = req_builder.build()?;
253    let resp = configuration.client.execute(req).await?;
254
255    let status = resp.status();
256    let content_type = resp
257        .headers()
258        .get("content-type")
259        .and_then(|v| v.to_str().ok())
260        .unwrap_or("application/octet-stream");
261    let content_type = super::ContentType::from(content_type);
262
263    if !status.is_client_error() && !status.is_server_error() {
264        let content = resp.text().await?;
265        match content_type {
266            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
267            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::GenerateResult`"))),
268            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::GenerateResult`")))),
269        }
270    } else {
271        let content = resp.text().await?;
272        let entity: Option<PostContentGenerateError> = serde_json::from_str(&content).ok();
273        Err(Error::ResponseError(ResponseContent { status, content, entity }))
274    }
275}
276
277/// Publish distributes one CMS content item to the channels recorded on it and returns the honest per-channel outcome. The item names itself — its caption, media and channel list are read from the stored document, not from this request. It is idempotent per channel (a channel already posted for this item is skipped), and a publish that loses the per-item lease to a live publisher answers status \"in_progress\" having posted nothing.
278pub async fn post_content_publish(configuration: &configuration::Configuration, publish_input: models::PublishInput) -> Result<models::PublishResult, Error<PostContentPublishError>> {
279    // add a prefix to parameters to efficiently prevent name collisions
280    let p_publish_input = publish_input;
281
282    let uri_str = format!("{}/v1/content/publish", configuration.base_path);
283    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
284
285    if let Some(ref user_agent) = configuration.user_agent {
286        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
287    }
288    if let Some(ref token) = configuration.bearer_access_token {
289        req_builder = req_builder.bearer_auth(token.to_owned());
290    };
291    req_builder = req_builder.json(&p_publish_input);
292
293    let req = req_builder.build()?;
294    let resp = configuration.client.execute(req).await?;
295
296    let status = resp.status();
297    let content_type = resp
298        .headers()
299        .get("content-type")
300        .and_then(|v| v.to_str().ok())
301        .unwrap_or("application/octet-stream");
302    let content_type = super::ContentType::from(content_type);
303
304    if !status.is_client_error() && !status.is_server_error() {
305        let content = resp.text().await?;
306        match content_type {
307            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
308            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::PublishResult`"))),
309            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::PublishResult`")))),
310        }
311    } else {
312        let content = resp.text().await?;
313        let entity: Option<PostContentPublishError> = serde_json::from_str(&content).ok();
314        Err(Error::ResponseError(ResponseContent { status, content, entity }))
315    }
316}
317