Preview. Usage Billing v1 is in development, and demos run on sample data.Read the guide

Stop building billing.Metering, quotas, per-user balances and top-ups in one API.

Kestrel LabsServing

Customer balance

7,400credits
Allowed
0
Denied
0
On hold
0
3 req/s
  1. authorize req_8841 allow -312
  2. authorize req_8840 allow -96
  3. authorize req_8839 allow -188

Simulated in this page. No network calls.

  • Metering every event once

  • Contracts every customer's price

  • Balances per customer and per member

  • Quotas checked before the model runs

  • Retries safe to send twice

  • Top-ups one crossing and one charge

  • Statements final means final

  • Portal self-serve and scoped

  • Budgets company, pool and member

  • Errors a code for every failure

You set out to ship a model.

Then a customer asked for a quota.

Then finance asked for an invoice.

None of that is your product.

So we run it.

Skip to the summary

What you stop buildingEach job below leaves your backlog.

  1. Event pipeline and dedupe
  2. Per-contract pricing
  3. Per-user balance ledger
  4. Real-time quota checks
  5. Budgets and spend limits
  6. Auto top-up and retries
  7. Monthly statements
  8. Customer portal

0 of 8 off your backlog

Eventsexactly once
1,204,318events counted this cycle1 client retry dropped

Meter it Every call becomes one event, and a client retry is counted once.

Rate cardcontract overrides

video.generate640 seconds× 210 credits

Kestrel Labsrate card

134,400credits

Halden18% contract discount

110,208credits

Same usage.Different contract.No code change.

Price it Each customer pays their contract rate, so a negotiated deal needs no code.

Balancesper customer, per member
Kestrel Labs total1,975

req_3112 yuki215 creditsprepaid 215

  • Inesown seat600
  • Tomasown seat300
  • Yukiown seat0
  • Team poolshared0
  • Prepaidshared1,075

Track it A live balance for every customer and every member, drawn down in a fixed order.

POST /v1/authorizebefore the model runs
Stopped
  1. req_1042180 creditsallowed
  2. req_1043240 creditsallowed
  3. req_1044200 creditsinsufficient_balance

balance left 80

Gate it The check runs before your model does. At the limit the request stops, and nothing is spent.

Budgetscompany, pool, member
Tomas stopped.

member_spend_limit_reached

Ines and Yuki keep working.

  1. 2,4001,500Company
  2. 1,8001,500Pool
  3. 900300Inesallowed
  4. 900900Tomasdenied
  5. no limit300Yukiallowed

Cap it Limits for the company, the pool and each member. Only the person at the limit stops.

Auto top-upone crossing, one charge

4requests crossed

1top-up opened

6,720credits in the balance

Recharge to 8,000Threshold 3,0003 saw it open5,750 credits charged

Refill it When the balance crosses your threshold, one top-up runs, however many requests cross it together.

StatementOctober

Kestrel LabsOct 1 to Oct 31

  1. Standard seats, 8 at $30.00$240.00
  2. Seat added Oct 17, 15 of 31 days$14.52
  3. Overage, 4,180 credits at $0.01$41.80
Total$296.32
Finalized

Corrections go on a credit note.

Bill it Seats, credits and overage close into one statement. Finalized means final.

Portal tokenscoped to one customer

Self-servePricing stays out of reach.

Token for Kestrel Labsbalance:readbudgets:writetop_up:write
  1. Reload thresholdwritable
  2. Member limitswritable
  3. Contract discount403 portal_scope_violation

Hand it over Customers manage their own budgets and top-ups through scoped tokens.

Two calls at runtime.Set up customers and contracts once.

Waffo runs

  • Event pipeline and dedupe
  • Per-contract pricing
  • Per-user balance ledger
  • Real-time quota checks
  • Budgets and spend limits
  • Auto top-up and retries
  • Monthly statements
  • Customer portal

You keep

  • Your model
  • Your product
  • Your prices
  • Your customers
run-job.tsSDK draft
// Call 1. Ask before the model runs.
const decision = await billing.authorize({
  request_id: job.id,
  customer_id: "cus_kestrel",
  seat_id: "ines",
  product_id: "video.generate",
  estimated_quantity: "12",
});// Branch on the answer.
if (!decision.allowed) {
  return reject(decision.reason);
}// Run your model.
const used = await runModel(job);// Call 2. Report the actual quantity.
await billing.ingest({
  events: [{
    transaction_id: job.id,
    customer_id: "cus_kestrel",
    seat_id: "ines",
    event_type: "video.generate",
    timestamp: new Date().toISOString(),
    properties: { seconds: String(used) },
    hold_id: decision.hold_id,
  }],
});

Try the rules yourself.

Each demo below runs the same billing model that drives this page.

Every customer gets their own price

Rates, discounts and fixed prices live on the contract, so a negotiated deal needs no code.

One rate card

Set list prices once, by product and model.

Overrides on the contract

Add a percentage discount or a fixed price. The two customers above share a rate card and get different bills.

Exact arithmetic

Usage is rated once, with one rounding rule. Money is a decimal string or an integer, and floats are rejected.

One request, four pockets

Each request draws from seat credit, then the team pool, then prepaid balance, then overage.

Seats carry credit

A seat tier sets the included credit each member gets every month. Tier credit can be zero.

A shared pool

When a seat runs out, the request moves to the pool the whole team shares.

Overage is opt-in

Overage is off by default. Turn it on per contract.

Checked before it runs, settled after

Authorize holds the estimate. Reporting usage swaps the hold for the actual cost.

Holds stop races

Concurrent requests count held amounts, so two jobs cannot spend the same credit.

Failed tasks cost nothing

Release the hold and every pocket gets its credit back.

Retries are safe

Authorize twice with one request id and you get the first decision back. Try it with the Retry button.

Top up before anyone notices

When the balance drops below your threshold, one payment refills it. The liquid below is the balance.

One crossing, one charge

Any number of requests can cross the threshold together. Only one top-up opens. Press Race to see it.

Failed cards

A failed payment leaves the balance alone and tries again on the next check, so nobody is charged twice.

Frequency limits

Cap top-ups per window, so a runaway job cannot empty a card.

Limits at every level

Set a monthly budget for the company, a quota for the pool and a limit for each member.

Only one person stops

A member at their limit is blocked. Everyone else keeps working.

Takes effect at once

Lower a budget below current spend and the next request stops.

Hard or soft

Stop requests at the limit, or keep serving and send an alert.

A statement that explains itself

Every month closes into one statement, and each line opens to the usage behind it.

Finalized means final

A closed statement never changes. Corrections are credit notes.

Late events still count

Usage that arrives after close moves to the next statement, labeled with the day it happened.

Collect your way

Statements go to your payment provider or your own invoicing.

Give customers the controls

Your customers manage their own budgets and top-ups through scoped portal tokens.

Scoped tokens

A portal token covers one customer and a short list of fields. Pricing stays out of reach.

API first

Everything the portal can do is an API call, so it fits inside your product.

Built for the bad day

Every error has a code, an HTTP status and a retry answer, and says what was charged.

Denials are answers

A denied request returns 200 with a reason, so your code can branch on it.

Every write is idempotent

Send the same Idempotency-Key again and the first result comes back.

Fails closed

If the money path is down, included credit still works and prepaid spend stops.

Start with the guide

See how the pieces fit, then read the contract for each endpoint.