Skip to content

Reservations

Some work only knows its cost afterwards, like an AI reply your product pays for by the token. A reservation sets aside what the work will probably cost while it runs, then you commit the actual amount. The examples on this page reserve 5 cents for a user’s ai-call against a budget of 200 cents a month, and commit what the call cost.

The SDKs’ reserve reserves cost, runs your code, then commits what it reports, or releases the reservation if your code fails:

import { createClient } from "@limitry/sdk";
const limitry = createClient();
const answer = await limitry.reserve(
{ subject: { kind: "user", id: "usr_123" }, action: "ai-call", cost: 5 },
async (reservation) => {
const response = await callModel(prompt);
reservation.cost(priceCents(response.usage)); // your prices, in cents
return response;
},
);

The steps below are what reserve does, for when you reserve, commit and release yourself.

Make a check with mode: "reserve" and what the work will probably cost as the cost:

import { createClient } from "@limitry/sdk";
const limitry = createClient();
const answer = await limitry.checks.create({
subject: { kind: "user", id: "usr_123" },
action: "ai-call",
cost: 5,
mode: "reserve",
});

It decides like any other check. If it’s allowed, Limitry reserves the cost: other checks for that subject see it as used. The answer includes the reservation:

{
"allowed": true,
"limits": [{ "limitId": "lmt_…", "remaining": 195, "…": "…" }],
"reservation": { "id": "rsv_…", "expiresAt": "2026-10-04T12:05:00Z" }
}

If the check is refused, Limitry reserves nothing and there’s no reservation.

When the work is done, commit what it actually used, here 3 cents:

await limitry.reservations.commit("rsv_…", { cost: 3 });

If it used less than it reserved, Limitry gives the rest back at once: the user’s budget shows 197 cents left, not 195. If it used more, Limitry records the actual cost in full, even past the limit, because the work did happen. The next check then sees the true total.

If the work didn’t happen, release the reservation instead: reservations.release(id) (limitry reservations release <reservation-id>) gives all of it back.

A commit or release is safe to retry: the same one gets the same answer. Committing again with a different cost, releasing a committed reservation, or committing a released one is refused with 400.

A reservation lasts ttlSeconds, which you set on the check: 300 by default, 3,600 at most. One that nobody commits or releases gives itself back when it expires, so a crashed worker never keeps a subject blocked. If the work did finish after all, a late commit within 24 hours still charges the full cost it reports.

For longer work, extend it as a heartbeat: reservations.extend(id, { ttlSeconds: 600 }) makes it expire that long from now (reservation.extend() inside reserve, in both SDKs). A reservation can also take a slot of a concurrent limit.

  • A subject can have at most 1,000 open reservations. Past that, the check is refused with reason.code: "too_many_reservations".
  • A reservation counts as one check on your plan. Committing and releasing are free.
  • Reservations need the same checks:write scope as checks.