An unofficial Rust client for the Metronome billing API, generated from Metronome's published OpenAPI spec with progenitor.
Metronome doesn't ship a Rust SDK (only TypeScript and Go). This crate generates a typed, async client (
reqwest+serde) from their OpenAPI spec.src/lib.rsis generated — do not hand-edit it. Re-run./scripts/generate.shinstead.
Metronome uses bearer-token auth. progenitor doesn't inject the token itself, so
attach it as a default header on the reqwest::Client and hand that to the
generated client:
use std::str::FromStr;
use metronome_sdk::Client;
use metronome_sdk::types::IngestV1BodyItem;
fn metronome(token: &str) -> Client {
let mut headers = reqwest::header::HeaderMap::new();
let mut auth = reqwest::header::HeaderValue::from_str(&format!("Bearer {token}")).unwrap();
auth.set_sensitive(true);
headers.insert(reqwest::header::AUTHORIZATION, auth);
let http = reqwest::Client::builder()
.default_headers(headers)
.build()
.unwrap();
Client::new_with_client("https://api.metronome.com", http)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = metronome(&std::env::var("METRONOME_API_KEY")?);
// Ingest a usage event. The id/customer/type fields are validated newtypes,
// so build them with FromStr (or TryFrom) rather than a bare String.
let events = vec![IngestV1BodyItem {
transaction_id: FromStr::from_str("txn-0001")?,
customer_id: FromStr::from_str("cust_abc123")?,
event_type: FromStr::from_str("api_request")?,
timestamp: "2026-06-15T00:00:00Z".to_string(), // RFC 3339
properties: serde_json::json!({ "tokens": 4096 })
.as_object().unwrap().clone(),
}];
client.ingest_v1(&events).await?;
Ok(())
}Every Metronome endpoint is available as an async method on Client (119 in
total) — e.g. ingest_v1, search_events_v1, create_billable_metric_v1.
Request/response types live under metronome_sdk::types. Constrained string
fields are generated as validated newtypes; construct them with
FromStr/TryFrom (both return a ConversionError if validation fails).
cargo install cargo-progenitor --version 0.14.0 # one-time
./scripts/generate.sh # regenerate from the vendored spec/
./scripts/generate.sh --refresh # re-download the spec from Metronome, then regenerateMetronome's published OpenAPI spec has constructs that progenitor/typify can't
consume directly (the official Go/TS SDKs use a private Stainless overlay we
don't have). scripts/patch_spec.py applies three mechanical, deterministic
fixes before generation:
- Case-insensitive enums →
string(618 of them). The spec enumerates every casing of each value (count/Count/COUNT), which can't map to unique Rust variants. Downgrading tostringround-trips losslessly regardless of the casing the API returns. - Mis-typed string enums →
type: string(7). Some enums declaretype: objectdespite having string values (e.g.billable_status). - Stray scalar
formaton array/object nodes stripped (2). e.g. atype: arraycarryingformat: uuid.
If a future spec revision introduces a new unsupported construct, generation
will fail loudly; extend patch_spec.py with a new rule.