1use std::collections::HashMap;
2
3use derive_builder::Builder;
4use reqwest::Client as HttpClient;
5use serde::{Deserialize, Serialize};
6use urlencoding::encode;
7
8use crate::{
9 api::models::ModelReasoning,
10 error::OpenRouterError,
11 transport::{request as transport_request, response as transport_response},
12 types::ApiResponse,
13};
14
15#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
17#[non_exhaustive]
18#[serde(untagged)]
19pub enum BigNumber {
20 String(String),
21 Number(f64),
22}
23
24#[derive(Serialize, Deserialize, Debug, Clone)]
26#[non_exhaustive]
27pub struct Provider {
28 pub name: String,
29 pub slug: String,
30 pub privacy_policy_url: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub terms_of_service_url: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub status_page_url: Option<String>,
35 #[serde(flatten)]
36 pub extra: HashMap<String, serde_json::Value>,
37}
38
39#[derive(Serialize, Deserialize, Debug, Clone)]
41#[non_exhaustive]
42pub struct PublicPricing {
43 pub prompt: BigNumber,
44 pub completion: BigNumber,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub request: Option<BigNumber>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub image: Option<BigNumber>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub image_token: Option<BigNumber>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub image_output: Option<BigNumber>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub audio: Option<BigNumber>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub audio_output: Option<BigNumber>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub input_audio_cache: Option<BigNumber>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub web_search: Option<BigNumber>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub internal_reasoning: Option<BigNumber>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub input_cache_read: Option<BigNumber>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub input_cache_write: Option<BigNumber>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub discount: Option<f64>,
69}
70
71#[derive(Serialize, Deserialize, Debug, Clone)]
73#[non_exhaustive]
74pub struct ModelArchitecture {
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub tokenizer: Option<String>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub instruct_type: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub modality: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub input_modalities: Option<Vec<String>>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub output_modalities: Option<Vec<String>>,
85}
86
87#[derive(Serialize, Deserialize, Debug, Clone)]
89#[non_exhaustive]
90pub struct TopProviderInfo {
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub context_length: Option<f64>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub max_completion_tokens: Option<f64>,
95 pub is_moderated: bool,
96}
97
98#[derive(Serialize, Deserialize, Debug, Clone)]
100#[non_exhaustive]
101pub struct PerRequestLimits {
102 pub prompt_tokens: f64,
103 pub completion_tokens: f64,
104}
105
106#[derive(Serialize, Deserialize, Debug, Clone)]
108#[non_exhaustive]
109pub struct UserModel {
110 pub id: String,
111 pub canonical_slug: String,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub hugging_face_id: Option<String>,
114 pub name: String,
115 pub created: f64,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub description: Option<String>,
118 pub pricing: PublicPricing,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub context_length: Option<f64>,
121 pub architecture: ModelArchitecture,
122 pub top_provider: TopProviderInfo,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub per_request_limits: Option<PerRequestLimits>,
125 #[serde(default)]
126 pub supported_parameters: Vec<String>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub supported_voices: Option<Vec<String>>,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub default_parameters: Option<serde_json::Value>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub expiration_date: Option<String>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub reasoning: Option<ModelReasoning>,
135 #[serde(flatten)]
136 pub extra: HashMap<String, serde_json::Value>,
137}
138
139#[derive(Serialize, Deserialize, Debug, Clone)]
141#[non_exhaustive]
142pub struct ModelsCountData {
143 pub count: u64,
144}
145
146#[derive(Serialize, Deserialize, Debug, Clone)]
148#[non_exhaustive]
149pub struct PercentileStats {
150 pub p50: f64,
151 pub p75: f64,
152 pub p90: f64,
153 pub p99: f64,
154}
155
156#[derive(Serialize, Deserialize, Debug, Clone)]
158#[non_exhaustive]
159pub struct PublicEndpoint {
160 pub name: String,
161 pub model_id: String,
162 pub model_name: String,
163 pub context_length: f64,
164 pub pricing: PublicPricing,
165 pub provider_name: String,
166 pub tag: String,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub quantization: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub max_completion_tokens: Option<f64>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub max_prompt_tokens: Option<f64>,
173 #[serde(default)]
174 pub supported_parameters: Vec<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub status: Option<i32>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub uptime_last_30m: Option<f64>,
179 pub supports_implicit_caching: bool,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 pub latency_last_30m: Option<PercentileStats>,
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub throughput_last_30m: Option<PercentileStats>,
184 #[serde(flatten)]
185 pub extra: HashMap<String, serde_json::Value>,
186}
187
188#[derive(Serialize, Deserialize, Debug, Clone)]
190#[non_exhaustive]
191pub struct ActivityItem {
192 pub date: String,
193 pub model: String,
194 pub model_permaslug: String,
195 pub endpoint_id: String,
196 pub provider_name: String,
197 pub usage: f64,
198 pub byok_usage_inference: f64,
199 pub requests: f64,
200 pub prompt_tokens: f64,
201 pub completion_tokens: f64,
202 pub reasoning_tokens: f64,
203 #[serde(flatten)]
204 pub extra: HashMap<String, serde_json::Value>,
205}
206
207#[derive(Serialize, Deserialize, Debug, Clone)]
209#[non_exhaustive]
210pub struct RankingsDailyItem {
211 pub date: String,
212 pub model_permaslug: String,
213 pub total_tokens: String,
214 #[serde(flatten)]
215 pub extra: HashMap<String, serde_json::Value>,
216}
217
218#[derive(Serialize, Deserialize, Debug, Clone)]
220#[non_exhaustive]
221pub struct RankingsDailyMeta {
222 pub as_of: String,
223 pub version: String,
224 pub start_date: String,
225 pub end_date: String,
226 #[serde(flatten)]
227 pub extra: HashMap<String, serde_json::Value>,
228}
229
230#[derive(Serialize, Deserialize, Debug, Clone)]
232#[non_exhaustive]
233pub struct RankingsDailyResponse {
234 pub data: Vec<RankingsDailyItem>,
235 pub meta: RankingsDailyMeta,
236}
237
238#[derive(Serialize, Deserialize, Debug, Clone, Default, Builder)]
240#[builder(build_fn(error = "OpenRouterError"))]
241#[non_exhaustive]
242pub struct AppRankingsParams {
243 #[builder(setter(into, strip_option), default)]
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub category: Option<String>,
246 #[builder(setter(into, strip_option), default)]
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub subcategory: Option<String>,
249 #[builder(setter(into, strip_option), default)]
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub sort: Option<String>,
252 #[builder(setter(into, strip_option), default)]
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub start_date: Option<String>,
255 #[builder(setter(into, strip_option), default)]
256 #[serde(skip_serializing_if = "Option::is_none")]
257 pub end_date: Option<String>,
258 #[builder(setter(strip_option), default)]
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub limit: Option<u32>,
261 #[builder(setter(strip_option), default)]
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub offset: Option<u32>,
264}
265
266impl AppRankingsParams {
267 pub fn builder() -> AppRankingsParamsBuilder {
268 AppRankingsParamsBuilder::default()
269 }
270
271 fn is_empty(&self) -> bool {
272 self.category.is_none()
273 && self.subcategory.is_none()
274 && self.sort.is_none()
275 && self.start_date.is_none()
276 && self.end_date.is_none()
277 && self.limit.is_none()
278 && self.offset.is_none()
279 }
280}
281
282#[derive(Serialize, Deserialize, Debug, Clone)]
284#[non_exhaustive]
285pub struct AppRankingsItem {
286 pub rank: u64,
287 pub app_id: u64,
288 pub app_name: String,
289 pub total_tokens: String,
290 pub total_requests: u64,
291 #[serde(flatten)]
292 pub extra: HashMap<String, serde_json::Value>,
293}
294
295#[derive(Serialize, Deserialize, Debug, Clone)]
297#[non_exhaustive]
298pub struct AppRankingsResponse {
299 pub data: Vec<AppRankingsItem>,
300 pub meta: RankingsDailyMeta,
301}
302
303#[derive(Serialize, Deserialize, Debug, Clone)]
305#[non_exhaustive]
306pub struct TaskClassificationModel {
307 pub id: String,
308 pub tag_usage_share: f64,
309 pub tag_token_share: f64,
310 #[serde(flatten)]
311 pub extra: HashMap<String, serde_json::Value>,
312}
313
314#[derive(Serialize, Deserialize, Debug, Clone)]
316#[non_exhaustive]
317pub struct TaskClassificationItem {
318 pub tag: String,
319 pub display_name: String,
320 pub macro_category: String,
321 pub usage_share: f64,
322 pub token_share: f64,
323 pub category_usage_share: f64,
324 pub category_token_share: f64,
325 pub models: Vec<TaskClassificationModel>,
326 #[serde(flatten)]
327 pub extra: HashMap<String, serde_json::Value>,
328}
329
330#[derive(Serialize, Deserialize, Debug, Clone)]
332#[non_exhaustive]
333pub struct TaskClassificationMacroCategory {
334 pub key: String,
335 pub label: String,
336 pub usage_share: f64,
337 pub token_share: f64,
338 #[serde(flatten)]
339 pub extra: HashMap<String, serde_json::Value>,
340}
341
342#[derive(Serialize, Deserialize, Debug, Clone)]
344#[non_exhaustive]
345pub struct TaskClassificationsData {
346 pub window_days: u64,
347 pub as_of: String,
348 pub classifications: Vec<TaskClassificationItem>,
349 pub macro_categories: Vec<TaskClassificationMacroCategory>,
350 #[serde(flatten)]
351 pub extra: HashMap<String, serde_json::Value>,
352}
353
354#[derive(Serialize, Deserialize, Debug, Clone)]
356#[non_exhaustive]
357pub struct TaskClassificationsResponse {
358 pub data: TaskClassificationsData,
359}
360
361#[derive(Serialize, Deserialize, Debug, Clone)]
363#[non_exhaustive]
364pub struct BenchmarkPricing {
365 pub prompt: String,
366 pub completion: String,
367 #[serde(flatten)]
368 pub extra: HashMap<String, serde_json::Value>,
369}
370
371#[derive(Serialize, Deserialize, Debug, Clone)]
373#[non_exhaustive]
374pub struct BenchmarksAAItem {
375 pub model_permaslug: String,
376 pub aa_name: String,
377 pub intelligence_index: Option<f64>,
378 pub coding_index: Option<f64>,
379 pub agentic_index: Option<f64>,
380 pub pricing: Option<BenchmarkPricing>,
381 #[serde(flatten)]
382 pub extra: HashMap<String, serde_json::Value>,
383}
384
385#[derive(Serialize, Deserialize, Debug, Clone)]
387#[non_exhaustive]
388pub struct BenchmarksAAMeta {
389 pub as_of: String,
390 pub version: String,
391 pub source: String,
392 pub source_url: String,
393 pub citation: String,
394 pub model_count: u64,
395 #[serde(flatten)]
396 pub extra: HashMap<String, serde_json::Value>,
397}
398
399#[derive(Serialize, Deserialize, Debug, Clone)]
401#[non_exhaustive]
402pub struct BenchmarksAAResponse {
403 pub data: Vec<BenchmarksAAItem>,
404 pub meta: BenchmarksAAMeta,
405}
406
407#[derive(Serialize, Deserialize, Debug, Clone)]
409#[non_exhaustive]
410pub struct DesignArenaTournamentStats {
411 pub first_place: Option<u64>,
412 pub second_place: Option<u64>,
413 pub third_place: Option<u64>,
414 pub fourth_place: Option<u64>,
415 pub total: Option<u64>,
416 #[serde(flatten)]
417 pub extra: HashMap<String, serde_json::Value>,
418}
419
420#[derive(Serialize, Deserialize, Debug, Clone)]
422#[non_exhaustive]
423pub struct BenchmarksDAItem {
424 pub model_permaslug: String,
425 pub display_name: String,
426 pub arena: String,
427 pub category: String,
428 pub elo: f64,
429 pub win_rate: f64,
430 pub avg_generation_time_ms: Option<f64>,
431 pub tournament_stats: DesignArenaTournamentStats,
432 pub pricing: Option<BenchmarkPricing>,
433 #[serde(flatten)]
434 pub extra: HashMap<String, serde_json::Value>,
435}
436
437#[derive(Serialize, Deserialize, Debug, Clone)]
439#[non_exhaustive]
440pub struct DesignArenaEloBounds {
441 pub min: f64,
442 pub max: f64,
443 #[serde(flatten)]
444 pub extra: HashMap<String, serde_json::Value>,
445}
446
447#[derive(Serialize, Deserialize, Debug, Clone)]
449#[non_exhaustive]
450pub struct BenchmarksDAMeta {
451 pub as_of: String,
452 pub version: String,
453 pub source: String,
454 pub source_url: String,
455 pub citation: String,
456 pub model_count: u64,
457 pub arena: String,
458 pub category: Option<String>,
459 pub elo_bounds: DesignArenaEloBounds,
460 #[serde(flatten)]
461 pub extra: HashMap<String, serde_json::Value>,
462}
463
464#[derive(Serialize, Deserialize, Debug, Clone)]
466#[non_exhaustive]
467pub struct BenchmarksDAResponse {
468 pub data: Vec<BenchmarksDAItem>,
469 pub meta: BenchmarksDAMeta,
470}
471
472#[derive(Serialize, Deserialize, Debug, Clone, Default, Builder)]
474#[builder(build_fn(error = "OpenRouterError"))]
475#[non_exhaustive]
476pub struct UnifiedBenchmarksParams {
477 #[builder(setter(into, strip_option), default)]
478 #[serde(skip_serializing_if = "Option::is_none")]
479 pub source: Option<String>,
480 #[builder(setter(into, strip_option), default)]
481 #[serde(skip_serializing_if = "Option::is_none")]
482 pub task_type: Option<String>,
483 #[builder(setter(into, strip_option), default)]
484 #[serde(skip_serializing_if = "Option::is_none")]
485 pub arena: Option<String>,
486 #[builder(setter(into, strip_option), default)]
487 #[serde(skip_serializing_if = "Option::is_none")]
488 pub category: Option<String>,
489 #[builder(setter(strip_option), default)]
490 #[serde(skip_serializing_if = "Option::is_none")]
491 pub max_results: Option<u32>,
492}
493
494impl UnifiedBenchmarksParams {
495 pub fn builder() -> UnifiedBenchmarksParamsBuilder {
496 UnifiedBenchmarksParamsBuilder::default()
497 }
498
499 pub fn artificial_analysis() -> Self {
500 Self {
501 source: Some("artificial-analysis".to_string()),
502 task_type: None,
503 arena: None,
504 category: None,
505 max_results: None,
506 }
507 }
508
509 pub fn design_arena() -> Self {
510 Self {
511 source: Some("design-arena".to_string()),
512 task_type: None,
513 arena: None,
514 category: None,
515 max_results: None,
516 }
517 }
518}
519
520#[derive(Serialize, Deserialize, Debug, Clone)]
522#[non_exhaustive]
523pub struct UnifiedBenchmarksAAItem {
524 pub source: String,
525 pub model_permaslug: String,
526 pub display_name: String,
527 pub intelligence_index: Option<f64>,
528 pub coding_index: Option<f64>,
529 pub agentic_index: Option<f64>,
530 pub pricing: Option<BenchmarkPricing>,
531 #[serde(flatten)]
532 pub extra: HashMap<String, serde_json::Value>,
533}
534
535#[derive(Serialize, Deserialize, Debug, Clone)]
537#[non_exhaustive]
538pub struct UnifiedBenchmarksDAItem {
539 pub source: String,
540 pub model_permaslug: String,
541 pub display_name: String,
542 pub arena: String,
543 pub category: String,
544 pub elo: f64,
545 pub win_rate: f64,
546 pub avg_generation_time_ms: Option<f64>,
547 pub tournament_stats: DesignArenaTournamentStats,
548 pub pricing: Option<BenchmarkPricing>,
549 #[serde(flatten)]
550 pub extra: HashMap<String, serde_json::Value>,
551}
552
553#[derive(Serialize, Deserialize, Debug, Clone)]
555#[serde(untagged)]
556#[non_exhaustive]
557pub enum UnifiedBenchmarkItem {
558 DesignArena(UnifiedBenchmarksDAItem),
559 ArtificialAnalysis(UnifiedBenchmarksAAItem),
560 Other(HashMap<String, serde_json::Value>),
561}
562
563#[derive(Serialize, Deserialize, Debug, Clone)]
565#[non_exhaustive]
566pub struct UnifiedBenchmarksMeta {
567 pub as_of: String,
568 pub version: String,
569 pub source: Option<String>,
570 pub source_url: Option<String>,
571 pub citation: Option<String>,
572 pub model_count: u64,
573 pub task_type: Option<String>,
574 #[serde(flatten)]
575 pub extra: HashMap<String, serde_json::Value>,
576}
577
578#[derive(Serialize, Deserialize, Debug, Clone)]
580#[non_exhaustive]
581pub struct UnifiedBenchmarksResponse {
582 pub data: Vec<UnifiedBenchmarkItem>,
583 pub meta: UnifiedBenchmarksMeta,
584}
585
586pub async fn list_providers(
588 base_url: &str,
589 api_key: &str,
590) -> Result<Vec<Provider>, OpenRouterError> {
591 let http_client = crate::transport::new_client()?;
592 list_providers_with_client(&http_client, base_url, api_key).await
593}
594
595pub(crate) async fn list_providers_with_client(
596 http_client: &HttpClient,
597 base_url: &str,
598 api_key: &str,
599) -> Result<Vec<Provider>, OpenRouterError> {
600 let url = format!("{base_url}/providers");
601 let response =
602 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key)
603 .send()
604 .await?;
605
606 if response.status().is_success() {
607 let parsed: ApiResponse<Vec<Provider>> =
608 transport_response::parse_json_response(response, "provider list").await?;
609 Ok(parsed.data)
610 } else {
611 transport_response::handle_error(response).await?;
612 unreachable!()
613 }
614}
615
616pub async fn list_models_for_user(
618 base_url: &str,
619 api_key: &str,
620) -> Result<Vec<UserModel>, OpenRouterError> {
621 let http_client = crate::transport::new_client()?;
622 list_models_for_user_with_client(&http_client, base_url, api_key).await
623}
624
625pub(crate) async fn list_models_for_user_with_client(
626 http_client: &HttpClient,
627 base_url: &str,
628 api_key: &str,
629) -> Result<Vec<UserModel>, OpenRouterError> {
630 let url = format!("{base_url}/models/user");
631 let response =
632 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key)
633 .send()
634 .await?;
635
636 if response.status().is_success() {
637 let parsed: ApiResponse<Vec<UserModel>> =
638 transport_response::parse_json_response(response, "user model list").await?;
639 Ok(parsed.data)
640 } else {
641 transport_response::handle_error(response).await?;
642 unreachable!()
643 }
644}
645
646pub async fn count_models(
648 base_url: &str,
649 api_key: &str,
650) -> Result<ModelsCountData, OpenRouterError> {
651 let http_client = crate::transport::new_client()?;
652 count_models_with_client(&http_client, base_url, api_key).await
653}
654
655pub(crate) async fn count_models_with_client(
656 http_client: &HttpClient,
657 base_url: &str,
658 api_key: &str,
659) -> Result<ModelsCountData, OpenRouterError> {
660 let url = format!("{base_url}/models/count");
661 let response =
662 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key)
663 .send()
664 .await?;
665
666 if response.status().is_success() {
667 let parsed: ApiResponse<ModelsCountData> =
668 transport_response::parse_json_response(response, "model count").await?;
669 Ok(parsed.data)
670 } else {
671 transport_response::handle_error(response).await?;
672 unreachable!()
673 }
674}
675
676pub async fn get_rankings_daily(
678 base_url: &str,
679 api_key: &str,
680 start_date: Option<&str>,
681 end_date: Option<&str>,
682) -> Result<RankingsDailyResponse, OpenRouterError> {
683 let http_client = crate::transport::new_client()?;
684 get_rankings_daily_with_client(&http_client, base_url, api_key, start_date, end_date).await
685}
686
687pub(crate) async fn get_rankings_daily_with_client(
688 http_client: &HttpClient,
689 base_url: &str,
690 api_key: &str,
691 start_date: Option<&str>,
692 end_date: Option<&str>,
693) -> Result<RankingsDailyResponse, OpenRouterError> {
694 #[derive(Serialize)]
695 struct RankingsDailyQuery<'a> {
696 #[serde(skip_serializing_if = "Option::is_none")]
697 start_date: Option<&'a str>,
698 #[serde(skip_serializing_if = "Option::is_none")]
699 end_date: Option<&'a str>,
700 }
701
702 let url = format!("{base_url}/datasets/rankings-daily");
703 let query = RankingsDailyQuery {
704 start_date,
705 end_date,
706 };
707 let req =
708 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key);
709 let response = if query.start_date.is_none() && query.end_date.is_none() {
710 req.send().await?
711 } else {
712 req.query(&query).send().await?
713 };
714
715 if response.status().is_success() {
716 transport_response::parse_json_response(response, "rankings daily").await
717 } else {
718 transport_response::handle_error(response).await?;
719 unreachable!()
720 }
721}
722
723pub async fn get_app_rankings(
725 base_url: &str,
726 api_key: &str,
727 params: Option<&AppRankingsParams>,
728) -> Result<AppRankingsResponse, OpenRouterError> {
729 let http_client = crate::transport::new_client()?;
730 get_app_rankings_with_client(&http_client, base_url, api_key, params).await
731}
732
733pub(crate) async fn get_app_rankings_with_client(
734 http_client: &HttpClient,
735 base_url: &str,
736 api_key: &str,
737 params: Option<&AppRankingsParams>,
738) -> Result<AppRankingsResponse, OpenRouterError> {
739 let url = format!("{base_url}/datasets/app-rankings");
740 let req =
741 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key);
742 let response = match params {
743 Some(params) if !params.is_empty() => req.query(params).send().await?,
744 _ => req.send().await?,
745 };
746
747 if response.status().is_success() {
748 transport_response::parse_json_response(response, "app rankings").await
749 } else {
750 transport_response::handle_error(response).await?;
751 unreachable!()
752 }
753}
754
755pub async fn get_task_classifications(
757 base_url: &str,
758 api_key: &str,
759 window: Option<&str>,
760) -> Result<TaskClassificationsResponse, OpenRouterError> {
761 let http_client = crate::transport::new_client()?;
762 get_task_classifications_with_client(&http_client, base_url, api_key, window).await
763}
764
765pub(crate) async fn get_task_classifications_with_client(
766 http_client: &HttpClient,
767 base_url: &str,
768 api_key: &str,
769 window: Option<&str>,
770) -> Result<TaskClassificationsResponse, OpenRouterError> {
771 let url = format!("{base_url}/classifications/task");
772 let req =
773 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key);
774 let response = match window {
775 Some(window) => req.query(&[("window", window)]).send().await?,
776 None => req.send().await?,
777 };
778
779 if response.status().is_success() {
780 transport_response::parse_json_response(response, "task classifications").await
781 } else {
782 transport_response::handle_error(response).await?;
783 unreachable!()
784 }
785}
786
787pub async fn get_benchmarks(
789 base_url: &str,
790 api_key: &str,
791 params: &UnifiedBenchmarksParams,
792) -> Result<UnifiedBenchmarksResponse, OpenRouterError> {
793 let http_client = crate::transport::new_client()?;
794 get_benchmarks_with_client(&http_client, base_url, api_key, params).await
795}
796
797pub(crate) async fn get_benchmarks_with_client(
798 http_client: &HttpClient,
799 base_url: &str,
800 api_key: &str,
801 params: &UnifiedBenchmarksParams,
802) -> Result<UnifiedBenchmarksResponse, OpenRouterError> {
803 let url = format!("{base_url}/benchmarks");
804 let response =
805 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key)
806 .query(params)
807 .send()
808 .await?;
809
810 if response.status().is_success() {
811 transport_response::parse_json_response(response, "benchmarks").await
812 } else {
813 transport_response::handle_error(response).await?;
814 unreachable!()
815 }
816}
817
818#[derive(Serialize)]
819struct BenchmarkMaxResultsQuery {
820 #[serde(skip_serializing_if = "Option::is_none")]
821 max_results: Option<u32>,
822}
823
824#[deprecated(note = "use get_benchmarks with source `artificial-analysis`")]
826pub async fn get_benchmarks_artificial_analysis(
827 base_url: &str,
828 api_key: &str,
829 max_results: Option<u32>,
830) -> Result<BenchmarksAAResponse, OpenRouterError> {
831 let http_client = crate::transport::new_client()?;
832 get_benchmarks_artificial_analysis_with_client(&http_client, base_url, api_key, max_results)
833 .await
834}
835
836pub(crate) async fn get_benchmarks_artificial_analysis_with_client(
837 http_client: &HttpClient,
838 base_url: &str,
839 api_key: &str,
840 max_results: Option<u32>,
841) -> Result<BenchmarksAAResponse, OpenRouterError> {
842 let url = format!("{base_url}/datasets/benchmarks/artificial-analysis");
843 let query = BenchmarkMaxResultsQuery { max_results };
844 let req =
845 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key);
846 let response = if query.max_results.is_none() {
847 req.send().await?
848 } else {
849 req.query(&query).send().await?
850 };
851
852 if response.status().is_success() {
853 transport_response::parse_json_response(response, "Artificial Analysis benchmarks").await
854 } else {
855 transport_response::handle_error(response).await?;
856 unreachable!()
857 }
858}
859
860#[derive(Serialize)]
861struct DesignArenaQuery<'a> {
862 #[serde(skip_serializing_if = "Option::is_none")]
863 arena: Option<&'a str>,
864 #[serde(skip_serializing_if = "Option::is_none")]
865 category: Option<&'a str>,
866 #[serde(skip_serializing_if = "Option::is_none")]
867 max_results: Option<u32>,
868}
869
870#[deprecated(note = "use get_benchmarks with source `design-arena`")]
872pub async fn get_benchmarks_design_arena(
873 base_url: &str,
874 api_key: &str,
875 arena: Option<&str>,
876 category: Option<&str>,
877 max_results: Option<u32>,
878) -> Result<BenchmarksDAResponse, OpenRouterError> {
879 let http_client = crate::transport::new_client()?;
880 get_benchmarks_design_arena_with_client(
881 &http_client,
882 base_url,
883 api_key,
884 arena,
885 category,
886 max_results,
887 )
888 .await
889}
890
891pub(crate) async fn get_benchmarks_design_arena_with_client(
892 http_client: &HttpClient,
893 base_url: &str,
894 api_key: &str,
895 arena: Option<&str>,
896 category: Option<&str>,
897 max_results: Option<u32>,
898) -> Result<BenchmarksDAResponse, OpenRouterError> {
899 let url = format!("{base_url}/datasets/benchmarks/design-arena");
900 let query = DesignArenaQuery {
901 arena,
902 category,
903 max_results,
904 };
905 let req =
906 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key);
907 let response =
908 if query.arena.is_none() && query.category.is_none() && query.max_results.is_none() {
909 req.send().await?
910 } else {
911 req.query(&query).send().await?
912 };
913
914 if response.status().is_success() {
915 transport_response::parse_json_response(response, "Design Arena benchmarks").await
916 } else {
917 transport_response::handle_error(response).await?;
918 unreachable!()
919 }
920}
921
922pub async fn list_zdr_endpoints(
924 base_url: &str,
925 api_key: &str,
926) -> Result<Vec<PublicEndpoint>, OpenRouterError> {
927 let http_client = crate::transport::new_client()?;
928 list_zdr_endpoints_with_client(&http_client, base_url, api_key).await
929}
930
931pub(crate) async fn list_zdr_endpoints_with_client(
932 http_client: &HttpClient,
933 base_url: &str,
934 api_key: &str,
935) -> Result<Vec<PublicEndpoint>, OpenRouterError> {
936 let url = format!("{base_url}/endpoints/zdr");
937 let response =
938 transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key)
939 .send()
940 .await?;
941
942 if response.status().is_success() {
943 let parsed: ApiResponse<Vec<PublicEndpoint>> =
944 transport_response::parse_json_response(response, "ZDR endpoint list").await?;
945 Ok(parsed.data)
946 } else {
947 transport_response::handle_error(response).await?;
948 unreachable!()
949 }
950}
951
952pub async fn get_activity(
956 base_url: &str,
957 management_key: &str,
958 date: Option<&str>,
959) -> Result<Vec<ActivityItem>, OpenRouterError> {
960 let http_client = crate::transport::new_client()?;
961 get_activity_with_client(&http_client, base_url, management_key, date).await
962}
963
964pub(crate) async fn get_activity_with_client(
965 http_client: &HttpClient,
966 base_url: &str,
967 management_key: &str,
968 date: Option<&str>,
969) -> Result<Vec<ActivityItem>, OpenRouterError> {
970 let url = if let Some(date) = date {
971 format!("{base_url}/activity?date={}", encode(date))
972 } else {
973 format!("{base_url}/activity")
974 };
975
976 let response = transport_request::with_bearer_auth(
977 transport_request::get(http_client, &url),
978 management_key,
979 )
980 .send()
981 .await?;
982
983 if response.status().is_success() {
984 let parsed: ApiResponse<Vec<ActivityItem>> =
985 transport_response::parse_json_response(response, "activity list").await?;
986 Ok(parsed.data)
987 } else {
988 transport_response::handle_error(response).await?;
989 unreachable!()
990 }
991}