Internet-Draft AAuth-Budgets August 2026
Hardt Expires 21 February 2027 [Page]
Workgroup:
TBD
Internet-Draft:
draft-hardt-aauth-budgets-latest
Published:
Intended Status:
Standards Track
Expires:
Author:
D. Hardt
Hellō

AAuth Budgets

Abstract

This document defines AAuth Budgets, an extension to the AAuth Protocol ([I-D.hardt-oauth-aauth-protocol]) that carries a spending ceiling from a person server to a resource. A budget is a ceiling on what an agent may consume at one resource, denominated in a unit the resource declares, carried as a claim in the auth token, and enforced by the resource. Budgets are structurally parallel to scope: the agent asks, the resource offers, the person server and access server may narrow, and the auth token carries what was granted. The extension adds a budget claim to resource tokens and auth tokens, a budget_consumed claim reporting recent consumption, a budget_units field and a usage_endpoint to resource metadata, and an AAuth-Budget response header reporting what a request cost and what remains.

Discussion Venues

Note: This section is to be removed before publishing as an RFC.

This document is part of the AAuth specification family. Source for this draft and an issue tracker can be found at https://github.com/dickhardt/AAuth.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 21 February 2027.

Table of Contents

1. Introduction

Status: Exploratory Draft

The AAuth Protocol ([I-D.hardt-oauth-aauth-protocol]) lets a person server (PS) decide whether an agent may access a resource, and lets the resource express what access it offers. Neither party has a way to say how much.

For a resource that meters and charges per call, that omission is the whole authorization decision. An agent harness calling a model inference endpoint on a person's account can spend without bound: the scope inference.completions is either granted or not, and once granted it says nothing about whether the agent may consume ten cents or ten thousand dollars of the person's money. The person's only controls are outside the protocol — a provider dashboard, a card limit, a bill that arrives after the fact.

This document defines a budget: a ceiling on what an agent may consume at one resource, denominated in a unit the resource declares, carried as a claim in the auth token, and enforced by the resource.

A budget is an authorization, not a hint. The PS has authorized the agent to spend up to a stated amount, and the resource is the party that counts. That distinction determines nearly every design choice in this document, in particular why the balance cannot be reported through RateLimit ([I-D.ietf-httpapi-ratelimit-headers]) — see Appendix A.7.

The granted budget is an allocation, not the person's ceiling. The ceiling is the person server's own state: no claim carries it, and it may not be shared with the agent. The PS sizes each auth token against what the work has cost so far, and the token's expiry or its budget's exhaustion — whichever comes first — brings the agent back for the next allocation. That return is the supervision point, and the PS's options there are the subject of Section 8. Nobody knows at mission approval what an agent's work will cost; a figure fixed once up front is either too small to finish or too large to be a control Appendix A.5.

Metered inference is the initiating use case, and Section 18 covers it as a named deployment pattern. The mechanism is general: any resource that meters and charges per call uses it unchanged.

TPX [TPX] profiles the same grant for OAuth 2.0: a person grants a human-driven app a metered inference budget — "a damage cap, not a payment" — from a provider the person chooses and pays. This document is the AAuth counterpart: the same grant, carried to an autonomous agent through the narrowing chain, and generalized beyond inference to any resource that meters. A provider implementing both accepts two authorization envelopes over one meter Section 18.

1.1. Non-Goals

  • Not a mission aggregate. A budget covers a single resource in a single unit. It does not express a cross-resource total such as "$5,000 for the Japan trip." Mission-wide totals require aggregation across resources that meter in different units, which this document does not define.
  • Not a rate limit. A budget is cumulative consumption, not per-window throughput. RateLimit and RateLimit-Policy ([I-D.ietf-httpapi-ratelimit-headers]) cover throughput. Both MAY appear on the same response as AAuth-Budget, meaning different things.
  • Not pricing. The resource prices its own service. A budget bounds spend at whatever prices the resource charges; this document defines no way to express a price.
  • Not composite. A budget is one amount in one unit. A resource that meters several quantities at different rates — input tokens, output tokens, cache reads — collapses them to one billing unit, typically currency, before denominating a budget.
  • Not payment or settlement. No funds move. 402 Payment Required and the resource's commercial arrangement with the person are untouched.
  • Not PS-enforced at request time. The PS authorizes a number. The resource counts against it. The PS is not in the request path.
  • Not an OAuth extension. This document defines claims in AAuth tokens (aa-resource+jwt, aa-auth+jwt), fields in aauth-resource.json, two resource endpoints, an AAuth capability value, and an AAuth response header. It registers nothing in an OAuth registry. The documents surveyed in Appendix B are cited as prior art and are non-normative.

2. Conventions and Definitions

{::boilerplate bcp14-tagged}

3. Terminology

4. Budget Model

4.1. A Budget Is Structurally a Scope

The AAuth Protocol defines scope in three positions with a narrowing rule (([I-D.hardt-oauth-aauth-protocol]), Scopes). A budget occupies the same three positions, plus the PS-to-AS hop in four-party access:

Table 1
Position scope budget
Authorization endpoint request what the agent asks for what the agent asks for
Resource token what the resource will grant what the resource will grant
PS-to-AS token request (n/a) what the PS will allow
Auth token granted, MUST NOT be broader granted, MUST NOT exceed

The base protocol's rule that a resource token MUST only include resource scopes the resource has declared in its scope_descriptions metadata has a direct parallel here: a resource token MUST only name a unit the resource has declared in budget_units Section 5.

This extension therefore introduces one new claim shape and no new authorization semantics. Every party that already knows how to narrow a scope knows how to narrow a budget.

4.2. The Narrowing Chain

Each stage MUST NOT exceed the previous stage. There is one asymmetry between the amount and the denomination: the resource settles the denomination, and no later party may change it.

Agent            Resource          PS               AS
  |                 |               |                |
  | budget request  |               |                |
  | (OPTIONAL)      |               |                |
  |---------------->|               |                |
  |                 | sets unit and decimals,        |
  |                 | MAY lower amount               |
  |                 |               |                |
  |  resource token (budget)        |                |
  |<----------------|               |                |
  |                 |               |                |
  |  resource token |               |                |
  |-------------------------------->|                |
  |                 |               | MAY lower      |
  |                 |               | amount         |
  |                 |               |                |
  |                 |     budget (four-party only)   |
  |                 |               |--------------->|
  |                 |               |                | MAY lower
  |                 |               |                | amount
  |                 |               |  auth token    |
  |                 |               |<---------------|
  |  auth token (granted budget)    |                |
  |<--------------------------------|                |

{: #fig-narrowing title="Budget narrowing. Only the resource sets the unit and scale."}

  1. The agent requests. OPTIONAL. A budget object in the authorization endpoint request Section 6. Omitting it means the resource applies its own default. Requesting more than the resource will allow is NOT an error; the resource narrows.

  2. The resource sets the unit and scale, and MAY lower the amount. The resource is the enforcer and the only party that knows its own pricing, so it settles the denomination. It MAY change unit from what the agent requested — for example converting a request denominated in tokens into a currency amount. After this stage, unit and decimals are fixed for the life of the grant.

  3. The PS MAY lower the amount. The PS MUST NOT change unit or decimals. It is applying the person's policy to a figure the resource denominated; a PS that redenominated would be stating a budget in something the resource may not meter in.

  4. The AS MAY lower the amount (four-party only), for its own credit or risk reasons. The same prohibition on changing unit or decimals applies.

Because the resource MAY change the unit, an agent MUST NOT assume the granted budget is directly comparable to what it requested. The agent reads what it actually got from the budget claim of its auth token.

The resource token carries one figure, not both the agent's request and the resource's own maximum. It is the minimum of the two, exactly as scope is. What the agent originally asked for has no bearing on the PS's decision; an agent that wants the PS to know it belongs in justification (([I-D.hardt-oauth-aauth-protocol]), PS Token Endpoint).

4.3. Value Representation

A budget amount is a non-negative integer in a scale the resource declares. It is not a floating-point number and not a decimal string.

The value of a budget is amount divided by 10 raised to the power of decimals, in unit. An amount of 5000000 with unit of USD and decimals of 6 is five US dollars.

The scale is declared per unit, not globally. A resource metering US dollars per inference call wants decimals of 6 to represent micro-dollars; a resource metering Japanese yen wants 0, matching the ISO 4217 [ISO4217] minor unit; a resource metering tokens wants 0.

decimals is the name x402 [x402] and ERC-20 [ERC20] use for this quantity. ISO 4217 and ISO 20022 call it the minor unit. This document does not introduce a third name. See Appendix A.1 for why the value is an integer and Appendix A.2 for why the scale travels with the amount rather than being derived from the unit identifier.

4.4. Range Limits

Two carriers bound the representable range:

  • A Structured Field Integer ([RFC9651], Section 3.3.1) is limited to 15 digits, which bounds remaining, cost, and reserved in the AAuth-Budget header Section 11.
  • A JSON number is exact only to 2^53 (approximately 9.0 x 10^15), which bounds the amount member of the budget claim.

US dollars at decimals of 6 therefore top out near 10^9 per budget, which is well beyond any plausible grant. Implementations MUST NOT issue a budget whose amount exceeds 999,999,999,999,999 (15 digits), so that the amount and every derived figure remain representable in both carriers.

If assets requiring 18 decimal places come into scope, both carriers break and amount would have to become a string, as it is in x402. This document does not define that change.

4.5. The Budget Object

One shape appears in every position — the authorization endpoint request, the resource token, the PS-to-AS token request, and the auth token:

{
  "amount": 5000000,
  "unit": "USD",
  "decimals": 6
}

Members:

  • amount (REQUIRED). A non-negative integer, subject to Section 4.4.
  • unit (REQUIRED). A string identifying what is metered. For currency, the value SHOULD be an ISO 4217 [ISO4217] alphabetic code. Units are resource-declared; this document establishes no registry of unit values, for the same reason the base protocol establishes no registry of scope values.
  • decimals (REQUIRED). A non-negative integer giving the scale of amount. When unit is an ISO 4217 code, decimals is NOT constrained to that currency's minor unit; see Appendix A.2.

The object appears alongside scope wherever both are present:

{
  "scope": "inference.completions",
  "budget": { "amount": 5000000, "unit": "USD", "decimals": 6 }
}

5. Resource Metadata Extensions

This document extends the /.well-known/aauth-resource.json document defined in AAuth Protocol ([I-D.hardt-oauth-aauth-protocol]) with two fields:

{
  "issuer": "https://inference.example",
  "jwks_uri": "https://inference.example/.well-known/jwks.json",
  "access_mode": "auth-token",
  "authorization_endpoint": "https://inference.example/authorize",
  "scope_descriptions": {
    "inference.completions": "Generate completions,
      billed to your account"
  },
  "budget_units": [
    { "unit": "USD", "decimals": 6, "max": 10000000 },
    { "unit": "tokens", "decimals": 0, "max": 5000000 }
  ],
  "usage_endpoint": "https://inference.example/usage"
}

budget_units (OPTIONAL). An array of objects, each declaring one unit the resource will denominate a budget in. Each object contains:

A resource that declares budget_units MUST NOT issue a resource token whose budget.unit is absent from the array, and MUST set budget.decimals to the value declared for that unit.

usage_endpoint (OPTIONAL). The HTTPS URL where a person server queries usage counters Section 15. The URL MUST conform to the Endpoint URL requirements of ([I-D.hardt-oauth-aauth-protocol]).

A PS or AS that receives a resource token carrying budget already fetches {iss}/.well-known/aauth-resource.json to discover the resource's JWKS, per the dwk claim ([I-D.hardt-httpbis-signature-key]). The unit declarations arrive in a fetch it was already making, so the cross-check in Section 17 costs no extra round trip.

6. Authorization Endpoint Extensions

An agent MAY include a budget object Section 4.5 in the authorization endpoint request body alongside scope:

POST /authorize HTTP/1.1
Host: inference.example
Content-Type: application/json
AAuth-Capabilities: interaction, budget
Signature-Input: sig=("@method" "@authority"
    "@path" "signature-key");created=1754611200
Signature: sig=:...signature bytes...:
Signature-Key: sig=jwt;jwt="eyJhbGc..."

{
  "scope": "inference.completions",
  "budget": { "amount": 10000000, "unit": "USD", "decimals": 6 }
}

budget (OPTIONAL). The ceiling the agent is requesting. All three members of the budget object are REQUIRED when budget is present.

The resource MUST NOT reject the request because budget.amount exceeds what it will grant; it narrows instead Section 4.2. The resource MAY reject a budget whose unit it has not declared Section 17.

When the agent obtains its resource token from a 401 challenge rather than the authorization endpoint (([I-D.hardt-oauth-aauth-protocol]), Auth Token Required), it has not stated a budget and the resource sizes the resource token on its own. This is why max in budget_units matters: it is what makes the proactive path useful on a first attempt.

How an agent knows an operation is metered before its first call is answered by R3 ([I-D.hardt-aauth-r3]): a resource MAY annotate individual operations in its vocabulary with a budget annotation, and an agent that reads one knows to include budget in this request. The annotation states the fact of metering; budget_units states the units and ceilings.

7. Resource Token Extensions

This document extends the resource token (a JWT with typ: aa-resource+jwt) with two optional claims.

{
  "iss": "https://inference.example",
  "dwk": "aauth-resource.json",
  "aud": "https://ps.example",
  "jti": "rt-4c81fa",
  "ps": "https://ps.example",
  "sub": "8f14e45fceea167a5a36dedd4bea2543",
  "presented_jti": "pt-3ab910",
  "agent_jkt": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
  "tenant": "corp",
  "mission_s256": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
  "scope": "inference.completions",
  "budget": { "amount": 5000000, "unit": "USD", "decimals": 6 },
  "budget_consumed": [
    { "jti": "at-71b9d0", "consumed": 2000000 },
    { "jti": "at-5e22c1", "consumed": 431200 },
    { "jti": "at-08aa37", "consumed": 1250000 }
  ],
  "iat": 1754611200,
  "exp": 1754611500
}

7.1. Consumption Records

budget_consumed is an array of consumption records, most recent first. Each record has two members, both REQUIRED:

  • jti: The jti claim of a prior auth token.
  • consumed: What the resource metered against that token's budget, a non-negative integer in the unit and decimals of the budget claim in the same resource token.

A resource MUST NOT include budget_consumed unless budget is present in the same token, and MUST omit any record it cannot state in that budget's unit and decimals — a record carried in a stale scale is the thousandfold error Section 17 in miniature.

Records cover prior auth tokens matching this token's context: the same (iss, sub, aud) the resource aggregates against Section 14.4, and, when this resource token carries mission_s256, the same mission_s256. A mission-context re-authorization thus reports that mission's grants; a mission-less one reports the person's most recent grants at this resource.

A resource SHOULD NOT include more than 20 records. The practical bound is size: the resource token travels inside the AAuth-Requirement header on the 401 path, where header-field limits apply; twenty records cost roughly a kilobyte after base64 encoding.

A record is deliberately two members and no more. Every jti it names is a token the PS issued — or, in four-party access, relayed from the AS — so the PS already holds the granted amount, the mission, the scope, and the issuance time for each record and joins them from its own ledger. Carrying those values again would duplicate what the PS knows and put more of the person's financial detail into a token the agent also reads. What the PS cannot know, and what the record supplies, is what the resource actually metered.

Records are attributable only when the resource can identify the person, which it can once it has verified an auth token for them. A resource issuing a resource token to an agent it has not previously authorized has nothing to report and omits budget_consumed.

The records ride in the resource token because it already travels resource → agent → PS at exactly the moment the PS re-decides: no extra round trip, resource-signed, and interpretable without a metadata fetch. Totals over horizons longer than 20 grants are served at the usage endpoint Section 15.

8. PS Token Endpoint Extensions

No new request parameter is defined for the agent's request to the PS's auth_token_endpoint. The budget reaches the PS inside the resource token.

An agent seeking a larger budget obtains a fresh resource token from the resource — stating the larger figure in the authorization endpoint request Section 6, or being handed one on a 401 Section 13 — and SHOULD explain the need in the justification parameter of the PS token request.

When the PS issues the auth token itself (three-party), it applies the person's policy and issues per Section 10. When it federates (four-party), it proceeds per Section 9.

8.1. What the PS Is Deciding

The resource token's budget states what the resource will allow. It is an offer, not a request the PS is obliged to answer in full.

Against that offer the PS holds a ceiling for the person at this resource — a standing limit, a mission's stated intent, an organizational policy, or a figure the person supplied when asked. The ceiling is PS state. This document defines no wire format for it, no claim that carries it, and no way for the agent to read it. What the PS issues is an allocation drawn against it.

Sizing the allocation is where the PS's supervision happens. A PS that issues the resource's full offer every time has authorized the resource's maximum and learns nothing until the money is gone. A PS that issues a fraction sees the agent again when that fraction is spent, with consumption records in hand, and decides then whether the work is going as the person expected.

The interval is not fixed by the clock. An auth token expires within an hour, and its budget is exhausted after however much work it took to spend — whichever comes first returns the agent to the PS. A mission running cheaply reports on the hour; one running expensively reports in minutes. The PS sets that frequency by sizing the allocation, and no party configures it Section 14.3.

8.2. What the PS Reads

Four inputs are available at the moment of the decision, and a PS applying the person's policy SHOULD use all of them:

  • budget_consumed Section 7.1 in the resource token the agent just presented: what the last several grants actually cost, resource-signed, arriving at no round-trip cost.
  • Usage counters Section 15 at the resource's usage_endpoint: totals over calendar periods, and for a mission query the mission's total to date — the figures that cover the stretch when the agent was not talking to the PS.
  • The mission log (([I-D.hardt-oauth-aauth-protocol]), Mission Log): every prior token request, justification, and clarification in this mission, which is what makes "faster than expected" a judgement the PS can actually make.
  • The justification parameter of this request: why the agent says it needs more.

The first two are the spend; the second two are the context. A budget escalation is not interpretable without both.

8.3. How the PS Responds

Six responses are available. None is new to this document; the base protocol defines each, and this section states which apply to a budget decision.

Table 2
Response Mechanism
Grant the offer Issue an auth token with budget equal to the resource token's Section 10
Grant less Issue a lower amount Section 4.2
Ask the agent 202 with requirement=clarification
Ask the person 202 with requirement=interaction
Decline with a figure Error response carrying suggested_budget Section 17.1
End the work Terminate the mission

Granting less needs no signalling: the amount in the issued claim is the answer, and the agent reads it from the token it received Section 4.2.

Clarification is the response for an escalation the PS is not ready to refuse or approve. A PS that sees consumption running ahead of what the mission implies MAY return 202 with requirement=clarification (([I-D.hardt-oauth-aauth-protocol]), Clarification Required), putting a question to the agent before deciding. This is the channel that lets the PS tell an agent it is overspending, which narrowing alone cannot do — a smaller amount is silent, and the agent cannot distinguish a PS applying pressure from a resource lowering its own offer.

HTTP/1.1 202 Accepted
Location: /pending/abc123
Retry-After: 0
AAuth-Requirement: requirement=clarification
Content-Type: application/json

{
  "status": "pending",
  "clarification": "This mission has spent $18 of an
    expected $25 and has not booked anything yet. What
    is the remaining $12 for?",
  "timeout": 120
}

The agent's three replies are already defined and all three are useful here: a clarification_response explaining the spend, an updated_request carrying a fresh resource token for a smaller figure, or a DELETE withdrawing the request. The PS SHOULD enforce the base protocol's limit on clarification rounds. An agent that did not declare the clarification capability cannot be asked, and the PS decides without it.

Asking the person is the same mechanism one step further out, and is the right response when the answer is the person's rather than the agent's — a ceiling raise rather than an allocation.

A PS that puts a budget to the person for consent MUST present the amount as a human-readable figure in its unit — "$5.00", not {5000000, USD, 6} — visually distinct from any resource-supplied description, which is Markdown and MUST be sanitized before rendering Section 5. The amount is the decision the person is making.

Ending the work is the response to an agent whose spending the PS cannot account for. Terminating a mission is not a budget mechanism and this document defines nothing about it; it is named here because a budget escalation is one of the few signals that reliably surfaces an agent behaving unlike its mission.

9. PS-to-AS Token Request Extensions

This document extends the PS-to-AS token request (([I-D.hardt-oauth-aauth-protocol]), AS Token Endpoint) with one parameter, used in four-party access only.

budget (OPTIONAL): A budget object Section 4.5 carrying the ceiling the PS will allow.

POST /token HTTP/1.1
Host: as.inference.example
Content-Type: application/json
Signature-Input: sig=("@method" "@authority"
    "@path" "signature-key");created=1754611200
Signature: sig=:...signature bytes...:
Signature-Key: sig=jwks_uri;
    jwks_uri="https://ps.example/.well-known/jwks.json"

{
  "resource_token": "eyJhbGc...",
  "agent_token": "eyJhbGc...",
  "budget": { "amount": 2000000, "unit": "USD", "decimals": 6 }
}

The PS MUST copy unit and decimals from the resource token's budget claim unchanged, and MUST NOT set amount higher than the resource token's budget.amount. The AS MUST NOT issue a budget claim exceeding this parameter, and MAY lower it further.

When the resource token carries budget and the PS omits this parameter, the AS MUST treat the resource token's budget as the PS's ceiling.

10. Auth Token Extensions

This document extends the auth token (a JWT with typ: aa-auth+jwt) with one optional claim.

budget (OPTIONAL): A budget object Section 4.5. This is the granted budget and is authoritative.

{
  "iss": "https://ps.example",
  "dwk": "aauth-person.json",
  "aud": "https://inference.example",
  "jti": "at-71b9d0",
  "ps": "https://ps.example",
  "sub": "8f14e45fceea167a5a36dedd4bea2543",
  "cnf": { "jwk": { "kty": "OKP", "crv": "Ed25519",
                    "x": "NzbLsXh8uDCcd...", "alg": "Ed25519" } },
  "scope": "inference.completions",
  "budget": { "amount": 2000000, "unit": "USD", "decimals": 6 },
  "iat": 1754611200,
  "exp": 1754614800
}

Issuer rules:

Resource rules:

11. AAuth-Budget Response Header

AAuth-Budget is a response header carrying the remaining balance of the granted budget. It is a Dictionary ([RFC9651], Section 3.2), matching AAuth-Requirement.

AAuth-Budget: cost=221200; remaining=1568800;
    unit="USD"; decimals=6

Members:

Recipients MUST ignore members they do not recognize.

The header carries no granted member, no cumulative consumption figure, and no token reference. The agent holds the auth token it signed the request with and reads granted from there; what it needs per response is what this call cost and what is left, which is what the field carries. Cumulative consumption is a PS-facing figure, reported in the resource token Section 7.1 and at the usage endpoint Section 15; see Appendix A.4 for why it is not also reported to the agent.

The scope of the reported figures is this auth token's budget, because the budget expires with the token Section 14.

11.1. Sending Rules

A resource that granted a budget SHOULD include AAuth-Budget on every response to a request bearing that auth token — success, error, and the 401 challenge, where it reads remaining=0 beside the AAuth-Requirement header Section 13.

This is SHOULD rather than MUST because the failing layer may sit below the metering layer: a gateway timeout, a crashed worker, or a fault in metering itself produces a response no budget figure can ride on. A resource MUST NOT omit the field for any other reason. An agent that misses the field learns the balance from its next response, and until then applies Section 12.3.

HTTP/1.1 401 Unauthorized
AAuth-Requirement: requirement=auth-token;
    resource-token="eyJ..."; reason=budget-exhausted
AAuth-Budget: remaining=0; unit="USD"; decimals=6

That the field appears on every response regardless of status is the point of putting it in a header. The agent reads the same field whether the body is JSON, a server-sent event stream, a streamed completion, or a problem document [RFC9457].

Intermediaries MUST NOT add, alter, or remove AAuth-Budget. The field reports the state of an authorization the resource issued; an intermediary rewriting it is asserting authorization state it does not hold. This is the inverse of the RateLimit rule permitting intermediaries to tighten values ([I-D.ietf-httpapi-ratelimit-headers]) — see Appendix A.7.

Recipients MUST ignore AAuth-Budget on a response served from cache with a positive current_age ([RFC9111], Section 4.2.3). This is the one RateLimit rule that carries over unchanged.

11.2. Denomination Conflict

The auth token's budget claim is authoritative. A recipient MUST NOT act on a header unit or decimals that disagrees with the budget claim in the auth token it presented, and SHOULD treat the whole field as unreliable for that response.

Including the pair makes the field self-describing for proxies and logs that never parse a JWT, at the cost of duplicating signed values; the conflict rule is that cost made explicit.

12. Streaming

Response headers are written before the body, and a streamed response's actual cost is known only when the stream ends. The resource therefore cannot state cost in the header. What it can state is what it has held: it reserved before serving Section 14.7, and remaining is already net of that reservation.

A resource serving a streamed response SHOULD send reserved in the header and cost in a trailer. A resource that cannot send trailers sends reserved alone Section 12.1.

HTTP/1.1 200 OK
Content-Type: text/event-stream
Trailer: AAuth-Budget
AAuth-Budget: remaining=1568800; reserved=431200;
    unit="USD"; decimals=6

   ...stream...

AAuth-Budget: cost=221200

The agent computes the balance after the request as remaining + reserved - cost. Here that is 1,778,800: the 431,200 held was not all spent, and the unspent 210,000 returns to the grant.

12.1. When cost Is Omitted

Not every resource can send a trailer. Trailers exist only on a chunked or HTTP/2-and-later response, and several widely deployed server runtimes provide no way to emit one at all. A resource in that position knows the cost of a streamed response only after its last opportunity to report it.

Such a resource omits cost and MUST send reserved in the header. The agent recovers the figure from the following response:

cost = previous remaining + reserved - current remaining

The subtraction works because remaining is already net of reservations Section 11: the earlier figure is net of the hold, the later one reflects the commit and the release of the unspent remainder. reserved is the term that connects them, which is why it stops being optional here.

It recovers one request's cost only where requests on that token are serial. An agent with several requests in flight on one token recovers the net of everything that settled between the two responses, not the cost of any single one, because every concurrent request moves the same remaining. An agent that wants per-request figures from a resource that omits cost serializes its requests on that token; an agent that only needs the balance does not have to.

Until that next response arrives the agent applies Section 12.3 and treats the request as having cost the full reserved amount. That is the conservative direction, and it is the same rule the agent already applies to a response it never received.

A resource MUST NOT omit both cost and reserved. That combination reports that a metered request happened and gives the agent no figure for it, neither exact nor conservative.

12.2. Trailer Rules

A resource MAY send AAuth-Budget as a trailer field, subject to three rules:

  1. The response MUST list AAuth-Budget in a Trailer header field ([RFC9110], Section 6.6.1).
  2. A trailer instance MUST NOT restate a member the header instance carried. It carries cost and nothing else.
  3. A recipient MUST NOT treat the trailer as necessary. A response that never delivers one is complete.

Rule 2 is what makes the field safe under either way a recipient handles trailers. A recipient that discards trailers keeps the header's remaining, which is a floor and therefore correct if conservative. A recipient that merges trailer fields into the header set produces a single Dictionary whose keys do not collide, because no key appears twice. Restating a member is the case that would break: AAuth-Budget is a Dictionary, duplicate keys resolve last-wins ([RFC9651], Section 3.2), and which value won would then depend on whether the recipient merged — a difference no sender can observe or control.

Rule 3 follows from trailers being droppable in transit ([RFC9110], Section 6.5.2) and from trailers existing only on a chunked or HTTP/2-and-later response. The figure a trailer would have carried is also in the next response's remaining, so nothing is lost that is not recovered on the following call.

12.3. Ambiguous Failures

A request whose response never arrives — a dropped connection, an aborted stream — leaves the agent unable to say whether it was metered. No trailer arrives on an aborted stream, and the header that would have carried cost is on the response that was lost.

The agent MUST assume the request cost as much as the resource had held for it: reserved where it saw one, and otherwise the request's maximum cost as the resource would have bounded it Section 14.7. It carries that assumption until a later response's remaining supersedes it, and it SHOULD NOT retry a chargeable request before then.

Assuming the maximum is the conservative direction: an agent that under-assumes plans spending it does not have and discovers the shortfall as a 401 Section 13.

Inference APIs commonly emit final usage in the stream's terminal event. That is application-layer and does not provide the application independence this header exists for, so a resource that emits it and can also send a trailer SHOULD send both. What it does mean is that the exact number exists when the stream ends, and a resource whose runtime offers no trailer has it and no protocol carrier for it until the next response Section 12.1.

13. Budget Exhaustion

Auth token expired. The budget expires with the token. This is already-defined behavior: 401 with AAuth-Requirement: requirement=auth-token; resource-token="...". This document adds nothing.

Budget exhausted, token still valid. The same response. The base protocol already permits a resource to return requirement=auth-token with a new resource token to a request that already carries an auth token, when the request requires higher authorization than the current token provides, and requires agents to be prepared for step-up at any time. Budget exhaustion is that case, and the agent's action is identical either way: take the fresh resource token to its PS.

Request exceeds the remainder. The budget has remainder, but this request's maximum cost exceeds it Section 14.7. The same 401 challenge, with reason=insufficient-budget. The agent has a second move here that exhaustion does not offer: lower the request's bound to fit the remaining reported beside the challenge, and retry on the token it already holds. The required member Section 13.2 is what makes that move a calculation rather than a search.

A request refused under this section MUST NOT draw down the budget or appear in the records and counters. The resource declined to serve it; metering the refusal would make exhaustion self-perpetuating.

The fresh resource token SHOULD carry updated budget_consumed records Section 7.1, which show the PS what the last several grants were actually consumed — the context for deciding whether to authorize more.

13.1. The reason Parameter

A resource challenging because the budget is exhausted rather than because the token expired SHOULD include a reason parameter on the requirement member:

HTTP/1.1 401 Unauthorized
AAuth-Requirement: requirement=auth-token;
    resource-token="eyJ..."; reason=budget-exhausted
AAuth-Budget: cost=180000; remaining=0;
    unit="USD"; decimals=6

reason is a Token. This document defines two values:

  • budget-exhausted: The granted budget is spent.
  • insufficient-budget: The budget has remainder, but this request's maximum cost exceeds it Section 14.7.

For either value, the enclosed resource token MAY carry a budget sized for what the resource would need to see granted. The denial is itself the re-authorization offer.

13.2. Refusing a Request That Does Not Fit

A resource refusing under insufficient-budget has computed the request's maximum cost — Section 14.7 requires it to, before serving — and SHOULD report that figure as the required member of AAuth-Budget:

HTTP/1.1 401 Unauthorized
AAuth-Requirement: requirement=auth-token;
    resource-token="eyJ..."; reason=insufficient-budget
AAuth-Budget: remaining=150000; required=400000;
    unit="USD"; decimals=6

The agent now knows both halves of the refusal: it has 0.15, and the request needed 0.40. Without required it knows only the first, and the move Section 13 offers it — lower the request's bound and retry on the token it already holds — becomes a search. It cannot compute the figure itself, because the bound is the resource's own calculation against its own pricing, and no part of this specification requires a resource to publish what an operation costs.

required is not the same figure as the budget claim of the enclosed resource token, and the two SHOULD differ. The resource token's budget is a re-authorization offer addressed to the PS, and a resource sizing it for exactly the refused request hands back a grant good for one call. required is a fact about the request that was refused, addressed to the agent. The header carries it because the agent is the party that acts on it, and because the retry path it enables does not involve the person server at all.

A resource MAY refuse without required — where the operation has no cost bound it is willing to state, or where stating it would disclose pricing the resource does not publish. The agent then falls back to remaining alone.

No new requirement value is minted. The base protocol says an agent that does not recognize a requirement value MUST NOT treat the response as satisfiable and surfaces it as an error, while recipients MUST ignore unknown parameters on the requirement member. A new value would hard-fail every budget-unaware agent on a condition that plain auth-token resolves correctly. That asymmetry — unknown values fail, unknown parameters are ignored — is why this extension extends by parameter.

An agent that understands the reason values knows what to do beyond re-authorizing: for budget-exhausted, request a larger budget and say why in the justification it sends to its PS; for insufficient-budget, either that, or shrink the request and retry without involving the PS at all. An agent that understands neither ignores the parameter and re-authorizes, which is always correct.

13.3. Boundaries

429 Too Many Requests is not used. It is rate limiting, and in this protocol it already means "increase the polling interval by 5 seconds" in the deferred response state machine.

402 Payment Required is a different condition: the resource needs payment rather than re-authorization from the person. The base protocol already permits AAuth-Requirement on a 402, and this document does not change that.

14. Enforcement

14.1. The Resource Enforces

The PS authorizes a number. The resource counts. The PS is not in the request path and does not meter.

14.2. Budgets Require Auth-Token Mode

A budget is carried in the budget claim of an auth token, so a resource can enforce one only where it issues auth tokens. A resource that declares access_mode: person-token and serves requests on the person's identity alone (([I-D.hardt-oauth-aauth-protocol]), Person Identity Access) has no auth token to read a budget from, and neither does a resource operating in agent-token or session-token mode.

A metered resource therefore declares access_mode: auth-token for the endpoints it meters. It MAY continue to serve unmetered endpoints on a person token; access modes apply per endpoint. A resource MUST NOT rely on this extension for an endpoint it serves without an auth token.

Where the resource holds the authorization state itself rather than reading it from a signed claim, there is nothing for the person server to have bounded.

14.3. Token Scope

A budget is scoped to the auth token that carries it and expires with it. There is no persistent grant identifier and no requirement that the PS carry a budget across re-issuance. This is the mechanism, not a gap: re-issuance is where the PS re-decides Section 8.1, and a budget that survived it would be a standing grant the PS no longer sizes.

The budget is revoked with the token. Any AAuth server that issues tokens MAY provide a revocation endpoint, and revoking an auth token by (iss, jti) (([I-D.hardt-oauth-aauth-protocol]), Token Revocation) ends its budget along with the rest of its authorization. Consumption already committed is unaffected — a budget is a ceiling on spending, not a claim on what was spent — and a request already in flight completes, because revocation stops a token being used again rather than interrupting a call. This document adds nothing to that mechanism; it is named here because a person hitting stop expects the money to stop, and expiry alone bounds that at an hour.

Two conditions return the agent to the PS, and either is sufficient. The auth token expires, which the base protocol caps at one hour. Or its budget is exhausted Section 13, which happens after however much work it took to spend. Expiry is proportional to time and exhaustion is proportional to spend, so the supervision interval tracks whichever is moving faster: a mission running cheaply reports on the hour, one running expensively reports in minutes, and no party configures the difference.

14.4. Aggregation

Two things are counted, against different keys, and they are not the same requirement.

The cap the resource enforces is per auth token. It is the budget claim of the token presented, and Section 14.7 states the invariant: committed consumption plus outstanding reservations against that token MUST NOT exceed its granted amount. A resource needs no cross-token arithmetic to enforce a budget.

The ledger the resource keeps is per person. The resource MUST aggregate consumption against the key (iss, sub, aud) of the auth token, which is what the consumption records Section 7.1 and the usage counters Section 15 report. (iss, sub) identifies the person — sub is unique within its issuer, and values from different issuers are different people — and aud is the resource itself. This document introduces no new identifier.

The ledger is not a second ceiling. A resource MUST NOT refuse a request that fits its token's budget because a per-person total has reached some figure the resource inferred; no party told it such a figure, and the budgets it was handed are what it was authorized to honor. Holding a person's spending across concurrent tokens within bounds is the PS's job Section 14.6, because the PS is the party that issues them and the only one that knows the ceiling Section 8.1.

A per-agent ceiling is not a resource-side key either. A person server that wants one agent capped at less than another issues it a smaller allocation Section 8.1; the enforcement is the token's own budget, and no resource-side dimension is involved. What a resource cannot supply from allocations alone is how much each agent actually spent, since an allocation is a ceiling rather than a figure — that is what the per-key query at the usage endpoint serves Section 15.2.2.

The ledger's key is the person, not the agent and not the mission. An auth token names no agent, and a person's spending at a resource is theirs whichever agent incurred it; a per-agent key would also reset every time the person changed agents. mission_s256 is optional — a token may carry one or not — so a mission-keyed ledger has no bucket for a mission-less token, and Section 18 requires mission-less tokens for standing inference budgets. The person is the only key present on every auth token. Missions are an attribution dimension over that ledger Section 14.10, not the ledger itself.

14.5. The Billing Account

A resource that meters usually charges someone for it, and the party it charges is an account in its own systems. Nothing in a budget names that account. The aggregation key above is (iss, sub, aud), and sub is directed per person server — it identifies a person at one PS and carries no meaning at the resource beyond what the resource has learned about it.

For most resources that is sufficient and no mechanism is needed. The base protocol keys a person's relationship with a resource on (iss, sub) precisely so it survives a change of agent, and a resource holding one account per person looks the account up from that pair, or from (iss, tenant, sub) where the person belongs to an organization. Consumption then meters against the account the resource already had.

Beyond that, two different questions arise, and they compose rather than substitute. The first is asked once per person; the second on every authorization.

Which person is this? The first budgeted request carrying a sub the resource has not seen is a question for the person, not the agent, and both access modes answer it with an interaction the person completes at the party that holds the account.

In three-party access the resource asks. It puts an interaction claim in the resource token, and the person server chains the person through the resource's own flow — signing in, creating an account, connecting a payment method — before completing its own consent (([I-D.hardt-oauth-aauth-protocol]), Resource-Initiated Interaction). Because the resource issues the resource token, it decides when to ask again: once per person, or once per mission, since it sees mission_s256 at that moment.

In four-party access the AS asks, returning 202 with requirement=interaction to the person server's token request (([I-D.hardt-oauth-aauth-protocol]), Access Server Federation). This is the same one-time binding the AS already performs to establish trust with a person server, answering a second question at the moment it is already asking the person who they are.

Because sub is directed per person server, a person reaching the same resource through two person servers presents two identifiers. The binding interaction is what attaches both to one account, and a resource that skips it sees two people and bills two ledgers.

Which of their accounts? Binding establishes who the person is. It does not say which of several accounts an authorization is for, and a person who holds more than one at the resource has to say. Account Binding (([I-D.hardt-oauth-aauth-protocol]), Account Binding) carries the answer: an OPTIONAL account parameter on the authorization endpoint request, named from the resource's own namespace, echoed as the account claim of the resource token and copied into the auth token.

This applies in both access modes; how account reaches the issuer, and what each party does with it, is specified there and not restated here. In four-party access it reaches the AS in the resource token, so the binding tells the AS who the person is and account tells it which of their accounts this authorization bills.

A metered resource should ask for account where a person may hold more than one, because a budget enforced against the wrong account is charged to the wrong payer. A resource holding one balance per person needs none of it: binding is the whole mechanism, and account never appears in its tokens.

None of this is specific to budgets, and this document defines no new mechanism for it. It is stated here because a metered resource meets these cases on its first request and the rest of this document is silent on them.

14.6. Concurrency

An agent may hold several concurrent auth tokens at the same resource — the mission_s256 claim means concurrent missions produce concurrent tokens, each with its own budget, for up to an hour. Handling this is mandatory, not optional:

  • A resource MUST apply the reserve-commit-release invariant of Section 14.7 atomically per auth token, so that concurrent requests presenting the same token cannot together exceed its budget.
  • A resource MUST post consumption to the (iss, sub, aud) ledger Section 14.4 atomically, so that concurrent requests across different tokens do not lose or double-count against the records and counters.
  • A PS SHOULD size per-token budgets so that their sum stays within whatever standing ceiling it holds for the person at that resource. This is the only place the cross-token total is enforced.

The bound on over-issuance is the auth token lifetime multiplied by the number of concurrent tokens. A PS that issues n concurrent tokens of X each has authorized up to nX for as long as an hour, regardless of any standing figure it intended to hold.

14.7. The Budget Is a Hard Cap

A resource MUST NOT let metered consumption exceed the granted amount. A budget is an authorization, and an authorization the enforcer may exceed is a hint.

Output is metered after it is generated, so honoring the cap means bounding the request before serving it. Before serving a chargeable request, the resource determines the request's maximum cost — from a bound the request declares, such as a maximum output length, or from a documented default — and refuses the request with reason=insufficient-budget Section 13.1 when that maximum exceeds the remainder. An operation with no finite cost bound MUST be given one, be truncated when the remainder is consumed, or be refused.

The implementation shape is reserve-commit-release: atomically reserve the maximum against the grant, serve, commit the actual charge, release the difference. The normative requirement is the invariant, not the mechanism: committed consumption plus outstanding reservations MUST NOT exceed the granted amount at any moment, including under concurrent requests Section 14.6.

The honest cost of the invariant lands near exhaustion: a request bounded at more than the remainder is refused even when its actual cost would have fit. The mitigation is the agent's — read remaining from the refusal's AAuth-Budget header and retry with a bound that fits — and that is the correct pressure, since it rewards realistic bounds.

14.8. Failed Calls

Whether a request that fails — a 5xx after input tokens were consumed — draws down the budget is the resource's metering policy. Whatever it meters, the records and counters MUST reflect, or the person's numbers will not reconcile with the bill.

14.9. Delegation

A budget is not delegable. Each auth token carries its own budget, and consumption through a delegated token draws down that token's budget alone. Both delegation paths — call chaining and sub-agent authorization ([I-D.hardt-oauth-aauth-protocol]) — obtain each downstream auth token from the person server, so the PS sizes every grant in a chain, and Section 14.6 already governs their sum. No sub-budget or draw-down-from-parent mechanism is defined.

14.10. Mission Attribution

Consumption is attributed to a mission using the mission_s256 claim of the auth token. That claim is PS-asserted throughout: the person server validated the mission when it issued the person token, the resource copied it into the resource token, and the person server bound the resource token to the person token it had issued. No party in the chain takes the mission on the agent's word, which is what makes a resource's per-mission figures worth reading.

15. Usage Counters

Consumption records reach the PS only when the agent brings a resource token back. The agent is the party being budgeted and also the courier of the evidence: it cannot falsify the records, but between re-authorizations it does not appear, and the PS is blind for up to an hour per token. The usage_endpoint Section 5 removes the agent from that loop: the PS queries the resource directly, on a channel the agent is never on.

The endpoint serves usage counters: pre-summed consumption totals the PS reads, acts on, and displays. It does not compute, convert, or round. The resource keeps a handful of running integers, incremented at metering time; serving the endpoint requires no per-record history.

15.1. Usage Request

The PS MUST make a signed POST to the usage_endpoint, authenticating exactly as it does at an AS auth_token_endpoint ([I-D.hardt-oauth-aauth-protocol]): an HTTP Sig whose Signature-Key header carries scheme=jwks_uri, with the signature additionally covering content-type and content-digest.

The body carries at most one scope key, naming a claim value the resource has seen in auth tokens:

  • sub: A directed person identifier. Scope: the person at this resource, across all their agents and missions.
  • tenant: A tenant identifier. Scope: the organization, across its people.
  • mission_s256: A mission identifier. Scope: one mission.

and one OPTIONAL member:

  • jkts: An array of JWK Thumbprints ([RFC7638]), each naming a signing key the resource has seen present an auth token. Asks for what each of those keys consumed Section 15.2.2.

A request MUST carry a scope key or jkts, and MAY carry both. At most one scope key may appear. A request with more than one scope key, or with neither a scope key nor jkts, is an error Section 15.3.

POST /usage HTTP/1.1
Host: inference.example
Content-Type: application/json
Content-Digest: sha-256=:...:
Signature-Input: sig=("@method" "@authority" "@path"
    "content-type" "content-digest"
    "signature-key");created=1754620000
Signature: sig=:...signature bytes...:
Signature-Key: sig=jwks_uri;
    jwks_uri="https://ps.example/.well-known/jwks.json"

{
  "sub": "8f14e45fceea167a5a36dedd4bea2543",
  "jkts": ["NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
           "0ZcOCORZNYy-DWpqq30BbmLzO1Yw3ZQhIgnHZQKxNVE"]
}

15.2. Usage Response

{
  "as_of": 1754619970,
  "aud": "https://ps.example",
  "unit": "USD",
  "decimals": 6,
  "sub": "8f14e45fceea167a5a36dedd4bea2543",
  "usage": {
    "day": 1243180,
    "week": 3118400,
    "month": 8432650,
    "year": 39847220,
    "all_time": 61438050
  },
  "jkts": {
    "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs": 38215600,
    "0ZcOCORZNYy-DWpqq30BbmLzO1Yw3ZQhIgnHZQKxNVE": 23222450
  }
}

  • as_of (REQUIRED): The time through which the figures are complete, in seconds since the Unix epoch. Metering aggregation MAY lag serving; as_of is what keeps a lagging figure honest.
  • aud (REQUIRED): The person server the response was produced for, identified as in the ps claim of a resource token. It is what stops a signed response being presented to a third party as a statement about them Section 15.2.4.
  • unit (REQUIRED) and decimals (REQUIRED): The unit every figure in the response is denominated in, and its scale, as in the budget object Section 4.5.
  • The scope key from the request, echoed unchanged — sub, tenant, or mission_s256 — present only when the request carried one.
  • usage (REQUIRED when the request carried a scope key): Calendar counters for that scope.
  • jkts (REQUIRED when the request carried jkts): An object mapping each thumbprint to what that key consumed Section 15.2.2.

15.2.1. Calendar Counters

The members of usage are day, week, month, year, and all_time, following the interval enumeration of Stripe Issuing [Stripe.Issuing] Appendix B.4. Each is a non-negative integer giving consumption within the current period:

  • day: since 00:00 UTC today.
  • week: since Monday 00:00 UTC of the current ISO 8601 week.
  • month, year: since the start of the current UTC calendar month or year.
  • all_time (REQUIRED): everything the resource has metered against budgets under this key.

All period boundaries are UTC. This is a definition, not a deployment choice: no timezone appears in metadata or in the response, and every party computes the same figure. The cost is that "today" resets mid-afternoon in Auckland, which Stripe Issuing accepts for the same reason this document does — the counter is decision context, not a bill.

The calendar counters other than all_time are OPTIONAL; a resource omits periods it does not track. For a mission_s256 query, all_time is the mission total — the figure a PS wants when deciding whether to fund a mission's continuation — and a resource MAY serve it alone.

Counters are subject to the 15-digit bound of Section 4.4. A resource whose cumulative figure would exceed it omits that counter rather than reporting an inexact number.

all_time reaches as far back as the resource retains. This document sets no retention requirement for scope keys; the person's bill is the durable record.

15.2.2. Per-Key Figures

Each member of jkts is a thumbprint mapped to a single non-negative integer: everything the resource has metered against budgets on auth tokens presented by that key.

There are no calendar periods here. A key's consumption is already bounded by the tokens issued to it, and an auth token lives at most an hour; a key that has stopped presenting tokens has a figure that no longer moves. Periods answer "how much this month", which is a question about a person, not about a key.

A resource SHOULD retain a key's figure for at least 24 hours after that key's last metered request, and MAY retain it longer. The bound is idle time rather than age, so a key in continuous use is never pruned. Beyond that window the PS is the party that accumulates: it polls, it knows which keys belonged to which agent across rotations, and it holds the history. The resource keeps a short tail.

A resource MUST omit a thumbprint from jkts rather than report zero for it when it holds no figure — because the key is unrecognized, or because its figure has been pruned. Absence means the resource cannot answer; a present zero means the key consumed nothing. This differs from the treatment of an unrecognized scope key Section 15.3, and the reason is that there is nothing to conceal: the PS issued or relayed every auth token, so it already knows the key exists, and a zero that means "pruned" would be a wrong answer to an allocation decision rather than a withheld one.

15.2.3. One Unit Per Response

Every figure in a response is in one unit, named once at the top level, and it is the unit the resource meters in. There is no request parameter selecting it.

The alternative is a per-unit array at every level, which costs every response the shape needed by deployments that meter in one unit — which is nearly all of them, since a resource that meters several quantities collapses them to one billing unit before denominating a budget Section 1.1. A resource may still declare several units in budget_units Section 5, because that is what an agent may ask a budget to be denominated in; what this endpoint reports is what the resource actually metered, and the response says which unit that was.

15.2.4. The Signed Response

Signing the usage response is RECOMMENDED. A resource that signs uses an HTTP Sig with a key from the jwks_uri in its resource metadata Section 5 — the same key material the PS already fetched to verify resource tokens. The signature MUST cover @status, content-type, and content-digest, and MUST be bound to the request by covering the request's @authority and @path with the req parameter ([RFC9421]).

HTTP/1.1 200 OK
Content-Type: application/json
Content-Digest: sha-256=:...:
Signature-Input: sig=("@status" "content-type"
    "content-digest" "@authority";req "@path";req);
    created=1754620001
Signature: sig=:...signature bytes...:
Signature-Key: sig=jwks_uri;
    jwks_uri="https://inference.example/.well-known/jwks.json"

The endpoint reports what a person owes for, so an unsigned figure is one the party that produced it can later disown. Signing makes the resource committed to what it reported: it cannot tell the person server one number and the biller another. It does not make the meter honest — the resource is the counterparty as well as the signer — and Section 19.4 covers what remains.

It also closes an asymmetry. Consumption records Section 7.1 are already resource-signed, because they ride inside a resource token. The usage endpoint is the only PS-facing consumption channel that is not.

It is RECOMMENDED rather than REQUIRED because the figures are decision context rather than authorization, and because this would be the first response-side signature in the AAuth family — the signature profile is request-side throughout Section 19.1. A person server receiving an unsigned response is not in a position to do anything but read it: refusing it leaves the PS with no figures rather than unattributable ones, which is the worse of the two. What signing changes is whether the resource can later disown what it said, and that is worth having wherever both ends will implement it.

aud is what keeps the signed response non-transferable. Without it a resource-signed statement of consumption could be handed to a third party as though it described them, and the figures carry no other indication of who asked.

15.3. Authorization and Errors

The jwks_uri in the Signature-Key header names the calling person server, and is the value the response echoes as aud. The resource MUST only answer for values that have appeared in auth tokens it accepted from that person server — tokens whose iss (three-party) or ps claim (four-party) names it. This applies to thumbprints in jkts as much as to scope keys. sub is directed per PS, so one person server cannot even name another's subjects; tenant, mission_s256, and thumbprints are not directed, and this check is what stops a third party from querying them.

The access server is not an entitled caller. The AS acts for the resource and sits in its trust domain; whatever consumption figures it needs, it gets from the resource directly, outside this protocol.

A query for a scope key the resource does not recognize returns 200 with usage omitted; "never seen" and "nothing consumed" are deliberately indistinguishable, so that a query cannot be used to discover whether a person holds an account. Unrecognized thumbprints are handled differently and for a stated reason Section 15.2.2.

invalid_request, using the error response format of ([I-D.hardt-oauth-aauth-protocol]), is returned for a body carrying more than one scope key, carrying neither a scope key nor jkts, or carrying a malformed value.

A resource MAY rate-limit the endpoint, using the RateLimit fields ([I-D.ietf-httpapi-ratelimit-headers]) as on any endpoint. A PS SHOULD poll no faster than its decisions require.

15.4. Division of Labor

The two PS-facing channels answer different questions at different moments. The consumption records Section 7.1 serve the re-authorization decision: they arrive in-band, resource-signed, at no round-trip cost, exactly when the PS is deciding. The usage counters serve everything else: supervision between re-authorizations, mission totals past the 20-record window, tenant-level exposure, how a person's spending divides among their agents Section 15.2.2, and the person's dashboard. A metered resource SHOULD implement both; the narrowing chain Section 4.2 functions with records alone. The agent's own view is neither of these: it is the AAuth-Budget header Section 11, scoped to the token it holds and to the request it just made.

16. Capability Negotiation

This document adds budget to the AAuth Capability Value Registry. An agent that understands budget semantics — the budget claim, the AAuth-Budget header, and the reason values Section 13.1 — SHOULD include budget in its AAuth-Capabilities request header, and in the capabilities parameter of its PS token requests:

AAuth-Capabilities: interaction, clarification, budget

This tells the resource up front whether the agent will read what it sends, rather than leaving it to rely on ignore-unknown-parameter behavior. It is the same negotiation interaction performs for requirement=interaction. Recipients MUST ignore unrecognized capability values.

A resource MUST NOT withhold AAuth-Budget from an agent that did not declare the capability. The header is safe to ignore.

17. Errors

Narrowing is never an error. A resource that will grant less than the agent asked for issues a smaller budget; a PS or AS that will allow less issues a smaller amount. No error is returned in either case.

Errors are reserved for statements that cannot be reconciled:

Table 3
Error Status Endpoint Meaning
invalid_budget 400 Authorization endpoint The budget object is malformed, or names a unit the resource has not declared in budget_units
invalid_budget 400 PS and AS auth token endpoints The resource token's budget.decimals disagrees with the value declared for that unit in the resource's budget_units metadata, or the object is otherwise malformed
invalid_request 400 Usage endpoint More than one scope key, neither a scope key nor jkts, or a malformed value Section 15.3

Error responses use the error response format defined in AAuth Protocol ([I-D.hardt-oauth-aauth-protocol]).

invalid_budget at the authorization endpoint parallels invalid_scope: the agent named something the resource does not recognize.

A decimals mismatch at the PS or AS MUST be a hard reject rather than a narrowing. A mismatch means one of the two figures is stale, and guessing which one is worse than failing — the error modes are a budget interpreted a thousandfold too large or too small.

A PS or AS that has no cached budget_units for the resource and cannot fetch the metadata MAY proceed on the resource token's decimals, which is signed by the resource. The cross-check is a defense against staleness, not against the resource.

17.1. Declining with Guidance

A PS granting less than the resource offered needs no mechanism: the amount in the issued claim says it. A PS or AS that declines a token request outright on budget grounds MAY include suggested_budget in its error response body: a budget object Section 4.5 whose unit and decimals are copied from the resource token, and whose amount is what the issuer would currently accept. It is guidance, not a grant — the agent's move is a fresh resource token at that figure Section 6 and a new token request, with the usual consent and policy evaluation.

18. Standing Authorization for Metered Inference

Metered inference is the initiating use case for this extension and has a property that distinguishes it from most resource access: the agent needs it before it can do anything else, including decide what else it needs.

Inference authorization is agent-scoped and standing. It is established at first PS contact, not per mission. The reason is a termination argument rather than a preference: a per-mission inference budget requires the agent to run inference in order to evaluate whether it needs more inference budget, and that evaluation itself consumes inference. A standing allocation terminates; a per-mission one does not.

Mission-less auth tokens are already legal. mission_s256 is optional in a person token and therefore in everything derived from it, and the PS permission and interaction endpoints work with or without a mission. No new token type is needed for this pattern.

Do not model the harness as a mission. A mission description is defined as human intent expressed in Markdown. Synthesizing an "operating mission" or "harness mission" to hold the inference budget would put a fabricated description into a signed, content-addressed blob, and mission revocation — a kill switch for one piece of work — would then also be the kill switch for the agent's ability to think. Those are two distinct controls and conflating them is a mistake.

Pre-mission inference runs on a mission-less auth token and lands in the person's usage with no mission attribution. Mission boundaries become natural auth token re-issuance points, which costs nothing: the agent is already talking to its PS to create the mission.

AP-bundled inference is out of scope. Where the agent provider pays for the agent's inference, the agent token is the credential, the person server never appears, and nothing in this document applies. This is the common deployment today, and readers will otherwise assume the extension covers it. The case that does engage this extension is a harness written by the agent provider spending against the person's inference account — which is where a ceiling matters most, because the agent provider's software is deciding how much of the person's money to consume.

A TPX provider adds agent support without touching its meter. A TPX [TPX] provider already prices per token, reports cost in each response's usage, and enforces a person-granted budget as a hard cap — for human-driven apps holding OAuth grants. Serving agents means accepting AAuth auth tokens carrying budget beside those grants: two authorization envelopes over one metering core. Deployed this way, this extension is the AAuth binding of TPX.

Neither envelope displaces the other. A person driving an app and an agent acting for that person are different situations, and a provider serving both has one meter under two front doors Section 22.

19. Security Considerations

19.1. The Header Is Unsigned

AAuth-Budget is not signed, so an intermediary can lie about the balance. The failure modes are bounded. Understating remaining makes the agent re-authorize earlier than it needed to. Overstating it makes the agent hit an unexpected 401. Neither causes overspend, because enforcement is the resource checking metered consumption against the signed budget claim of the auth token — the header is a pacing signal, not the authorization.

An implementation that needs the balance to be trustworthy rather than merely harmless can cover AAuth-Budget with an HTTP Message Signature on the response ([RFC9421]), as the usage endpoint recommends for its own responses Section 15.2.4. It is not required here, and the difference is what each response is for. A usage response is a statement of what a person owes for, read by a party that may later have to hold the resource to it. A balance on a request response is a pacing signal the agent acts on immediately and re-reads on the next call, and signing every metered response to protect a figure that is superseded seconds later buys little for what it costs at inference volumes.

19.2. Budget Is Not a Substitute for Scope

A budget bounds how much an agent may consume, not what it may do. An agent with a small budget at a resource that performs irreversible actions can still perform them. Resources MUST continue to enforce scope, and SHOULD NOT treat the presence of a budget as evidence that the person reviewed anything beyond the amount.

19.3. Over-Issuance Through Concurrency

A PS that issues concurrent auth tokens without tracking their sum has authorized their sum, not any single one of them Section 14.6. A resource that aggregates non-atomically across concurrent requests can be driven past the ceiling by parallel calls. Both are implementation errors that produce real overspend, and both are easy to make.

19.4. Consumption Reports as Attack Surface

budget_consumed records are resource-signed and usage counters are served from the resource's authenticated endpoint, signed where the resource follows Section 15.2.4; both are the resource's own account of what it metered. A resource that inflates them can induce a PS to authorize more than the person intended, or to refuse further authorization. A PS SHOULD reconcile them against the person's billing relationship with the resource where one exists, and SHOULD NOT treat them as authoritative for anything other than its own next decision.

Signing the usage response Section 15.2.4 does not change this. It makes the resource committed to a figure rather than able to disown it, which is what stops it reporting one number to the person server and another to the biller. It does not make the meter honest, because the resource meters, reports, and signs. The person's bill is the record a dispute settles against, and a signed report is evidence of what the resource said, not of what it consumed.

19.5. Unit Substitution

Because a resource may change the unit between what the agent requested and what it offers Section 4.2, a PS reading a budget claim must read unit and decimals rather than assuming the denomination the agent described in justification. The cross-check against budget_units in Section 17 is the defense against a stale scale; there is no defense against a resource that misdenominates deliberately, and none is needed — that resource is equally free to ignore the budget it was granted.

20. Privacy Considerations

A budget amount is financial information about the person. It travels from the resource to the PS in the resource token and back in the auth token, and appears in plaintext in the AAuth-Budget response header on every response.

Consumption records and usage counters are more revealing than the budget itself: counters describe the person's spending at that resource over time and by mission, and records itemize it per grant. All of it is visible to the person's PS by design — the PS is deciding on the person's behalf.

The two channels differ in who else can read them. Usage counters travel only between resource and PS, so tenant-scope and long-horizon figures exist nowhere the agent can see. Consumption records ride in the resource token, which the agent relays and can read, and because records are matched per person rather than per agent Section 7.1, they can name tokens the person's other agents incurred. Each record is only a jti and an amount — the minimal record shape is what caps the exposure there — and a resource that considers even that too revealing omits budget_consumed and serves the usage endpoint alone.

Because AAuth-Budget is unsigned and unencrypted above TLS, every intermediary on the path sees the person's remaining balance at that resource, and the price of the request that produced the response. Carrying no cumulative figure bounds this: an intermediary sees what one call cost and what is left of one hour's grant, not the person's spending history at that resource. Deployments that consider even the per-call figure sensitive may omit the OPTIONAL cost member from the header, at the price of leaving the agent to derive it from successive remaining values.

21. IANA Considerations

21.1. HTTP Header Field Registration

This specification registers the following HTTP header field in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" established by [RFC9110]:

  • Header Field Name: AAuth-Budget
  • Status: permanent
  • Structured Type: Dictionary
  • Reference: This document, Section 11

21.2. JWT Claims Registration

This document requests registration of the following claims in the IANA "JSON Web Token Claims" registry established by [RFC7519]:

Table 4
Claim Name Claim Description Change Controller Reference
budget Authorized spending ceiling at a resource IETF This document, Section 4.5
budget_consumed Recent per-token consumption records reported by a resource IETF This document, Section 7.1

21.3. AAuth Capability Value Registry

This document requests registration of the following value in the AAuth Capability Value Registry established by AAuth Protocol ([I-D.hardt-oauth-aauth-protocol]). The registry policy is Specification Required ([RFC8126], Section 4.6).

Table 5
Value Reference
budget This document, Section 16

21.4. No Budget Unit Registry

This document deliberately establishes no registry of unit values. Units are declared by each resource in its budget_units metadata, exactly as scope values are declared in scope_descriptions. See Appendix A.9.

22. Implementation Status

Note: This section is to be removed before publishing as an RFC.

This section records the status of known implementations of the protocol defined by this specification at the time of posting of this Internet-Draft, and is based on a proposal described in [RFC7942]. The description of implementations in this section is intended to assist the IETF in its decision processes in progressing drafts to RFCs.

22.1. Implementations in Progress

tokenpony (Infinite Logic PBC) meters LLM inference and implements TPX [TPX], an OAuth 2.0 profile carrying the same grant for human-driven apps. That deployment is live, and this extension is being added over the same metering core, with AuthGravity as the person server and Harness News as the agent, in four-party access. TPX is a complete deployment on its own: it needs no person server and nothing from this document, and the two specifications share no wire surface — one meter, two independent authorization envelopes.

Regent Protocol is implementing both sides: the budget claim in auth tokens issued by its gate, with allocation and lifetime derived from the owner's mandate, and resource-side metering middleware in regent-httpsig performing atomic reserve-commit-release and emitting AAuth-Budget.

The editor is implementing this extension in several services.

Implementation reports and test vectors are expected from these efforts and will be recorded here.

23. Document History

Note: This section is to be removed before publishing as an RFC.

This document has not been submitted to the datatracker. Everything below is a change to the editor's copy, made while the design was being explored against implementations in progress. The log is reset at first submission, which becomes draft-hardt-aauth-budgets-00; readers wanting the detail behind any entry will find it in the repository's history and pull requests.

23.1. Exploratory Changes

  • Dropped the unit request parameter from the usage endpoint. It existed for a resource metering one person in more than one unit, which Section 1.1 already discourages, and it bought an error condition and an arbitrary notion of a primary unit. The response reports in the unit the resource meters in and says which that is.
  • Made signing the usage response RECOMMENDED rather than REQUIRED Section 15.2.4. It would be the first response-side signature in the family, the figures are decision context rather than authorization, and a person server refusing an unsigned response is left with no figures rather than unattributable ones.
  • Stated that the granted budget is an allocation drawn against a ceiling the PS holds and may not share with the agent, rather than the person's whole authorization. This was the design throughout and was nowhere written down; a reviewer read the document end to end and concluded a durable grant was missing. See the Introduction and Appendix A.5.
  • Expanded Section 8 with what the PS is deciding Section 8.1, the four inputs it reads Section 8.2, and its six responses Section 8.3. Named requirement=clarification as the response for an escalation the PS is not ready to refuse or approve — the channel that lets a PS tell an agent it is overspending, which narrowing an amount cannot do.
  • Rewrote Section 14.3 from a disclaimer about the absent grant identifier into a statement of the mechanism, including that expiry is proportional to time and exhaustion to spend, so the supervision interval tracks whichever moves faster.
  • Added a fourth reason to Appendix A.4: cumulative consumption across a series of allocations lets an agent infer the ceiling by subtraction.
  • Permitted a resource to omit cost entirely Section 12.1. The member previously had two carriers, header and trailer, and a resource that meters a streamed response on a runtime with no trailer support could use neither — making every such response non-conformant. reserved becomes REQUIRED in that case, and the agent recovers the exact figure from the following response's remaining. Removed the sentence in Section 12.3 stating that a resource emitting usage in its stream is not excused from the trailer.
  • Added the required member of AAuth-Budget Section 13.2, the maximum cost a resource computed for a request it refused under reason=insufficient-budget. The figure was previously available only in the enclosed resource token, whose aud is the PS, which left the agent unable to size the shrink-and-retry that Section 13 offers it. It is deliberately distinct from the resource token's budget, which is a re-authorization offer rather than a fact about the refused request.
  • Separated the two things a resource counts Section 14.4. The enforced cap is the presented auth token's own budget; the (iss, sub, aud) aggregate is a ledger for the records and counters and is not a second ceiling the resource may refuse against. Concurrency Section 14.6 said "aggregate atomically across all live auth tokens", which read as a cross-token cap; the atomicity requirements are now stated separately for the per-token invariant and for the ledger. Holding the cross-token total is the PS's job. Also stated why the ledger is keyed on the person: mission_s256 is optional and Section 18 requires mission-less tokens, so a mission-keyed ledger has no bucket for them.
  • Reshaped the usage endpoint Section 15. unit and decimals are stated once at the top of the response rather than repeated per entry, since budget_units fixes the scale for a unit and a response reports in one unit Section 15.2.3; usage is consequently a counter object rather than an array. The response echoes the scope key and carries aud naming the person server it was produced for. The request takes at most one scope key, and both the scope key and unit are now optional given the addition below.
  • Added the jkts query Section 15.2.2: the PS names the signing keys it wants figures for and the resource returns what each consumed. Allocations are ceilings, so a PS supervising several agents for one person could not previously learn how the spending divided among them without waiting for consumption records. Per-key figures carry no calendar periods — a key's spend is already bounded by tokens that live an hour — and a resource SHOULD retain one for 24 hours after that key's last metered request, leaving accumulation to the PS. An unrecognized or pruned key is omitted rather than reported as zero, which differs from the treatment of scope keys for a reason stated in place.
  • Required the usage response to be signed Section 15.2.4, bound to the request, using the key material the PS already fetched for resource tokens. The endpoint reports what a person owes for, and consumption records were already resource-signed by virtue of riding in a resource token; this was the only PS-facing consumption channel that a resource could disown. Reconciled Section 19.1, which had said a response-side profile was out of scope, and Section 19.4, which now states what signing does and does not fix.
  • Stated in Section 14.3 that revoking an auth token revokes its budget, pointing at the base protocol's revocation endpoint. Committed consumption is unaffected and an in-flight request completes.
  • Stated in Section 14.4 that a per-agent ceiling is a PS sizing decision rather than a resource-side key, and noted in Appendix A.9 that no registry does not mean no constraint — a monetary unit SHOULD still be an ISO 4217 code.
  • Recorded known implementations Section 22: tokenpony, which implements TPX [TPX] over the same metering core; Regent Protocol, implementing both the PS-side claim and resource-side metering middleware; and the editor's own services. The relationship between TPX and this document is stated there as a fact about deployments rather than as a positional claim in Section 18, which keeps one sentence.
  • Noted in Section 12.1 that recovering cost by subtraction is exact only for serial requests on one token; concurrent requests on one token recover the net of everything that settled between the two responses.
  • Rewrote the garbled unit/decimals sentence in Section 11.
  • Added The Billing Account Section 14.5: which account a metered resource charges. Most resources need nothing beyond the (iss, sub) lookup the base protocol already provides. Beyond that there are two questions, asked at different frequencies and answered by different mechanisms: which person this is, answered once by an interaction the person completes at the resource (three-party) or at the AS (four-party); and which of their accounts an authorization is for, answered on every authorization by the account parameter and claim. No new mechanism; the document was silent on a question every metered resource meets on its first request.

  • Replaced the header's cumulative consumed member with cost, what this request cost. The agent's question per response is the price of the call it just made and whether it can afford another; cumulative spend answered neither, was derivable as granted - remaining, and collided with the consumed of a consumption record, which is a per-token total. See Appendix A.4.

  • Added the OPTIONAL reserved member, what the resource holds for a request whose cost it cannot yet state. It is a fact about the request and is never revised.

  • Permitted AAuth-Budget as a trailer, carrying cost for a streamed response. A trailer MUST NOT restate a member the header carried, which makes the field correct whether a recipient merges trailers or discards them, without mandating either. Reverses this document's earlier position that trailers are not used; see Appendix A.6.

  • Added Ambiguous Failures Section 12.3: an agent that never receives a response assumes the request cost what the resource had held for it, until a later remaining supersedes that.

  • Removed the balance_endpoint and its metadata field. It was OPTIONAL and existed for the ambiguous-failure case, which a local conservative rule now covers without a round trip.

Note: written against draft-hardt-oauth-aauth-protocol-11, which introduces the person token, replaces the mission object with mission_s256, and removes the agent identifier from auth tokens.

24. Acknowledgments

The author thanks Abay Aubakirov, Alex Polvi, and Karl McGuinness for feedback on early drafts.

25. References

25.1. Normative References

[I-D.hardt-httpbis-signature-key]
Hardt, D. and T. Meunier, "HTTP Signature Keys", , <https://datatracker.ietf.org/doc/draft-hardt-httpbis-signature-key>.
[I-D.hardt-oauth-aauth-protocol]
Hardt, D., "AAuth Protocol", , <https://datatracker.ietf.org/doc/draft-hardt-oauth-aauth-protocol>.
[RFC7519]
Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token (JWT)", RFC 7519, DOI 10.17487/RFC7519, , <https://www.rfc-editor.org/info/rfc7519>.
[RFC7638]
Jones, M. and N. Sakimura, "JSON Web Key (JWK) Thumbprint", RFC 7638, DOI 10.17487/RFC7638, , <https://www.rfc-editor.org/info/rfc7638>.
[RFC8126]
Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, , <https://www.rfc-editor.org/info/rfc8126>.
[RFC9110]
Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, , <https://www.rfc-editor.org/info/rfc9110>.
[RFC9111]
Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Caching", STD 98, RFC 9111, DOI 10.17487/RFC9111, , <https://www.rfc-editor.org/info/rfc9111>.
[RFC9421]
Backman, A., Ed., Richer, J., Ed., and M. Sporny, "HTTP Message Signatures", RFC 9421, DOI 10.17487/RFC9421, , <https://www.rfc-editor.org/info/rfc9421>.
[RFC9651]
Nottingham, M. and P. Kamp, "Structured Field Values for HTTP", RFC 9651, DOI 10.17487/RFC9651, , <https://www.rfc-editor.org/info/rfc9651>.

25.2. Informative References

[AP2]
Google, "Agent Payments Protocol (AP2)", , <https://ap2-protocol.org/>.
[ERC20]
Vogelsteller, F. and V. Buterin, "EIP-20: Token Standard", , <https://eips.ethereum.org/EIPS/eip-20>.
[I-D.hardt-aauth-r3]
Hardt, D., "AAuth Rich Resource Requests (R3)", , <https://github.com/dickhardt/AAuth>.
[I-D.ietf-httpapi-ratelimit-headers]
Polli, R., Ruiz, A. M., and D. Miller, "RateLimit header fields for HTTP", Work in Progress, Internet-Draft, draft-ietf-httpapi-ratelimit-headers-11, , <https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-11>.
[ISO4217]
International Organization for Standardization, "ISO 4217:2015 Codes for the representation of currencies", , <https://www.iso.org/iso-4217-currency-codes.html>.
[OBIE.VRP]
Open Banking Implementation Entity, "Variable Recurring Payments Profile, Read/Write Data API", , <https://openbankinguk.github.io/read-write-api-site3/v4.0/profiles/vrp-profile.html>.
[ODRL]
W3C, "ODRL Vocabulary and Expression 2.2", , <https://www.w3.org/TR/odrl-vocab/>.
[RFC7942]
Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, , <https://www.rfc-editor.org/info/rfc7942>.
[RFC9396]
Lodderstedt, T., Richer, J., and B. Campbell, "OAuth 2.0 Rich Authorization Requests", RFC 9396, DOI 10.17487/RFC9396, , <https://www.rfc-editor.org/info/rfc9396>.
[RFC9457]
Nottingham, M., Wilde, E., and S. Dalal, "Problem Details for HTTP APIs", RFC 9457, DOI 10.17487/RFC9457, , <https://www.rfc-editor.org/info/rfc9457>.
[Stripe.Issuing]
Stripe, "Stripe Issuing: Card spending controls", , <https://docs.stripe.com/api/cards/object#card_object-spending_controls>.
[TPX]
Infinite Logic PBC, "TPX: Token Pony Express — An OAuth 2.0 Profile for Metered LLM Inference Grants", , <https://tokenpony.dev/spec/>.
[W3C.PaymentRequest]
W3C, "Payment Request API", , <https://www.w3.org/TR/payment-request/>.
[x402]
x402 Foundation, "x402: HTTP 402 Payment Protocol", , <https://docs.x402.org>.

Appendix A. Design Rationale

A.1. Why an Integer in a Declared Scale

Why not a Structured Field Decimal. [RFC9651], Section 3.3.2 caps a Decimal at 12 integer digits and 3 fractional digits, with round-half-to-even serialization. Inference is priced per million tokens, so a single call can cost on the order of $0.000015, which serializes to 0.000. The header would report zero consumption on every request while the person receives a bill.

Why not a binary float. IEEE 754 cannot represent 0.1 exactly, and a budget is a running sum, so the error accumulates rather than cancelling. No monetary prior art surveyed in Appendix B uses a float.

Why not a decimal string, which is what most monetary prior art does use ([W3C.PaymentRequest], [x402]). An integer in a declared scale is the only representation under which the Structured Field header value and the JWT claim value are the same number. A decimal string in the claim beside a Structured Field Decimal in the header would disagree at the third decimal place, and every implementation would have to define its own rounding to reconcile them.

A.2. Why the Scale Is Explicit

x402 [x402] omits a scale because the asset contract address is the unit and the scale comes from the contract, immutably. USD does not work that way. ISO 4217 [ISO4217] fixes the minor unit for USD at 2, and 2 decimal places cannot express a fifteen-microdollar call. The resource is therefore choosing a scale that the unit identifier does not determine, and a scale that is chosen has to travel with the amount.

A.3. Why decimals Is in the Signed Claim

decimals is declared in budget_units metadata and also carried in the budget claim. The duplication is deliberate, and the reason is not primarily an attack argument — the resource is the enforcer, and a resource willing to reinterpret the scale is equally willing to ignore the budget outright.

The reasons are versioning and self-description:

  • Versioning. Auth tokens live up to an hour. A resource changing its declared precision as an ordinary product decision — cents to micro-dollars — would silently reinterpret every token in flight by a factor of ten thousand.
  • Self-description. The resource is not the only reader. The PS ledgers against the number, the person's dashboard displays it, a proxy logs it, a dispute cites it. Each would otherwise have to resolve the number against mutable metadata fetched at some unspecified later time.

A.4. Why the Agent Is Not Told Its Cumulative Consumption

An earlier revision of this document carried a consumed member in the header, giving what had been consumed against the auth token to date, and a balance_endpoint where the agent could read the same figure on demand. Both are gone. The agent is told what a request cost and what is left; cumulative consumption is reported to the person server and not to the agent.

Four reasons.

The figure is redundant. granted is in the auth token the agent signed the request with, and remaining is in the response, so cumulative consumption is granted - remaining whenever nothing is reserved. Carrying a member the recipient can already compute is weight without information.

The word is already spoken for. A consumption record is a {jti, consumed} pair Section 7.1 where consumed means the total metered against one token's whole budget. A person server reads those records and this header's semantics against each other. Having consumed mean a per-request figure in one place and a per-token total in the other is the kind of collision that survives review and then costs an implementer a day.

Cumulative spend is the more revealing figure. It describes a pattern rather than a transaction, and AAuth-Budget travels unsigned past every intermediary on the path Section 20. What the agent genuinely needs per response is the price of the call it just made and whether it can afford another. Both are per-request facts, and that is what the field now carries.

There is a fourth reason that applies across tokens rather than within one. An agent holding its cumulative consumption over a series of allocations can watch the series and infer the ceiling behind it — how much the PS is willing to release, and how fast. The ceiling is deliberately not disclosed Appendix A.5, and a per-token figure that reconstructs it by subtraction discloses it anyway.

The balance_endpoint went with it. It was OPTIONAL, existed for one case — an ambiguous failure, where the agent cannot say whether a request was metered — and cost a resource an endpoint to implement and this document a section to specify. That case is now answered by a rule the agent applies locally Section 12.3: assume the maximum until a later remaining says otherwise. A conservative default that every agent applies is better than an optional round trip that some resources offer.

A.5. Why the Granted Budget Is Not the Person's Ceiling

A person server could authorize the whole of a person's intended spend at a resource in one auth token and let the agent draw it down. TPX [TPX] does the OAuth equivalent: the budget sits on a durable grant, the app spends against it unsupervised, and the person hears about it when the grant runs dry. This document does not, and the difference is not a missing feature.

The figure is not knowable when it would have to be fixed. A mission is approved before the work is done, and the work is what determines the cost. A person asked at approval for a number is guessing. Too low and the agent stops mid-task and the person is interrupted anyway. Too high and the number is not a control, because the agent will never reach it and nothing is checked before it does. An allocation sized against what the work has actually cost so far does not require the guess to be right.

A ceiling the agent can read is a ceiling the agent plans against. An agent that knows it has been authorized for a pool treats the pool as available. An agent that knows only its current allocation asks when the allocation runs out, and asking is what puts the PS back in the decision. The justification accompanying that request, and the consumption records arriving with it, are the person server's evidence — and neither exists if the agent never has to come back.

The check-in is the point, not a cost of it. Re-authorization is where the PS reads what the last allocation bought Section 8.2, compares it against the mission, and chooses among its six responses Section 8.3 — including the two that are not a number at all: asking the agent to account for the spend, and ending the work. A single up-front grant has no such moment. It has one, at approval, when the least is known.

This is why the ceiling appears nowhere on the wire. There is no claim for it, it may not be shared with the agent, and cumulative consumption that would reveal it by subtraction is withheld as well Appendix A.4. What the resource enforces is the allocation in the token in front of it. What the person authorized is a matter between the person and their PS.

A.6. Why a Trailer Only Adds a Member

AAuth-Budget may be sent as a trailer, carrying cost for a response whose cost was unknown when the header was written Section 12. This document rejected trailers in an earlier revision, on the reasoning of the RateLimit work ([I-D.ietf-httpapi-ratelimit-headers]): intermediaries drop them, and combining a header value with a trailer value complicates clients.

The first objection carries less weight here. It is largely a browser property — fetch does not surface trailers — and the traffic this document governs is an agent calling a resource, which is server-to-server and commonly HTTP/2. Where a trailer is dropped anyway, rule 3 of Section 12.2 makes that harmless.

The second objection is the real one, and Section 12.2 answers it structurally rather than by mandating client behavior. A recipient may discard trailer fields or merge them into the header set, and a sender can neither observe nor control which. Restating a member across the two would therefore produce a value that depends on the recipient's choice: AAuth-Budget is a Dictionary, duplicate keys resolve last-wins ([RFC9651], Section 3.2), so merging yields the trailer's value and discarding yields the header's. Adding a member that the header did not carry has no such fork. Merge yields a complete picture, discard yields a conservative one, and neither is wrong.

This is why reserved is a statement about a request rather than a running balance. A running figure would go stale the moment the reservation was released, and a stale member surviving a merge is exactly the failure the rule exists to prevent. What was held for a request does not change after the fact.

A.7. Why Not RateLimit

RateLimit and RateLimit-Policy ([I-D.ietf-httpapi-ratelimit-headers]) already report a server-side quota and its remaining balance. Reusing them would avoid a new header. Four things prevent it, all following from a budget being an authorization rather than a capacity hint:

  1. Stated non-goal. The RateLimit specification excludes authorization from its scope. Reporting the balance of a PS-issued grant through a field whose own specification says it is not for access control is a misuse a reviewer will name.
  2. No unit carrier. q and r MUST be non-negative Integers, and the quota units registry covers request, content-bytes, and concurrent-requests. There is no currency carrier, so the denomination would be invisible in the field reporting the number.
  3. Opposite reliability contracts. RateLimit says servers need not send the fields on every response, clients must not assume future responses will carry them, and a positive r is not a guarantee of anything. Those are correct properties for a capacity hint and wrong ones for the remaining portion of an authorization, which is why Section 11.1 says MUST send rather than MAY.
  4. Intermediary rewriting. Intermediaries MAY tighten RateLimit values. An intermediary tightening a budget balance is forging authorization state.

The two fields are complementary and MAY appear on the same response. A resource limiting an agent to 100 requests per minute and to five dollars of spend is stating two different things, and collapsing them loses one.

A.8. Why Budget Is Not a Mission Aggregate

A mission description may well say "budget around $5,000," and the obvious next step is to make that a protocol object the PS enforces across every resource the mission touches. This document does not, for two reasons.

The resources in a mission meter in units that do not add up. An airline meters in USD, an inference endpoint in micro-dollars or tokens, a storage service in gigabyte-months. Aggregating requires conversion rates and a common denomination, neither of which the protocol has, and both of which change.

More fundamentally, the enforcement point is wrong. The party that can enforce a per-resource ceiling is the resource, because it meters. No party meters the mission. A mission-level total is a PS policy input — the PS decides how much of the person's $5,000 to authorize at each resource as it goes — which is exactly what the narrowing chain in Section 4.2 gives it. The mission description already carries the person's intent, and the PS already reads it.

A.9. Why Units Are Not Registered

RateLimit establishes an IANA registry of quota units because its units — request, content-bytes, concurrent-requests — are protocol-generic and every server means the same thing by them.

Budget units are not generic. A unit is meaningful only against a resource's own pricing, and the parties that need to interpret it are the resource that declared it and the PS that fetched the resource's metadata. This is the same situation as scope values, which the base protocol leaves to each resource's scope_descriptions rather than registering. Registering USD would add nothing that ISO 4217 does not already provide, and registering tokens would suggest an interoperable meaning that does not exist.

No registry does not mean no constraint. A monetary unit SHOULD be an ISO 4217 alphabetic code Section 4.5, which is what keeps a consent screen able to render "$5.00" rather than a resource-invented string the person has to interpret. What is left unregistered is the non-monetary case, where no external register exists to point at.

Appendix B. Prior Art

This section is non-normative. It records where the field shapes and value encodings in this document come from, and what gap remains.

B.1. RFC 9396, Rich Authorization Requests

[RFC9396] (OAuth WG, May 2023), Section 2.2 defines the complete set of common data fields for an authorization detail: type, locations, actions, datatypes, identifier, privileges. There is no amount or quantity field among them. instructedAmount appears only in the document's examples and belongs to the payment_initiation authorization details type, which comes from Berlin Group NextGenPSD2 and carries ISO 20022 ActiveCurrencyAndAmount semantics; it is not defined by RFC 9396.

Section 10 states that registration of authorization details types with the AS is outside the specification's scope, and Section 14 registers the request parameter, the claim, the metadata fields, and the invalid_authorization_details error — but establishes no registry of types.

Section 6.1 is the load-bearing citation: there is no standardized mechanism for comparing two arbitrary authorization detail requests, and an AS should not rely on simple object comparison. AAuth Budgets closes that gap for one narrow case. Because the unit is resource-declared and the value is an integer in a declared scale, "is this grant less than the prior one" is a numeric comparison rather than a structural one.

R3 ([I-D.hardt-aauth-r3]) covers why AAuth does not profile RAR generally.

B.2. TPX

TPX [TPX] is an OAuth 2.0 profile for metered LLM inference grants: apps ship without provider keys, and the person grants each app a metered budget from a provider the person chooses and pays. The budget rides RFC 9396 authorization_details as {type: "llm-inference", budget, models}; the user or provider MAY grant less than requested and the client MUST read the granted figure from the token response — the requested/granted split of the narrowing chain Section 4.2, in OAuth form.

Three of its mechanisms have direct counterparts here. Its GET /credits spend summary and budget_used introspection member report cumulative spend on a grant, which this document reports to the person server Section 15 rather than to the agent Appendix A.4. Its separation of budget_exhausted from balance_exhausted is the same boundary Section 13.3 draws between re-authorization and payment. And its budget is "a damage cap, not a payment" — the hard-cap property Section 14.7 states, which TPX asserts as a property of the grant and this document additionally realizes with reservations, since an agent's operations are unbounded before generation.

TPX denominates in USD as decimal JSON numbers with at most six fractional digits, where this document uses an integer in a declared scale Appendix A.1. TPX serves human-driven apps over OAuth; Section 18 describes the deployment where one provider's meter serves both envelopes.

B.3. UK Open Banking Variable Recurring Payments

[OBIE.VRP] ControlParameters is the most complete standing-budget model in production: MaximumIndividualAmount, MaximumCumulativeAmount, MaximumCumulativeNumberOfPayments, and PeriodicLimits[] carrying PeriodType and PeriodAlignment. PeriodAlignmentConsent versus Calendar — names the choice the usage counters Section 15 make: calendar alignment, with the timezone question answered by fixing UTC.

B.4. Stripe Issuing Spending Controls

[Stripe.Issuing] spending_controls.spending_limits[] is {amount, interval, categories}, with amount an integer in the currency's smallest unit. interval spans per_authorization through all_time, which is the precedent for expressing a per-transaction cap and a periodic cap in a single enumeration rather than two fields; the usage counters Section 15 adopt the date-based portion of that enumeration directly. Stripe computes all date-based intervals from midnight UTC — the precedent for fixing UTC by definition — and documents spending aggregation as best-effort with up to 30 seconds of delay, the precedent for as_of.

B.5. W3C Payment Request API

[W3C.PaymentRequest] PaymentCurrencyAmount is {currency, value} with value a decimal string. It is reused by Google's Agent Payments Protocol [AP2] in CartMandate. AP2's IntentMandate carries a maximum price, an expiry, and a merchant allowlist as user-signed constraints, which is the closest prior art to a user-authorized agent ceiling.

B.6. x402

[x402] carries maxAmountRequired as a string in the asset's atomic units, alongside asset (a contract address) and network; v2 renames the field to amount and moves network to CAIP-2 form. There is no currency field: the asset identifier is the unit, and the exponent comes from the contract. This is the precedent for using a unit identifier rather than a currency field, and for decimals as the name of the scale.

B.7. ODRL

[ODRL] payAmount with unit is the precedent for a field literally named unit holding a currency code.

B.8. RateLimit Header Fields

[I-D.ietf-httpapi-ratelimit-headers] (HTTPAPI WG, Standards Track, not yet an RFC) defines RateLimit-Policy with q, qu, w, and pk, and RateLimit with r, t, and pk, both as RFC 9651 Lists. It establishes an IANA RateLimit Quota Units registry (Specification Required) with initial entries request, content-bytes, and concurrent-requests, and three RFC 9457 problem types: quota-exceeded (429), temporary-reduced-capacity (503), and abnormal-usage-detected (429).

It is cited here for why AAuth-Budget exists separately Appendix A.7, for the rejection of trailers Section 12, and for pk as the precedent for a documented, client-predictable partition key.

Author's Address

Dick Hardt
Hellō