Limit AI spend per user of your product
Say each user on your product’s free plan gets a $2 AI budget a month. You don’t know what a reply costs until it’s done, because it depends on the tokens used. So you hold an estimate before the call and settle the real cost after it.
1. Create the budget
Section titled “1. Create the budget”Count in cents. $2 is 200:
import { createClient } from "@limitry/sdk";
const limitry = createClient();
await limitry.limits.create({ name: "AI budget", subjectKind: "user", action: "ai-call", amount: 200, unit: "cents", window: "month", failMode: "closed",});from limitry import Limitry
limitry = Limitry()
limitry.limits.create( name="AI budget", subject_kind="user", action="ai-call", amount=200, unit="cents", window="month", fail_mode="closed",)limitry limits create --name "AI budget" --subject-kind user \ --action ai-call --amount 200 --unit cents --window month \ --fail-mode closedcurl -X POST https://api.limitry.com/v1/limits \ -H "Authorization: Bearer $LIMITRY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"AI budget","subjectKind":"user","action":"ai-call", "amount":200,"unit":"cents","window":"month","failMode":"closed"}'The fail mode is closed: if Limitry can’t be reached, refuse the call
rather than spend money nobody is counting. Choose open if you’d
rather keep answering users.
2. Hold, call, settle
Section titled “2. Hold, call, settle”Estimate on the high side. Whatever the call doesn’t use, Limitry gives back straight away.
import { createClient, LimitExceeded } from "@limitry/sdk";
const limitry = createClient(); // reads LIMITRY_API_KEY
// Your prices, in cents per million tokens.const priceCents = (usage: { inputTokens: number; outputTokens: number }) => Math.ceil((usage.inputTokens * 300 + usage.outputTokens * 1500) / 1_000_000);
export async function answer(userId: string, prompt: string) { try { return await limitry.hold( { subject: { kind: "user", id: userId }, action: "ai-call", estimate: 5 }, async (held) => { const res = await callModel(prompt); held.cost(priceCents(res.usage)); return res.text; }, ); } catch (err) { if (err instanceof LimitExceeded) { return "You've used this month's AI allowance. It resets on the 1st."; } throw err; }}import math
from limitry import LimitExceeded, Limitry
limitry = Limitry() # reads LIMITRY_API_KEY
# Your prices, in cents per million tokens.def price_cents(usage) -> int: return math.ceil((usage.input_tokens * 300 + usage.output_tokens * 1500) / 1_000_000)
def answer(user_id: str, prompt: str) -> str: try: with limitry.hold( subject={"kind": "user", "id": user_id}, action="ai-call", estimate=5 ) as held: res = call_model(prompt) held.cost(price_cents(res.usage)) return res.text except LimitExceeded: return "You've used this month's AI allowance. It resets on the 1st."If the work inside hold throws, hold releases the estimate, so a
failed reply costs the user nothing.
3. One budget, or a budget per model
Section titled “3. One budget, or a budget per model”A check carries the subject, the action and the cost, and nothing
else. Limitry doesn’t store the model, the token counts or the prompt
of each call. Keep those details in your own logs. The cost is where
models differ: priceCents charges each call at its model’s price.
The simplest setup sends the same action, ai-call, for every model,
so the one budget from step 1 covers them all. A limit matches its
action name exactly, so a call sent as ai-call.claude would miss a
limit on ai-call.
To cap a model on its own, or to read spend per model, name the model
in the action and use * for the overall budget. Every limit that
matches a check applies, so a call sent as ai-call.claude counts against
both of these:
| Limit | Subject kind | Action | Amount |
|---|---|---|---|
| Model budget | user |
* |
200 cents a month |
| Claude spend | user |
ai-call.claude |
150 cents a month |
| GPT spend | user |
ai-call.gpt |
100 cents a month |
Limitry reports usage per limit, so each model’s limit shows that model’s spend. A model with no limit of its own still counts against the budget, but has no figure of its own.
A limit with the action * counts every check for its subject kind. If
your user subjects also make other checks, such as exports, send AI
checks with a kind of their own, such as { kind: "ai-user", id }, and
put these limits on that kind.
4. Different budgets per plan
Section titled “4. Different budgets per plan”Give paying users more by putting a second limit on a different subject
kind, and sending that kind for them. For example, a pro-user limit of
2,000 cents a month, and { kind: "pro-user", id } as the subject for
Pro users.
5. A short limit from first use
Section titled “5. A short limit from first use”Many AI products also limit bursts: “45 messages per 5 hours, starting from your first message”. That’s a second limit, next to the monthly budget. It refills five hours after a user’s first message, and nothing runs while they’re away:
import { createClient } from "@limitry/sdk";
const limitry = createClient();
await limitry.limits.create({ name: "Messages per 5 hours", subjectKind: "user", action: "chat", amount: 45, window: "hour", every: 5, start: "first_use",});from limitry import Limitry
limitry = Limitry()
limitry.limits.create( name="Messages per 5 hours", subject_kind="user", action="chat", amount=45, window="hour", every=5, start="first_use",)A limit matches its action exactly, so the burst limit counts only
checks sent with the action chat. Send one before each message, with
the default cost of 1, next to the ai-call reservation for the
budget. The two limits use different actions because a check charges
the same cost to every limit it matches: one in cents would use up 45
messages at once. See When limits refill.
6. Know when someone runs out
Section titled “6. Know when someone runs out”Add a webhook endpoint under Settings → Webhooks. The first time a
user hits the budget in a month, you receive
limit.exceeded, which you can
use to show an upgrade offer.
Related: Reservations, Pools, balances and budgets.