Per-Row Reservation Pattern for Billing Bulk API Batches
A bulk GSTIN verification endpoint reveals why single balance-check billing fails when some rows are free, and how per-row atomic reservation fixes overselling and race conditions.
Building a bulk GSTIN verification endpoint for the solo GST SaaS GSTExtract, the developer found that the working per-invoice billing pattern (reserve, settle, refund) broke down once batches mixed billable and free rows. In a list of up to 50 GST numbers, cached hits, not-found results, and invalid formats are all free, but which rows are billable only becomes clear after the paid lookup runs. A naive approach that reads the balance once and charges for the whole batch afterward oversells credits under concurrent requests.
The fix is to reserve one credit per item via an atomic conditional UPDATE gated on row count, rather than reading and writing the balance separately. If the reservation fails, the paid API is never called for that row, so money is never spent on lookups the user can't cover; if the row turns out not-found, the reservation is quietly refunded instead of counted as abuse. The author also describes a subtler bug: moving the reservation logic onto a worker thread broke SQLite, since only the slow HTTP fetch should leave the event loop while database writes stay on it.
The piece closes with a smaller but sharp warning about Excel exports: cell values starting with characters like '=', '+', '-', or '@' can trigger formula injection when opened, and are neutralized by prefixing a single quote. The broader lesson is that batch billing is really three problems in one — unknown final cost, partial free-of-charge outcomes, and concurrency — and per-item atomic reservation collapses all three into a single correct primitive.