How the Billing Subscription System Tracks User Usage Credits in Open‑SEO
Open‑SEO employs a dual-balance ledger system that atomically tracks monthly allocation credits and purchased top-up credits, deducting from the monthly pool first before drawing from rollover reserves whenever a billable operation is invoked.
The every-app/open-seo repository manages API consumption costs through a deterministic credit-gating mechanism defined in src/server/billing/subscription.ts. This billing subscription system tracks user usage credits by maintaining separate database counters for plan allowances and purchased supplements, enforcing strict validation before executing expensive DataForSEO or LLM calls.
Dual-Credit Balance Architecture
At the database layer, the system maintains two distinct integer counters for each organization in the billing schema:
usage_credits– Represents the monthly allocation of free credits provided by the organization's current subscription tier. This balance resets periodically according to the billing cycle.topup_credits– Represents rolled-over or purchased top-up credits that supplement the monthly allowance. These credits persist across billing cycles until consumed.
The schema definition in src/db/billing.schema.ts enforces these fields as non-negative integers, ensuring that credit balances cannot drift into negative values through database constraints.
Credit Consumption Workflow
When a feature such as rank checking or keyword research initiates a billable operation, it invokes the consumeCredits helper exported from src/server/billing/subscription.ts. This function implements the core tracking logic through a three-phase process.
Balance Retrieval and Validation
The helper first queries the organization's current balances using an internal retrieval function:
// Simplified logic from subscription.ts
async function getCredits(orgId: string) {
const org = await db.billing.findUnique({ where: { orgId } });
return {
usageCredits: org.usage_credits,
topupCredits: org.topup_credits,
totalCredits: org.usage_credits + org.topup_credits,
};
}
The system calculates the total available credits by summing both pools. If the requested operation requires N credits and the total available is less than N, the function immediately throws an INSUFFICIENT_CREDITS error without modifying the database.
Deduction Priority and Atomic Updates
When sufficient credits exist, the system decrements balances according to a strict priority order:
- Deduct from
usage_creditsfirst – The monthly allocation serves as the primary spending pool. - Deduct remainder from
topup_credits– If the monthly balance is insufficient to cover the full cost, the remaining amount is subtracted from the top-up pool.
This sequential deduction ensures that organizations utilize their recurring allowances before consuming purchased rollover credits. The database updates occur within the same transaction to prevent race conditions during concurrent API calls.
Error Handling and User Feedback
If the credit gate fails, the system throws an INSUFFICIENT_CREDITS error defined in the billing module. This error propagates to the client layer where src/client/lib/error-messages.ts maps it to a user-facing message: "You've run out of credits. Add more credits or upgrade your plan to continue." This prevents partial operation execution and ensures users receive immediate feedback on quota exhaustion.
Integration with Feature Workflows
All credit-consuming endpoints in the Model Context Protocol (MCP) tool layer invoke the consumption helper before making external API requests. For example, the rank-checking workflow calls:
import { consumeCredits } from '@/server/billing/subscription';
async function runRankCheck(orgId: string, requiredCredits: number) {
// Validates and deducts credits atomically
await consumeCredits(orgId, requiredCredits, 'rank_check');
// Proceeds only if credits were available
const results = await dataForSeoClient.getRankings(...);
return results;
}
Similar patterns appear in src/server/mcp/tools/research-keywords.ts, src/server/mcp/tools/get-backlinks-profile.ts, and other billable feature implementations. Each tool specifies the operation name (e.g., 'rank_check', 'keyword_research') to enable granular telemetry tracking.
Telemetry and Auditing
The consumeCredits function emits structured telemetry events to support usage auditing and alerting:
usage:credits_consume– Fired when credits are successfully deducted, including the organization ID, amount consumed, and operation type.usage:credits_gate_refused– Fired when an operation is blocked due to insufficient credits, enabling the platform to trigger notifications when organizations approach their limits.
These events allow the billing system to maintain an accurate audit trail of credit consumption patterns across the platform.
Summary
- Dual-balance tracking – Open‑SEO maintains separate
usage_credits(monthly) andtopup_credits(rollover) counters for each organization. - Priority deduction – The billing subscription system draws from monthly allocations first, then supplements with top-up credits when tracking user usage credits consumption.
- Atomic validation – The
consumeCreditshelper insrc/server/billing/subscription.tsvalidates sufficient balances before executing billable operations. - Graceful degradation – Insufficient credits trigger immediate
INSUFFICIENT_CREDITSerrors with user-friendly messages fromsrc/client/lib/error-messages.ts. - Full auditability – Telemetry events capture every credit consumption and refusal for monitoring and alerting purposes.
Frequently Asked Questions
How does the system decide which credit balance to use first?
The billing subscription system always deducts from the usage_credits balance first, as implemented in the consumption logic within src/server/billing/subscription.ts. Only if the monthly allocation is insufficient to cover the operation cost does the system draw the remaining amount from topup_credits. This priority ensures that recurring plan allowances are consumed before purchased rollover credits.
What happens when a user runs out of both credit types?
When the sum of usage_credits and topup_credits falls below the required amount for an operation, the consumeCredits function throws an INSUFFICIENT_CREDITS error. This prevents the external API call from executing, and the user receives a message from src/client/lib/error-messages.ts stating they must add more credits or upgrade their plan to continue.
Can credit balances go negative if multiple requests happen simultaneously?
No. The database schema defined in src/db/billing.schema.ts enforces non-negative integer constraints on both credit fields. Additionally, the consumption logic performs atomic read-modify-write operations or uses database transactions to ensure that concurrent requests cannot overdraw an organization's balance, preventing negative credit scenarios.
Where can I find examples of features that consume credits?
All billable features invoke the consumption helper before executing external calls. Examples include the rank-tracking service in src/server/features/rank-tracking/services/RankTrackingService.test.ts, which includes test cases for insufficient credit handling, and the MCP tools directory at src/server/mcp/tools/, containing implementations for keyword research, backlink analysis, and SERP fetching that each call consumeCredits.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →