Skip to content

Budgets at several levels

Say your product sells to companies (tenants), each company has teams, and each team has users. Every month, each tenant may spend 1,000 credits, each team 100, and each user 10. A user’s request has to fit all three: their own 10, their team’s 100 and their tenant’s 1,000.

In Limitry that’s three limits, one per level, and one check that names all three subjects.

Each level is its own limit, with its own counts. The kind is your own label for each level:

Limit subject.kind amount window
1,000 credits per tenant tenant 1000 month
100 credits per team team 100 month
10 credits per user user 10 month

In a limitry.json:

{
"limits": [
{
"name": "Tenant credits",
"subject": { "kind": "tenant" },
"amount": 1000,
"unit": "credits",
"window": "month"
},
{
"name": "Team credits",
"subject": { "kind": "team" },
"amount": 100,
"unit": "credits",
"window": "month"
},
{
"name": "User credits",
"subject": { "kind": "user" },
"amount": 10,
"unit": "credits",
"window": "month"
}
]
}

Or create them from your code. Without a name, each is named after its sentence, such as “1,000 credits per tenant per month”:

import { createClient } from "@limitry/sdk";
const limitry = createClient(); // reads LIMITRY_API_KEY
for (const [kind, amount] of [
["tenant", 1000],
["team", 100],
["user", 10],
] as const) {
await limitry.limits.create({
subject: { kind },
amount,
unit: "credits",
window: "month",
});
}

In the app, create the first one, then click Add another limit: it opens with the same unit and period, so only the kind and the amount change.

Limitry doesn’t know which team a user is in, or which tenant a team belongs to. Your product knows, so each check says it: the user is the subject, and the team and the tenant go in shared.

import { createClient, LimitReached } from "@limitry/sdk";
const limitry = createClient();
try {
const report = await limitry.guard(
{
subject: { kind: "user", id: user.id },
shared: [
{ kind: "team", id: user.teamId },
{ kind: "tenant", id: user.tenantId },
],
action: "report",
cost: 2,
},
() => buildReport(params),
);
} catch (error) {
if (error instanceof LimitReached) {
// error.limitId says which level was used up.
console.log(error.message);
}
}

The check is allowed only if the cost fits every limit on the user, on the team and on the tenant. Then Limitry charges all three, or none: a refused check uses nothing anywhere. The answer’s limits lists each level with what is left on it, and when one is used up, reason.limitId says which.

When the cost is known only afterwards, use reserve with the same shared subjects. See Reservations.

  • Each level counts on its own. A user in two teams is counted on whichever team the check names. Name the team the work is for.
  • Send the same kinds every time. A check that names teams instead of team reaches no team limit. The Limits page flags a limit that no check has reached in 7 days, so a misspelt kind shows up there.
  • Up to 4 shared subjects. A user, their team, their tenant and a project fit in one check.
  • One customer can get more. A limit for one subject, such as {"kind": "tenant", "id": "acme"} with 5,000, replaces the tenant limit for that tenant only. See Limits.

To show each user where they stand on all three levels, call usage.standing with the same shared subjects. See Show users where they stand.

Related: Pools, balances and budgets, Checks.