Skip to content

The Guard limits spec

This content is not available in your language yet.

A limits spec is the small file that says what one Guard may let through to one service. This page shows how it is built, with one example, and how to write one for an API of your own.

Guard sits in front of one service, such as a payment service or a WhatsApp provider. The limits spec is the JSON file that says what that Guard may let through. The command geo-guard compile turns the spec into the policy file Guard loads. Anything the compiler does not understand, it refuses, and it lists every problem it finds.

A spec belongs to one client and one service. A shop that takes payments and sends WhatsApp messages has two specs, one for each Guard.

A person writes or reviews the numbers. The spec has a reviewed_by field, which is a declaration of who read them: the compiler cannot know that someone did.

What a spec holds

Part What it says
tenant, tool, name, version, reviewed_by Whose spec it is, which service it covers, its name, a version that goes up with every change, and who reviewed it. tenant and tool must match the same two settings in that Guard’s guard.toml
routes The calls that exist. Each route is a method and a path, such as POST /v1/refunds, and gives that call an operation name, such as create_refund. A call that matches no route is refused as unlisted
rules What must hold for a call to an operation: an amount inside a range, a field that must be exactly one value, a call held for a person above a threshold, a call that may happen at most once, or a call that is always refused
limits Caps Guard counts: a total amount or a number of calls, per day or per hour, for the whole client or per value of a field
intents What makes two calls the same action, so that the same refund is not made twice
fail_policy What to do when Guard cannot decide. Left empty, it refuses

The rules read fields of the call: the query parameters, the top-level fields of the JSON body, and the parts of the path written in braces. A rule that needs a field the call does not have refuses the call. Guard never treats a missing field as a pass.

Amounts in a spec are written as text ("50000"), not as JSON numbers, and argument_bound says whether the call must write its own amount as a number or as text.

This is part of the spec the companies kit builds for a made-up refunds API. It lists three routes, bounds a refund, holds a large one for a person, and refuses transfers by name.

{
"schema": 1,
"tenant": "brightside",
"tool": "refunds",
"name": "brightside-refunds",
"version": 1,
"reviewed_by": "priya.nair",
"routes": [
{ "method": "POST", "path": "/v1/refunds", "operation": "create_refund" },
{ "method": "GET", "path": "/v1/refunds/{refund_id}", "operation": "get_refund" },
{ "method": "POST", "path": "/v1/transfers", "operation": "create_transfer" }
],
"rules": [
{ "id": "create_refund-amount-bound", "kind": "argument_bound",
"operation": "create_refund", "field": "amount", "min": "1", "max": "50000" },
{ "id": "create_refund-needs-approval", "kind": "call_bound_approval",
"operation": "create_refund", "amount_field": "amount", "threshold": "10000" },
{ "id": "create_transfer-forbidden", "kind": "forbidden_call",
"operation": "create_transfer" }
],
"limits": [
{ "id": "create_refund-spend-per-day", "kind": "spend", "operation": "create_refund",
"amount_field": "amount", "scope": [], "cap": "200000", "window_seconds": 86400 }
]
}

Read it as plain sentences:

  • A call to POST /v1/refunds is a create_refund. Its amount field must be between 1 and 50000. Anything outside is refused.
  • A refund above 10000 is held until a named person approves that exact call (see Approve or deny held calls).
  • A call to POST /v1/transfers is always refused, and the refusal names the rule.
  • All refunds together are capped at 200000 a day. A call that would go over is refused before it is sent.
  • Any other call, for example one to /v1/customers, matches no route and is refused as unlisted.

Because a refund must also be inside the 1 to 50000 range, the approval threshold has to be below the top of the range. Otherwise no call could ever be held, and the compiler says so.

The agent templates come with specs ready to adjust. For a shop, the numbers come from its own settings: the deposit or fee range for payment links, the refund amount the agent may make alone, and the shop’s own WhatsApp number or calendar. The refund amount in the settings and the threshold in the spec are the same number, so they cannot disagree.

Each shop’s Guard also has a licence block in its guard.toml, next to the spec. The deploy bundle writes it. It names the server and the shop, and which calls are refused if the subscription lapses. It is not part of the spec, so a builder writing a spec for their own API does not write it.

If you build agents for a company, the companies kit in the open agents repository takes you from your API’s OpenAPI file (JSON) to a compiled spec, with a person in the middle.

  1. Draft. node companies/openapi-to-spec.ts draft <openapi.json> --out draft.json reads every call, marks the ones that look consequential (a delete, or a post that carries an amount, a refund, a transfer and the like), and suggests rules and limits with a note for each. It leaves the numbers blank. A draft is not a spec, and Guard cannot load it.
  2. Review. Copy accept.template.json to accept.json. Put your name in reviewed_by, switch on the routes the agent may call, and for each suggestion you keep, fill in every blank. Whatever you leave off is dropped.
  3. Build. node companies/openapi-to-spec.ts build --draft draft.json --accept accept.json --out spec.json writes the spec, then runs geo-guard compile and geo-guard verify-bundle and prints what they say. It writes nothing if a blank is left in something you kept.
  4. Read what Guard could not check. The build lists rules that need a live store to test, and things only the service itself can settle, such as a refund whose reply was lost. They are part of what your reviewers should be told was not covered.

Then give Guard its two credentials, put the printed digest into guard.toml, and raise version with every change you make. The kit’s README has the full steps, including how to run Guard beside your agent.