Release candidate. This describes v0.3-rc1, the current release candidate. New and existing relying parties are on it (profile
delegus-base-v3). It is not frozen: the commitment construction is awaiting an external cryptography review, and the v0.2 specification stays the normative text until v0.3 is frozen. Details can still change. Budgets apply only to a relying party ondelegus-base-v3;delegus-base-v1is unchanged.
What a budget adds#
Without a budget, every check is about one action at a time: is this purchase under maxAmount, in the right currency, for an allowed resource. A budget adds memory. Delegus keeps a running count per permission, a consumption ledger, and checks each new action against what has already been spent.
Words used on this page:
- Grant: the signed permission a company (the Principal) gives its agent. It lists one or more Capabilities, each an action type with its limits.
- Relying party: the business that receives the agent's request and asks Delegus to check it.
- Headroom: what is left in a budget before this action.
- Receipt: the signed record of each decision, ALLOW or DENY.
The budget constraint#
A budget is one more key inside a Capability's constraints. It is an object with at least one of three members and no others:
| Member | Type | Meaning | Allowed on |
|---|---|---|---|
total | non-negative integer, minor units of the Capability's currency | The most that may be spent over the Grant's whole life | commerce:purchase only |
perPeriod | { "amount": <non-negative integer, minor units>, "period": "day" \| "week" \| "month" }, exactly those two members | The most that may be spent in one period | commerce:purchase only |
uses | positive integer | How many ALLOW decisions this Capability may authorize | every action type |
Amounts are integers in minor units, like everywhere else in Delegus: 50000 in USD is $500.00. There are no decimals.
| Action type | Budget members it accepts |
|---|---|
commerce:purchase | total, perPeriod, uses |
api:call | uses |
commerce:quote | uses |
commerce:accept | uses |
commerce:fulfil | uses |
The Grant is malformed, and every request with it is refused at check P1 with GRANT_MALFORMED, if the budget:
- is empty (
{}); - has a member other than the three above;
- has a value of the wrong type (a decimal, a negative number,
uses: 0, an unknownperiod); - puts
totalorperPeriodon any action type other thancommerce:purchase.
Money budgets on commerce:quote and commerce:accept are an open question in the rc1 text (§16, item 12). Until that is settled, those types take uses only.
A relying party still on delegus-base-v1 does not know the budget key. For it, a Grant with a budget fails check P17 with UNKNOWN_. Budgets need a relying party on delegus-base-v3.
A Capability with a budget, as it appears in a Grant:
{
"action": "commerce:purchase",
"constraints": {
"maxAmount": 50000,
"currency": "USD",
"budget": {
"total": 150000,
"perPeriod": { "amount": 100000, "period": "week" },
"uses": 20
}
},
"resources": ["computeco/orders/*"]
}maxAmount still limits each single purchase. The budget limits the purchases added together.
Periods#
A period budget resets on a schedule that starts at the Grant's validFrom, in UTC. It does not follow the calendar week or month.
day: a new period every 24 hours fromvalidFrom.week: a new period every 7 days fromvalidFrom.month: a new period on the same day of the month and the same time of day asvalidFrom. When a month has no such day, the period starts on that month's last day. A Grant valid from 31 January has periods starting 28 or 29 February, 31 March, 30 April, and so on.
Spending from an earlier period does not count against the current one.
How a check spends#
Two steps handle a budget, and they sit at opposite ends of the decision.
- P21, the headroom check. After the ordinary checks (the signatures, dates, revocation, replay, action type, currency, amount and resource), Delegus reads the Grant's current counts and asks: does this action fit? It checks the dimensions in the order total, period, uses, and the first one without room fails. If the Grant has more than one Capability that could authorize the action, they are tried in Grant order and the first one with headroom, or the first with no budget, is the one used.
- T7, the spend. Only when every other check is true does Delegus spend: one atomic decrement of the chosen Capability's counts. Its answer is trust check T7.
The spend is the very last step of an evaluation, so a refusal never spends. In the spec's words, "a DENY never spends budget" (§5.3). A request that is replayed, revoked, expired, sent to the wrong relying party, or refused for any other reason is decided before P21 or skips it, and never touches the budget.
P21 itself runs only when every earlier check is true, including the trust checks T1, T2, T3 and the replay check T5. When any of them fails, P21 is "skipped", no counts are read, and the receipt carries no consumption evidence.
What each allowed action spends:
| Action type | total and period spend | uses spend |
|---|---|---|
commerce:purchase | the action's amount | 1 |
| every other type | nothing (no money budget exists) | 1 |
What each outcome looks like#
Every outcome below is a signed receipt returned with HTTP 200. None is an HTTP error. The spend and its receipt are recorded together or not at all (§5.4): if Delegus cannot record them, /verify answers HTTP 503 with no receipt and nothing spent.
| Situation | Decision | reason | Checks | Budget |
|---|---|---|---|---|
| The action fits, the spend succeeds | ALLOW | null | P21 true, T7 true | Spent |
| The action does not fit (total, period or uses) | DENY | AUTHORITY_EXHAUSTED | P21 false, T7 "skipped" | Nothing spent |
| The action fitted at P21, but a request running at the same moment took the last headroom first | DENY | AUTHORITY_EXHAUSTED | P21 true, T7 false | Nothing spent by this request |
| Delegus could not read the counts for a budgeted Capability | DENY | SERVICE_UNAVAILABLE | P21 false | Nothing spent |
| Delegus could not reach the store to spend | DENY | SERVICE_UNAVAILABLE | T7 false | Nothing spent |
| Any other check failed | DENY | that check's reason | P21 and T7 "skipped" or not reached | Nothing spent |
Both AUTHORITY_ cases come after the replay check T5, so the agent's Proof has been used. To try again, the agent signs a fresh Proof.
The same reason code, AUTHORITY_, comes from two checks. To tell them apart, look at which check is false in the receipt: protocol. or trust.checks.T7.
The receipt's consumption evidence#
When the matched Capability has a budget, or P21 failed on one, the receipt carries evidence.:
"consumption": {
"snapshot_hash": "sha256:...",
"as_of": "2026-09-10T15:02:11.400Z",
"capability_key_commitment": "sha256:...",
"capability_index": 0,
"consumed": "consumed"
}| Field | Meaning |
|---|---|
snapshot_hash | The hash of the counts Delegus read for this Grant, the consumption snapshot, exactly as P21 used them |
as_of | When those counts were read |
capability_key_commitment | Identifies which Capability was spent from, without revealing it. Only the receipt's parties can open it (see below) |
capability_index | The position of that Capability in the Grant's list |
consumed | The answer of the spend, when it ran: consumed, exhausted or unavailable |
The receipt names it capability_ (spec §6.2, §6.3, §R.6).
On a P21 refusal for lack of headroom, evidence. also carries exhausted: total, period or uses, the first dimension without headroom (spec §6.2).
The counts themselves are not in the receipt. The receipt's relying party and Principal can fetch the snapshot with GET /, which returns it as consumption_; anyone else gets a 404. With the snapshot, P21 can be re-run offline. The snapshot looks like this:
{
"grant_id": "urn:delegus:grant:01J9X4QK7M3Y2Z8N6P5R4T3V2W",
"as_of": "2026-09-10T15:02:11.400Z",
"version": 7,
"capabilities": [
{ "key": "sha256:...", "spent_total": 57300, "spent_in_period": 0,
"period_start": "2026-09-08T00:00:00Z", "uses_spent": 3 }
]
}Beside the receipt, the /verify response returns authority (the matched Capability in plain text), authority_, and, when the receipt pins a capability-key commitment, capability_. The relying party uses them to confirm what the receipt pins. They are not part of the signed receipt.
Giving budget back: cancelled orders#
Budget comes back in one case only: the relying party reports that the order was cancelled.
- The relying party named in the receipt sends an outcome for an ALLOW receipt:
POST /withdecisions/ {receipt_ id}/ outcomes "type": "cancelled". - Delegus reverses that receipt's spend once: the
totalamount and the use come back. The period amount comes back only if the receipt's period is still the current one; a past period is not reopened. - A second
cancelledon the same receipt changes nothing. - A later outcome with
correction_ofnaming thecancelledone takes the restore back, once. - Every other outcome type (
fulfilled, disputes, chargebacks,loss_reported) leaves the budget as it is.
When the outcome moved budget, the 201 response carries "consumption": { "cause": "restore", "version": <n> }.
Restores change the counts, not any receipt. A receipt signed before a restore still pins the snapshot it was decided against.
A cancelled outcome attestation on an ALLOW receipt restores what that receipt consumed, once; an attestation that corrects a cancelled one applies the spend again. No other outcome type touches consumption (spec §3.3.1). The trust configuration trust_0003 records this as restore_on.
Seeing the balance#
GET / returns, per Capability, the balances (spent_total, spent_in_period, uses_spent, remaining, limits, period_start) and the consumption log: events whose cause is consume, restore or restore_, with their deltas and the receipt or attestation that caused each. It takes a Principal, organization or relying-party key; the API reference says who may read which Grant.
Worked example#
Acme gives its purchasing agent, procurement-7, a Grant to buy compute from ComputeCo. The Grant is valid from Wednesday 9 September 2026, 00:00 UTC. It has one Capability:
commerce:purchase, up to $500.00 per order (maxAmount: 50000,currency: "USD");budget.: at most $1,500.00 over the Grant's life;total: 150000 budget.: at most $1,000.00 per week, with weeks starting each Wednesday at 00:00 UTC, counted fromperPeriod: { "amount": 100000, "period": "week" } validFrom.
ComputeCo is a relying party on delegus-base-v3. Each time procurement-7 places an order, ComputeCo sends the Grant, the agent's Proof and the order to /verify.
| # | When (UTC) | Order | P21 asks | Result | Spent after (total / this week) |
|---|---|---|---|---|---|
| 1 | Thu 10 Sep | $427.00 (42700) | 0 + 42700 ≤ 150000; 0 + 42700 ≤ 100000 | ALLOW, T7 consumed | 42700 / 42700 |
| 2 | Sat 12 Sep | $450.00 (45000) | 42700 + 45000 ≤ 150000; 42700 + 45000 ≤ 100000 | ALLOW | 87700 / 87700 |
| 3 | Mon 14 Sep | $200.00 (20000) | total: 107700 ≤ 150000, fits; week: 87700 + 20000 = 107700 > 100000 | DENY AUTHORITY_EXHAUSTED at P21. Nothing spent | 87700 / 87700 |
| 4 | Wed 16 Sep, 10:00 | $200.00 (20000) | A new week began at 00:00 on 16 Sep, so this week's spend is 0. 87700 + 20000 ≤ 150000; 0 + 20000 ≤ 100000 | ALLOW | 107700 / 20000 |
Order 3 was refused only because of the weekly cap: the total still had room. Its receipt shows protocol., trust., and evidence. with the snapshot it was checked against. procurement-7 can place the same order again once the next week starts, with a fresh Proof.
Now ComputeCo cancels order 2 and reports it:
curl -s https://api.delegus.ai/decisions/drc_.../outcomes \
-H "authorization: Bearer $DELEGUS_API_KEY" \
-H "content-type: application/json" \
-d '{"type":"cancelled","occurred_at":"2026-09-17T09:00:00Z"}'Delegus restores order 2's spend: the total drops by 45000 to 62700. Order 2 was spent in the week that began 9 September, which is over, so the current week's spend stays at 20000. Acme's agent has $877.00 of its total left and $800.00 left this week.
Two orders arriving at the same moment for the last of a budget are settled by the spend itself. Both may pass P21, because each reads the counts before either has spent. The spend is designed as one atomic decrement, so the two cannot both spend the same headroom: the request that loses gets AUTHORITY_ at T7, and nothing is spent for it.
Next#
- DENY reason codes: every code, including the v0.3 additions.
- Profile delegus-base-v3: the checks in order, with P21 and T7.
- Receipts and offline verification: what a receipt proves and how to re-check it.