Skip to content

limitry.json

limitry.json is optional: use it if you want your limits in your repository, changed through pull requests. Otherwise create them in the app, from the API or with the CLI (Limits). The file lists the limits you want. limitry sync reads it (./limitry.json unless you pass another path) and makes your limits match it. To run that on every merge, see Sync limits from GitHub.

{
"limits": [
{
"name": "AI budget",
"subject": { "kind": "user" },
"action": "ai-call",
"amount": 200,
"unit": "cents",
"window": "month",
"failMode": "closed"
},
{
"name": "API burst",
"subject": { "kind": "apikey" },
"action": "api-request",
"amount": 60,
"window": "minute",
"rolling": true
},
{
"name": "Exports at once",
"subject": { "kind": "workspace" },
"action": "export",
"amount": 3,
"window": "concurrent"
}
]
}
Field Description
limits (array, required) Every limit you want, at most 200. Each entry takes the fields below, and its name is unique.

Each entry takes the same fields as creating a limit, and name is required: a sync matches limits by name.

FieldDescription
namestringA name, unique among the environment's active limits. Default: the sentence the limit reads as, such as "200 cents per user per month". If a limit already has that name, the answer is 409: give this one a name.
subjectobject, requiredWho it applies to: {"kind": "user"} for every user, {"kind": "user", "id": "usr_123"} for one.
actionstringThe action to limit. Default: *, every action.
amountnumber, requiredHow many units each subject may use per window.
unitstringWhat the amount counts, in words. Default: units.
windowstring, requiredminute, hour, day, week, month or year: a period of every of them, reset where start says (or rolling: see rolling). total: a balance that never resets, raised by credits. concurrent: at most amount open reservations at once.
everynumberHow many window units a period lasts: window: "month", every: 3 is a quarter, window: "hour", every: 5 five hours. Default: 1.
startstringWhere a period begins. calendar: UTC boundaries (midnight, Monday, the 1st, 1 January). billing_date: from the subject's billing date, set with subjects.update (its first check until then), so an annual plan can reset monthly from its start day. first_use: at the first check after the previous period ended. Default: calendar.
rollingbooleanCount the last window instead of the calendar one (minute, hour or day only). Default: false.
enabledbooleanWhether checks enforce it. Default: true.
failModestringWhat your code should do when Limitry can't be reached: open (allow) or closed (deny). Default: open.

From the API's Create a limit.

Names are the key. A sync creates a limit whose name the environment doesn’t have, changes one whose fields differ, and leaves alone one that already matches. Each entry describes its limit in full: a field you leave out means its default, as when you create a limit. Remove "failMode": "closed" from the file and the next sync sets it back to open.

A sync can change a limit’s amount, failMode and enabled. What a limit counts is fixed once it exists: subject, action, unit, window, every, start and rolling (see What can change). If an entry changes one of those under the same name, the sync refuses, changes nothing, and says which entry to fix.

To change one, rename the entry. The sync then creates a new limit under the new name and leaves the old one as it is, or archives it with --archive-missing. The old limit’s history stays.

Terminal window
limitry sync --preview # what a sync would change; changes nothing
limitry sync # make your limits match the file
limitry sync other.json # read another file

A key acts in its own environment, so a test key syncs your test limits and a live key your live ones. Limitry checks the whole file before it changes anything: if one entry is wrong, nothing changes and the error says which entry to fix. Add --json for the API’s answer as JSON.

Once a sync runs on every merge, the file decides. If someone changes one of these limits in the app, the next sync sets it back to what the file says. Change the file instead.

Limits the file doesn’t name are left alone, so limits you manage in the app keep working beside the file. To make the file the whole list, add --archive-missing to both commands in the workflow. A sync then archives every active limit that isn’t in the file. Archiving keeps a limit’s usage history and credits, and you can restore it from the Limits page or with limitry limits restore <limit-id>. Run limitry sync --preview before you merge: it lists the limits a sync would archive.

The SDKs have the same operation, limits.sync, if you build the list in code, for example from your own plan catalog:

import { createClient } from "@limitry/sdk";
const limitry = createClient(); // reads LIMITRY_API_KEY
const preview = await limitry.limits.sync({
limits: [
{
name: "AI budget",
subject: { kind: "user" },
action: "ai-call",
amount: 200,
unit: "cents",
window: "month",
},
],
preview: true,
});
console.log(preview.created, preview.updated, preview.archived);

Leave out preview to apply it, and pass archiveMissing (archive_missing) to archive limits the list doesn’t name. The answer lists the names created, updated (with each field’s old and new value), archived and unchanged.