1use std::collections::HashMap;
2
3use derive_builder::Builder;
4use reqwest::Client as HttpClient;
5use serde::{Deserialize, Serialize, Serializer, ser::SerializeMap};
6use serde_json::Value;
7use urlencoding::encode;
8
9use crate::{
10 error::OpenRouterError,
11 transport::{request as transport_request, response as transport_response},
12 types::{ApiResponse, PaginationOptions},
13};
14
15#[derive(Serialize)]
16struct ListWorkspacesQuery {
17 #[serde(skip_serializing_if = "Option::is_none")]
18 offset: Option<u32>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 limit: Option<u32>,
21}
22
23#[derive(Serialize, Deserialize, Debug, Clone)]
24#[non_exhaustive]
25pub struct Workspace {
26 pub id: String,
27 pub name: String,
28 pub slug: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub description: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub default_guardrail_id: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub default_text_model: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub default_image_model: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub default_provider_sort: Option<String>,
39 #[serde(default)]
40 pub include_byok_in_budgets: bool,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub io_logging_api_key_ids: Option<Vec<u64>>,
43 pub io_logging_sampling_rate: f64,
44 pub is_observability_io_logging_enabled: bool,
45 pub is_observability_broadcast_enabled: bool,
46 pub is_data_discount_logging_enabled: bool,
47 pub created_at: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub updated_at: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub created_by: Option<String>,
52}
53
54#[derive(Serialize, Deserialize, Debug, Clone)]
55#[non_exhaustive]
56pub struct WorkspaceListResponse {
57 pub data: Vec<Workspace>,
58 pub total_count: f64,
59}
60
61#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
62#[builder(build_fn(error = "OpenRouterError"))]
63#[non_exhaustive]
64pub struct CreateWorkspaceRequest {
65 #[builder(setter(into))]
66 pub name: String,
67 #[builder(setter(into, strip_option), default)]
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub slug: Option<String>,
70 #[builder(setter(into, strip_option), default)]
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub description: Option<String>,
73 #[builder(setter(into, strip_option), default)]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub default_text_model: Option<String>,
76 #[builder(setter(into, strip_option), default)]
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub default_image_model: Option<String>,
79 #[builder(setter(into, strip_option), default)]
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub default_provider_sort: Option<String>,
82 #[builder(setter(strip_option), default)]
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub io_logging_api_key_ids: Option<Vec<u64>>,
85 #[builder(setter(strip_option), default)]
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub io_logging_sampling_rate: Option<f64>,
88 #[builder(setter(strip_option), default)]
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub is_data_discount_logging_enabled: Option<bool>,
91 #[builder(setter(strip_option), default)]
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub is_observability_broadcast_enabled: Option<bool>,
94 #[builder(setter(strip_option), default)]
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub is_observability_io_logging_enabled: Option<bool>,
97}
98
99impl CreateWorkspaceRequest {
100 pub fn builder() -> CreateWorkspaceRequestBuilder {
101 CreateWorkspaceRequestBuilder::default()
102 }
103}
104
105#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
106#[builder(build_fn(error = "OpenRouterError"))]
107#[non_exhaustive]
108pub struct UpdateWorkspaceRequest {
109 #[builder(setter(into, strip_option), default)]
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub name: Option<String>,
112 #[builder(setter(into, strip_option), default)]
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub slug: Option<String>,
115 #[builder(setter(into, strip_option), default)]
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub description: Option<String>,
118 #[builder(setter(into, strip_option), default)]
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub default_text_model: Option<String>,
121 #[builder(setter(into, strip_option), default)]
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub default_image_model: Option<String>,
124 #[builder(setter(into, strip_option), default)]
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub default_provider_sort: Option<String>,
127 #[builder(setter(strip_option), default)]
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub io_logging_api_key_ids: Option<Vec<u64>>,
130 #[builder(setter(strip_option), default)]
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub io_logging_sampling_rate: Option<f64>,
133 #[builder(setter(strip_option), default)]
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub is_data_discount_logging_enabled: Option<bool>,
136 #[builder(setter(strip_option), default)]
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub is_observability_broadcast_enabled: Option<bool>,
139 #[builder(setter(strip_option), default)]
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub is_observability_io_logging_enabled: Option<bool>,
142}
143
144#[derive(Debug, Clone, Copy)]
145pub struct UpdateWorkspaceRequestWithClearedIoLoggingApiKeyIds<'a> {
146 request: &'a UpdateWorkspaceRequest,
147}
148
149impl Serialize for UpdateWorkspaceRequestWithClearedIoLoggingApiKeyIds<'_> {
150 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151 where
152 S: Serializer,
153 {
154 let mut map = serializer.serialize_map(None)?;
155 if let Some(value) = &self.request.name {
156 map.serialize_entry("name", value)?;
157 }
158 if let Some(value) = &self.request.slug {
159 map.serialize_entry("slug", value)?;
160 }
161 if let Some(value) = &self.request.description {
162 map.serialize_entry("description", value)?;
163 }
164 if let Some(value) = &self.request.default_text_model {
165 map.serialize_entry("default_text_model", value)?;
166 }
167 if let Some(value) = &self.request.default_image_model {
168 map.serialize_entry("default_image_model", value)?;
169 }
170 if let Some(value) = &self.request.default_provider_sort {
171 map.serialize_entry("default_provider_sort", value)?;
172 }
173 map.serialize_entry("io_logging_api_key_ids", &Option::<Vec<u64>>::None)?;
174 if let Some(value) = &self.request.io_logging_sampling_rate {
175 map.serialize_entry("io_logging_sampling_rate", value)?;
176 }
177 if let Some(value) = &self.request.is_data_discount_logging_enabled {
178 map.serialize_entry("is_data_discount_logging_enabled", value)?;
179 }
180 if let Some(value) = &self.request.is_observability_broadcast_enabled {
181 map.serialize_entry("is_observability_broadcast_enabled", value)?;
182 }
183 if let Some(value) = &self.request.is_observability_io_logging_enabled {
184 map.serialize_entry("is_observability_io_logging_enabled", value)?;
185 }
186 map.end()
187 }
188}
189
190impl UpdateWorkspaceRequest {
191 pub fn builder() -> UpdateWorkspaceRequestBuilder {
192 UpdateWorkspaceRequestBuilder::default()
193 }
194
195 pub fn with_cleared_io_logging_api_key_ids(
196 &self,
197 ) -> UpdateWorkspaceRequestWithClearedIoLoggingApiKeyIds<'_> {
198 UpdateWorkspaceRequestWithClearedIoLoggingApiKeyIds { request: self }
199 }
200}
201
202#[derive(Serialize, Deserialize, Debug, Clone)]
203struct DeleteWorkspaceResponse {
204 deleted: bool,
205}
206
207#[derive(Serialize, Deserialize, Debug, Clone)]
208#[non_exhaustive]
209pub struct WorkspaceMember {
210 pub id: String,
211 pub workspace_id: String,
212 pub user_id: String,
213 pub role: String,
214 pub created_at: String,
215}
216
217#[derive(Serialize, Deserialize, Debug, Clone)]
218#[non_exhaustive]
219pub struct ListWorkspaceMembersResponse {
220 pub data: Vec<WorkspaceMember>,
221 pub total_count: f64,
222}
223
224#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
225#[builder(build_fn(error = "OpenRouterError"))]
226#[non_exhaustive]
227pub struct WorkspaceMembersRequest {
228 pub user_ids: Vec<String>,
229}
230
231impl WorkspaceMembersRequest {
232 pub fn builder() -> WorkspaceMembersRequestBuilder {
233 WorkspaceMembersRequestBuilder::default()
234 }
235}
236
237#[derive(Serialize, Deserialize, Debug, Clone)]
238#[non_exhaustive]
239pub struct WorkspaceMembersAddResponse {
240 pub added_count: f64,
241 pub data: Vec<WorkspaceMember>,
242}
243
244#[derive(Serialize, Deserialize, Debug, Clone)]
245#[non_exhaustive]
246pub struct WorkspaceMembersRemoveResponse {
247 pub removed_count: f64,
248}
249
250#[derive(Serialize, Deserialize, Debug, Clone)]
251#[non_exhaustive]
252pub struct WorkspaceBudget {
253 pub id: String,
254 pub workspace_id: String,
255 pub limit_usd: f64,
256 pub reset_interval: Option<String>,
257 pub created_at: String,
258 pub updated_at: String,
259 #[serde(flatten)]
260 pub extra: HashMap<String, Value>,
261}
262
263#[derive(Serialize, Deserialize, Debug, Clone)]
264#[non_exhaustive]
265pub struct ListWorkspaceBudgetsResponse {
266 pub data: Vec<WorkspaceBudget>,
267 #[serde(default)]
268 pub include_byok_in_budgets: bool,
269}
270
271#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
272#[builder(build_fn(error = "OpenRouterError"))]
273#[non_exhaustive]
274pub struct UpsertWorkspaceBudgetRequest {
275 pub limit_usd: f64,
276 #[builder(setter(strip_option), default)]
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub include_byok_in_budgets: Option<bool>,
279}
280
281impl UpsertWorkspaceBudgetRequest {
282 pub fn builder() -> UpsertWorkspaceBudgetRequestBuilder {
283 UpsertWorkspaceBudgetRequestBuilder::default()
284 }
285}
286
287#[derive(Serialize, Deserialize, Debug, Clone)]
288#[non_exhaustive]
289pub struct UpsertWorkspaceBudgetResponse {
290 pub data: WorkspaceBudget,
291 #[serde(default)]
292 pub include_byok_in_budgets: bool,
293}
294
295#[derive(Serialize, Deserialize, Debug, Clone)]
296struct DeleteWorkspaceBudgetResponse {
297 deleted: bool,
298}
299
300pub async fn list_workspaces(
301 base_url: &str,
302 management_key: &str,
303 pagination: Option<PaginationOptions>,
304) -> Result<WorkspaceListResponse, OpenRouterError> {
305 let http_client = crate::transport::new_client()?;
306 list_workspaces_with_client(&http_client, base_url, management_key, pagination).await
307}
308
309pub(crate) async fn list_workspaces_with_client(
310 http_client: &HttpClient,
311 base_url: &str,
312 management_key: &str,
313 pagination: Option<PaginationOptions>,
314) -> Result<WorkspaceListResponse, OpenRouterError> {
315 let url = format!("{base_url}/workspaces");
316 let query = ListWorkspacesQuery {
317 offset: pagination.and_then(|p| p.offset),
318 limit: pagination.and_then(|p| p.limit),
319 };
320 let req = transport_request::with_bearer_auth(
321 transport_request::get(http_client, &url),
322 management_key,
323 );
324 let response = if query.offset.is_none() && query.limit.is_none() {
325 req.send().await?
326 } else {
327 req.query(&query).send().await?
328 };
329
330 if response.status().is_success() {
331 transport_response::parse_json_response(response, "workspace list").await
332 } else {
333 transport_response::handle_error(response).await?;
334 unreachable!()
335 }
336}
337
338pub async fn create_workspace(
339 base_url: &str,
340 management_key: &str,
341 request: &CreateWorkspaceRequest,
342) -> Result<Workspace, OpenRouterError> {
343 let http_client = crate::transport::new_client()?;
344 create_workspace_with_client(&http_client, base_url, management_key, request).await
345}
346
347pub(crate) async fn create_workspace_with_client(
348 http_client: &HttpClient,
349 base_url: &str,
350 management_key: &str,
351 request: &CreateWorkspaceRequest,
352) -> Result<Workspace, OpenRouterError> {
353 let url = format!("{base_url}/workspaces");
354 let response = transport_request::with_bearer_auth(
355 transport_request::post(http_client, &url),
356 management_key,
357 )
358 .json(request)
359 .send()
360 .await?;
361
362 if response.status().is_success() {
363 let payload: ApiResponse<Workspace> =
364 transport_response::parse_json_response(response, "workspace creation").await?;
365 Ok(payload.data)
366 } else {
367 transport_response::handle_error(response).await?;
368 unreachable!()
369 }
370}
371
372pub async fn get_workspace(
373 base_url: &str,
374 management_key: &str,
375 id: &str,
376) -> Result<Workspace, OpenRouterError> {
377 let http_client = crate::transport::new_client()?;
378 get_workspace_with_client(&http_client, base_url, management_key, id).await
379}
380
381pub(crate) async fn get_workspace_with_client(
382 http_client: &HttpClient,
383 base_url: &str,
384 management_key: &str,
385 id: &str,
386) -> Result<Workspace, OpenRouterError> {
387 let url = format!("{base_url}/workspaces/{}", encode(id));
388 let response = transport_request::with_bearer_auth(
389 transport_request::get(http_client, &url),
390 management_key,
391 )
392 .send()
393 .await?;
394
395 if response.status().is_success() {
396 let payload: ApiResponse<Workspace> =
397 transport_response::parse_json_response(response, "workspace lookup").await?;
398 Ok(payload.data)
399 } else {
400 transport_response::handle_error(response).await?;
401 unreachable!()
402 }
403}
404
405pub async fn update_workspace(
406 base_url: &str,
407 management_key: &str,
408 id: &str,
409 request: &UpdateWorkspaceRequest,
410) -> Result<Workspace, OpenRouterError> {
411 let http_client = crate::transport::new_client()?;
412 update_workspace_with_client(&http_client, base_url, management_key, id, request).await
413}
414
415pub async fn update_workspace_with_cleared_io_logging_api_key_ids(
416 base_url: &str,
417 management_key: &str,
418 id: &str,
419 request: &UpdateWorkspaceRequest,
420) -> Result<Workspace, OpenRouterError> {
421 let http_client = crate::transport::new_client()?;
422 update_workspace_with_cleared_io_logging_api_key_ids_with_client(
423 &http_client,
424 base_url,
425 management_key,
426 id,
427 request,
428 )
429 .await
430}
431
432pub(crate) async fn update_workspace_with_client(
433 http_client: &HttpClient,
434 base_url: &str,
435 management_key: &str,
436 id: &str,
437 request: &UpdateWorkspaceRequest,
438) -> Result<Workspace, OpenRouterError> {
439 update_workspace_payload_with_client(http_client, base_url, management_key, id, request).await
440}
441
442pub(crate) async fn update_workspace_with_cleared_io_logging_api_key_ids_with_client(
443 http_client: &HttpClient,
444 base_url: &str,
445 management_key: &str,
446 id: &str,
447 request: &UpdateWorkspaceRequest,
448) -> Result<Workspace, OpenRouterError> {
449 let request = request.with_cleared_io_logging_api_key_ids();
450 update_workspace_payload_with_client(http_client, base_url, management_key, id, &request).await
451}
452
453async fn update_workspace_payload_with_client<T: Serialize + ?Sized>(
454 http_client: &HttpClient,
455 base_url: &str,
456 management_key: &str,
457 id: &str,
458 request: &T,
459) -> Result<Workspace, OpenRouterError> {
460 let url = format!("{base_url}/workspaces/{}", encode(id));
461 let response = transport_request::with_bearer_auth(
462 transport_request::patch(http_client, &url),
463 management_key,
464 )
465 .json(request)
466 .send()
467 .await?;
468
469 if response.status().is_success() {
470 let payload: ApiResponse<Workspace> =
471 transport_response::parse_json_response(response, "workspace update").await?;
472 Ok(payload.data)
473 } else {
474 transport_response::handle_error(response).await?;
475 unreachable!()
476 }
477}
478
479pub async fn delete_workspace(
480 base_url: &str,
481 management_key: &str,
482 id: &str,
483) -> Result<bool, OpenRouterError> {
484 let http_client = crate::transport::new_client()?;
485 delete_workspace_with_client(&http_client, base_url, management_key, id).await
486}
487
488pub(crate) async fn delete_workspace_with_client(
489 http_client: &HttpClient,
490 base_url: &str,
491 management_key: &str,
492 id: &str,
493) -> Result<bool, OpenRouterError> {
494 let url = format!("{base_url}/workspaces/{}", encode(id));
495 let response = transport_request::with_bearer_auth(
496 transport_request::delete(http_client, &url),
497 management_key,
498 )
499 .send()
500 .await?;
501
502 if response.status().is_success() {
503 let payload: DeleteWorkspaceResponse =
504 transport_response::parse_json_response(response, "workspace deletion").await?;
505 Ok(payload.deleted)
506 } else {
507 transport_response::handle_error(response).await?;
508 unreachable!()
509 }
510}
511
512pub async fn list_workspace_budgets(
513 base_url: &str,
514 management_key: &str,
515 id: &str,
516) -> Result<ListWorkspaceBudgetsResponse, OpenRouterError> {
517 let http_client = crate::transport::new_client()?;
518 list_workspace_budgets_with_client(&http_client, base_url, management_key, id).await
519}
520
521pub(crate) async fn list_workspace_budgets_with_client(
522 http_client: &HttpClient,
523 base_url: &str,
524 management_key: &str,
525 id: &str,
526) -> Result<ListWorkspaceBudgetsResponse, OpenRouterError> {
527 let url = format!("{base_url}/workspaces/{}/budgets", encode(id));
528 let response = transport_request::with_bearer_auth(
529 transport_request::get(http_client, &url),
530 management_key,
531 )
532 .send()
533 .await?;
534
535 if response.status().is_success() {
536 transport_response::parse_json_response(response, "workspace budget list").await
537 } else {
538 transport_response::handle_error(response).await?;
539 unreachable!()
540 }
541}
542
543pub async fn upsert_workspace_budget(
544 base_url: &str,
545 management_key: &str,
546 id: &str,
547 interval: &str,
548 request: &UpsertWorkspaceBudgetRequest,
549) -> Result<UpsertWorkspaceBudgetResponse, OpenRouterError> {
550 let http_client = crate::transport::new_client()?;
551 upsert_workspace_budget_with_client(
552 &http_client,
553 base_url,
554 management_key,
555 id,
556 interval,
557 request,
558 )
559 .await
560}
561
562pub(crate) async fn upsert_workspace_budget_with_client(
563 http_client: &HttpClient,
564 base_url: &str,
565 management_key: &str,
566 id: &str,
567 interval: &str,
568 request: &UpsertWorkspaceBudgetRequest,
569) -> Result<UpsertWorkspaceBudgetResponse, OpenRouterError> {
570 let url = format!(
571 "{base_url}/workspaces/{}/budgets/{}",
572 encode(id),
573 encode(interval)
574 );
575 let response = transport_request::with_bearer_auth(
576 transport_request::put(http_client, &url),
577 management_key,
578 )
579 .json(request)
580 .send()
581 .await?;
582
583 if response.status().is_success() {
584 transport_response::parse_json_response(response, "workspace budget upsert").await
585 } else {
586 transport_response::handle_error(response).await?;
587 unreachable!()
588 }
589}
590
591pub async fn delete_workspace_budget(
592 base_url: &str,
593 management_key: &str,
594 id: &str,
595 interval: &str,
596) -> Result<bool, OpenRouterError> {
597 let http_client = crate::transport::new_client()?;
598 delete_workspace_budget_with_client(&http_client, base_url, management_key, id, interval).await
599}
600
601pub(crate) async fn delete_workspace_budget_with_client(
602 http_client: &HttpClient,
603 base_url: &str,
604 management_key: &str,
605 id: &str,
606 interval: &str,
607) -> Result<bool, OpenRouterError> {
608 let url = format!(
609 "{base_url}/workspaces/{}/budgets/{}",
610 encode(id),
611 encode(interval)
612 );
613 let response = transport_request::with_bearer_auth(
614 transport_request::delete(http_client, &url),
615 management_key,
616 )
617 .send()
618 .await?;
619
620 if response.status().is_success() {
621 let payload: DeleteWorkspaceBudgetResponse =
622 transport_response::parse_json_response(response, "workspace budget deletion").await?;
623 Ok(payload.deleted)
624 } else {
625 transport_response::handle_error(response).await?;
626 unreachable!()
627 }
628}
629
630pub async fn list_workspace_members(
631 base_url: &str,
632 management_key: &str,
633 id: &str,
634 pagination: Option<PaginationOptions>,
635) -> Result<ListWorkspaceMembersResponse, OpenRouterError> {
636 let http_client = crate::transport::new_client()?;
637 list_workspace_members_with_client(&http_client, base_url, management_key, id, pagination).await
638}
639
640pub(crate) async fn list_workspace_members_with_client(
641 http_client: &HttpClient,
642 base_url: &str,
643 management_key: &str,
644 id: &str,
645 pagination: Option<PaginationOptions>,
646) -> Result<ListWorkspaceMembersResponse, OpenRouterError> {
647 let url = format!("{base_url}/workspaces/{}/members", encode(id));
648 let query = ListWorkspacesQuery {
649 offset: pagination.and_then(|p| p.offset),
650 limit: pagination.and_then(|p| p.limit),
651 };
652 let req = transport_request::with_bearer_auth(
653 transport_request::get(http_client, &url),
654 management_key,
655 );
656 let response = if query.offset.is_none() && query.limit.is_none() {
657 req.send().await?
658 } else {
659 req.query(&query).send().await?
660 };
661
662 if response.status().is_success() {
663 transport_response::parse_json_response(response, "workspace member list").await
664 } else {
665 transport_response::handle_error(response).await?;
666 unreachable!()
667 }
668}
669
670pub async fn add_workspace_members(
671 base_url: &str,
672 management_key: &str,
673 id: &str,
674 request: &WorkspaceMembersRequest,
675) -> Result<WorkspaceMembersAddResponse, OpenRouterError> {
676 let http_client = crate::transport::new_client()?;
677 add_workspace_members_with_client(&http_client, base_url, management_key, id, request).await
678}
679
680pub(crate) async fn add_workspace_members_with_client(
681 http_client: &HttpClient,
682 base_url: &str,
683 management_key: &str,
684 id: &str,
685 request: &WorkspaceMembersRequest,
686) -> Result<WorkspaceMembersAddResponse, OpenRouterError> {
687 let url = format!("{base_url}/workspaces/{}/members/add", encode(id));
688 let response = transport_request::with_bearer_auth(
689 transport_request::post(http_client, &url),
690 management_key,
691 )
692 .json(request)
693 .send()
694 .await?;
695
696 if response.status().is_success() {
697 transport_response::parse_json_response(response, "workspace member bulk add").await
698 } else {
699 transport_response::handle_error(response).await?;
700 unreachable!()
701 }
702}
703
704pub async fn remove_workspace_members(
705 base_url: &str,
706 management_key: &str,
707 id: &str,
708 request: &WorkspaceMembersRequest,
709) -> Result<WorkspaceMembersRemoveResponse, OpenRouterError> {
710 let http_client = crate::transport::new_client()?;
711 remove_workspace_members_with_client(&http_client, base_url, management_key, id, request).await
712}
713
714pub(crate) async fn remove_workspace_members_with_client(
715 http_client: &HttpClient,
716 base_url: &str,
717 management_key: &str,
718 id: &str,
719 request: &WorkspaceMembersRequest,
720) -> Result<WorkspaceMembersRemoveResponse, OpenRouterError> {
721 let url = format!("{base_url}/workspaces/{}/members/remove", encode(id));
722 let response = transport_request::with_bearer_auth(
723 transport_request::post(http_client, &url),
724 management_key,
725 )
726 .json(request)
727 .send()
728 .await?;
729
730 if response.status().is_success() {
731 transport_response::parse_json_response(response, "workspace member bulk removal").await
732 } else {
733 transport_response::handle_error(response).await?;
734 unreachable!()
735 }
736}