Notes

· 3 min read

A budget for every AI call: reserving cost in a multi-tenant SaaS

AI features are priced per token, but merchants are not. Here is the pattern nqrah uses to make sure no store, and no bug, can run up an unbounded bill.

Mahmoud AttiaFounder of nqrah · Medical student · Full-stack engineer
  • AI
  • Engineering
  • SaaS
اقرأ بالعربية

nqrah uses language models to write product pages from a photo, translate, and help both customers and store owners. Those features are genuinely useful, and every one of them has a variable cost. In a multi-tenant platform, that cost needs a ceiling, because a single loop, a single abusive store or a single prompt that grows out of control can turn a feature into an invoice.

The pattern we use is simple to explain: reserve before you call, settle after, and refuse when the budget says no.

Three limits, three jobs

  • A monthly cap per store. Each store has an AI budget for the month. It protects the platform from any one tenant and makes cost predictable for the merchant.
  • A daily breaker for the whole platform. If total spend in a day crosses a threshold, new AI calls stop until someone looks. It protects against bugs we haven’t found yet.
  • A per-call maximum. Every request has a maximum number of output tokens, so one call can never be enormous.

Reserve, call, settle

Before calling a model, we estimate the most the call could cost and reserve that amount against the store’s budget in the same database transaction that checks the remaining balance. Only if the reservation succeeds do we make the call. Afterwards we record the real cost and release the difference. In simplified form:

async function withAiBudget<T>(storeId: string, est: Estimate, run: () => Promise<{ value: T; usage: Usage }>) {
  const maxCost = priceOf(est.model, est.inputTokens, est.maxOutputTokens);

  // 1. Reserve atomically: fails if the store or the platform is over budget.
  const reservation = await db.transaction(async (tx) => {
    await assertPlatformBreakerClosed(tx);
    const left = await remainingBudget(tx, storeId); // locks the store's budget row
    if (left < maxCost) throw new BudgetExceeded(storeId);
    return tx.insert(aiReservations).values({ storeId, amount: maxCost }).returning();
  });

  try {
    // 2. Call the provider.
    const { value, usage } = await run();
    // 3. Settle with the real cost.
    await settle(reservation.id, priceOf(est.model, usage.input, usage.output));
    return value;
  } catch (err) {
    await release(reservation.id); // nothing was spent
    throw err;
  }
}

Reserving the maximum matters. If ten requests start at once, each one reserves its worst case, so they can’t all squeeze through on the same remaining balance.

Log every call with its cost

Every call is recorded with the store, the feature, the model, the tokens and the cost. This gives three things for free: a usage page for the merchant, a per-feature view of what is worth its price, and evidence when something behaves strangely.

Keep personal data out of prompts

Cost is not the only budget. Customer names, phone numbers and addresses never reach a model in nqrah. Features that need context get the product, the policy or the order status, not the person. It is easier to keep a rule like that from day one than to retrofit it.

Don’t marry one provider

The platform talks to Claude, OpenAI, Gemini and others through one interface, and the pricing table is data, not code. That makes it possible to choose a model per feature, switch when prices or quality change, and keep the budget logic in one place.

What this buys you

A merchant can turn on AI features without fearing a surprise. The platform can offer them at a predictable price, because the worst case is known in advance. And when something goes wrong at 3 a.m., the breaker trips before the bill does.

If you are adding AI to a product with more than one customer, start with the reservation, not the prompt.

↑↓ navigate↵ openesc close
mahmoud@attia: ~