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}