metronome-sdk 0.1.0

An unofficial, generated Rust client for the Metronome billing API.
Documentation
# metronome-sdk

An unofficial Rust client for the [Metronome](https://metronome.com) billing API,
generated from Metronome's published OpenAPI spec with
[progenitor](https://github.com/oxidecomputer/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.rs` is **generated — do not hand-edit it.** Re-run `./scripts/generate.sh`
> instead.

## Usage

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:

```rust
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).

## Regenerating

```bash
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 regenerate
```

### Why the spec needs patching

Metronome'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:

1. **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 to `string` round-trips losslessly
   regardless of the casing the API returns.
2. **Mis-typed string enums → `type: string`** (7). Some enums declare
   `type: object` despite having string values (e.g. `billable_status`).
3. **Stray scalar `format` on array/object nodes stripped** (2). e.g. a
   `type: array` carrying `format: uuid`.

If a future spec revision introduces a new unsupported construct, generation
will fail loudly; extend `patch_spec.py` with a new rule.