openmeter/
lib.rs

1//! A simple Rust library that demonstrates how to call the Meter endpoints of OpenMeter.
2//!
3//! Add the following to your `Cargo.toml`:
4//!
5//! ```toml
6//! [dependencies]
7//! reqwest = "0.11"
8//! serde = { version = "1.0", features = ["derive"] }
9//! serde_json = "1.0"
10//! tokio = { version = "1", features = ["macros"] }
11//! ```
12//!
13//! # Example
14//! ```ignore
15//! use openmeter::{MeterClient, CreateMeterRequest};
16//! #[tokio::main]
17//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
18//!     let client = MeterClient::new("https://openmeter.cloud".to_string(), "YOUR_SECRET_TOKEN".to_string());
19//!
20//!     // Create a meter
21//!     let create_req = CreateMeterRequest {
22//!         slug: "tokens_total".to_string(),
23//!         name: Some("Tokens Total".to_string()),
24//!         description: Some("AI Token Usage".to_string()),
25//!         aggregation: "SUM".to_string(),
26//!         event_type: "prompt".to_string(),
27//!         event_from: None,
28//!         value_property: Some("$.tokens".to_string()),
29//!         group_by: Some(serde_json::json!({
30//!             "model": "$.model",
31//!             "type": "$.type"
32//!         })),
33//!         metadata: None,
34//!     };
35//!     let meter = client.create_meter(&create_req).await?;
36//!     println!("Created meter: {:?}", meter);
37//!
38//!     Ok(())
39//! }
40//! ```
41
42#![allow(dead_code)]
43
44use reqwest::{Client, Error};
45use serde::{Deserialize, Serialize};
46use serde_json::Value;
47
48/// A convenient client to interact with the Meter esndpoints.
49#[derive(Debug, Clone)]
50pub struct MeterClient {
51    base_url: String,
52    token: String,
53    http_client: Client,
54}
55
56impl MeterClient {
57    /// Create a new client with the given base URL and an auth token.
58    pub fn new(base_url: String, token: String) -> Self {
59        MeterClient {
60            base_url,
61            token,
62            http_client: Client::new(),
63        }
64    }
65
66    fn get_auth_header(&self) -> String {
67        format!("Bearer {}", self.token)
68    }
69
70    /// Create a new meter.
71    ///
72    /// POST /api/v1/meters
73    pub async fn create_meter(&self, body: &CreateMeterRequest) -> Result<Meter, Error> {
74        let url = format!("{}/api/v1/meters", self.base_url);
75        let resp = self
76            .http_client
77            .post(url)
78            .header("Authorization", self.get_auth_header())
79            .header("Content-Type", "application/json")
80            .json(&body)
81            .send()
82            .await?
83            .error_for_status()?;
84
85        Ok(resp.json::<Meter>().await?)
86    }
87
88    /// Delete a meter by ID or slug.
89    ///
90    /// DELETE /api/v1/meters/{meterIdOrSlug}
91    pub async fn delete_meter(&self, meter_id_or_slug: &str) -> Result<(), Error> {
92        let url = format!("{}/api/v1/meters/{}", self.base_url, meter_id_or_slug);
93        self.http_client
94            .delete(url)
95            .header("Authorization", self.get_auth_header())
96            .send()
97            .await?
98            .error_for_status()?;
99
100        Ok(())
101    }
102
103    /// Get a meter by ID or slug.
104    ///
105    /// GET /api/v1/meters/{meterIdOrSlug}
106    pub async fn get_meter(&self, meter_id_or_slug: &str) -> Result<Meter, Error> {
107        let url = format!("{}/api/v1/meters/{}", self.base_url, meter_id_or_slug);
108        let resp = self
109            .http_client
110            .get(url)
111            .header("Authorization", self.get_auth_header())
112            .send()
113            .await?
114            .error_for_status()?;
115
116        Ok(resp.json::<Meter>().await?)
117    }
118
119    /// List all meters.
120    ///
121    /// GET /api/v1/meters
122    pub async fn list_meters(&self) -> Result<Vec<Meter>, Error> {
123        let url = format!("{}/api/v1/meters", self.base_url);
124        let resp = self
125            .http_client
126            .get(url)
127            .header("Authorization", self.get_auth_header())
128            .send()
129            .await?
130            .error_for_status()?;
131
132        Ok(resp.json::<Vec<Meter>>().await?)
133    }
134
135    /// List subjects for a meter.
136    ///
137    /// GET /api/v1/meters/{meterIdOrSlug}/subjects
138    pub async fn list_meter_subjects(&self, meter_id_or_slug: &str) -> Result<Vec<String>, Error> {
139        let url = format!(
140            "{}/api/v1/meters/{}/subjects",
141            self.base_url, meter_id_or_slug
142        );
143        let resp = self
144            .http_client
145            .get(url)
146            .header("Authorization", self.get_auth_header())
147            .send()
148            .await?
149            .error_for_status()?;
150
151        Ok(resp.json::<Vec<String>>().await?)
152    }
153
154    /// Query meter usage.
155    ///
156    /// GET /api/v1/meters/{meterIdOrSlug}/query
157    ///
158    /// Supports optional query parameters:
159    /// - from
160    /// - to
161    /// - windowSize
162    /// - windowTimeZone
163    /// - subject (repeated)
164    /// - filterGroupBy[...] (etc.)
165    pub async fn query_meter(
166        &self,
167        meter_id_or_slug: &str,
168        params: &QueryParams,
169    ) -> Result<QueryResponse, Error> {
170        let url = format!("{}/api/v1/meters/{}/query", self.base_url, meter_id_or_slug);
171        let request_builder = self
172            .http_client
173            .get(url)
174            .header("Authorization", self.get_auth_header());
175
176        // We build the query parameters manually:
177        let mut query_params = vec![];
178        if let Some(ref from) = params.from {
179            query_params.push(("from", from.clone()));
180        }
181        if let Some(ref to) = params.to {
182            query_params.push(("to", to.clone()));
183        }
184        if let Some(ref window_size) = params.window_size {
185            query_params.push(("windowSize", window_size.clone()));
186        }
187        if let Some(ref window_tz) = params.window_time_zone {
188            query_params.push(("windowTimeZone", window_tz.clone()));
189        }
190        if let Some(subjects) = &params.subject {
191            for s in subjects {
192                query_params.push(("subject", s.clone()));
193            }
194        }
195        // If you have groupBy filters or other items, add them here as well.
196
197        let resp = request_builder
198            .query(&query_params)
199            .send()
200            .await?
201            .error_for_status()?;
202
203        Ok(resp.json::<QueryResponse>().await?)
204    }
205
206    /// Update a meter by ID or slug.
207    ///
208    /// PUT /api/v1/meters/{meterIdOrSlug}
209    pub async fn update_meter(
210        &self,
211        meter_id_or_slug: &str,
212        body: &UpdateMeterRequest,
213    ) -> Result<Meter, Error> {
214        let url = format!("{}/api/v1/meters/{}", self.base_url, meter_id_or_slug);
215        let resp = self
216            .http_client
217            .put(url)
218            .header("Authorization", self.get_auth_header())
219            .header("Content-Type", "application/json")
220            .json(&body)
221            .send()
222            .await?
223            .error_for_status()?;
224
225        Ok(resp.json::<Meter>().await?)
226    }
227
228    /*  -----------------------------
229    Event Endpoints
230    ----------------------------- */
231
232    /// Ingest a single or batch of CloudEvents.
233    ///
234    /// POST /api/v1/events
235    pub async fn ingest_events(&self, events: &[CloudEvent]) -> Result<(), Error> {
236        let url = format!("{}/api/v1/events", self.base_url);
237        // The docs mention `application/cloudevents+json`, but the example uses `application/json`.
238        // Adjust if your server enforces `application/cloudevents+json`.
239        self.http_client
240            .post(url)
241            .header("Authorization", self.get_auth_header())
242            .header("Content-Type", "application/json")
243            .json(&events)
244            .send()
245            .await?
246            .error_for_status()?;
247
248        Ok(())
249    }
250
251    /// List ingested events within a time range or by other filters.
252    ///
253    /// GET /api/v1/events
254    ///
255    /// For example:
256    /// - from / to
257    /// - ingestedAtFrom / ingestedAtTo
258    /// - subject (partial match)
259    /// - id (partial match)
260    /// - limit
261    pub async fn list_events(
262        &self,
263        params: &ListEventsParams,
264    ) -> Result<Vec<IngestedEvent>, Error> {
265        let url = format!("{}/api/v1/events", self.base_url);
266        let mut query_params = vec![];
267
268        if let Some(ref client_id) = params.client_id {
269            query_params.push(("clientId", client_id.clone()));
270        }
271        if let Some(ref ingested_at_from) = params.ingested_at_from {
272            query_params.push(("ingestedAtFrom", ingested_at_from.clone()));
273        }
274        if let Some(ref ingested_at_to) = params.ingested_at_to {
275            query_params.push(("ingestedAtTo", ingested_at_to.clone()));
276        }
277        if let Some(ref id) = params.id {
278            query_params.push(("id", id.clone()));
279        }
280        if let Some(ref subject) = params.subject {
281            query_params.push(("subject", subject.clone()));
282        }
283        if let Some(ref from) = params.from {
284            query_params.push(("from", from.clone()));
285        }
286        if let Some(ref to) = params.to {
287            query_params.push(("to", to.clone()));
288        }
289        if let Some(limit) = params.limit {
290            query_params.push(("limit", format!("{}", limit)));
291        }
292
293        let resp = self
294            .http_client
295            .get(url)
296            .header("Authorization", self.get_auth_header())
297            .query(&query_params)
298            .send()
299            .await?
300            .error_for_status()?;
301
302        Ok(resp.json::<Vec<IngestedEvent>>().await?)
303    }
304
305    // -----------------------------------------------------------------------
306    // Entitlements & Features
307    // -----------------------------------------------------------------------
308
309    //
310    // 1. Create Entitlement: POST /api/v1/subjects/{subjectIdOrKey}/entitlements
311    //
312    pub async fn create_entitlement(
313        &self,
314        subject_id_or_key: &str,
315        req: CreateEntitlementRequest,
316    ) -> Result<Entitlement, Error> {
317        let url = format!(
318            "{}/api/v1/subjects/{}/entitlements",
319            self.base_url, subject_id_or_key
320        );
321        let resp = self
322            .http_client
323            .post(url)
324            .header("Authorization", self.get_auth_header())
325            .json(&req)
326            .send()
327            .await?
328            .error_for_status()?;
329
330        Ok(resp.json::<Entitlement>().await?)
331    }
332
333    //
334    // 2. Create Feature: POST /api/v1/features
335    //
336    pub async fn create_feature(&self, req: CreateFeatureRequest) -> Result<Feature, Error> {
337        let url = format!("{}/api/v1/features", self.base_url);
338        let resp = self
339            .http_client
340            .post(url)
341            .header("Authorization", self.get_auth_header())
342            .json(&req)
343            .send()
344            .await?
345            .error_for_status()?;
346
347        Ok(resp.json::<Feature>().await?)
348    }
349
350    //
351    // 3. Create Grant: POST /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementIdOrFeatureKey}/grants
352    //
353    pub async fn create_grant(
354        &self,
355        subject_id_or_key: &str,
356        entitlement_id_or_feature_key: &str,
357        req: GrantRequest,
358    ) -> Result<Grant, Error> {
359        let url = format!(
360            "{}/api/v1/subjects/{}/entitlements/{}/grants",
361            self.base_url, subject_id_or_key, entitlement_id_or_feature_key
362        );
363        let resp = self
364            .http_client
365            .post(url)
366            .header("Authorization", self.get_auth_header())
367            .json(&req)
368            .send()
369            .await?
370            .error_for_status()?;
371
372        Ok(resp.json::<Grant>().await?)
373    }
374
375    //
376    // 4. Delete Entitlement: DELETE /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementId}
377    //
378    pub async fn delete_entitlement(
379        &self,
380        subject_id_or_key: &str,
381        entitlement_id: &str,
382    ) -> Result<(), Error> {
383        let url = format!(
384            "{}/api/v1/subjects/{}/entitlements/{}",
385            self.base_url, subject_id_or_key, entitlement_id
386        );
387        self.http_client
388            .delete(url)
389            .header("Authorization", self.get_auth_header())
390            .send()
391            .await?
392            .error_for_status()?;
393        Ok(())
394    }
395
396    //
397    // 5. Delete Feature: DELETE /api/v1/features/{featureId}
398    //
399    pub async fn delete_feature(&self, feature_id: &str) -> Result<(), Error> {
400        let url = format!("{}/api/v1/features/{}", self.base_url, feature_id);
401        self.http_client
402            .delete(url)
403            .header("Authorization", self.get_auth_header())
404            .send()
405            .await?
406            .error_for_status()?;
407        Ok(())
408    }
409
410    //
411    // 6. Get Entitlement: GET /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementId}
412    //
413    pub async fn get_entitlement(
414        &self,
415        subject_id_or_key: &str,
416        entitlement_id: &str,
417    ) -> Result<Entitlement, Error> {
418        let url = format!(
419            "{}/api/v1/subjects/{}/entitlements/{}",
420            self.base_url, subject_id_or_key, entitlement_id
421        );
422        let resp = self
423            .http_client
424            .get(url)
425            .header("Authorization", self.get_auth_header())
426            .send()
427            .await?
428            .error_for_status()?;
429
430        Ok(resp.json::<Entitlement>().await?)
431    }
432
433    //
434    // 7. Get Entitlement by ID: GET /api/v1/entitlements/{entitlementId}
435    //
436    pub async fn get_entitlement_by_id(&self, entitlement_id: &str) -> Result<Entitlement, Error> {
437        let url = format!("{}/api/v1/entitlements/{}", self.base_url, entitlement_id);
438        let resp = self
439            .http_client
440            .get(url)
441            .header("Authorization", self.get_auth_header())
442            .send()
443            .await?
444            .error_for_status()?;
445
446        Ok(resp.json::<Entitlement>().await?)
447    }
448
449    //
450    // 8. Get Entitlement History: GET /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementId}/history
451    //
452    pub async fn get_entitlement_history(
453        &self,
454        subject_id_or_key: &str,
455        entitlement_id: &str,
456        from: Option<String>,
457        to: Option<String>,
458        window_size: String, // MINUTE, HOUR, DAY
459        window_time_zone: Option<String>,
460    ) -> Result<Value, Error> {
461        let url = format!(
462            "{}/api/v1/subjects/{}/entitlements/{}/history",
463            self.base_url, subject_id_or_key, entitlement_id
464        );
465
466        let mut query_params = vec![("windowSize", window_size)];
467        if let Some(f) = from {
468            query_params.push(("from", f));
469        }
470        if let Some(t) = to {
471            query_params.push(("to", t));
472        }
473        if let Some(tz) = window_time_zone {
474            query_params.push(("windowTimeZone", tz));
475        }
476
477        let resp = self
478            .http_client
479            .get(url)
480            .header("Authorization", self.get_auth_header())
481            .query(&query_params)
482            .send()
483            .await?
484            .error_for_status()?;
485
486        Ok(resp.json::<Value>().await?)
487    }
488
489    //
490    // 9. Get Entitlement Value: GET /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementIdOrFeatureKey}/value
491    //
492    pub async fn get_entitlement_value(
493        &self,
494        subject_id_or_key: &str,
495        entitlement_id_or_feature_key: &str,
496        time: Option<String>,
497    ) -> Result<Value, Error> {
498        let url = format!(
499            "{}/api/v1/subjects/{}/entitlements/{}/value",
500            self.base_url, subject_id_or_key, entitlement_id_or_feature_key
501        );
502
503        let mut query_params = vec![];
504        if let Some(t) = time {
505            query_params.push(("time", t));
506        }
507
508        let resp = self
509            .http_client
510            .get(url)
511            .header("Authorization", self.get_auth_header())
512            .query(&query_params)
513            .send()
514            .await?
515            .error_for_status()?;
516
517        Ok(resp.json::<Value>().await?)
518    }
519
520    //
521    // 10. Get Feature: GET /api/v1/features/{featureId}
522    //
523    pub async fn get_feature(&self, feature_id: &str) -> Result<Feature, Error> {
524        let url = format!("{}/api/v1/features/{}", self.base_url, feature_id);
525        let resp = self
526            .http_client
527            .get(url)
528            .header("Authorization", self.get_auth_header())
529            .send()
530            .await?
531            .error_for_status()?;
532
533        Ok(resp.json::<Feature>().await?)
534    }
535
536    //
537    // 11. List All Entitlements: GET /api/v1/entitlements
538    //
539    pub async fn list_all_entitlements(&self) -> Result<Vec<Entitlement>, Error> {
540        let url = format!("{}/api/v1/entitlements", self.base_url);
541        let resp = self
542            .http_client
543            .get(url)
544            .header("Authorization", self.get_auth_header())
545            .send()
546            .await?
547            .error_for_status()?;
548
549        Ok(resp.json::<Vec<Entitlement>>().await?)
550    }
551
552    //
553    // 12. List Entitlement Grants: GET /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementIdOrFeatureKey}/grants
554    //
555    pub async fn list_entitlement_grants(
556        &self,
557        subject_id_or_key: &str,
558        entitlement_id_or_feature_key: &str,
559    ) -> Result<Vec<Grant>, Error> {
560        let url = format!(
561            "{}/api/v1/subjects/{}/entitlements/{}/grants",
562            self.base_url, subject_id_or_key, entitlement_id_or_feature_key
563        );
564        let resp = self
565            .http_client
566            .get(url)
567            .header("Authorization", self.get_auth_header())
568            .send()
569            .await?
570            .error_for_status()?;
571
572        Ok(resp.json::<Vec<Grant>>().await?)
573    }
574
575    //
576    // 13. List Entitlements for a subject: GET /api/v1/subjects/{subjectIdOrKey}/entitlements
577    //
578    pub async fn list_entitlements(
579        &self,
580        subject_id_or_key: &str,
581    ) -> Result<Vec<Entitlement>, Error> {
582        let url = format!(
583            "{}/api/v1/subjects/{}/entitlements",
584            self.base_url, subject_id_or_key
585        );
586        let resp = self
587            .http_client
588            .get(url)
589            .header("Authorization", self.get_auth_header())
590            .send()
591            .await?
592            .error_for_status()?;
593
594        Ok(resp.json::<Vec<Entitlement>>().await?)
595    }
596
597    //
598    // 14. List Features: GET /api/v1/features
599    //
600    pub async fn list_features(&self) -> Result<Vec<Feature>, Error> {
601        let url = format!("{}/api/v1/features", self.base_url);
602        let resp = self
603            .http_client
604            .get(url)
605            .header("Authorization", self.get_auth_header())
606            .send()
607            .await?
608            .error_for_status()?;
609
610        Ok(resp.json::<Vec<Feature>>().await?)
611    }
612
613    //
614    // 15. List Grants: GET /api/v1/grants
615    //
616    pub async fn list_grants(&self) -> Result<Vec<Grant>, Error> {
617        let url = format!("{}/api/v1/grants", self.base_url);
618        let resp = self
619            .http_client
620            .get(url)
621            .header("Authorization", self.get_auth_header())
622            .send()
623            .await?
624            .error_for_status()?;
625
626        Ok(resp.json::<Vec<Grant>>().await?)
627    }
628
629    //
630    // 16. Override Entitlement: PUT /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementIdOrFeatureKey}/override
631    //
632    pub async fn override_entitlement(
633        &self,
634        subject_id_or_key: &str,
635        entitlement_id_or_feature_key: &str,
636        req: CreateEntitlementRequest,
637    ) -> Result<Entitlement, Error> {
638        let url = format!(
639            "{}/api/v1/subjects/{}/entitlements/{}/override",
640            self.base_url, subject_id_or_key, entitlement_id_or_feature_key
641        );
642        let resp = self
643            .http_client
644            .put(url)
645            .header("Authorization", self.get_auth_header())
646            .json(&req)
647            .send()
648            .await?
649            .error_for_status()?;
650
651        Ok(resp.json::<Entitlement>().await?)
652    }
653
654    //
655    // 17. Reset Entitlement: POST /api/v1/subjects/{subjectIdOrKey}/entitlements/{entitlementId}/reset
656    //
657    pub async fn reset_entitlement(
658        &self,
659        subject_id_or_key: &str,
660        entitlement_id: &str,
661        req: ResetEntitlementRequest,
662    ) -> Result<(), Error> {
663        let url = format!(
664            "{}/api/v1/subjects/{}/entitlements/{}/reset",
665            self.base_url, subject_id_or_key, entitlement_id
666        );
667        self.http_client
668            .post(url)
669            .header("Authorization", self.get_auth_header())
670            .json(&req)
671            .send()
672            .await?
673            .error_for_status()?;
674        Ok(())
675    }
676
677    //
678    // 18. Void Grant: DELETE /api/v1/grants/{grantId}
679    //
680    pub async fn void_grant(&self, grant_id: &str) -> Result<(), Error> {
681        let url = format!("{}/api/v1/grants/{}", self.base_url, grant_id);
682        self.http_client
683            .delete(url)
684            .header("Authorization", self.get_auth_header())
685            .send()
686            .await?
687            .error_for_status()?;
688        Ok(())
689    }
690}
691
692/// Request body to create a meter.
693///
694/// This mirrors the required format for POST /api/v1/meters.
695#[derive(Debug, Serialize, Deserialize)]
696pub struct CreateMeterRequest {
697    /// A unique, human-readable identifier for the meter.
698    /// Must consist only of alphanumeric and underscore characters.
699    #[serde(rename = "slug")]
700    pub slug: String,
701
702    /// Human-readable name for the resource.
703    /// Defaults to the slug if not specified.
704    #[serde(rename = "name")]
705    pub name: Option<String>,
706
707    /// Optional description for the meter.
708    #[serde(rename = "description")]
709    pub description: Option<String>,
710
711    /// The aggregation type to use for the meter (SUM, UNIQUE_COUNT, COUNT, etc.).
712    #[serde(rename = "aggregation")]
713    pub aggregation: String,
714
715    /// The event type to aggregate.
716    #[serde(rename = "eventType")]
717    pub event_type: String,
718
719    /// The date since the meter should include events.
720    /// Example "2023-01-01T00:00:00Z". If not specified, all events are included.
721    #[serde(rename = "eventFrom")]
722    pub event_from: Option<String>,
723
724    /// JSONPath expression to extract the value from the ingested event's data property.
725    #[serde(rename = "valueProperty")]
726    pub value_property: Option<String>,
727
728    /// Named JSONPath expressions to extract the group by values from the event data.
729    #[serde(rename = "groupBy")]
730    pub group_by: Option<Value>,
731
732    /// Additional metadata for the resource.
733    #[serde(rename = "metadata")]
734    pub metadata: Option<Value>,
735}
736
737/// Request body to update a meter.
738///
739/// This mirrors the required format for PUT /api/v1/meters/{meterIdOrSlug}.
740#[derive(Debug, Serialize, Deserialize)]
741pub struct UpdateMeterRequest {
742    /// Optional new name for the meter.
743    #[serde(rename = "name")]
744    pub name: Option<String>,
745
746    /// Optional new description for the meter.
747    #[serde(rename = "description")]
748    pub description: Option<String>,
749
750    /// Named JSONPath expressions to extract the group by values from the event data.
751    #[serde(rename = "groupBy")]
752    pub group_by: Option<Value>,
753
754    /// Additional metadata for the resource.
755    #[serde(rename = "metadata")]
756    pub metadata: Option<Value>,
757}
758
759/// Represents a Meter object returned from the API.
760#[derive(Debug, Serialize, Deserialize)]
761pub struct Meter {
762    pub id: String,
763    pub slug: String,
764    pub name: String,
765    pub description: Option<String>,
766    pub aggregation: String,
767    pub eventType: String,
768    pub valueProperty: Option<String>,
769    pub groupBy: Option<Value>,
770    pub createdAt: String,
771    pub updatedAt: String,
772}
773
774/// Represents optional parameters for querying meter usage.
775#[derive(Debug, Default)]
776pub struct QueryParams {
777    pub from: Option<String>,
778    pub to: Option<String>,
779    pub window_size: Option<String>,
780    pub window_time_zone: Option<String>,
781    /// If you want to filter usage by subject(s).
782    pub subject: Option<Vec<String>>,
783    // Example extension: pub filter_group_by: Option<HashMap<String, String>>,
784}
785
786/// Represents the response structure from Querying usage.
787#[derive(Debug, Serialize, Deserialize)]
788pub struct QueryResponse {
789    pub from: String,
790    pub to: String,
791    pub windowSize: Option<String>,
792    pub data: Vec<QueryData>,
793}
794
795/// Individual data point in the usage query response.
796#[derive(Debug, Serialize, Deserialize)]
797pub struct QueryData {
798    pub value: f64,
799    pub windowStart: Option<String>,
800    pub windowEnd: Option<String>,
801    pub subject: Option<String>,
802
803    /// The groupBy object if grouping was requested.
804    #[serde(default)]
805    pub groupBy: Option<Value>,
806}
807
808/*  -----------------------------
809 Events Data Structures
810----------------------------- */
811
812/// Represents a single CloudEvent.
813///
814/// See CloudEvents schema: https://github.com/cloudevents/spec
815#[derive(Debug, Serialize, Deserialize)]
816pub struct CloudEvent {
817    /// Identifies the event.
818    pub id: String,
819
820    /// Identifies the context in which an event happened.
821    pub source: String,
822
823    /// The version of the CloudEvents specification (defaults: "1.0").
824    pub specversion: String,
825
826    /// The type of event (e.g., "prompt").
827    #[serde(rename = "type")]
828    pub r#type: String,
829
830    /// Subject of the event. Must be at least 1 character.
831    pub subject: String,
832
833    /// Timestamp of when the occurrence happened. Must adhere to RFC 3339 or be omitted.
834    pub time: Option<String>,
835
836    /// Identifies the schema that data adheres to.
837    pub dataschema: Option<String>,
838
839    /// Content type of the CloudEvents data value, typically "application/json".
840    pub datacontenttype: Option<String>,
841
842    /// The event payload. Optional, if present must be a JSON object.
843    pub data: Option<Value>,
844}
845
846/// Parameters for listing events.
847///
848/// If neither `from` nor `ingested_at_from` is specified, it defaults to last 72 hours.
849#[derive(Debug, Default)]
850pub struct ListEventsParams {
851    pub client_id: Option<String>,
852    pub ingested_at_from: Option<String>,
853    pub ingested_at_to: Option<String>,
854    pub id: Option<String>,
855    pub subject: Option<String>,
856    pub from: Option<String>,
857    pub to: Option<String>,
858    pub limit: Option<u32>,
859}
860
861/// Represents an event that was ingested, including timestamps.
862#[derive(Debug, Serialize, Deserialize)]
863pub struct IngestedEvent {
864    pub event: CloudEvent,
865    pub ingestedAt: String,
866    pub storedAt: String,
867}
868
869// ---------------------------------------------------------------------------
870// Request/Response Models for Entitlements & Features
871// ---------------------------------------------------------------------------
872
873/// Request body to create an Entitlement (all entitlement types).
874#[derive(Debug, Serialize, Deserialize)]
875#[serde(rename_all = "camelCase")]
876pub struct CreateEntitlementRequest {
877    /// Feature key for the entitlement.
878    pub feature_key: String,
879
880    /// Optional feature ID to link (if known).
881    pub feature_id: Option<String>,
882
883    /// One of "metered", "boolean", "static".
884    pub r#type: String,
885
886    /// For "metered" entitlements only
887    #[serde(skip_serializing_if = "Option::is_none")]
888    pub is_soft_limit: Option<bool>,
889
890    #[serde(skip_serializing_if = "Option::is_none")]
891    pub is_unlimited: Option<bool>,
892
893    #[serde(skip_serializing_if = "Option::is_none")]
894    pub usage_period: Option<Value>, // e.g. { "interval": "DAY", "anchor": "2023-01-01T01:01:01Z" }
895
896    #[serde(skip_serializing_if = "Option::is_none")]
897    pub measure_usage_from: Option<String>, // e.g. "CURRENT_PERIOD_START"
898
899    #[serde(skip_serializing_if = "Option::is_none")]
900    pub metadata: Option<Value>,
901}
902
903/// Response model for an Entitlement.
904#[derive(Debug, Serialize, Deserialize)]
905#[serde(rename_all = "camelCase")]
906pub struct Entitlement {
907    pub id: String,
908    pub feature_id: String,
909    pub feature_key: String,
910    pub subject_key: String,
911    pub r#type: String,
912    pub is_soft_limit: bool,
913    pub is_unlimited: bool,
914    pub created_at: String,
915    #[serde(skip_serializing_if = "Option::is_none")]
916    pub deleted_at: Option<String>,
917    // etc. (expand as needed)
918}
919
920/// Request body to create a Feature.
921#[derive(Debug, Serialize, Deserialize)]
922#[serde(rename_all = "camelCase")]
923pub struct CreateFeatureRequest {
924    pub key: String,
925    pub name: String,
926    #[serde(skip_serializing_if = "Option::is_none")]
927    pub meter_slug: Option<String>,
928    #[serde(skip_serializing_if = "Option::is_none")]
929    pub meter_group_by_filters: Option<Value>,
930    #[serde(skip_serializing_if = "Option::is_none")]
931    pub metadata: Option<Value>,
932}
933
934/// Response model for a Feature.
935#[derive(Debug, Serialize, Deserialize)]
936#[serde(rename_all = "camelCase")]
937pub struct Feature {
938    pub id: String,
939    pub key: String,
940    pub name: String,
941    #[serde(skip_serializing_if = "Option::is_none")]
942    pub deleted_at: Option<String>,
943    // etc. (expand as needed)
944}
945
946/// Request body to create a Grant.
947#[derive(Debug, Default, Serialize, Deserialize)]
948#[serde(rename_all = "camelCase")]
949pub struct GrantRequest {
950    pub amount: f64,
951    #[serde(skip_serializing_if = "Option::is_none")]
952    pub priority: Option<u8>,
953    pub effective_at: String,
954    #[serde(skip_serializing_if = "Option::is_none")]
955    pub expiration: Option<Value>,
956    #[serde(skip_serializing_if = "Option::is_none")]
957    pub max_rollover_amount: Option<f64>,
958    #[serde(skip_serializing_if = "Option::is_none")]
959    pub min_rollover_amount: Option<f64>,
960    #[serde(skip_serializing_if = "Option::is_none")]
961    pub metadata: Option<Value>,
962    #[serde(skip_serializing_if = "Option::is_none")]
963    pub recurrence: Option<Value>,
964}
965
966/// Response model for a Grant.
967#[derive(Debug, Serialize, Deserialize)]
968#[serde(rename_all = "camelCase")]
969pub struct Grant {
970    pub id: String,
971    pub entitlement_id: String,
972    pub amount: f64,
973    pub effective_at: String,
974    #[serde(skip_serializing_if = "Option::is_none")]
975    pub deleted_at: Option<String>,
976    // etc. (expand as needed)
977}
978
979/// Request body to reset an Entitlement.
980#[derive(Debug, Serialize, Deserialize)]
981#[serde(rename_all = "camelCase")]
982pub struct ResetEntitlementRequest {
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub effective_at: Option<String>,
985    #[serde(skip_serializing_if = "Option::is_none")]
986    pub retain_anchor: Option<bool>,
987    #[serde(skip_serializing_if = "Option::is_none")]
988    pub preserve_overage: Option<bool>,
989}