Invoices and credits
The billing cycle, invoice structure, credits, payment methods, dunning and disputes.
The cycle
Finalisation is deliberately two hours after the period closes, so late-arriving usage events from a node that was buffering are included rather than spilling into the next period.
Invoice structure
curl -sS "$WB_BASE_URL/invoices/inv_8Pq2Vd?expand=lines" -H "Authorization: Bearer $WB_API_KEY"
{
"id": "inv_8Pq2Vd",
"object": "invoice",
"organization_id": "org_2kQ9xw",
"period_start": "2026-09-01",
"period_end": "2026-09-30",
"status": "paid",
"currency": "USD",
"lines": [
{
"meter": "tier2.session_s",
"description": "Tier 2 CLI agent session time",
"quantity": 18442000,
"unit": "s",
"unit_price_minor": 0.0062,
"amount_minor": 114340,
"discount_minor": -17151,
"breakdown_by": "project",
"breakdown": [
{ "project_id": "proj_7fTn21", "name": "production", "quantity": 14002000, "amount_minor": 86812 },
{ "project_id": "proj_3Lm9Kd", "name": "staging", "quantity": 4440000, "amount_minor": 27528 }
]
},
{ "meter": "egress_gb", "quantity": 4128.4, "unit_price_minor": 1.0, "amount_minor": 4128,
"free_allowance_applied": 100 }
],
"subtotal_minor": 1284200,
"discounts_minor": -192630,
"credits_applied_minor": -100000,
"tax_minor": 213156,
"total_minor": 1204726,
"finalized_at": "2026-10-01T02:00:00Z",
"due_at": "2026-10-31T00:00:00Z",
"pdf_url": "https://api.workspaces.build/v1/invoices/inv_8Pq2Vd/pdf"
}
Lines break down by project by default. Request breakdown_by=end_user or breakdown_by=region for
a different cut.
Immutability
A finalized invoice never changes. Corrections are issued as credit notes against it:
{
"id": "cn_4Jd8Pq",
"object": "credit_note",
"invoice_id": "inv_8Pq2Vd",
"amount_minor": -42000,
"reason": "sla_credit",
"detail": "Tier 2 availability 99.71% in bom1, below the 99.9% SLO. 10% service credit."
}
This is what keeps a past invoice explainable from the rated usage that produced it. An edited invoice would mean the stored rated usage no longer reconciles with the amount charged.
Credits
curl -sS "$WB_BASE_URL/credits" -H "Authorization: Bearer $WB_API_KEY"
{
"balance_minor": 420000,
"currency": "USD",
"grants": [
{ "id": "cred_4Jd8Pq", "amount_minor": 500000, "remaining_minor": 420000,
"reason": "annual_commit", "granted_at": "2026-01-01T00:00:00Z",
"expires_at": "2027-01-01T00:00:00Z" },
{ "id": "cred_9Km2Xp", "amount_minor": 50000, "remaining_minor": 0,
"reason": "sla_credit", "expires_at": null }
]
}
| Reason | Source |
|---|---|
prepaid_purchase | Bought credits |
annual_commit | Committed-use agreement |
promotional | Sales or marketing grant |
sla_credit | SLO breach, applied automatically |
goodwill | Support resolution |
refund | Returned payment |
Credits apply before any invoiceable usage, oldest-expiring first, so a grant about to lapse is consumed before one that is not. Expired credits are forfeited and appear as an expiry entry rather than disappearing.
Prepaid
An organization in prepaid mode consumes credits in real time. At zero balance, POST /sessions
returns 402 insufficient_credits.
curl -sS "$WB_BASE_URL/credits/auto-recharge" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "enabled": true, "threshold_minor": 50000, "amount_minor": 500000 }'
Auto-recharge is strongly recommended for prepaid production workloads. Without it, the failure mode
is every session create failing at once with no warning other than the credit.low webhook.
Payment
| Mode | Who | Mechanics |
|---|---|---|
prepaid | Self-serve, trials | Buy credits up front, consumed in real time |
postpaid | Most customers | Card charged at finalisation |
invoiced | Enterprise | Net 30, bank transfer, PO reference supported |
curl -sS "$WB_BASE_URL/payment-methods" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "type": "card", "token": "tok_…", "default": true }'
Card details never touch the platform — the token comes from the payment processor's client SDK.
Dunning
day 0 payment fails -> invoice.payment_failed webhook, email to billing contacts day 1 retry day 3 retry day 7 retry; organization status -> past_due; dashboard banner day 14 retry; new session creation blocked day 21 retry; running sessions stopped day 30 organization suspended; data retained day 90 data deletion begins after written notice
Each step is reversible by paying. Nothing is deleted before day 90, and deletion requires written notice first — a lapsed card should not destroy a customer's volumes and snapshots.
Disputes
curl -sS "$WB_BASE_URL/invoices/inv_8Pq2Vd/dispute" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{
"lines": ["tier4.session_s"],
"reason": "Sessions were running during the 2026-09-14 incident and could not be used.",
"amount_minor": 42000
}'
Raising a dispute pauses dunning for the disputed amount. The undisputed remainder stays due.
To investigate before disputing, pull the session-granularity export for the period — it contains every session with its meters and cost, which is almost always enough to find the cause.
curl -sS "$WB_BASE_URL/usage/export?period=last_month&granularity=session&format=jsonl" \ -H "Authorization: Bearer $WB_API_KEY" -o sep.jsonl
# the usual answer: sessions that idled out instead of being deleted
jq -r 'select(.ended_reason == "idle_timeout") | .amount_minor' sep.jsonl \
| awk '{s+=$1} END {print s/100 " USD lost to idle timeouts"}'
Tax
Computed per jurisdiction at finalisation. Registered customers can supply a VAT, GST or equivalent identifier for reverse charge where applicable.
curl -sS -X PATCH "$WB_BASE_URL/organizations/org_2kQ9xw" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "tax_id": { "type": "in_gst", "value": "29AABCU9603R1ZJ" }, "billing_address": { … } }'
Add the tax ID before the period closes. Retroactive tax adjustment on a finalized invoice requires a credit note and a reissue.
Reporting
# monthly totals for the year curl -sS "$WB_BASE_URL/invoices?limit=12" -H "Authorization: Bearer $WB_API_KEY" # current period, live curl -sS "$WB_BASE_URL/usage?period=month_to_date&group_by=meter" -H "Authorization: Bearer $WB_API_KEY" # forecast from trailing burn curl -sS "$WB_BASE_URL/usage/forecast?period=month" -H "Authorization: Bearer $WB_API_KEY"
{
"period_end": "2026-10-31T23:59:59Z",
"spend_to_date_minor": 184220,
"forecast_minor": 912400,
"forecast_method": "trailing_7d_rate",
"confidence": "medium",
"spend_cap_minor": 500000,
"projected_cap_breach_at": "2026-10-19T04:00:00Z"
}
The forecast is a straight-line projection from the trailing seven days. It is wrong for workloads
with strong weekly seasonality, which most agent workloads have — treat projected_cap_breach_at as
an early warning, not a date.