How GSTExtract Built Concurrency-Safe Metered Billing for Its API
A solo founder details the 'reserve, settle, refund' pattern for usage-based billing and the atomic database fix required to prevent a critical race condition that allowed users to overspend. A user…
A solo founder details the 'reserve, settle, refund' pattern for usage-based billing and the atomic database fix required to prevent a critical race condition that allowed users to overspend.
A user with one credit left must never get two extractions. For GSTExtract, a solo-built SaaS that bills per invoice, this rule exposed a critical race condition in its metered billing system. The founder's public breakdown details a common but dangerous bug in usage-based products, where the cost of a task is only known after it completes.
The 'reserve, settle, refund' pattern
GSTExtract charges users per invoice extracted from uploaded PDF files. The core problem is that the number of invoices in a given PDF is unknown until after an expensive AI vision model has processed it. Charging a fixed fee upfront is inaccurate, and charging after the fact opens a window for users with a zero balance to consume resources.
The founder, writing as Ritusmita Kaushik, reports implementing a three-step ledger pattern. First, the system reserves a single credit before starting the extraction. This acts as a gate; if the user has no credits, the job is rejected. Second, after a successful extraction, the system settles the final bill by charging for the actual number of invoices found, minus the one credit already reserved. Third, if the extraction fails, the system refunds the reserved credit. This ensures users are never charged for failed jobs.
A race condition on the last credit
The initial implementation of this logic contained a concurrency flaw. The application code would first read the user's credit balance from the database. Then, within the Python code, it would check if the balance was sufficient. If it was, the code would proceed with the job and later write the new, decremented balance back to the database.
This read-modify-write sequence is not atomic. The founder provides a specific failure case: a user with exactly one credit double-clicks the upload button, sending two near-simultaneous requests. Request A reads the balance (1) and approves the job. Before it can write the new balance (0), Request B also reads the balance (still 1) and also approves its job. Both requests succeed. The user receives two extractions for a single credit, and the final database state is inconsistent.
The fix is an atomic database operation
The bug was not in the logic, but in its implementation. The fix for this type of race condition is to push the check-and-decrement operation into a single, atomic database transaction. Instead of the application reading the value, making a decision, and writing a new value, it sends one command to the database.
This is typically done with a conditional UPDATE statement, such as UPDATE credits SET remaining = remaining - 1 WHERE user_id = ? AND remaining >= 1. The database guarantees that this entire operation is atomic. The application then checks how many rows were affected by the command. If one row was affected, the credit was successfully debited. If zero rows were affected, it means the WHERE clause failed because the user had no credits left, and the application can safely reject the job.
What We'd Change
The founder’s breakdown is a clear technical document. The described pattern is a robust, standard solution for this class of problem. The primary critique is not of the implementation, but of the strategic choice to build it from scratch.
Modern billing platforms like Stripe Metered Billing or dedicated metering services like Togai are built to handle these concurrency issues out of the box. For a solo founder, the opportunity cost of building, debugging, and maintaining custom billing logic is high. While GSTExtract’s use case is specific, it is not unique. A third-party solution would have outsourced this exact race-condition problem, along with dunning, invoicing, and tax compliance.
The decision to self-host billing infrastructure implies a strategic choice. It could be driven by a need for absolute control over the pricing model, a desire to avoid vendor transaction fees on a low-ARPU product, or simply a founder's technical interest. For others following this playbook, the first step should be a rigorous build-versus-buy analysis. The time spent solving race conditions is time not spent on customer acquisition.
Landing
The integrity of a billing ledger is absolute. A single error can corrupt user trust and create unrecoverable accounting debt. While "build in public" narratives often focus on product features and growth metrics, this post from the GSTExtract founder is a valuable look at the less glamorous infrastructure work required to build a durable business. It demonstrates that for SaaS products, correctness is not a feature but a prerequisite. The most dangerous bugs are not those that crash the server, but those that silently corrupt the state of the system.
The investor read
The technical depth shown in building a custom, concurrency-safe billing ledger is a positive signal about the founder's engineering capability. However, it's also a potential red flag. Investors would question the decision to build versus buy, as platforms like Stripe handle this complexity. This choice can signal a bootstrapped, margin-focused strategy, but also a potential bottleneck to scaling or a misallocation of founder attention away from growth. For the business to be investable, this custom infrastructure would need to be justified as a core competitive advantage or a significant cost-saver, not just a technical preference.
Pull quote: “A user with one credit left must never get two extractions.”
Every claim ties to a primary source. See our methodology.