Skip to content

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.

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",
});

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.

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;
}
}

If the work inside hold throws, hold releases the estimate, so a failed reply costs the user nothing.

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.

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.

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",
});

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.

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.