Skip to main content

ccwire_oauth/
types.rs

1//! The response types.
2//!
3//! Every field the API is known to send is typed, and nearly all are
4//! `Option`: the endpoints are undocumented, so a missing or `null` field,
5//! or one of an unexpected type, reads as `None` (a list as empty, skipping
6//! elements it can't read) instead of failing the whole response. Amounts
7//! also read from a string holding a number (`"12.50"`). Whatever else a
8//! response carries lands in the struct's `extra` map.
9//!
10//! The hard requirements are few: [`Profile::account`] and its
11//! [`Account::uuid`]; a profile without them is
12//! [`Error::Decode`](crate::Error::Decode). An organization without a
13//! string [`Organization::uuid`] reads as `None`, like any other field it
14//! can't read.
15
16use alloc::collections::BTreeMap;
17use alloc::string::String;
18use alloc::vec::Vec;
19
20use serde::Deserialize;
21
22use crate::lenient;
23
24/// Fields a response carried that this crate doesn't type, by name.
25pub type Extra = BTreeMap<String, serde_json::Value>;
26
27/// `GET /api/oauth/usage`: the subscription's rate-limit windows and its
28/// extra (paid) usage.
29#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
30#[non_exhaustive]
31pub struct Usage {
32    /// The rolling 5-hour window.
33    #[serde(default, deserialize_with = "lenient::opt")]
34    pub five_hour: Option<Window>,
35    /// The 7-day window over all models.
36    #[serde(default, deserialize_with = "lenient::opt")]
37    pub seven_day: Option<Window>,
38    /// The 7-day window for Opus models, on plans that have one.
39    #[serde(default, deserialize_with = "lenient::opt")]
40    pub seven_day_opus: Option<Window>,
41    /// The 7-day window for Sonnet models, on plans that have one.
42    #[serde(default, deserialize_with = "lenient::opt")]
43    pub seven_day_sonnet: Option<Window>,
44    /// The 7-day window for third-party OAuth apps.
45    #[serde(default, deserialize_with = "lenient::opt")]
46    pub seven_day_oauth_apps: Option<Window>,
47    /// Extra usage (usage credits billed past the plan), if the account
48    /// has it.
49    #[serde(default, deserialize_with = "lenient::opt")]
50    pub extra_usage: Option<ExtraUsage>,
51    /// The server's list of meters, when it sends one (newer responses).
52    /// Empty when it is missing or `null`.
53    #[serde(default, deserialize_with = "lenient::list")]
54    pub limits: Vec<Limit>,
55    /// Anything else (more windows as the plans grow them).
56    #[serde(flatten)]
57    pub extra: Extra,
58}
59
60/// One rate-limit window.
61#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
62#[non_exhaustive]
63pub struct Window {
64    /// Percent of the window used, `0.0` to `100.0` (it can pass 100).
65    #[serde(default, deserialize_with = "lenient::num")]
66    pub utilization: Option<f64>,
67    /// When the window resets, RFC 3339. `None` for an idle window that
68    /// hasn't started.
69    #[serde(default, deserialize_with = "lenient::opt")]
70    pub resets_at: Option<String>,
71    /// Anything else.
72    #[serde(flatten)]
73    pub extra: Extra,
74}
75
76/// Extra usage: credits spent past the plan's limits this month.
77///
78/// Amounts are in the currency's minor units (cents).
79#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
80#[non_exhaustive]
81pub struct ExtraUsage {
82    /// Whether extra usage is turned on.
83    #[serde(default, deserialize_with = "lenient::opt")]
84    pub is_enabled: Option<bool>,
85    /// The monthly limit, minor units.
86    #[serde(default, deserialize_with = "lenient::num")]
87    pub monthly_limit: Option<f64>,
88    /// Spent this month, minor units.
89    #[serde(default, deserialize_with = "lenient::num")]
90    pub used_credits: Option<f64>,
91    /// Percent of the monthly limit spent.
92    #[serde(default, deserialize_with = "lenient::num")]
93    pub utilization: Option<f64>,
94    /// ISO 4217 code (`USD`).
95    #[serde(default, deserialize_with = "lenient::opt")]
96    pub currency: Option<String>,
97    /// Anything else.
98    #[serde(flatten)]
99    pub extra: Extra,
100}
101
102/// One row of [`Usage::limits`].
103#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
104#[non_exhaustive]
105pub struct Limit {
106    /// The meter's kind (`session`, `weekly_all`, `weekly_scoped`, ...).
107    #[serde(default, deserialize_with = "lenient::opt")]
108    pub kind: Option<String>,
109    /// The row's group (`session`, `weekly`, ...).
110    #[serde(default, deserialize_with = "lenient::opt")]
111    pub group: Option<String>,
112    /// Percent used.
113    #[serde(default, deserialize_with = "lenient::num")]
114    pub utilization: Option<f64>,
115    /// When it resets, RFC 3339.
116    #[serde(default, deserialize_with = "lenient::opt")]
117    pub resets_at: Option<String>,
118    /// Whether this meter is the one currently limiting.
119    #[serde(default, deserialize_with = "lenient::opt")]
120    pub is_active: Option<bool>,
121    /// What the meter covers, for scoped meters.
122    #[serde(default, deserialize_with = "lenient::opt")]
123    pub scope: Option<LimitScope>,
124    /// Anything else.
125    #[serde(flatten)]
126    pub extra: Extra,
127}
128
129/// What a scoped [`Limit`] covers.
130#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
131#[non_exhaustive]
132pub struct LimitScope {
133    /// The model family, for a per-model meter.
134    #[serde(default, deserialize_with = "lenient::opt")]
135    pub model: Option<LimitModel>,
136    /// Anything else.
137    #[serde(flatten)]
138    pub extra: Extra,
139}
140
141/// The model of a per-model [`Limit`].
142#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
143#[non_exhaustive]
144pub struct LimitModel {
145    /// Its display name, as the server labels it.
146    #[serde(default, deserialize_with = "lenient::opt")]
147    pub display_name: Option<String>,
148    /// Anything else.
149    #[serde(flatten)]
150    pub extra: Extra,
151}
152
153/// `GET /api/oauth/profile`: the account and its organization.
154#[derive(Debug, Clone, PartialEq, Deserialize)]
155#[non_exhaustive]
156pub struct Profile {
157    /// The user.
158    pub account: Account,
159    /// The organization the token acts for.
160    #[serde(default, deserialize_with = "lenient::opt")]
161    pub organization: Option<Organization>,
162    /// Anything else.
163    #[serde(flatten)]
164    pub extra: Extra,
165}
166
167impl Profile {
168    /// The consumer subscription, from the account's flags. `None` for an
169    /// account with neither (a Team or Enterprise seat, or no plan): see
170    /// [`Organization::organization_type`] for those.
171    pub fn plan(&self) -> Option<Plan> {
172        if self.account.has_claude_max == Some(true) {
173            Some(Plan::Max)
174        } else if self.account.has_claude_pro == Some(true) {
175            Some(Plan::Pro)
176        } else {
177            None
178        }
179    }
180}
181
182/// A consumer subscription, as [`Profile::plan`] reads it.
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184#[non_exhaustive]
185pub enum Plan {
186    /// Claude Pro.
187    Pro,
188    /// Claude Max.
189    Max,
190}
191
192/// The user behind a token.
193#[derive(Debug, Clone, PartialEq, Deserialize)]
194#[non_exhaustive]
195pub struct Account {
196    /// The account's id.
197    pub uuid: String,
198    /// The login email.
199    #[serde(default, alias = "email_address", deserialize_with = "lenient::opt")]
200    pub email: Option<String>,
201    /// The full name.
202    #[serde(default, deserialize_with = "lenient::opt")]
203    pub full_name: Option<String>,
204    /// The display name.
205    #[serde(default, deserialize_with = "lenient::opt")]
206    pub display_name: Option<String>,
207    /// Whether the account has Claude Max.
208    #[serde(default, deserialize_with = "lenient::opt")]
209    pub has_claude_max: Option<bool>,
210    /// Whether the account has Claude Pro.
211    #[serde(default, deserialize_with = "lenient::opt")]
212    pub has_claude_pro: Option<bool>,
213    /// When the account was created, RFC 3339.
214    #[serde(default, deserialize_with = "lenient::opt")]
215    pub created_at: Option<String>,
216    /// Anything else.
217    #[serde(flatten)]
218    pub extra: Extra,
219}
220
221/// The organization a token acts for.
222#[derive(Debug, Clone, PartialEq, Deserialize)]
223#[non_exhaustive]
224pub struct Organization {
225    /// The organization's id: the `org` argument of the org-scoped calls.
226    pub uuid: String,
227    /// Its name.
228    #[serde(default, deserialize_with = "lenient::opt")]
229    pub name: Option<String>,
230    /// Its type (`claude_pro`, `claude_max`, `claude_team`, ...).
231    #[serde(default, deserialize_with = "lenient::opt")]
232    pub organization_type: Option<String>,
233    /// How it is billed.
234    #[serde(default, deserialize_with = "lenient::opt")]
235    pub billing_type: Option<String>,
236    /// Its rate-limit tier.
237    #[serde(default, deserialize_with = "lenient::opt")]
238    pub rate_limit_tier: Option<String>,
239    /// Whether extra usage is turned on for it.
240    #[serde(default, deserialize_with = "lenient::opt")]
241    pub has_extra_usage_enabled: Option<bool>,
242    /// The subscription's status (`active`, ...).
243    #[serde(default, deserialize_with = "lenient::opt")]
244    pub subscription_status: Option<String>,
245    /// Anything else.
246    #[serde(flatten)]
247    pub extra: Extra,
248}
249
250/// `GET /api/oauth/claude_cli/roles`: the user's roles in the token's
251/// organization.
252#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
253#[non_exhaustive]
254pub struct Roles {
255    /// The organization role (`admin`, `user`, ...).
256    #[serde(default, deserialize_with = "lenient::opt")]
257    pub organization_role: Option<String>,
258    /// The workspace role, where workspaces apply.
259    #[serde(default, deserialize_with = "lenient::opt")]
260    pub workspace_role: Option<String>,
261    /// The organization's name.
262    #[serde(default, deserialize_with = "lenient::opt")]
263    pub organization_name: Option<String>,
264    /// Anything else.
265    #[serde(flatten)]
266    pub extra: Extra,
267}
268
269/// `GET /api/oauth/organizations/{org}/prepaid/credits`: the prepaid
270/// balance.
271#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
272#[non_exhaustive]
273pub struct PrepaidCredits {
274    /// The balance, minor units. `None` where prepaid credits don't apply.
275    #[serde(default, deserialize_with = "lenient::num")]
276    pub amount: Option<f64>,
277    /// ISO 4217 code.
278    #[serde(default, deserialize_with = "lenient::opt")]
279    pub currency: Option<String>,
280    /// The auto-reload settings.
281    #[serde(default, deserialize_with = "lenient::opt")]
282    pub auto_reload_settings: Option<AutoReload>,
283    /// After how many months unused credits expire, if they do.
284    #[serde(default, deserialize_with = "lenient::count")]
285    pub expiry_policy_months: Option<u32>,
286    /// Anything else.
287    #[serde(flatten)]
288    pub extra: Extra,
289}
290
291/// Prepaid auto-reload, read-only.
292#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
293#[non_exhaustive]
294pub struct AutoReload {
295    /// Whether it is on.
296    #[serde(default, deserialize_with = "lenient::opt")]
297    pub enabled: Option<bool>,
298    /// Anything else (thresholds).
299    #[serde(flatten)]
300    pub extra: Extra,
301}
302
303/// `GET /api/oauth/organizations/{org}/overage_spend_limit`: the extra
304/// usage (overage) monthly limit.
305///
306/// Amounts are in minor units.
307#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
308#[non_exhaustive]
309pub struct OverageSpendLimit {
310    /// Whether overage is turned on.
311    #[serde(default, deserialize_with = "lenient::opt")]
312    pub is_enabled: Option<bool>,
313    /// The monthly limit, minor units.
314    #[serde(default, deserialize_with = "lenient::num")]
315    pub monthly_credit_limit: Option<f64>,
316    /// ISO 4217 code.
317    #[serde(default, deserialize_with = "lenient::opt")]
318    pub currency: Option<String>,
319    /// Spent this month, minor units.
320    #[serde(default, deserialize_with = "lenient::num")]
321    pub used_credits: Option<f64>,
322    /// Until when overage is off, RFC 3339, if it was turned off.
323    #[serde(default, deserialize_with = "lenient::opt")]
324    pub disabled_until: Option<String>,
325    /// Why it is off, if it is.
326    #[serde(default, deserialize_with = "lenient::opt")]
327    pub disabled_reason: Option<String>,
328    /// Anything else.
329    #[serde(flatten)]
330    pub extra: Extra,
331}