Skip to main content

uarp_sdk/generated/api/
admin.rs

1// Code generated by @uarp/codegen from spec/openapi.json. DO NOT EDIT.
2//!
3//! Platform admin: tenants, audit, reconciliation, DLQ, analytics
4
5#![allow(unused_imports, clippy::too_many_arguments)]
6
7use reqwest::Method;
8use serde::{Deserialize, Serialize};
9use futures_core::Stream;
10
11use crate::client::{Client, Request, NO_BODY, NO_QUERY};
12use crate::error::Result;
13use crate::generated::models;
14use crate::multipart::{field_text, FilePart};
15use crate::pagination::CursorGuard;
16use crate::util::encode_path;
17
18/// Query and header parameters for `adminAnalyticsAgents`.
19#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
20pub struct AdminAnalyticsAgentsParams {
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub days: Option<i64>,
23}
24
25/// Query and header parameters for `adminAnalyticsEvents`.
26#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
27pub struct AdminAnalyticsEventsParams {
28    #[serde(default, skip_serializing_if = "Option::is_none")]
29    pub days: Option<i64>,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub limit: Option<i64>,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub r#type: Option<String>,
34}
35
36/// Query and header parameters for `adminAnalyticsOverview`.
37#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
38pub struct AdminAnalyticsOverviewParams {
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub days: Option<i64>,
41}
42
43/// Query and header parameters for `adminDataExplorerRawKeys`.
44#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
45pub struct AdminDataExplorerRawKeysParams {
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub cursor: Option<String>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub limit: Option<i64>,
50    /// Comma-separated KV key parts; restricts the scan range.
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub prefix: Option<String>,
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub search: Option<String>,
55}
56
57/// Query and header parameters for `getAuditForTarget`.
58#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
59pub struct GetAuditForTargetParams {
60    /// Target type (e.g., agent, run, tenant)
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub r#type: Option<String>,
63}
64
65/// Query and header parameters for `getImmutableAudit`.
66#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
67pub struct GetImmutableAuditParams {
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub agent_id: Option<String>,
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub event: Option<String>,
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub from: Option<String>,
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub to: Option<String>,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub limit: Option<i64>,
78}
79
80/// Query and header parameters for `getPlatformEconomics`.
81#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
82pub struct GetPlatformEconomicsParams {
83    /// `1` bypasses the cache and recomputes.
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub refresh: Option<models::DeleteCustomPlanForce>,
86}
87
88/// Query and header parameters for `getTenantUsage`.
89#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
90pub struct GetTenantUsageParams {
91    /// ISO YYYY-MM period (defaults to current month)
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub period: Option<String>,
94}
95
96/// Query and header parameters for `listAndroidTesters`.
97#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
98pub struct ListAndroidTestersParams {
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub limit: Option<i64>,
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub cursor: Option<String>,
103}
104
105/// Query and header parameters for `listFeedback`.
106#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
107pub struct ListFeedbackParams {
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub status: Option<models::ErrorReportStatus>,
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub limit: Option<i64>,
112}
113
114/// Query and header parameters for `queryAuditLog`.
115#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
116pub struct QueryAuditLogParams {
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub limit: Option<i64>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub action: Option<String>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub actor_id: Option<String>,
123}
124
125/// Platform admin: tenants, audit, reconciliation, DLQ, analytics
126#[derive(Debug, Clone)]
127pub struct AdminApi {
128    pub(crate) client: Client,
129}
130
131impl Client {
132    /// Platform admin: tenants, audit, reconciliation, DLQ, analytics
133    pub fn admin(&self) -> AdminApi {
134        AdminApi { client: self.clone() }
135    }
136}
137
138impl AdminApi {
139    /// Add addresses to the roster by hand
140    ///
141    /// **Super-admin only.** Addresses reach the owner from the Play console and from people who
142    /// write directly, so the landing form is not the only door. Partial success is normal: each
143    /// address lands in exactly one of the three lists and the call is still 200.
144    ///
145    /// `POST /api/v1/admin/testers/android`
146    ///
147    /// Required scopes: `admin`.
148    pub async fn add_android_testers(&self, body: &models::AddAndroidTestersRequest) -> Result<models::AddAndroidTestersResponse> {
149        self.client
150            .request_json(Request {
151                method: Method::POST,
152                path: "/api/v1/admin/testers/android".to_string(),
153                query: NO_QUERY,
154                body: Some(body),
155                headers: Vec::new(),
156                idempotent: true,
157            })
158            .await
159    }
160
161    /// Platform-wide agent analytics
162    ///
163    /// `GET /api/v1/admin/analytics/agents`
164    ///
165    /// Required scopes: `admin`.
166    pub async fn admin_analytics_agents(&self, params: &AdminAnalyticsAgentsParams) -> Result<models::AgentAnalyticsSummary> {
167        self.client
168            .request_json(Request {
169                method: Method::GET,
170                path: "/api/v1/admin/analytics/agents".to_string(),
171                query: Some(params),
172                body: NO_BODY,
173                headers: Vec::new(),
174                idempotent: false,
175            })
176            .await
177    }
178
179    /// Platform-wide event analytics
180    ///
181    /// `GET /api/v1/admin/analytics/events`
182    ///
183    /// Required scopes: `admin`.
184    pub async fn admin_analytics_events(&self, params: &AdminAnalyticsEventsParams) -> Result<models::AdminAnalyticsEventsResponse> {
185        self.client
186            .request_json(Request {
187                method: Method::GET,
188                path: "/api/v1/admin/analytics/events".to_string(),
189                query: Some(params),
190                body: NO_BODY,
191                headers: Vec::new(),
192                idempotent: false,
193            })
194            .await
195    }
196
197    /// Platform-wide analytics overview
198    ///
199    /// `GET /api/v1/admin/analytics/overview`
200    ///
201    /// Required scopes: `admin`.
202    pub async fn admin_analytics_overview(&self, params: &AdminAnalyticsOverviewParams) -> Result<models::AdminAnalyticsOverviewResponse> {
203        self.client
204            .request_json(Request {
205                method: Method::GET,
206                path: "/api/v1/admin/analytics/overview".to_string(),
207                query: Some(params),
208                body: NO_BODY,
209                headers: Vec::new(),
210                idempotent: false,
211            })
212            .await
213    }
214
215    /// Full KV scan (admin diagnostic)
216    ///
217    /// Dangerous: full DB key scan. Admin-only. Use the namespace-scoped endpoints under
218    /// `/admin/data-explorer/*` for normal browsing.
219    ///
220    /// `GET /api/v1/admin/data-explorer/raw-keys`
221    ///
222    /// Required scopes: `admin`.
223    pub async fn admin_data_explorer_raw_keys(&self, params: &AdminDataExplorerRawKeysParams) -> Result<models::AdminDataExplorerRawKeysResponse> {
224        self.client
225            .request_json(Request {
226                method: Method::GET,
227                path: "/api/v1/admin/data-explorer/raw-keys".to_string(),
228                query: Some(params),
229                body: NO_BODY,
230                headers: Vec::new(),
231                idempotent: false,
232            })
233            .await
234    }
235
236    /// Stream every item returned by `adminDataExplorerRawKeys`, following the `cursor` cursor
237    /// until the server reports no further pages.
238    pub fn admin_data_explorer_raw_keys_all<'a>(&'a self, params: &'a AdminDataExplorerRawKeysParams) -> impl Stream<Item = Result<models::AdminDataExplorerRawKeysResponseKey>> + 'a {
239        async_stream::try_stream! {
240            let mut guard = CursorGuard::new();
241            let mut cursor = params.cursor.clone();
242            loop {
243                let mut page_params = params.clone();
244                page_params.cursor = cursor.clone();
245                let page = self.admin_data_explorer_raw_keys(&page_params).await?;
246                let items = page.keys;
247                let was_empty = items.is_empty();
248                for item in items {
249                    yield item;
250                }
251                match guard.advance(page.cursor, None, was_empty) {
252                    Some(next) => cursor = Some(next),
253                    None => break,
254                }
255            }
256        }
257    }
258
259    /// Remove OAuth login provider (super-admin)
260    ///
261    /// `DELETE /api/v1/admin/oauth-login-providers/{provider}`
262    ///
263    /// Required scopes: `admin`.
264    pub async fn admin_delete_o_auth_provider(&self, provider: &models::OAuthLoginProviderConfigStatusProvider) -> Result<models::OAuthLoginProviderConfigDeleted> {
265        self.client
266            .request_json(Request {
267                method: Method::DELETE,
268                path: format!("/api/v1/admin/oauth-login-providers/{}", encode_path(&provider.to_string())),
269                query: NO_QUERY,
270                body: NO_BODY,
271                headers: Vec::new(),
272                idempotent: true,
273            })
274            .await
275    }
276
277    /// Admin: get landing-page featured-agent config
278    ///
279    /// `GET /api/v1/admin/config/landing`
280    ///
281    /// Required scopes: `admin`.
282    pub async fn admin_get_landing_config(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
283        self.client
284            .request_json(Request {
285                method: Method::GET,
286                path: "/api/v1/admin/config/landing".to_string(),
287                query: NO_QUERY,
288                body: NO_BODY,
289                headers: Vec::new(),
290                idempotent: false,
291            })
292            .await
293    }
294
295    /// Admin: get curated model catalog
296    ///
297    /// `GET /api/v1/admin/config/model-catalog`
298    ///
299    /// Required scopes: `admin`.
300    pub async fn admin_get_model_catalog(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
301        self.client
302            .request_json(Request {
303                method: Method::GET,
304                path: "/api/v1/admin/config/model-catalog".to_string(),
305                query: NO_QUERY,
306                body: NO_BODY,
307                headers: Vec::new(),
308                idempotent: false,
309            })
310            .await
311    }
312
313    /// Admin: model catalog default seed
314    ///
315    /// `GET /api/v1/admin/config/model-catalog/seed`
316    ///
317    /// Required scopes: `admin`.
318    pub async fn admin_get_model_catalog_seed(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
319        self.client
320            .request_json(Request {
321                method: Method::GET,
322                path: "/api/v1/admin/config/model-catalog/seed".to_string(),
323                query: NO_QUERY,
324                body: NO_BODY,
325                headers: Vec::new(),
326                idempotent: false,
327            })
328            .await
329    }
330
331    /// Admin: live per-model pricing (synced from the configured provider)
332    ///
333    /// `GET /api/v1/admin/config/model-pricing`
334    ///
335    /// Required scopes: `admin`.
336    pub async fn admin_get_model_pricing(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
337        self.client
338            .request_json(Request {
339                method: Method::GET,
340                path: "/api/v1/admin/config/model-pricing".to_string(),
341                query: NO_QUERY,
342                body: NO_BODY,
343                headers: Vec::new(),
344                idempotent: false,
345            })
346            .await
347    }
348
349    /// Get configured OAuth login provider (super-admin)
350    ///
351    /// Returns provider config with the client_secret masked (last 4 chars only). Returns
352    /// `{configured: false}` for unconfigured providers.
353    ///
354    /// `GET /api/v1/admin/oauth-login-providers/{provider}`
355    ///
356    /// Required scopes: `admin`.
357    pub async fn admin_get_o_auth_provider(&self, provider: &models::OAuthLoginProviderConfigStatusProvider) -> Result<models::OAuthLoginProviderConfigStatus> {
358        self.client
359            .request_json(Request {
360                method: Method::GET,
361                path: format!("/api/v1/admin/oauth-login-providers/{}", encode_path(&provider.to_string())),
362                query: NO_QUERY,
363                body: NO_BODY,
364                headers: Vec::new(),
365                idempotent: false,
366            })
367            .await
368    }
369
370    /// Get last stored cost-reconciliation result for a tenant
371    ///
372    /// `GET /api/v1/admin/reconciliation/{tenantId}`
373    ///
374    /// Required scopes: `admin`.
375    pub async fn admin_get_reconciliation(&self, tenant_id: &str) -> Result<models::AdminGetReconciliationResponse> {
376        self.client
377            .request_json(Request {
378                method: Method::GET,
379                path: format!("/api/v1/admin/reconciliation/{}", encode_path(tenant_id)),
380                query: NO_QUERY,
381                body: NO_BODY,
382                headers: Vec::new(),
383                idempotent: false,
384            })
385            .await
386    }
387
388    /// Admin: get default voice (STT/TTS) config
389    ///
390    /// `GET /api/v1/admin/config/voice`
391    ///
392    /// Required scopes: `admin`.
393    pub async fn admin_get_voice_config(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
394        self.client
395            .request_json(Request {
396                method: Method::GET,
397                path: "/api/v1/admin/config/voice".to_string(),
398                query: NO_QUERY,
399                body: NO_BODY,
400                headers: Vec::new(),
401                idempotent: false,
402            })
403            .await
404    }
405
406    /// Admin: get per-model voice presets
407    ///
408    /// `GET /api/v1/admin/config/voice-presets`
409    ///
410    /// Required scopes: `admin`.
411    pub async fn admin_get_voice_presets(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
412        self.client
413            .request_json(Request {
414                method: Method::GET,
415                path: "/api/v1/admin/config/voice-presets".to_string(),
416                query: NO_QUERY,
417                body: NO_BODY,
418                headers: Vec::new(),
419                idempotent: false,
420            })
421            .await
422    }
423
424    /// Admin: enumerate available built-in tools
425    ///
426    /// `GET /api/v1/admin/tools`
427    ///
428    /// Required scopes: `admin`.
429    pub async fn admin_list_tools(&self) -> Result<models::AdminListToolsResponse> {
430        self.client
431            .request_json(Request {
432                method: Method::GET,
433                path: "/api/v1/admin/tools".to_string(),
434                query: NO_QUERY,
435                body: NO_BODY,
436                headers: Vec::new(),
437                idempotent: false,
438            })
439            .await
440    }
441
442    /// List dead-lettered webhook deliveries
443    ///
444    /// `GET /api/v1/admin/webhooks/dlq`
445    ///
446    /// Required scopes: `admin`.
447    pub async fn admin_list_webhook_dlq(&self) -> Result<models::AdminListWebhookDLQResponse> {
448        self.client
449            .request_json(Request {
450                method: Method::GET,
451                path: "/api/v1/admin/webhooks/dlq".to_string(),
452                query: NO_QUERY,
453                body: NO_BODY,
454                headers: Vec::new(),
455                idempotent: false,
456            })
457            .await
458    }
459
460    /// Admin: set landing-page featured-agent config
461    ///
462    /// `PUT /api/v1/admin/config/landing`
463    ///
464    /// Required scopes: `admin`.
465    pub async fn admin_put_landing_config(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
466        self.client
467            .request_json(Request {
468                method: Method::PUT,
469                path: "/api/v1/admin/config/landing".to_string(),
470                query: NO_QUERY,
471                body: Some(body),
472                headers: Vec::new(),
473                idempotent: true,
474            })
475            .await
476    }
477
478    /// Admin: replace curated model catalog
479    ///
480    /// `PUT /api/v1/admin/config/model-catalog`
481    ///
482    /// Required scopes: `admin`.
483    pub async fn admin_put_model_catalog(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
484        self.client
485            .request_json(Request {
486                method: Method::PUT,
487                path: "/api/v1/admin/config/model-catalog".to_string(),
488                query: NO_QUERY,
489                body: Some(body),
490                headers: Vec::new(),
491                idempotent: true,
492            })
493            .await
494    }
495
496    /// Set/update OAuth login provider (super-admin)
497    ///
498    /// Merge semantics: omitting client_secret keeps the existing one (so toggling `enabled`
499    /// doesn't require re-pasting the secret). First-time PUT requires both client_id and
500    /// client_secret. Stored encrypted at rest.
501    ///
502    /// `PUT /api/v1/admin/oauth-login-providers/{provider}`
503    ///
504    /// Required scopes: `admin`.
505    pub async fn admin_put_o_auth_provider(&self, provider: &models::OAuthLoginProviderConfigStatusProvider, body: &models::OAuthLoginProviderConfigUpdate) -> Result<models::OAuthLoginProviderConfigUpdateResponse> {
506        self.client
507            .request_json(Request {
508                method: Method::PUT,
509                path: format!("/api/v1/admin/oauth-login-providers/{}", encode_path(&provider.to_string())),
510                query: NO_QUERY,
511                body: Some(body),
512                headers: Vec::new(),
513                idempotent: true,
514            })
515            .await
516    }
517
518    /// Admin: set default voice config
519    ///
520    /// `PUT /api/v1/admin/config/voice`
521    ///
522    /// Required scopes: `admin`.
523    pub async fn admin_put_voice_config(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
524        self.client
525            .request_json(Request {
526                method: Method::PUT,
527                path: "/api/v1/admin/config/voice".to_string(),
528                query: NO_QUERY,
529                body: Some(body),
530                headers: Vec::new(),
531                idempotent: true,
532            })
533            .await
534    }
535
536    /// Admin: set per-model voice presets
537    ///
538    /// `PUT /api/v1/admin/config/voice-presets`
539    ///
540    /// Required scopes: `admin`.
541    pub async fn admin_put_voice_presets(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
542        self.client
543            .request_json(Request {
544                method: Method::PUT,
545                path: "/api/v1/admin/config/voice-presets".to_string(),
546                query: NO_QUERY,
547                body: Some(body),
548                headers: Vec::new(),
549                idempotent: true,
550            })
551            .await
552    }
553
554    /// Replay a dead-lettered webhook event
555    ///
556    /// `POST /api/v1/admin/webhooks/dlq/{eventId}/replay`
557    ///
558    /// Required scopes: `admin`.
559    pub async fn admin_replay_webhook_dlq(&self, event_id: &str) -> Result<models::AdminReplayWebhookDLQResponse> {
560        self.client
561            .request_json(Request {
562                method: Method::POST,
563                path: format!("/api/v1/admin/webhooks/dlq/{}/replay", encode_path(event_id)),
564                query: NO_QUERY,
565                body: NO_BODY,
566                headers: Vec::new(),
567                idempotent: true,
568            })
569            .await
570    }
571
572    /// Write a post by hand
573    ///
574    /// `source` is stamped `manual` and cannot be set by the caller. The slug is derived from the
575    /// title and made unique; `status` defaults to `draft`.
576    ///
577    /// `POST /api/v1/admin/blog/posts`
578    ///
579    /// Required scopes: `admin`.
580    pub async fn create_admin_blog_post(&self, body: &models::CreateAdminBlogPostRequest) -> Result<models::CreateAdminBlogPostResponse> {
581        self.client
582            .request_json(Request {
583                method: Method::POST,
584                path: "/api/v1/admin/blog/posts".to_string(),
585                query: NO_QUERY,
586                body: Some(body),
587                headers: Vec::new(),
588                idempotent: true,
589            })
590            .await
591    }
592
593    /// Create custom provider
594    ///
595    /// `POST /api/v1/admin/providers`
596    ///
597    /// Required scopes: `admin`.
598    pub async fn create_admin_provider(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
599        self.client
600            .request_json(Request {
601                method: Method::POST,
602                path: "/api/v1/admin/providers".to_string(),
603                query: NO_QUERY,
604                body: Some(body),
605                headers: Vec::new(),
606                idempotent: true,
607            })
608            .await
609    }
610
611    /// Create new tenant
612    ///
613    /// `POST /api/v1/admin/tenants`
614    ///
615    /// Required scopes: `admin`.
616    pub async fn create_tenant(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<models::Tenant> {
617        self.client
618            .request_json(Request {
619                method: Method::POST,
620                path: "/api/v1/admin/tenants".to_string(),
621                query: NO_QUERY,
622                body: Some(body),
623                headers: Vec::new(),
624                idempotent: true,
625            })
626            .await
627    }
628
629    /// Delete a post
630    ///
631    /// `DELETE /api/v1/admin/blog/posts/{postId}`
632    ///
633    /// Required scopes: `admin`.
634    pub async fn delete_admin_blog_post(&self, post_id: &str) -> Result<models::DeleteAdminBlogPostResponse> {
635        self.client
636            .request_json(Request {
637                method: Method::DELETE,
638                path: format!("/api/v1/admin/blog/posts/{}", encode_path(post_id)),
639                query: NO_QUERY,
640                body: NO_BODY,
641                headers: Vec::new(),
642                idempotent: true,
643            })
644            .await
645    }
646
647    /// Remove one provider's stored OAuth credentials
648    ///
649    /// Deletes the record outright — this is the only way to clear a stored credential, since the
650    /// PUT treats a blank value as "keep". Idempotent: deleting a provider that has nothing stored
651    /// is still 200.
652    ///
653    /// `DELETE /api/v1/admin/integration-oauth-providers/{provider}`
654    ///
655    /// Required scopes: `admin`.
656    pub async fn delete_admin_integration_o_auth_provider(&self, provider: &models::GetAdminIntegrationOAuthProviderProvider) -> Result<models::DeleteAdminIntegrationOAuthProviderResponse> {
657        self.client
658            .request_json(Request {
659                method: Method::DELETE,
660                path: format!("/api/v1/admin/integration-oauth-providers/{}", encode_path(&provider.to_string())),
661                query: NO_QUERY,
662                body: NO_BODY,
663                headers: Vec::new(),
664                idempotent: true,
665            })
666            .await
667    }
668
669    /// Remove the platform API key for a provider
670    ///
671    /// Drops the stored platform key. The PROVIDER survives — this is the key, not the definition;
672    /// `DELETE /api/v1/admin/providers/{providerId}` is the other one, and it removes a custom
673    /// provider outright.
674    ///
675    /// Answers `configured: false` for the provider, which is the same field `GET
676    /// /api/v1/admin/llm-defaults` reports per provider, so the caller can apply the answer without
677    /// a re-read.
678    ///
679    /// `DELETE /api/v1/admin/llm-defaults/{providerId}`
680    ///
681    /// Required scopes: `admin`.
682    pub async fn delete_admin_llm_default(&self, provider_id: &str) -> Result<models::DeleteAdminLLMDefaultResponse> {
683        self.client
684            .request_json(Request {
685                method: Method::DELETE,
686                path: format!("/api/v1/admin/llm-defaults/{}", encode_path(provider_id)),
687                query: NO_QUERY,
688                body: NO_BODY,
689                headers: Vec::new(),
690                idempotent: true,
691            })
692            .await
693    }
694
695    /// Remove a custom provider (super-admin, fresh MFA)
696    ///
697    /// Removes a CUSTOM provider and its settings. A built-in provider is not deletable and answers
698    /// 404 here — that 404 means "no such CUSTOM provider", not "no such provider", which is worth
699    /// knowing before reading it as a routing mistake.
700    ///
701    /// Gated on a fresh MFA challenge because it is irreversible: the definition and its settings
702    /// are deleted outright, not disabled. To stop using a provider without losing it, PATCH
703    /// `enabled: false` instead. To drop the platform API KEY while keeping the provider, use
704    /// `DELETE /api/v1/admin/llm-defaults/{providerId}` — a different route with a different
705    /// subject.
706    ///
707    /// `DELETE /api/v1/admin/providers/{providerId}`
708    ///
709    /// Required scopes: `admin`.
710    pub async fn delete_admin_provider(&self, provider_id: &str) -> Result<models::DeleteAdminProviderResponse> {
711        self.client
712            .request_json(Request {
713                method: Method::DELETE,
714                path: format!("/api/v1/admin/providers/{}", encode_path(provider_id)),
715                query: NO_QUERY,
716                body: NO_BODY,
717                headers: Vec::new(),
718                idempotent: true,
719            })
720            .await
721    }
722
723    /// Take one address off the roster
724    ///
725    /// **Super-admin only.** The address is a path segment, so it arrives percent-encoded.
726    ///
727    /// `DELETE /api/v1/admin/testers/android/{email}`
728    ///
729    /// Required scopes: `admin`.
730    pub async fn delete_android_tester(&self, email: &str) -> Result<models::DeleteAndroidTesterResponse> {
731        self.client
732            .request_json(Request {
733                method: Method::DELETE,
734                path: format!("/api/v1/admin/testers/android/{}", encode_path(email)),
735                query: NO_QUERY,
736                body: NO_BODY,
737                headers: Vec::new(),
738                idempotent: true,
739            })
740            .await
741    }
742
743    /// Run the authoring agent now and create a post
744    ///
745    /// Generates immediately, ignoring `frequency` — this is the operator's manual trigger, not a
746    /// schedule nudge. Whether the result lands published or as a draft follows the stored
747    /// `auto_publish`.
748    ///
749    /// A generation that FAILS is **422**, not a 200 carrying an error field, so a client reads the
750    /// status. Success is **201**.
751    ///
752    /// `POST /api/v1/admin/blog/generate`
753    ///
754    /// Required scopes: `admin`.
755    pub async fn generate_admin_blog_post(&self) -> Result<models::GenerateAdminBlogPostResponse> {
756        self.client
757            .request_json(Request {
758                method: Method::POST,
759                path: "/api/v1/admin/blog/generate".to_string(),
760                query: NO_QUERY,
761                body: NO_BODY,
762                headers: Vec::new(),
763                idempotent: true,
764            })
765            .await
766    }
767
768    /// Get the blog's authoring configuration
769    ///
770    /// Super-admin only. Wrapped in `{config}` rather than returned bare — the whole blog surface
771    /// uses envelopes.
772    ///
773    /// `GET /api/v1/admin/blog/config`
774    ///
775    /// Required scopes: `admin`.
776    pub async fn get_admin_blog_config(&self) -> Result<models::GetAdminBlogConfigResponse> {
777        self.client
778            .request_json(Request {
779                method: Method::GET,
780                path: "/api/v1/admin/blog/config".to_string(),
781                query: NO_QUERY,
782                body: NO_BODY,
783                headers: Vec::new(),
784                idempotent: false,
785            })
786            .await
787    }
788
789    /// Read one provider's stored OAuth credentials (secret masked)
790    ///
791    /// **The response has two shapes and a client must handle both.** With nothing stored it is the
792    /// three-field form — `{provider, enabled: false, configured: false}` — and `client_id`,
793    /// `client_secret_hint` and `scopes` are ABSENT, not null. With a record stored, all six are
794    /// present.
795    ///
796    /// The secret is never echoed. `client_secret_hint` is the last four characters behind dots,
797    /// enough for an operator to confirm which credential is stored without seeing it, and it is
798    /// null when the stored secret is empty.
799    ///
800    /// `GET /api/v1/admin/integration-oauth-providers/{provider}`
801    ///
802    /// Required scopes: `admin`.
803    pub async fn get_admin_integration_o_auth_provider(&self, provider: &models::GetAdminIntegrationOAuthProviderProvider) -> Result<models::GetAdminIntegrationOAuthProviderResponse> {
804        self.client
805            .request_json(Request {
806                method: Method::GET,
807                path: format!("/api/v1/admin/integration-oauth-providers/{}", encode_path(&provider.to_string())),
808                query: NO_QUERY,
809                body: NO_BODY,
810                headers: Vec::new(),
811                idempotent: false,
812            })
813            .await
814    }
815
816    /// List platform API keys (masked)
817    ///
818    /// `GET /api/v1/admin/llm-defaults`
819    ///
820    /// Required scopes: `admin`.
821    pub async fn get_admin_llm_defaults(&self) -> Result<models::GetAdminLLMDefaultsResponse> {
822        self.client
823            .request_json(Request {
824                method: Method::GET,
825                path: "/api/v1/admin/llm-defaults".to_string(),
826                query: NO_QUERY,
827                body: NO_BODY,
828                headers: Vec::new(),
829                idempotent: false,
830            })
831            .await
832    }
833
834    /// Get pricing configuration
835    ///
836    /// `GET /api/v1/admin/config/pricing`
837    ///
838    /// Required scopes: `admin`.
839    pub async fn get_admin_pricing(&self) -> Result<serde_json::Value> {
840        self.client
841            .request_json(Request {
842                method: Method::GET,
843                path: "/api/v1/admin/config/pricing".to_string(),
844                query: NO_QUERY,
845                body: NO_BODY,
846                headers: Vec::new(),
847                idempotent: false,
848            })
849            .await
850    }
851
852    /// Get single provider
853    ///
854    /// `GET /api/v1/admin/providers/{providerId}`
855    ///
856    /// Required scopes: `admin`.
857    pub async fn get_admin_provider(&self, provider_id: &str) -> Result<serde_json::Map<String, serde_json::Value>> {
858        self.client
859            .request_json(Request {
860                method: Method::GET,
861                path: format!("/api/v1/admin/providers/{}", encode_path(provider_id)),
862                query: NO_QUERY,
863                body: NO_BODY,
864                headers: Vec::new(),
865                idempotent: false,
866            })
867            .await
868    }
869
870    /// Aggregate usage stats
871    ///
872    /// `GET /api/v1/admin/stats`
873    ///
874    /// Required scopes: `admin`.
875    pub async fn get_admin_stats(&self) -> Result<models::GetAdminStatsResponse> {
876        self.client
877            .request_json(Request {
878                method: Method::GET,
879                path: "/api/v1/admin/stats".to_string(),
880                query: NO_QUERY,
881                body: NO_BODY,
882                headers: Vec::new(),
883                idempotent: false,
884            })
885            .await
886    }
887
888    /// Trace delegation chain
889    ///
890    /// `GET /api/v1/admin/trace/{traceId}`
891    ///
892    /// Required scopes: `admin`.
893    pub async fn get_admin_trace(&self, trace_id: &str) -> Result<models::GetAdminTraceResponse> {
894        self.client
895            .request_json(Request {
896                method: Method::GET,
897                path: format!("/api/v1/admin/trace/{}", encode_path(trace_id)),
898                query: NO_QUERY,
899                body: NO_BODY,
900                headers: Vec::new(),
901                idempotent: false,
902            })
903            .await
904    }
905
906    /// Get audit entries for a specific target
907    ///
908    /// `GET /api/v1/admin/audit/{targetId}`
909    ///
910    /// Required scopes: `admin`.
911    pub async fn get_audit_for_target(&self, target_id: &str, params: &GetAuditForTargetParams) -> Result<serde_json::Value> {
912        self.client
913            .request_json(Request {
914                method: Method::GET,
915                path: format!("/api/v1/admin/audit/{}", encode_path(target_id)),
916                query: Some(params),
917                body: NO_BODY,
918                headers: Vec::new(),
919                idempotent: false,
920            })
921            .await
922    }
923
924    /// EU AI Act conformity report
925    ///
926    /// `GET /api/v1/admin/conformity-report`
927    ///
928    /// Required scopes: `admin`.
929    pub async fn get_conformity_report(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
930        self.client
931            .request_json(Request {
932                method: Method::GET,
933                path: "/api/v1/admin/conformity-report".to_string(),
934                query: NO_QUERY,
935                body: NO_BODY,
936                headers: Vec::new(),
937                idempotent: false,
938            })
939            .await
940    }
941
942    /// Immutable audit log
943    ///
944    /// `GET /api/v1/admin/immutable-audit`
945    ///
946    /// Required scopes: `admin`.
947    pub async fn get_immutable_audit(&self, params: &GetImmutableAuditParams) -> Result<models::GetImmutableAuditResponse> {
948        self.client
949            .request_json(Request {
950                method: Method::GET,
951                path: "/api/v1/admin/immutable-audit".to_string(),
952                query: Some(params),
953                body: NO_BODY,
954                headers: Vec::new(),
955                idempotent: false,
956            })
957            .await
958    }
959
960    /// Full maintenance record
961    ///
962    /// The whole record, including who turned it on and when — the audit trail the public status
963    /// deliberately omits. **Super-admin only**, and a caller on the synthetic default tenant is
964    /// 401 rather than 403.
965    ///
966    /// `GET /api/v1/admin/maintenance`
967    ///
968    /// Required scopes: `admin`.
969    pub async fn get_maintenance_state(&self) -> Result<models::MaintenanceState> {
970        self.client
971            .request_json(Request {
972                method: Method::GET,
973                path: "/api/v1/admin/maintenance".to_string(),
974                query: NO_QUERY,
975                body: NO_BODY,
976                headers: Vec::new(),
977                idempotent: false,
978            })
979            .await
980    }
981
982    /// Platform revenue, host cost and margin
983    ///
984    /// Stripe subscriptions against real DigitalOcean spend, with the computed margin.
985    /// **Super-admin only.**
986    ///
987    /// Served from a short-lived cache; `cache` says whether this response was a hit, a miss, or a
988    /// forced recomputation. A Stripe or provider outage does not fail the call — the affected
989    /// block carries `error` and the rest is still served, so a partial answer is never mistaken
990    /// for zeros.
991    ///
992    /// `GET /api/v1/admin/economics`
993    ///
994    /// Required scopes: `admin`.
995    pub async fn get_platform_economics(&self, params: &GetPlatformEconomicsParams) -> Result<models::PlatformEconomics> {
996        self.client
997            .request_json(Request {
998                method: Method::GET,
999                path: "/api/v1/admin/economics".to_string(),
1000                query: Some(params),
1001                body: NO_BODY,
1002                headers: Vec::new(),
1003                idempotent: false,
1004            })
1005            .await
1006    }
1007
1008    /// Get tenant details
1009    ///
1010    /// `GET /api/v1/admin/tenants/{tenantId}`
1011    ///
1012    /// Required scopes: `admin`.
1013    pub async fn get_tenant(&self, tenant_id: &str) -> Result<models::Tenant> {
1014        self.client
1015            .request_json(Request {
1016                method: Method::GET,
1017                path: format!("/api/v1/admin/tenants/{}", encode_path(tenant_id)),
1018                query: NO_QUERY,
1019                body: NO_BODY,
1020                headers: Vec::new(),
1021                idempotent: false,
1022            })
1023            .await
1024    }
1025
1026    /// One tenant's mission-framework overrides, and what they resolve to
1027    ///
1028    /// Answers both layers at once: `mef_config` is what an operator stored for this tenant,
1029    /// `effective` is what the runtime will actually do. They differ whenever the platform is the
1030    /// deciding factor — with the mission service absent, every effective flag is false no matter
1031    /// what the tenant record says, so an operator reading only `mef_config` sees settings that do
1032    /// nothing.
1033    ///
1034    /// `mef_config` is `null` when nothing is overridden, not an empty object.
1035    ///
1036    /// `GET /api/v1/admin/tenants/{tenantId}/mef-config`
1037    ///
1038    /// Required scopes: `admin`.
1039    pub async fn get_tenant_mef_config(&self, tenant_id: &str) -> Result<models::TenantMefConfigResponse> {
1040        self.client
1041            .request_json(Request {
1042                method: Method::GET,
1043                path: format!("/api/v1/admin/tenants/{}/mef-config", encode_path(tenant_id)),
1044                query: NO_QUERY,
1045                body: NO_BODY,
1046                headers: Vec::new(),
1047                idempotent: false,
1048            })
1049            .await
1050    }
1051
1052    /// Get tenant usage metrics
1053    ///
1054    /// `GET /api/v1/admin/tenants/{tenantId}/usage`
1055    ///
1056    /// Required scopes: `admin`.
1057    pub async fn get_tenant_usage(&self, tenant_id: &str, params: &GetTenantUsageParams) -> Result<models::GetTenantUsageResponse> {
1058        self.client
1059            .request_json(Request {
1060                method: Method::GET,
1061                path: format!("/api/v1/admin/tenants/{}/usage", encode_path(tenant_id)),
1062                query: Some(params),
1063                body: NO_BODY,
1064                headers: Vec::new(),
1065                idempotent: false,
1066            })
1067            .await
1068    }
1069
1070    /// Internal domain verification
1071    ///
1072    /// `GET /api/v1/internal/verify-domain`
1073    pub async fn internal_verify_domain(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
1074        self.client
1075            .request_json(Request {
1076                method: Method::GET,
1077                path: "/api/v1/internal/verify-domain".to_string(),
1078                query: NO_QUERY,
1079                body: NO_BODY,
1080                headers: Vec::new(),
1081                idempotent: false,
1082            })
1083            .await
1084    }
1085
1086    /// List every post, drafts included
1087    ///
1088    /// The admin view: unlike the public blog read, drafts are included. Unpaged.
1089    ///
1090    /// `GET /api/v1/admin/blog/posts`
1091    ///
1092    /// Required scopes: `admin`.
1093    pub async fn list_admin_blog_posts(&self) -> Result<models::ListAdminBlogPostsResponse> {
1094        self.client
1095            .request_json(Request {
1096                method: Method::GET,
1097                path: "/api/v1/admin/blog/posts".to_string(),
1098                query: NO_QUERY,
1099                body: NO_BODY,
1100                headers: Vec::new(),
1101                idempotent: false,
1102            })
1103            .await
1104    }
1105
1106    /// Every tenant's custom domain, worst first
1107    ///
1108    /// **The order is the product.** Rows are sorted by problem severity — failed, then drift, then
1109    /// renewal_due, then pending, then healthy — so an operator's eye lands on what is broken. A
1110    /// client that re-sorts alphabetically throws that away and should sort back, or not sort at
1111    /// all.
1112    ///
1113    /// Tenants with no custom domain are omitted entirely, so `count` is the number of configured
1114    /// domains and not the number of tenants.
1115    ///
1116    /// Bounded: the tenant registry scan takes at most 5000 entries in one pass and is not paged,
1117    /// so on a platform past that size this list is silently partial.
1118    ///
1119    /// `GET /api/v1/admin/domains/health`
1120    ///
1121    /// Required scopes: `admin`.
1122    pub async fn list_admin_domain_health(&self) -> Result<models::ListAdminDomainHealthResponse> {
1123        self.client
1124            .request_json(Request {
1125                method: Method::GET,
1126                path: "/api/v1/admin/domains/health".to_string(),
1127                query: NO_QUERY,
1128                body: NO_BODY,
1129                headers: Vec::new(),
1130                idempotent: false,
1131            })
1132            .await
1133    }
1134
1135    /// Which integration providers an operator has configured
1136    ///
1137    /// Super-admin only, and never returns a secret — provider id, an enabled bit, and whether
1138    /// credentials exist at all. The dashboard uses it to decide which connector rows need a
1139    /// "Configure" call to action.
1140    ///
1141    /// Distinct from `/api/v1/integrations/catalog`, which is tenant-facing and lists every known
1142    /// connector regardless of OAuth-readiness.
1143    ///
1144    /// The list is the SUPPORTED set, not the stored set: every supported provider appears, with
1145    /// `configured: false` where nothing is stored. So an empty result means the supported set is
1146    /// empty, never that nothing is configured.
1147    ///
1148    /// `GET /api/v1/admin/integration-oauth-providers`
1149    ///
1150    /// Required scopes: `admin`.
1151    pub async fn list_admin_integration_o_auth_providers(&self) -> Result<models::ListAdminIntegrationOAuthProvidersResponse> {
1152        self.client
1153            .request_json(Request {
1154                method: Method::GET,
1155                path: "/api/v1/admin/integration-oauth-providers".to_string(),
1156                query: NO_QUERY,
1157                body: NO_BODY,
1158                headers: Vec::new(),
1159                idempotent: false,
1160            })
1161            .await
1162    }
1163
1164    /// List providers with admin settings
1165    ///
1166    /// `GET /api/v1/admin/providers`
1167    ///
1168    /// Required scopes: `admin`.
1169    pub async fn list_admin_providers(&self) -> Result<models::ListAdminProvidersResponse> {
1170        self.client
1171            .request_json(Request {
1172                method: Method::GET,
1173                path: "/api/v1/admin/providers".to_string(),
1174                query: NO_QUERY,
1175                body: NO_BODY,
1176                headers: Vec::new(),
1177                idempotent: false,
1178            })
1179            .await
1180    }
1181
1182    /// The Android closed-testing roster
1183    ///
1184    /// **Super-admin only.** The addresses the owner works from when adding people to the Play
1185    /// group. `count`, `not_yet_emailed` and `given_up` describe THIS PAGE, not the whole roster:
1186    /// all three are computed over the rows returned, so a roster longer than `limit` under-reports
1187    /// until every page is walked with `cursor`. `not_yet_emailed` counts rows no letter has gone
1188    /// to; `given_up` counts the ones the backfill has stopped retrying after repeated refusals,
1189    /// which need an eye rather than another pass.
1190    ///
1191    /// `GET /api/v1/admin/testers/android`
1192    ///
1193    /// Required scopes: `admin`.
1194    pub async fn list_android_testers(&self, params: &ListAndroidTestersParams) -> Result<models::ListAndroidTestersResponse> {
1195        self.client
1196            .request_json(Request {
1197                method: Method::GET,
1198                path: "/api/v1/admin/testers/android".to_string(),
1199                query: Some(params),
1200                body: NO_BODY,
1201                headers: Vec::new(),
1202                idempotent: false,
1203            })
1204            .await
1205    }
1206
1207    /// Stream every item returned by `listAndroidTesters`, following the `cursor` cursor until the
1208    /// server reports no further pages.
1209    pub fn list_android_testers_all<'a>(&'a self, params: &'a ListAndroidTestersParams) -> impl Stream<Item = Result<models::AndroidTester>> + 'a {
1210        async_stream::try_stream! {
1211            let mut guard = CursorGuard::new();
1212            let mut cursor = params.cursor.clone();
1213            loop {
1214                let mut page_params = params.clone();
1215                page_params.cursor = cursor.clone();
1216                let page = self.list_android_testers(&page_params).await?;
1217                let items = page.testers;
1218                let was_empty = items.is_empty();
1219                for item in items {
1220                    yield item;
1221                }
1222                match guard.advance(page.cursor, None, was_empty) {
1223                    Some(next) => cursor = Some(next),
1224                    None => break,
1225                }
1226            }
1227        }
1228    }
1229
1230    /// The reports inbox
1231    ///
1232    /// Every report from every tenant, newest first. **Super-admin only.** `new_count` counts the
1233    /// unresolved reports in the returned set, so a filtered list does not silently under-report
1234    /// the backlog.
1235    ///
1236    /// `GET /api/v1/admin/feedback`
1237    ///
1238    /// Required scopes: `admin`.
1239    pub async fn list_feedback(&self, params: &ListFeedbackParams) -> Result<models::ListFeedbackResponse> {
1240        self.client
1241            .request_json(Request {
1242                method: Method::GET,
1243                path: "/api/v1/admin/feedback".to_string(),
1244                query: Some(params),
1245                body: NO_BODY,
1246                headers: Vec::new(),
1247                idempotent: false,
1248            })
1249            .await
1250    }
1251
1252    /// List all tenants (super admin only)
1253    ///
1254    /// `GET /api/v1/admin/tenants`
1255    ///
1256    /// Required scopes: `admin`.
1257    pub async fn list_tenants(&self) -> Result<models::ListTenantsResponse> {
1258        self.client
1259            .request_json(Request {
1260                method: Method::GET,
1261                path: "/api/v1/admin/tenants".to_string(),
1262                query: NO_QUERY,
1263                body: NO_BODY,
1264                headers: Vec::new(),
1265                idempotent: false,
1266            })
1267            .await
1268    }
1269
1270    /// Purge tenant
1271    ///
1272    /// `DELETE /api/v1/admin/tenants/{tenantId}`
1273    ///
1274    /// Required scopes: `admin`.
1275    pub async fn purge_admin_tenant(&self, tenant_id: &str) -> Result<models::PurgeAdminTenantResponse> {
1276        self.client
1277            .request_json(Request {
1278                method: Method::DELETE,
1279                path: format!("/api/v1/admin/tenants/{}", encode_path(tenant_id)),
1280                query: NO_QUERY,
1281                body: NO_BODY,
1282                headers: Vec::new(),
1283                idempotent: true,
1284            })
1285            .await
1286    }
1287
1288    /// Query admin audit log
1289    ///
1290    /// `GET /api/v1/admin/audit`
1291    ///
1292    /// Required scopes: `admin`.
1293    pub async fn query_audit_log(&self, params: &QueryAuditLogParams) -> Result<serde_json::Value> {
1294        self.client
1295            .request_json(Request {
1296                method: Method::GET,
1297                path: "/api/v1/admin/audit".to_string(),
1298                query: Some(params),
1299                body: NO_BODY,
1300                headers: Vec::new(),
1301                idempotent: false,
1302            })
1303            .await
1304    }
1305
1306    /// Reactivate a suspended tenant
1307    ///
1308    /// `PUT /api/v1/admin/tenants/{tenantId}/reactivate`
1309    ///
1310    /// Required scopes: `admin`.
1311    pub async fn reactivate_tenant(&self, tenant_id: &str) -> Result<serde_json::Value> {
1312        self.client
1313            .request_json(Request {
1314                method: Method::PUT,
1315                path: format!("/api/v1/admin/tenants/{}/reactivate", encode_path(tenant_id)),
1316                query: NO_QUERY,
1317                body: NO_BODY,
1318                headers: Vec::new(),
1319                idempotent: true,
1320            })
1321            .await
1322    }
1323
1324    /// Store or update one provider's OAuth credentials
1325    ///
1326    /// WRITE SEMANTICS: merges, and the merge is what makes the route usable. An omitted or blank
1327    /// `client_id` or `client_secret` keeps the stored one, so an operator can flip `enabled` or
1328    /// rotate `scopes` WITHOUT re-pasting a secret they cannot read back. Blank counts as omitted
1329    /// here: a whitespace-only value does not clear anything.
1330    ///
1331    /// The consequence is that there is no way to clear a credential through this route — DELETE
1332    /// the provider instead.
1333    ///
1334    /// A FIRST write still needs both: with no stored record and either missing, the answer is 400.
1335    /// `enabled` defaults to true on a first write and otherwise keeps its stored value. A present
1336    /// `scopes` REPLACES the stored list.
1337    ///
1338    /// The response is the short form, not the record: `{provider, enabled, configured: true}`,
1339    /// with no echo of the credentials just written.
1340    ///
1341    /// `PUT /api/v1/admin/integration-oauth-providers/{provider}`
1342    ///
1343    /// Required scopes: `admin`.
1344    pub async fn set_admin_integration_o_auth_provider(&self, provider: &models::GetAdminIntegrationOAuthProviderProvider, body: &models::SetAdminIntegrationOAuthProviderRequest) -> Result<models::SetAdminIntegrationOAuthProviderResponse> {
1345        self.client
1346            .request_json(Request {
1347                method: Method::PUT,
1348                path: format!("/api/v1/admin/integration-oauth-providers/{}", encode_path(&provider.to_string())),
1349                query: NO_QUERY,
1350                body: Some(body),
1351                headers: Vec::new(),
1352                idempotent: true,
1353            })
1354            .await
1355    }
1356
1357    /// Set platform API key
1358    ///
1359    /// `PUT /api/v1/admin/llm-defaults/{providerId}`
1360    ///
1361    /// Required scopes: `admin`.
1362    pub async fn set_admin_llm_default(&self, provider_id: &str, body: &models::SetAdminLLMDefaultRequest) -> Result<models::SetAdminLLMDefaultResponse> {
1363        self.client
1364            .request_json(Request {
1365                method: Method::PUT,
1366                path: format!("/api/v1/admin/llm-defaults/{}", encode_path(provider_id)),
1367                query: NO_QUERY,
1368                body: Some(body),
1369                headers: Vec::new(),
1370                idempotent: true,
1371            })
1372            .await
1373    }
1374
1375    /// Set default model config
1376    ///
1377    /// `PUT /api/v1/admin/llm-defaults/model-config`
1378    ///
1379    /// Required scopes: `admin`.
1380    pub async fn set_admin_model_config(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
1381        self.client
1382            .request_json(Request {
1383                method: Method::PUT,
1384                path: "/api/v1/admin/llm-defaults/model-config".to_string(),
1385                query: NO_QUERY,
1386                body: Some(body),
1387                headers: Vec::new(),
1388                idempotent: true,
1389            })
1390            .await
1391    }
1392
1393    /// Turn maintenance mode on or off (super-admin)
1394    ///
1395    /// Undocumented until now, while the GET beside it was described in full — so a generated
1396    /// client could READ the maintenance state and had no way to change it. The web has been
1397    /// calling this all along (`lib/hooks/use-maintenance-mode.ts`).
1398    ///
1399    /// Answers the whole record back, the same shape the GET serves, so a client need not re-read
1400    /// to learn `enabled_at` and `enabled_by_email`.
1401    ///
1402    /// WRITE SEMANTICS: replaces. The record is rebuilt from this body and written whole; nothing
1403    /// is read first. Turning maintenance ON without a `message` DROPS the message a previous ON
1404    /// had set, and turning it OFF wipes the message unconditionally, keeping only the timestamp
1405    /// and the actor so the audit trail still shows who closed the window. `enabled_at` and
1406    /// `enabled_by_email` are stamped on EVERY call, including one that changes nothing.
1407    ///
1408    /// `PUT /api/v1/admin/maintenance`
1409    ///
1410    /// Required scopes: `admin`.
1411    pub async fn set_maintenance_state(&self, body: &models::SetMaintenanceStateRequest) -> Result<models::MaintenanceState> {
1412        self.client
1413            .request_json(Request {
1414                method: Method::PUT,
1415                path: "/api/v1/admin/maintenance".to_string(),
1416                query: NO_QUERY,
1417                body: Some(body),
1418                headers: Vec::new(),
1419                idempotent: true,
1420            })
1421            .await
1422    }
1423
1424    /// Suspend a tenant
1425    ///
1426    /// `PUT /api/v1/admin/tenants/{tenantId}/suspend`
1427    ///
1428    /// Required scopes: `admin`.
1429    pub async fn suspend_tenant(&self, tenant_id: &str, body: &models::SuspendTenantRequest) -> Result<serde_json::Value> {
1430        self.client
1431            .request_json(Request {
1432                method: Method::PUT,
1433                path: format!("/api/v1/admin/tenants/{}/suspend", encode_path(tenant_id)),
1434                query: NO_QUERY,
1435                body: Some(body),
1436                headers: Vec::new(),
1437                idempotent: true,
1438            })
1439            .await
1440    }
1441
1442    /// Pull one provider's model list into the catalogue
1443    ///
1444    /// Asks the provider what models it offers and MERGES the result into the platform catalogue.
1445    /// Additive only — nothing is removed, so a model the provider has withdrawn stays in the
1446    /// catalogue until it is deleted deliberately.
1447    ///
1448    /// CUSTOM providers only: the 404 means "no custom provider with that id", so a built-in
1449    /// provider id is also 404 here. `added` counts new entries, `scanned` is the provider's
1450    /// reported inventory, and `total` is the catalogue size after the merge — so `added` is zero
1451    /// on a run that changed nothing, which is the normal result of a second run.
1452    ///
1453    /// `POST /api/v1/admin/providers/{providerId}/sync-models`
1454    ///
1455    /// Required scopes: `admin`.
1456    pub async fn sync_provider_models(&self, provider_id: &str) -> Result<models::SyncProviderModelsResponse> {
1457        self.client
1458            .request_json(Request {
1459                method: Method::POST,
1460                path: format!("/api/v1/admin/providers/{}/sync-models", encode_path(provider_id)),
1461                query: NO_QUERY,
1462                body: NO_BODY,
1463                headers: Vec::new(),
1464                idempotent: true,
1465            })
1466            .await
1467    }
1468
1469    /// Update the blog's authoring configuration
1470    ///
1471    /// WRITE SEMANTICS: merges. The body is spread over the stored record, so an omitted field
1472    /// keeps its value.
1473    ///
1474    /// One field is not a plain merge: setting `agent_id` also pins `agent_tenant_id` to the
1475    /// CALLING tenant, because the cron that auto-writes posts runs without a request context and
1476    /// would otherwise have no tenant to run the agent in. Clearing `agent_id` to `null` nulls
1477    /// both. `agent_tenant_id` is therefore never sent by a client and never has to be — it is
1478    /// derived.
1479    ///
1480    /// Answers the stored record, so a client sees what took effect.
1481    ///
1482    /// `PUT /api/v1/admin/blog/config`
1483    ///
1484    /// Required scopes: `admin`.
1485    pub async fn update_admin_blog_config(&self, body: &models::UpdateAdminBlogConfigRequest) -> Result<models::UpdateAdminBlogConfigResponse> {
1486        self.client
1487            .request_json(Request {
1488                method: Method::PUT,
1489                path: "/api/v1/admin/blog/config".to_string(),
1490                query: NO_QUERY,
1491                body: Some(body),
1492                headers: Vec::new(),
1493                idempotent: true,
1494            })
1495            .await
1496    }
1497
1498    /// Edit a post
1499    ///
1500    /// WRITE SEMANTICS: merges. An omitted field keeps its value; `tags` present REPLACES the list.
1501    ///
1502    /// Three things move on their own and a client should not try to send them. Editing the `title`
1503    /// or the `body` re-stamps `source` to `manual`, even on a post the agent wrote — the record
1504    /// then says who last shaped it rather than who started it. Changing the `title` mints a new
1505    /// unique `slug`, so a published post's URL changes under it. And `published_at` follows
1506    /// `status`: it is stamped on the first transition to `published` and set back to null on
1507    /// `draft`, so a republished post carries a NEW timestamp rather than its original one.
1508    ///
1509    /// `PATCH /api/v1/admin/blog/posts/{postId}`
1510    ///
1511    /// Required scopes: `admin`.
1512    pub async fn update_admin_blog_post(&self, post_id: &str, body: &models::UpdateAdminBlogPostRequest) -> Result<models::UpdateAdminBlogPostResponse> {
1513        self.client
1514            .request_json(Request {
1515                method: Method::PATCH,
1516                path: format!("/api/v1/admin/blog/posts/{}", encode_path(post_id)),
1517                query: NO_QUERY,
1518                body: Some(body),
1519                headers: Vec::new(),
1520                idempotent: true,
1521            })
1522            .await
1523    }
1524
1525    /// Update pricing configuration
1526    ///
1527    /// `PUT /api/v1/admin/config/pricing`
1528    ///
1529    /// Required scopes: `admin`.
1530    pub async fn update_admin_pricing(&self, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Value> {
1531        self.client
1532            .request_json(Request {
1533                method: Method::PUT,
1534                path: "/api/v1/admin/config/pricing".to_string(),
1535                query: NO_QUERY,
1536                body: Some(body),
1537                headers: Vec::new(),
1538                idempotent: true,
1539            })
1540            .await
1541    }
1542
1543    /// Update provider
1544    ///
1545    /// `PATCH /api/v1/admin/providers/{providerId}`
1546    ///
1547    /// Required scopes: `admin`.
1548    pub async fn update_admin_provider(&self, provider_id: &str, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
1549        self.client
1550            .request_json(Request {
1551                method: Method::PATCH,
1552                path: format!("/api/v1/admin/providers/{}", encode_path(provider_id)),
1553                query: NO_QUERY,
1554                body: Some(body),
1555                headers: Vec::new(),
1556                idempotent: true,
1557            })
1558            .await
1559    }
1560
1561    /// Update tenant settings
1562    ///
1563    /// `PATCH /api/v1/admin/tenants/{tenantId}/settings`
1564    ///
1565    /// Required scopes: `admin`.
1566    pub async fn update_admin_tenant_settings(&self, tenant_id: &str, body: &serde_json::Map<String, serde_json::Value>) -> Result<serde_json::Map<String, serde_json::Value>> {
1567        self.client
1568            .request_json(Request {
1569                method: Method::PATCH,
1570                path: format!("/api/v1/admin/tenants/{}/settings", encode_path(tenant_id)),
1571                query: NO_QUERY,
1572                body: Some(body),
1573                headers: Vec::new(),
1574                idempotent: true,
1575            })
1576            .await
1577    }
1578
1579    /// Mark one feedback report resolved, or reopen it
1580    ///
1581    /// **Only the exact string `resolved` resolves a report; every other value sets it to `new`.**
1582    /// There is no validation and no error path: `"Resolved"`, `"closed"`, a typo, a missing
1583    /// `status`, or a body that is not JSON at all are each accepted with 200 and REOPEN a resolved
1584    /// report. A client must send the literal value and must not rely on being told when it did
1585    /// not.
1586    ///
1587    /// WRITE SEMANTICS: replaces the status field only; nothing else on the report is touched.
1588    ///
1589    /// `PATCH /api/v1/admin/feedback/{reportId}`
1590    ///
1591    /// Required scopes: `admin`.
1592    pub async fn update_feedback_report_status(&self, report_id: &str, body: &models::UpdateFeedbackReportStatusRequest) -> Result<models::UpdateFeedbackReportStatusResponse> {
1593        self.client
1594            .request_json(Request {
1595                method: Method::PATCH,
1596                path: format!("/api/v1/admin/feedback/{}", encode_path(report_id)),
1597                query: NO_QUERY,
1598                body: Some(body),
1599                headers: Vec::new(),
1600                idempotent: true,
1601            })
1602            .await
1603    }
1604
1605    /// Set or clear one tenant's mission-framework overrides
1606    ///
1607    /// WRITE SEMANTICS: merges, per key. Only the four known keys are read; a key the body omits
1608    /// keeps its stored value, and a key sent as `null` CLEARS that override so the flag falls back
1609    /// to the platform default. Unknown keys are ignored silently rather than rejected.
1610    ///
1611    /// A non-boolean, non-null value for a known key is **422**. Note the title on that one is
1612    /// `ValidationError` without a space, unlike the `Validation Error` used elsewhere on this
1613    /// surface.
1614    ///
1615    /// When clearing the last override leaves nothing set, the whole `mef_config` is dropped rather
1616    /// than stored as `{}` — a later read answers `null`. And every accepted request writes: even a
1617    /// body that changes nothing stamps a new `updated_at` on the tenant record.
1618    ///
1619    /// Answers the same body as the GET, so a client sees both the stored overrides and what they
1620    /// now resolve to.
1621    ///
1622    /// `PATCH /api/v1/admin/tenants/{tenantId}/mef-config`
1623    ///
1624    /// Required scopes: `admin`.
1625    pub async fn update_tenant_mef_config(&self, tenant_id: &str, body: &models::UpdateTenantMefConfigRequest) -> Result<models::TenantMefConfigResponse> {
1626        self.client
1627            .request_json(Request {
1628                method: Method::PATCH,
1629                path: format!("/api/v1/admin/tenants/{}/mef-config", encode_path(tenant_id)),
1630                query: NO_QUERY,
1631                body: Some(body),
1632                headers: Vec::new(),
1633                idempotent: true,
1634            })
1635            .await
1636    }
1637
1638    /// Set a tenant's plan (super-admin)
1639    ///
1640    /// The operator's manual plan grant, undocumented while GET, PATCH and DELETE on this same path
1641    /// were described. It is not a general tenant update: `plan` is required and it is what the
1642    /// route is for.
1643    ///
1644    /// WRITE SEMANTICS: mixed. The write is a compare-and-set MERGE onto the current record, so
1645    /// `name`, `slug` and `quota_overrides` keep their stored values when omitted. `plan` and
1646    /// `quotas` do not: both are written on every call, and omitting `quotas` REPLACES the tenant
1647    /// quotas with the resolved plan defaults rather than leaving them alone. A caller raising one
1648    /// dimension must send `quota_overrides`, not `quotas`.
1649    ///
1650    /// `quotas` REPLACES the plan's quotas for this tenant. `quota_overrides` is the durable one —
1651    /// a partial grant that survives a later Stripe subscription change, where a plain `quotas`
1652    /// write does not. When a tenant's plan keeps reverting, the override is the field that makes
1653    /// it stick, and it must be set BEFORE the plan is put back, not after.
1654    ///
1655    /// Granting any paid plan clears a stale `billing_status` (a "cancelled" left over from an
1656    /// earlier Stripe cancellation becomes "active"); a downgrade to free leaves the status alone,
1657    /// since free is never gated.
1658    ///
1659    /// `PUT /api/v1/admin/tenants/{tenantId}`
1660    ///
1661    /// Required scopes: `admin`.
1662    pub async fn update_tenant_plan(&self, tenant_id: &str, body: &models::UpdateTenantPlanRequest) -> Result<models::UpdateTenantPlanResponse> {
1663        self.client
1664            .request_json(Request {
1665                method: Method::PUT,
1666                path: format!("/api/v1/admin/tenants/{}", encode_path(tenant_id)),
1667                query: NO_QUERY,
1668                body: Some(body),
1669                headers: Vec::new(),
1670                idempotent: true,
1671            })
1672            .await
1673    }
1674}