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