ccwire_oauth/lib.rs
1//! A typed async client for the Claude account endpoints an OAuth login can
2//! call: usage, profile, token validation, roles, prepaid credits and the
3//! overage limit, and the refresh-token grant.
4//!
5//! Everything is read-only. Nothing here spends money, changes a setting
6//! or mints a key; the only `POST`s are the token check
7//! ([`Client::validate`]) and the refresh grant ([`Client::refresh`]).
8//!
9//! This crate is not affiliated with or endorsed by Anthropic. The
10//! endpoints are undocumented: they are the ones Claude Code itself calls.
11//! Their shapes can change without notice, so nearly every field is an
12//! `Option` that reads as `None` when it is missing or of an unexpected
13//! type, and each type keeps whatever else the response carried in its
14//! `extra` map.
15//!
16//! - [`Client`]: one method per endpoint. It holds no token: each call
17//! takes the one it acts with.
18//! - [`Secret`]: a token. It never prints and its own buffer is zeroed on
19//! drop (copies made along the way may not be: see [`Secret`]).
20//! - [`Error`]: [`Error::Unauthorized`] (re-login), [`Error::Forbidden`]
21//! (a good token without the scope or role),
22//! [`Error::RateLimited`] (with `Retry-After`), [`Error::Http`],
23//! [`Error::Decode`] (with a scrubbed body snippet), and the rest.
24//!
25//! # Read usage
26//!
27//! Add `ccwire-oauth = "0.1"` and Tokio to your application. Obtain an
28//! access token through an existing Claude OAuth login; this crate does
29//! not perform browser login or persist credentials.
30//!
31//! ```no_run
32//! # async fn usage() -> Result<(), Box<dyn std::error::Error>> {
33//! use ccwire_oauth::{Client, Secret};
34//!
35//! let token = Secret::new(std::env::var("CLAUDE_ACCESS_TOKEN")?);
36//! let usage = Client::new()?.usage(&token).await?;
37//! if let Some(window) = usage.five_hour {
38//! println!("utilization: {:?}%", window.utilization);
39//! }
40//! # Ok(()) }
41//! ```
42//!
43//! # Refresh and inspect a profile
44//!
45//! ```no_run
46//! # async fn run() -> Result<(), ccwire_oauth::Error> {
47//! use ccwire_oauth::{Client, RefreshRequest, Secret};
48//!
49//! let client = Client::new()?;
50//! let stored = Secret::new("stored-refresh-token");
51//! // Don't cancel a refresh once it's sent (see `Client::refresh`).
52//! let tokens = client.refresh(&RefreshRequest::new(stored)).await?;
53//! // Store tokens.refresh_token only if the stored one is still the one
54//! // posted (compare-and-swap); it has been spent either way.
55//! let profile = client.profile(&tokens.access_token).await?;
56//! if let Some(org) = &profile.organization {
57//! let limit = client.overage_spend_limit(&tokens.access_token, &org.uuid).await?;
58//! println!("{:?} {:?}", limit.is_enabled, limit.used_credits);
59//! }
60//! # Ok(()) }
61//! ```
62//!
63//! # License
64//!
65//! Licensed under the Apache License, Version 2.0 (`LICENSE`).
66
67#![warn(missing_docs)]
68#![deny(rustdoc::broken_intra_doc_links)]
69
70extern crate alloc;
71
72mod client;
73mod error;
74mod lenient;
75mod secret;
76mod token;
77mod types;
78
79pub use client::{
80 Client, ClientBuilder, DEFAULT_BASE_URL, DEFAULT_TIMEOUT, DEFAULT_TOKEN_URL,
81 DEFAULT_USER_AGENT, MAX_BODY, OAUTH_BETA,
82};
83pub use error::Error;
84pub use secret::Secret;
85pub use token::{CLAUDE_CODE_CLIENT_ID, DEFAULT_SCOPES, RefreshRequest, TokenSet};
86pub use types::{
87 Account, AutoReload, Extra, ExtraUsage, Limit, LimitModel, LimitScope, Organization,
88 OverageSpendLimit, Plan, PrepaidCredits, Profile, Roles, Usage, Window,
89};
90
91/// The README's example, compiled as a doctest.
92#[cfg(doctest)]
93#[doc = include_str!("../README.md")]
94struct ReadmeDoctests;