Teach org billing usage to stop panicking about money#21
Conversation
For its entire existence, `baseten org billing usage` has answered the
question "how much am I spending" by panicking and dumping a goroutine
stack trace at whoever asked. It was a literal
`panic("TODO: implement org billing usage")`—the most honest a stub has
ever been, and completely unshippable.
It now asks the management API how much you owe and prints it in a table.
Users, I'm told, prefer this to the stack trace.
The parts worth knowing:
- Every cost field comes back as either a JSON number or a JSON string,
seemingly per the API's mood. In production you get "0.00"—a string—for
an amount of money. I've stopped asking why; the parser accepts both and
gets on with its day.
- Money and minutes now carry thousands separators, because a billing tool
that renders a real invoice as $1234567.89 is just a confession.
- --since (default 7d, max 31d) or --start/--end, never both. Asking for a
relative and an absolute window at once isn't a query, it's a koan.
- Flag types went from string to time.Duration/time.Time so the window
parsing matches `model deployment metrics` instead of hand-rolling date
math out of spite.
Tested against the live API, where it confirmed I owe nothing—the first
good news this command has delivered in its life.
There was a problem hiding this comment.
Pull request overview
Implements baseten org billing usage by replacing the previous panic("TODO") stub with a real Management API call that validates time windows and renders either JSON (--output json / --jq) or a human-readable usage table.
Changes:
- Add window parsing/validation for
--since(default 7d, max 31d) vs--start/--end(ISO-8601), including an earliest-available cutoff. - Fetch
/v1/billing/usage_summaryfrom the Management API and render usage as JSON or as a per-category table with an optional “All” total row. - Add thorough command tests and extend the management API test harness to capture query parameters.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| internal/cmd/command.org.go | Implements billing usage command logic, API call, table rendering, and money parsing helpers. |
| internal/cmd/command.org_billing_test.go | Adds integration-style tests covering table output, totals arithmetic, JSON output, and flag validation. |
| internal/cmd/command_test.go | Extends mock API call recording to include query parameters for assertions. |
| cmd/command.org.go | Updates the declarative command definition, output schema, examples, and flag types to time.Duration/time.Time. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Two fixes from PR review: - If a category's cost won't parse, the "All" row no longer prints a confident sum that quietly omits it. The affected column shows "-" instead, because a total that silently understates is worse than no total. (Adding 0 was never an undercount, but the displayed sum was still a lie of omission.) - The 2026-01-01 floor is a UTC instant while bare --start/--end parse as local, so the cutoff error and the help text now say so out loud.
There was a problem hiding this comment.
This looks great, thanks! I am not marking approved yet because I have to setup CLA on this repo and get you to click the button. Give me a bit to set that up, thanks.
EDIT: Ok, I have setup CLA. There is a comment below with a CLA link badge. If you could click through CLA to sign, I can merge this. Thanks again!
| // billingMinutes renders an optional minutes count with thousands separators, | ||
| // using "-" for categories (e.g. model APIs) that are billed by tokens rather | ||
| // than time. | ||
| func billingMinutes(minutes *int) string { |
There was a problem hiding this comment.
Some of these tiny helpers could be inlined, but not a big deal since they are reasonably qualified
What this does
baseten org billing usagepreviously responded to being run by panickingand printing a goroutine stack trace. As a billing command this had the
twin virtues of being honest about the codebase's feelings toward finance
and not working at all. It was a
panic("TODO: implement org billing usage")shipped to users.It now returns your usage. From the management API. In a table.
How it behaves
--since(default7d, capped at31d) for a sliding window, or--start/--endfor an explicit ISO 8601 range. Mutually exclusive,because "the last 7 days, but also specifically May 1st through 8th" is
not a window. Won't go back past 2026-01-01, where the data stops anyway.
MINUTES · TOTAL · CREDITS · SUBTOTAL, with anAlltotal row that declines to appearwhen there's only one category to total.
--output jsonfor the raw thing,--jqfor when you only wanted onenumber anyway.
The part you'll want to argue about
The API encodes money as a union of "number or string." Not one.
Either. Per field. In the wild it returns
"0.00"—a string—for a dollaramount. Rather than pick a side and get paged when the other shows up, the
parser eats both. I've made my peace with it; you may need a minute.
Flag types went from
stringtotime.Duration/time.Time. This makesbilling consistent with
model deployment metricsand lets the framework dothe duration and ISO-8601 parsing it already knows. The old string version
was the outlier reinventing the wheel. It does change
OrgBillingUsageFlags,so: flagged, on purpose, in writing.
Diligence, performed
Build, vet, and the full suite pass; lint is clean on the changed files. I
ran it against the live API for the default window, an explicit range,
--jq,--output json, and every validation error, and confirmed thestring-encoded-money branch—the one most likely to detonate later—works on
real responses.
For the #13 author
--sinceis now atime.Duration, so the docs-schema golden will need aregen. The new shape already matches what
logsandmetricsemit, sobilling finally stops being the weird one.