{"id":51399916,"url":"https://github.com/spiraldb/metronome-sdk","last_synced_at":"2026-07-04T05:35:46.565Z","repository":{"id":367116678,"uuid":"1279312704","full_name":"spiraldb/metronome-sdk","owner":"spiraldb","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-24T15:29:23.000Z","size":1786,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-07-04T05:35:39.383Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spiraldb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-24T15:11:43.000Z","updated_at":"2026-06-24T15:29:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/spiraldb/metronome-sdk","commit_stats":null,"previous_names":["spiraldb/metronome-sdk"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/spiraldb/metronome-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spiraldb%2Fmetronome-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spiraldb%2Fmetronome-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spiraldb%2Fmetronome-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spiraldb%2Fmetronome-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spiraldb","download_url":"https://codeload.github.com/spiraldb/metronome-sdk/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spiraldb%2Fmetronome-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35111429,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-04T05:35:46.079Z","updated_at":"2026-07-04T05:35:46.561Z","avatar_url":"https://github.com/spiraldb.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# metronome-sdk\n\n[![crates.io](https://img.shields.io/crates/v/metronome-sdk.svg)](https://crates.io/crates/metronome-sdk)\n[![docs.rs](https://img.shields.io/docsrs/metronome-sdk)](https://docs.rs/metronome-sdk)\n[![License: MIT](https://img.shields.io/crates/l/metronome-sdk.svg)](LICENSE)\n\nAn unofficial Rust client for the [Metronome](https://metronome.com) billing API,\ngenerated from Metronome's published OpenAPI spec with\n[progenitor](https://github.com/oxidecomputer/progenitor).\n\n\u003e Metronome doesn't ship a Rust SDK (only TypeScript and Go). This crate\n\u003e generates a typed, async client (`reqwest` + `serde`) from their OpenAPI spec.\n\u003e `src/lib.rs` is **generated — do not hand-edit it.** Re-run `./scripts/generate.sh`\n\u003e instead.\n\n## Usage\n\nMetronome uses bearer-token auth. progenitor doesn't inject the token itself, so\nattach it as a default header on the `reqwest::Client` and hand that to the\ngenerated client:\n\n```rust\nuse std::str::FromStr;\nuse metronome_sdk::Client;\nuse metronome_sdk::types::IngestV1BodyItem;\n\nfn metronome(token: \u0026str) -\u003e Client {\n    let mut headers = reqwest::header::HeaderMap::new();\n    let mut auth = reqwest::header::HeaderValue::from_str(\u0026format!(\"Bearer {token}\")).unwrap();\n    auth.set_sensitive(true);\n    headers.insert(reqwest::header::AUTHORIZATION, auth);\n\n    let http = reqwest::Client::builder()\n        .default_headers(headers)\n        .build()\n        .unwrap();\n\n    Client::new_with_client(\"https://api.metronome.com\", http)\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    let client = metronome(\u0026std::env::var(\"METRONOME_API_KEY\")?);\n\n    // Ingest a usage event. The id/customer/type fields are validated newtypes,\n    // so build them with FromStr (or TryFrom) rather than a bare String.\n    let events = vec![IngestV1BodyItem {\n        transaction_id: FromStr::from_str(\"txn-0001\")?,\n        customer_id: FromStr::from_str(\"cust_abc123\")?,\n        event_type: FromStr::from_str(\"api_request\")?,\n        timestamp: \"2026-06-15T00:00:00Z\".to_string(), // RFC 3339\n        properties: serde_json::json!({ \"tokens\": 4096 })\n            .as_object().unwrap().clone(),\n    }];\n    client.ingest_v1(\u0026events).await?;\n\n    Ok(())\n}\n```\n\nEvery Metronome endpoint is available as an `async` method on `Client` (119 in\ntotal) — e.g. `ingest_v1`, `search_events_v1`, `create_billable_metric_v1`.\nRequest/response types live under `metronome_sdk::types`. Constrained string\nfields are generated as validated newtypes; construct them with\n`FromStr`/`TryFrom` (both return a `ConversionError` if validation fails).\n\n## Regenerating\n\n```bash\ncargo install cargo-progenitor --version 0.14.0   # one-time\n./scripts/generate.sh             # regenerate from the vendored spec/\n./scripts/generate.sh --refresh   # re-download the spec from Metronome, then regenerate\n```\n\n### Why the spec needs patching\n\nMetronome's published OpenAPI spec has constructs that progenitor/typify can't\nconsume directly (the official Go/TS SDKs use a private Stainless overlay we\ndon't have). `scripts/patch_spec.py` applies three mechanical, deterministic\nfixes before generation:\n\n1. **Case-insensitive enums → `string`** (618 of them). The spec enumerates\n   every casing of each value (`count`/`Count`/`COUNT`), which can't map to\n   unique Rust variants. Downgrading to `string` round-trips losslessly\n   regardless of the casing the API returns.\n2. **Mis-typed string enums → `type: string`** (7). Some enums declare\n   `type: object` despite having string values (e.g. `billable_status`).\n3. **Stray scalar `format` on array/object nodes stripped** (2). e.g. a\n   `type: array` carrying `format: uuid`.\n\nIf a future spec revision introduces a new unsupported construct, generation\nwill fail loudly; extend `patch_spec.py` with a new rule.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspiraldb%2Fmetronome-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspiraldb%2Fmetronome-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspiraldb%2Fmetronome-sdk/lists"}