Why "just add credits" becomes a full-blown sub-system
What starts as a single column in the database quickly grows into plans, grants, expirations, and more. Before you know it you've got a whole product inside your product. A guide for developers.
If you're building an API, a SaaS product, or any app that meters usage, the naive approach is tempting: add a credits column to your users table, decrement it when they call your API, and call it a day. A few days of work, a simple dashboard, and you're done. Right?
Not so fast
In practice, that single column is only the beginning. Before long you need:
- Plans: free vs. pro vs. enterprise
- Grants: one-off top-ups, promotional credits, support gestures
- Expirations: use-by dates, monthly resets
- Rollovers: what happens to unused credits?
- Refunds: support gave a customer their money back; now what?
Each of these touches your schema, your background jobs, your support tooling, and your API contract. What looked like a one-week feature becomes a sub-system that never quite stops growing.
This article walks through why "just add credits" balloons into a full-blown credits and plans system, what that system actually entails, and how to think about build vs. buy so you can ship your real product faster.
The naive model and where it breaks
The minimal implementation is straightforward: one numeric column, one decrement per use, and a check before allowing the action.
For a side project or an internal tool, that can be enough. The moment you have paying customers, multiple tiers, or compliance expectations, the model breaks down.
Plans and tiers
Free users get 100 credits/month; pro users get 10,000.
Questions that arise:
- Do you store "plan" and "credits" and reset credits on a schedule?
- What's a "month"—calendar month? rolling 30 days? which timezone?
- What happens when they upgrade or downgrade mid-cycle?
The schema grows: you're not just storing a number, you're storing which plan, when it started, and when it renews.
Grants and one-off credits
Real-world scenarios:
- A customer had an outage and you give them 500 credits
- An enterprise deal includes 50,000 upfront credits
- A promo gives "double credits this month"
If all you have is a single balance, you can't:
- Tell grant credits from plan credits
- Expire the promo credits separately
- Report on "how many credits did we give away in Q1?"
You need a ledger: who gave how many credits, when, until when, and for what reason.
Expirations and rollovers
Policy decisions multiply:
- Credits that expire at month-end vs. credits that never expire
- "Use oldest first" vs. "use promo first"
Each policy affects how you store and query data. Time zones and "midnight" boundaries introduce bugs that only show up in production when a user in Tokyo hits the API at 00:01 their time and gets a different result than you expected.
Refunds and support
A customer disputes a charge. You refund them. Now what?
- Do you claw back credits?
- Prorate?
- What if they've already used some?
You need rules, auditability, and often a way for support to manually adjust balances without running raw SQL.
The reality: it's a subsystem
Once you add plans, grants, expirations, and manual adjustments, you're no longer maintaining a counter. You're maintaining a credits and plans sub-system: a domain with its own data model, business rules, background processes, and API surface.
That's a whole product inside your product.
Scope creep is the norm
In the real world, requirements arrive in waves:
- Wave 1: "We need a free and a paid plan"
- Wave 2: "We need to give credits for referrals"
- Wave 3: "Enterprise wants custom limits and an annual grant"
- Wave 4: "Legal wants to know exactly when we granted or revoked credits and who approved it"
The layers accumulate
Each wave adds tables, jobs, and edge cases. The "simple" credits column becomes:
- A ledger of grants and consumptions
- A plan and scheduling layer
- A support and audit layer
The decision tree expands
You're deciding:
- Consumption order (oldest first? plan credits before promo?)
- Time zones for "monthly reset"
- How to expose something coherent to your API and dashboard
Complexity is in the interactions
The real challenge isn't individual features—it's how they interact:
- Upgrades
- Downgrades
- Partial refunds
- Expired grants
- Concurrent usage
All of these have to behave correctly together.
What a real credits system actually includes
A production-ready usage-based credits system typically includes:
1. Balance and ledger
Not just "current balance" but a traceable history of grants and debits (who, when, why), so you can debug disputes and support "show me what happened."
2. Plans and entitlements
Definitions of what each plan includes (allowances, limits, features), when they start and end, and how they interact with one-off grants.
3. Scheduling and renewal
Jobs that apply renewals, expire old credits, send notifications, and handle trial-to-paid or plan changes at the right time. That includes timezone and "end of period" semantics.
4. Support and operations
Safe ways for support to grant or revoke credits, issue refunds, and fix mistakes, with an audit trail so you're not debugging from database dumps.
5. API and developer experience
Clear, consistent endpoints for checking balance, reserving or consuming credits, and previewing the impact of a plan change. That way your own app and your customers can integrate without duplicating business logic.
None of this is optional
Once you have paying customers and multiple tiers, all of these become essential. The question is whether you build it all yourself or adopt a dedicated solution.
This is a separate product
Credits and plans are infrastructure for your real product. The features that differentiate you—your AI, your analytics, your workflow—sit on top of that infrastructure.
The opportunity cost
Building and maintaining the infrastructure diverts engineering time from the work that actually moves the needle:
- Renewals and expiration jobs
- Audit logs for support
- Reconciliation for finance
- Plan and pricing experiments
Many teams discover too late that they're running a second product: the credits system. It doesn't ship in a week; it ships in phases and then keeps evolving (new plan types, new grant types, new reporting, new compliance requirements).
Build vs. buy
If you're weighing build vs. buy, it's worth being explicit about scope. Treat "just add credits" as the first step of a much longer journey. Then decide whether that journey is core to your roadmap or something you'd rather delegate.
credits.dev is built for exactly this: usage-based credits, plans, and grants as an API, so you can ship faster and keep the complexity out of your main codebase. You get a ledger, scheduling, and auditability without turning your team into a billing and credits squad.
That leaves you free to focus on what makes your product unique.