Billing API
Your balance, every movement of money on your account, and your funding history — derived from an append-only record of every movement.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/billing/balance | Ledger-derived balance and lifetime totals. |
GET | /v1/billing/transactions | Append-only ledger history. |
GET | /v1/billing/invoices | Monthly usage statements available on this account. |
GET | /v1/billing/invoices/{number} | One monthly usage statement. |
GET | /v1/billing/funding | Funding-purchase history. |
Account-session authentication, like the usage endpoints.
*_units as an integer, and
*_usd as a decimal string. 1 USD = 100,000,000 units.
Compare and accumulate the integers; display the string. No float ever touches
a balance, here or inside Astrodyne.
Balance
balance_units is derived by summing every entry, not read from a
counter that could drift. The lifetime totals satisfy
funded − spend − refunds + adjustments = balance.
# Balance, derived from the append-only transaction record. curl https://api.astrodyne.ai/v1/billing/balance \ -H "Authorization: Bearer $ASTRODYNE_SESSION_TOKEN" # Transaction history. curl "https://api.astrodyne.ai/v1/billing/transactions?limit=25" \ -H "Authorization: Bearer $ASTRODYNE_SESSION_TOKEN"
import os
import httpx
session = {"Authorization": f"Bearer {os.environ['ASTRODYNE_SESSION_TOKEN']}"}
with httpx.Client(base_url="https://api.astrodyne.ai") as http:
balance = http.get("/v1/billing/balance", headers=session).json()
# Integer units are the source of truth: 1 USD = 100_000_000 units.
# The decimal string is provided so you never have to divide.
print(balance["balance_units"], "units =", balance["balance_usd"], "USD")
history = http.get("/v1/billing/transactions", headers=session,
params={"limit": 25}).json()
for entry in history["data"]:
# type is a closed set: funding, usage, refund, reversal, adjustment.
print(entry["created_at"], entry["type"], entry["amount_usd"])
const session = { Authorization: `Bearer ${process.env.ASTRODYNE_SESSION_TOKEN}` };
const balance = await (await fetch(
"https://api.astrodyne.ai/v1/billing/balance", { headers: session },
)).json();
// Integer units are the source of truth: 1 USD = 100_000_000 units.
console.log(balance.balance_units, "units =", balance.balance_usd, "USD");
const history = await (await fetch(
"https://api.astrodyne.ai/v1/billing/transactions?limit=25", { headers: session },
)).json();
for (const entry of history.data) {
// type is a closed set: funding, usage, refund, reversal, adjustment.
console.log(entry.created_at, entry.type, entry.amount_usd);
}
Transaction history
Entries are append-only: nothing is ever edited or deleted. A correction appears as a new entry, not as a change to an old one.
type is a closed set:
| type | Meaning |
|---|---|
funding | Money you added. |
usage | A settled API charge. reference_id is the request id. |
refund | Money returned to you. |
reversal | A reversed movement. |
adjustment | A manual correction. |
An internal category that does not map to one of these appears as
adjustment rather than leaking a new name, so the set above is
the complete list a client needs to handle.
Funding history
Purchases carry an opaque id, timestamps, status, amounts, and
payment_method. Payment-processor identifiers are deliberately
absent — a payment-intent id is a handle to an object in someone else's
system, and publishing it would be handing out a reference you cannot revoke.
A redelivered payment webhook does not duplicate a purchase: the purchase is the unit, not the event.
Checking the numbers
- Every
usageentry corresponds to exactly one request, and itsreference_idis that request's id. - The sum of
usageentries equals the sum ofcharged_unitsin the usage API, to the unit. - A request whose charge needed review is flagged
needs_billing_reviewin the usage API and still appears here with the amount actually charged.