quotastack Docs
Docs / Cookbook / End-to-End Billing Setup: From Zero to Working Credits in 10 Steps

End-to-End Billing Setup: From Zero to Working Credits in 10 Steps

Complete walkthrough building a working billing system with Acme Chat — metric, metering rule, plan, variant, customer, subscription, and usage in one linear sequence.

COMPLETE BILLING SETUP · 10 STEPS

Mental model — The full journey from zero to working billing in ten API calls: create a metric, set pricing, build a plan, subscribe a customer, and watch credits flow. Every step shows the exact API call and its response. Ten calls, zero ambiguity.

Quick take

  • Every billing setup follows the same sequence: metric → rule → plan → variant → grant template → customer → subscribe → check → use → verify
  • Metering rules price the metric; credit grants fund the customer; entitlement checks gate access
  • Usage events are fire-and-forget (202 Accepted); balance updates are eventually consistent within seconds
  • Idempotency keys on every mutating call protect against network retries

End-to-End Billing Setup

Pattern: the full billing pipeline, one step at a time.

The goal

Go from an empty QuotaStack tenant to a working billing system where a customer has a subscription, credit grants fire automatically, usage debits credits, and entitlement checks gate access.

We’ll use Acme Chat (a chat companion app) as the running example. By the end, Alice (a customer) will have a weekly subscription that grants 50 credits, and each chat message will cost 1 credit.

Phase 1: Define what you charge for

Step 1: Create a billable metric

A billable metric is a named action you charge for. Acme Chat charges per message:

POST /v1/billable-metrics
Idempotency-Key: metric:chat_message

{ "key": "chat_message", "name": "Chat Message" }

The key is the stable identifier you’ll reference everywhere — metering rules, usage events, entitlement checks. It defaults to type: metered (credit-based).

Step 2: Set the price with a metering rule

POST /v1/metering-rules
Idempotency-Key: rule:chat_message

{ "billable_metric_key": "chat_message", "cost_type": "per_unit", "unit_cost": 1000 }

1,000mc per unit = 1 credit per message. Now QuotaStack knows: when a usage event arrives for chat_message, debit 1,000mc from the customer’s balance.

Phase 2: Build the plan

Step 3: Create a plan and variant

The plan is the product (“Acme Chat”). The variant is the specific pricing tier (“Weekly Standard” — prepaid, weekly billing cycle):

POST /v1/plans
Idempotency-Key: plan:acme-chat

{ "name": "Acme Chat", "description": "Companion chat subscription plans" }
POST /v1/plans/{plan_id}/variants
Idempotency-Key: variant:weekly-standard

{ "name": "Weekly Standard", "billing_cycle": "weekly", "billing_mode": "prepaid" }

Step 4: Attach a credit grant template

Tell QuotaStack to automatically grant 50,000mc (50 credits) every week:

POST /v1/plans/{plan_id}/variants/{variant_id}/credit-grants
Idempotency-Key: grant-template:weekly-50k

{
  "credits": 50000,
  "grant_interval": "billing_cycle",
  "grant_type": "recurring",
  "expires_after_seconds": 604800,
  "rollover_percentage": 0,
  "priority": 0
}

expires_after_seconds: 604800 = 7 days. Each grant expires when the next one arrives — no hoarding. rollover_percentage: 0 means unused credits don’t carry over.

At this point you have a complete plan: metric → pricing → variant → automatic grants. Nothing has happened yet because no customer is subscribed.

Phase 3: Onboard a customer

Step 5: Create a customer

POST /v1/customers
Idempotency-Key: customer:user_alice

{ "external_id": "user_alice", "display_name": "Alice" }

Step 6: Subscribe them

For a manual flow, start the subscription after your provider confirms Alice paid:

POST /v1/subscriptions
Idempotency-Key: sub:alice-weekly-001

{ "external_customer_id": "user_alice", "plan_variant_id": "{variant_id}" }

On activation, QuotaStack immediately fires the credit grant template — Alice now has 50,000mc (50 credits). The subscription status is active, with current_period_end set to one week from now.

With a payment connector, use hosted checkout for the mapped prepaid plan. QuotaStack starts the subscription after it can prove payment. Do not also call this manual route.

Phase 4: Use it

Step 7: Check entitlement before each message

GET /v1/customer-by-external-id/user_alice/entitlements/chat_message
{
  "allowed": true,
  "balance": 50000,
  "estimated_cost": 1000,
  "balance_after": 49000
}

allowed: true means Alice can send a message. balance_after: 49000 is what her balance will be afterward. If allowed were false, your UI shows “you’ve used all your messages this week.”

Step 8: Record usage

Alice sends 5 messages. Your backend fires a usage event for each (or batches them):

POST /v1/usage
Idempotency-Key: usage:msg_001

{
  "external_customer_id": "user_alice",
  "billable_metric_key": "chat_message",
  "units": 5,
  "idempotency_key": "msg_batch_001"
}

Returns 202 Accepted — usage events process asynchronously. 5 × 1,000mc = 5,000mc will be debited within seconds.

Step 9: Verify the balance

GET /v1/customer-by-external-id/user_alice/credits
{
  "balance": 45000,
  "reserved_balance": 0,
  "pending_balance": 0,
  "effective_balance": 45000,
  "lifetime_earned": 50000
}

lifetime_earned still reads 50,000 — it counts every credit ever granted and never goes down. The balance is what dropped: 50,000 − 5,000 = 45,000mc. Alice has 45 messages left this week.

To see what she has consumed, read the ledger at GET /v1/customer-by-external-id/user_alice/credits/history?type=consumption. There is no lifetime_consumed field on the account.

What you’ve built

chat_message metric     → "what we charge for"
  ↓ priced by
per_unit metering rule  → "1,000mc per message"
  ↓ used by
Weekly Standard variant → "50,000mc/week, expires in 7 days"
  ↓ subscribed by
Alice                   → "active, 45,000mc remaining"
  ↓ gated by
entitlement check       → "allowed: true"
  ↓ consumed by
usage events            → "5 messages = 5,000mc debited"

The metric defines WHAT you track. The metering rule defines HOW MUCH it costs. The plan variant defines WHO gets credits and when. The subscription binds a customer to a variant. Entitlements gate access. Usage events consume credits.

Next steps

  • Add variable companion pricing — charge premium companions more by varying the units field
  • Set up webhooks to react to subscription.renewal_due and credit.consumed events
  • Add a wallet for pay-as-you-go overflow when weekly credits run out
  • Use reservations for long-running AI calls that might fail mid-generation

Concepts used