Skip to main content

uarp_sdk/generated/api/
tenants.rs

1// Code generated by @uarp/codegen from spec/openapi.json. DO NOT EDIT.
2//!
3//! Tenant management and API keys
4
5#![allow(unused_imports, clippy::too_many_arguments)]
6
7use reqwest::Method;
8use serde::{Deserialize, Serialize};
9
10use crate::client::{Client, Request, NO_BODY, NO_QUERY};
11use crate::error::Result;
12use crate::generated::models;
13use crate::multipart::{field_text, FilePart};
14use crate::util::encode_path;
15
16/// Tenant management and API keys
17#[derive(Debug, Clone)]
18pub struct TenantsApi {
19    pub(crate) client: Client,
20}
21
22impl Client {
23    /// Tenant management and API keys
24    pub fn tenants(&self) -> TenantsApi {
25        TenantsApi { client: self.clone() }
26    }
27}
28
29impl TenantsApi {
30    /// Accept a pending invite from the tenant picker
31    ///
32    /// The tenant is in the PATH, and that is the whole reason this route exists beside `POST
33    /// /api/v1/users/invites/{inviteId}/accept`. The older route resolves the invite against the
34    /// caller's active tenant, which cannot work here: the caller is not a member of the inviting
35    /// tenant yet — making them one is what the call is for. Without a tenant-in-path route the
36    /// picker's Accept button can never succeed for a cross-tenant invite.
37    ///
38    /// Semantics are otherwise identical to the older route: the same atomic create-user + email
39    /// index + membership index + `invite.status = accepted` write.
40    ///
41    /// Four refusals, and they are different questions: **403** the invite is addressed to another
42    /// email, or the `token` does not match; **409** the invite is not `pending` (already accepted,
43    /// revoked, declined) or the email already belongs to a member; **410** the invite has expired;
44    /// **404** no such invite, or the caller has no user record.
45    ///
46    /// `POST /api/v1/me/invites/{tenantId}/{inviteId}/accept`
47    pub async fn accept_invite_from_picker(&self, tenant_id: &str, invite_id: &str, body: &models::AcceptInviteFromPickerRequest) -> Result<models::AcceptInviteFromPickerResponse> {
48        self.client
49            .request_json(Request {
50                method: Method::POST,
51                path: format!("/api/v1/me/invites/{}/{}/accept", encode_path(tenant_id), encode_path(invite_id)),
52                query: NO_QUERY,
53                body: Some(body),
54                headers: Vec::new(),
55                idempotent: true,
56            })
57            .await
58    }
59
60    /// Create a new API key
61    ///
62    /// `POST /api/v1/tenants/me/keys`
63    ///
64    /// Required scopes: `tenants:write`.
65    pub async fn create_api_key(&self, body: &models::CreateAPIKeyRequest) -> Result<models::APIKeyResponse> {
66        self.client
67            .request_json(Request {
68                method: Method::POST,
69                path: "/api/v1/tenants/me/keys".to_string(),
70                query: NO_QUERY,
71                body: Some(body),
72                headers: Vec::new(),
73                idempotent: true,
74            })
75            .await
76    }
77
78    /// Create a new tenant with the caller as owner
79    ///
80    /// Provisions a brand-new tenant on the `free` plan (or `enterprise` for super-admin). The
81    /// caller becomes the founding owner. Onboarding wizard auto-runs on /browser to provision the
82    /// Head Agent.
83    ///
84    /// `POST /api/v1/me/tenants/create`
85    pub async fn create_my_tenant(&self, body: &models::CreateMyTenantRequest) -> Result<models::CreateMyTenantResponse> {
86        self.client
87            .request_json(Request {
88                method: Method::POST,
89                path: "/api/v1/me/tenants/create".to_string(),
90                query: NO_QUERY,
91                body: Some(body),
92                headers: Vec::new(),
93                idempotent: true,
94            })
95            .await
96    }
97
98    /// Decline a pending invite
99    ///
100    /// Deliberately distinct from an admin's revoke, so the audit trail and the Members page can
101    /// tell "the invitee said no" from "an admin pulled it". The caller's email must match the
102    /// invite's — without that check anyone with a current session could decline someone else's
103    /// invites.
104    ///
105    /// No token is required here, unlike accept: declining grants nothing.
106    ///
107    /// `POST /api/v1/me/invites/{tenantId}/{inviteId}/decline`
108    pub async fn decline_invite_from_picker(&self, tenant_id: &str, invite_id: &str) -> Result<models::DeclineInviteFromPickerResponse> {
109        self.client
110            .request_json(Request {
111                method: Method::POST,
112                path: format!("/api/v1/me/invites/{}/{}/decline", encode_path(tenant_id), encode_path(invite_id)),
113                query: NO_QUERY,
114                body: NO_BODY,
115                headers: Vec::new(),
116                idempotent: true,
117            })
118            .await
119    }
120
121    /// Get current tenant
122    ///
123    /// Returns the calling tenant's profile. Any authenticated key passes — no scope required.
124    ///
125    /// `GET /api/v1/tenants/me`
126    ///
127    /// Required scopes: `api_keys:read`.
128    pub async fn get_current_tenant(&self) -> Result<models::Tenant> {
129        self.client
130            .request_json(Request {
131                method: Method::GET,
132                path: "/api/v1/tenants/me".to_string(),
133                query: NO_QUERY,
134                body: NO_BODY,
135                headers: Vec::new(),
136                idempotent: false,
137            })
138            .await
139    }
140
141    /// Tiered Head Agent template for the onboarding wizard
142    ///
143    /// Returns Basic/Standard/Full tier metadata for the current tenant under the Variant-A
144    /// SPEC-driven contract. Each tier names the `@platform/*` SPECs the wizard installs (via
145    /// `install_specs`) plus an `auto_approve_tools` HITL list. Availability is a plan-rank check
146    /// (`required_plan`); locked tiers ship empty `install_specs`.
147    ///
148    /// `GET /api/v1/me/head-agent-template`
149    pub async fn get_my_head_agent_template(&self) -> Result<models::GetMyHeadAgentTemplateResponse> {
150        self.client
151            .request_json(Request {
152                method: Method::GET,
153                path: "/api/v1/me/head-agent-template".to_string(),
154                query: NO_QUERY,
155                body: NO_BODY,
156                headers: Vec::new(),
157                idempotent: false,
158            })
159            .await
160    }
161
162    /// DNS and certificate state for this tenant's custom domain
163    ///
164    /// **Two different 404s, and a client should tell them apart:** no such tenant, and a tenant
165    /// with no custom domain configured. The second is the ordinary state of most tenants and is
166    /// not an error condition — a UI that renders both as a failure will report a fault to every
167    /// customer who has not set up a vanity domain.
168    ///
169    /// Records written before the lifecycle schema are lifted on read, so `dns` and `cert` are
170    /// present here even for a domain added under the old flat fields.
171    ///
172    /// `GET /api/v1/tenants/me/domain/health`
173    ///
174    /// Required scopes: `api_keys:read`.
175    pub async fn get_tenant_domain_health(&self) -> Result<models::GetTenantDomainHealthResponse> {
176        self.client
177            .request_json(Request {
178                method: Method::GET,
179                path: "/api/v1/tenants/me/domain/health".to_string(),
180                query: NO_QUERY,
181                body: NO_BODY,
182                headers: Vec::new(),
183                idempotent: false,
184            })
185            .await
186    }
187
188    /// Leave a tenant
189    ///
190    /// Removes the caller's own membership. The user-record cascade matches an admin-driven
191    /// removal.
192    ///
193    /// Two refusals, both **409**, and both name a specific thing to do first. The caller is the
194    /// only ACTIVE owner: transfer ownership before leaving. Or the caller is the only veto-holding
195    /// ambassador: rotate the founder ambassador via `/api/v1/governance/ambassadors` first. The
196    /// second check is skipped entirely when governance is not enabled, so its absence is not a
197    /// promise that no such constraint exists.
198    ///
199    /// `DELETE /api/v1/me/memberships/{tenantId}`
200    pub async fn leave_tenant(&self, tenant_id: &str) -> Result<models::LeaveTenantResponse> {
201        self.client
202            .request_json(Request {
203                method: Method::DELETE,
204                path: format!("/api/v1/me/memberships/{}", encode_path(tenant_id)),
205                query: NO_QUERY,
206                body: NO_BODY,
207                headers: Vec::new(),
208                idempotent: true,
209            })
210            .await
211    }
212
213    /// List API keys
214    ///
215    /// `GET /api/v1/tenants/me/keys`
216    ///
217    /// Required scopes: `api_keys:read`.
218    pub async fn list_api_keys(&self) -> Result<models::ListAPIKeysResponse> {
219        self.client
220            .request_json(Request {
221                method: Method::GET,
222                path: "/api/v1/tenants/me/keys".to_string(),
223                query: NO_QUERY,
224                body: NO_BODY,
225                headers: Vec::new(),
226                idempotent: false,
227            })
228            .await
229    }
230
231    /// List the caller's tenant memberships + pending invites
232    ///
233    /// Returns every tenant the calling user is a member of, plus any pending invites addressed to
234    /// their email. Used by the Netflix-style picker (/browser/tenants).
235    ///
236    /// `GET /api/v1/me/tenants`
237    pub async fn list_my_tenants(&self) -> Result<models::ListMyTenantsResponse> {
238        self.client
239            .request_json(Request {
240                method: Method::GET,
241                path: "/api/v1/me/tenants".to_string(),
242                query: NO_QUERY,
243                body: NO_BODY,
244                headers: Vec::new(),
245                idempotent: false,
246            })
247            .await
248    }
249
250    /// Find which tenant owns the given agent across caller's memberships
251    ///
252    /// Used by the chat layout fallback when the URL points at an agent under a different active
253    /// tenant. Returns the owning tenant_id in one round-trip; the alternative was N client-side
254    /// probes that surfaced expected 404s in the browser console.
255    ///
256    /// `GET /api/v1/me/agents/{agentId}/locate`
257    pub async fn locate_my_agent(&self, agent_id: &str) -> Result<models::LocateMyAgentResponse> {
258        self.client
259            .request_json(Request {
260                method: Method::GET,
261                path: format!("/api/v1/me/agents/{}/locate", encode_path(agent_id)),
262                query: NO_QUERY,
263                body: NO_BODY,
264                headers: Vec::new(),
265                idempotent: false,
266            })
267            .await
268    }
269
270    /// Partial update tenant
271    ///
272    /// Partial update of tenant settings. Requires the `tenants:write` scope and role `owner`.
273    ///
274    /// `PATCH /api/v1/tenants/me`
275    ///
276    /// Required scopes: `tenants:write`.
277    pub async fn patch(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
278        self.client
279            .request_json(Request {
280                method: Method::PATCH,
281                path: "/api/v1/tenants/me".to_string(),
282                query: NO_QUERY,
283                body: Some(body),
284                headers: Vec::new(),
285                idempotent: true,
286            })
287            .await
288    }
289
290    /// Revoke an API key
291    ///
292    /// `DELETE /api/v1/tenants/me/keys/{keyId}`
293    ///
294    /// Required scopes: `tenants:write`.
295    pub async fn revoke_api_key(&self, key_id: &str) -> Result<models::RevokeAPIKeyResponse> {
296        self.client
297            .request_json(Request {
298                method: Method::DELETE,
299                path: format!("/api/v1/tenants/me/keys/{}", encode_path(key_id)),
300                query: NO_QUERY,
301                body: NO_BODY,
302                headers: Vec::new(),
303                idempotent: true,
304            })
305            .await
306    }
307
308    /// Update current tenant settings
309    ///
310    /// `PUT /api/v1/tenants/me`
311    ///
312    /// Required scopes: `tenants:write`.
313    pub async fn update(&self, body: &models::UpdateTenantRequest) -> Result<serde_json::Value> {
314        self.client
315            .request_json(Request {
316                method: Method::PUT,
317                path: "/api/v1/tenants/me".to_string(),
318                query: NO_QUERY,
319                body: Some(body),
320                headers: Vec::new(),
321                idempotent: true,
322            })
323            .await
324    }
325
326    /// Verify domain
327    ///
328    /// `POST /api/v1/tenants/me/domain/verify`
329    ///
330    /// Required scopes: `tenants:write`.
331    pub async fn verify_tenant_domain(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<models::VerifyTenantDomainResponse> {
332        self.client
333            .request_json(Request {
334                method: Method::POST,
335                path: "/api/v1/tenants/me/domain/verify".to_string(),
336                query: NO_QUERY,
337                body: Some(body),
338                headers: Vec::new(),
339                idempotent: true,
340            })
341            .await
342    }
343}