TBD D. Hardt Internet-Draft Hellō Intended status: Standards Track 2 September 2026 Expires: 6 March 2027 AAuth Budgets draft-hardt-aauth-budgets-latest 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, a usage_endpoint, and a balance_endpoint to resource metadata, and an AAuth-Budget response header reporting the remaining balance. 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 (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 6 March 2027. Copyright Notice Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/ license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction 1.1. Non-Goals 2. Conventions and Definitions 3. Terminology 4. Budget Model 4.1. A Budget Is Structurally a Scope 4.2. The Narrowing Chain 4.3. Value Representation 4.4. Range Limits 4.5. The Budget Object 5. Resource Metadata Extensions 6. Authorization Endpoint Extensions 7. Resource Token Extensions 7.1. Consumption Records 8. PS Token Endpoint Extensions 9. PS-to-AS Token Request Extensions 10. Auth Token Extensions 11. AAuth-Budget Response Header 11.1. Sending Rules 11.2. Denomination Conflict 12. Streaming 13. Balance Endpoint 14. Budget Exhaustion 14.1. The reason Parameter 14.2. Boundaries 15. Enforcement 15.1. The Resource Enforces 15.2. Budgets Require Auth-Token Mode 15.3. Token Scope 15.4. Aggregation 15.5. Concurrency 15.6. The Budget Is a Hard Cap 15.7. Failed Calls 15.8. Delegation 15.9. Mission Attribution 16. Usage Counters 16.1. Usage Request 16.2. Usage Response 16.3. Authorization and Errors 16.4. Division of Labor 17. Capability Negotiation 18. Errors 18.1. Declining with Guidance 19. Standing Authorization for Metered Inference 20. Security Considerations 20.1. The Header Is Unsigned 20.2. Budget Is Not a Substitute for Scope 20.3. Over-Issuance Through Concurrency 20.4. Consumption Reports as Attack Surface 20.5. Unit Substitution 21. Privacy Considerations 22. IANA Considerations 22.1. HTTP Header Field Registration 22.2. JWT Claims Registration 22.3. AAuth Capability Value Registry 22.4. No Budget Unit Registry 23. Implementation Status 24. Document History 25. Acknowledgments 26. References 26.1. Normative References 26.2. Informative References Appendix A. Design Rationale A.1. Why an Integer in a Declared Scale A.2. Why the Scale Is Explicit A.3. Why decimals Is in the Signed Claim A.4. Why Not RateLimit A.5. Why Budget Is Not a Mission Aggregate A.6. Why Units Are Not Registered Appendix B. Prior Art B.1. RFC 9396, Rich Authorization Requests B.2. TPX B.3. UK Open Banking Variable Recurring Payments B.4. Stripe Issuing Spending Controls B.5. W3C Payment Request API B.6. x402 B.7. ODRL B.8. RateLimit Header Fields Author's Address 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.4. Metered inference is the initiating use case, and Section 19 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 19. 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 * *Budget*: A ceiling on what an agent may consume at one resource, expressed as an amount in a unit the resource declares. * *Unit*: A resource-declared identifier for what is being metered — a currency code, a token count, a call count. * *Scale*: The number of decimal places implied by a budget amount, carried as decimals. An amount of 5000000 with decimals of 6 is 5.000000 of the unit. * *Granted budget*: The budget claim of an auth token. The figure the resource enforces against. * *Consumption*: What the resource has metered against a granted budget, in the same unit and scale. * *Consumption record*: A {jti, consumed} pair reporting what the resource metered against one prior auth token's budget. Carried in the budget_consumed claim of a resource token. * *Usage counters*: Calendar-aligned consumption totals a person server reads at the resource's usage_endpoint. 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: +==================+===================+===================+ | Position | scope | budget | +==================+===================+===================+ | Authorization | what the agent | what the agent | | endpoint request | asks for | asks for | +------------------+-------------------+-------------------+ | Resource token | what the resource | what the resource | | | will grant | will grant | +------------------+-------------------+-------------------+ | PS-to-AS token | (n/a) | what the PS will | | request | | allow | +------------------+-------------------+-------------------+ | Auth token | granted, MUST NOT | granted, MUST NOT | | | be broader | exceed | +------------------+-------------------+-------------------+ Table 1 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 and consumed 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", "balance_endpoint": "https://inference.example/balance" } *budget_units* (OPTIONAL). An array of objects, each declaring one unit the resource will denominate a budget in. Each object contains: * *unit* (REQUIRED). The unit identifier. * *decimals* (REQUIRED). The scale the resource uses for this unit. A resource MUST use this value in every budget it issues for this unit. * *max* (RECOMMENDED). A non-negative integer, in this unit's scale, giving the largest amount the resource will accept on a single auth token. It lets an agent on the proactive path request something the resource will honor rather than discovering the ceiling by having its request narrowed. * *description* (OPTIONAL). A Markdown string describing what the unit meters, for display at a consent screen. Implementations MUST sanitize the Markdown before rendering to users. 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 16. *balance_endpoint* (OPTIONAL). The HTTPS URL where an agent reads the balance of its own granted budget Section 13. Both endpoint URLs 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 18 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 18. 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. * *budget* (OPTIONAL): A budget object Section 4.5. The ceiling the resource is willing to have granted. Subject to the rules in Section 5. * *budget_consumed* (OPTIONAL): An array of consumption records — what recent grants were actually consumed, as decision context for the PS. Not a grant. { "iss": "https://inference.example", "dwk": "aauth-resource.json", "aud": "https://ps.example", "jti": "rt-4c81fa", "ps": "https://ps.example", "sub": "8f14e45fceea167a5a36dedd4bea2543", "person_token_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 18 in miniature. Records cover prior auth tokens matching this token's context: the same (iss, sub, aud) the resource aggregates against Section 15.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 16. 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 14 — 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. 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. 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: * The issuer MUST copy unit and decimals from the resource token's budget claim unchanged. * The issuer MUST NOT set amount higher than the resource token's budget.amount, or, in four-party access, higher than the budget parameter of the PS-to-AS token request Section 9. * An issuer MUST NOT include budget in an auth token when the resource token carried no budget claim. The resource, not the PS or AS, denominates. Resource rules: * A resource that issued a budget in a resource token and receives an auth token without a budget claim MUST treat the request as carrying no budget authorization under this extension and apply its own default. * A resource MUST enforce against the auth token's unit and decimals, not against its own current budget_units metadata. Auth tokens live up to an hour and metadata can change within that hour; see Appendix A.3. 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: remaining=1568800; consumed=431200; unit="USD"; decimals=6 Members: * *remaining* (REQUIRED): A non-negative Integer, in the granted scale, giving what is left of the budget on this auth token, net of reservations for requests in flight Section 15.6. It is a floor — committed consumption will not exceed the grant — though the figure may lag metering. Exhaustion is signaled by the 401 Section 14, for which the agent stays prepared regardless. * *consumed* (OPTIONAL): A non-negative Integer, in the granted scale, giving what has been consumed against this auth token. It never exceeds the granted amount Section 15.6. Provided for reconciliation against the person's bill. * *unit* (OPTIONAL): A String naming the unit. *decimals* (OPTIONAL): an Integer giving its scale. Informational, and a pair: a sender MUST include both or neither, because an amount carrying a unit but no scale misreads by a factor of 10^decimals to exactly the readers self-description serves. Recipients MUST ignore members they do not recognize. The header carries no granted member and no token reference: the agent holds the auth token it signed the request with, and reads both from there. The scope of the reported figures is this auth token's budget, because the budget expires with the token Section 15. Consumption records and usage counters are a different set of numbers, reported to the PS in the resource token Section 7.1 and at the usage endpoint Section 16, not to the agent. 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 14. 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. Where the figure matters and the header may be missing, the agent has the balance endpoint Section 13. HTTP/1.1 401 Unauthorized AAuth-Requirement: requirement=auth-token; resource-token="eyJ..."; reason=budget-exhausted AAuth-Budget: remaining=0; consumed=2000000; 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.4. 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 remaining on that response already covers it at its maximum: the resource reserved before serving Section 15.6, and the figure is net of the reservation. When the stream completes, the unused portion of the reservation is released into the next response's figure. Trailers are not used. The RateLimit work considered and rejected them: intermediaries drop trailers, and combining a header value with a trailer value complicates clients. Both objections apply here unchanged. 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, but it does mean the exact number exists when the stream ends, so a resource wanting to report it on the same response has somewhere to put it without this document defining anything. 13. Balance Endpoint A resource MAY publish a balance_endpoint Section 5 where an agent reads the balance of its own granted budget without consuming any of it. The agent makes a signed GET, presenting its auth token via the Signature-Key header exactly as on any other request to the resource. The auth token is the whole query: its budget claim names the grant being asked about. The resource MUST NOT meter the call. The response carries the AAuth-Budget header Section 11 and a body repeating the same figures as JSON: HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store AAuth-Budget: remaining=1568800; consumed=431200; unit="USD"; decimals=6 { "remaining": 1568800, "consumed": 431200, "unit": "USD", "decimals": 6 } Body members remaining, consumed, unit, and decimals are all REQUIRED — a body pays no size cost for self-description. The response MUST carry Cache-Control: no-store. An auth token without a budget claim gets invalid_request Section 18; an invalid or expired auth token gets the 401 challenge it would get anywhere else at the resource. The endpoint exists for ambiguous failures. When a connection drops mid-request, the agent does not know whether the request charged, and the AAuth-Budget header that would have said so is on the response that was lost. An agent SHOULD NOT retry a chargeable request after an ambiguous failure without first learning whether the original charged — from the balance endpoint where the resource offers one, or from the AAuth-Budget header of its next response otherwise. consumed is the figure that answers the question, which is why the body requires it. 14. 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 15.6. 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. 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. 14.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: remaining=0; consumed=2000000; 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 15.6. For either value, the enclosed resource token MAY carry a budget sized for what the resource would need to see granted — for insufficient-budget, the cost of the refused operation. The denial is itself the re-authorization offer. 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. 14.2. 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. 15. Enforcement 15.1. The Resource Enforces The PS authorizes a number. The resource counts. The PS is not in the request path and does not meter. 15.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. 15.3. Token Scope A budget is scoped to the auth token that carries it and expires with it. This document defines no persistent grant identifier and does not require the PS to carry a budget across re-issuance. Auth tokens are capped at one hour by the base protocol; re-issuance is the intended point at which the PS re-decides, not a bypass. 15.4. Aggregation The resource MUST aggregate consumption against the key (iss, sub, aud) of the auth token. (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 key is the person, not the agent. 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. 15.5. 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 aggregate atomically against (iss, sub, aud) across all live auth tokens. * A PS SHOULD size per-token budgets so that their sum stays within whatever standing ceiling it holds for the person at that resource. 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. 15.6. 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 14.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 15.5. 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. 15.7. 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. 15.8. 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 15.5 already governs their sum. No sub-budget or draw-down-from-parent mechanism is defined. 15.9. 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. 16. 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 over fixed calendar periods. The PS reads figures it can act on and display; it does not compute, convert, or round. The resource keeps a handful of running integers per key and unit, incremented at metering time; serving the endpoint requires no per-record history. 16.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 exactly one of three query keys, each 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. 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" } 16.2. Usage Response { "as_of": 1754619970, "usage": [ { "unit": "USD", "decimals": 6, "day": 12400000, "week": 31200000, "month": 84300000, "year": 412000000, "all_time": 989200000 } ] } * *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. * *usage* (REQUIRED): An array with one entry per unit the resource has metered budgets in under the queried key. Each entry carries unit and decimals as in the budget object Section 4.5, plus counters in that scale. The counters 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 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; the person's bill is the durable record. 16.3. Authorization and Errors The jwks_uri in the Signature-Key header names the calling person server. The resource MUST only answer for key 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. sub is directed per PS, so one person server cannot even name another's subjects; tenant and mission_s256 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 value the resource does not recognize returns 200 with an empty usage array; "never seen" and "nothing consumed" are deliberately indistinguishable. A body carrying zero or several query keys, or a malformed value, returns invalid_request using the error response format of ([I-D.hardt-oauth-aauth-protocol]). 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. 16.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, 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 and the balance endpoint Section 13, scoped to the token it holds. 17. 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 14.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. 18. 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: +=================+========+===============+========================+ | Error | Status | Endpoint | Meaning | +=================+========+===============+========================+ | invalid_budget | 400 | Authorization | The budget object is | | | | endpoint | malformed, or names | | | | | a unit the resource | | | | | has not declared in | | | | | budget_units | +-----------------+--------+---------------+------------------------+ | invalid_budget | 400 | PS and AS | The resource token's | | | | auth token | budget.decimals | | | | endpoints | 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 | Zero or several | | | | endpoint | query keys, or a | | | | | malformed value | | | | | Section 16.3 | +-----------------+--------+---------------+------------------------+ | invalid_request | 400 | Balance | The auth token | | | | endpoint | presented carries no | | | | | budget claim | | | | | Section 13 | +-----------------+--------+---------------+------------------------+ Table 2 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. 18.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. 19. 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. 20. Security Considerations 20.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]). AAuth's signature profile is request-side today, and adding a response-side profile is a larger change than this extension makes. 20.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. 20.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 15.5. 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. 20.4. Consumption Reports as Attack Surface budget_consumed records are resource-signed and usage counters are served from the resource's authenticated endpoint; 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. 20.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 18 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. 21. 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. Deployments that consider the balance sensitive should weigh omitting the OPTIONAL consumed member, which is the member that most directly describes spending. 22. IANA Considerations 22.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 22.2. JWT Claims Registration This document requests registration of the following claims in the IANA "JSON Web Token Claims" registry established by [RFC7519]: +=================+==================+============+=============+ | Claim Name | Claim | Change | Reference | | | Description | Controller | | +=================+==================+============+=============+ | budget | Authorized | IETF | This | | | spending ceiling | | document, | | | at a resource | | Section 4.5 | +-----------------+------------------+------------+-------------+ | budget_consumed | Recent per-token | IETF | This | | | consumption | | document, | | | records reported | | Section 7.1 | | | by a resource | | | +-----------------+------------------+------------+-------------+ Table 3 22.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). +========+===========================+ | Value | Reference | +========+===========================+ | budget | This document, Section 17 | +--------+---------------------------+ Table 4 22.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.6. 23. 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. There are currently no known implementations. 24. Document History _Note: This section is to be removed before publishing as an RFC._ * draft-hardt-aauth-budgets-00 - Initial submission _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._ 25. Acknowledgments The author would like to thank reviewers for their feedback. 26. References 26.1. Normative References [I-D.hardt-httpbis-signature-key] Hardt, D. and T. Meunier, "HTTP Signature Keys", 2026, . [I-D.hardt-oauth-aauth-protocol] Hardt, D., "AAuth Protocol", 2026, . [RFC7519] Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015, . [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, June 2017, . [RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, . [RFC9111] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Caching", STD 98, RFC 9111, DOI 10.17487/RFC9111, June 2022, . [RFC9421] Backman, A., Ed., Richer, J., Ed., and M. Sporny, "HTTP Message Signatures", RFC 9421, DOI 10.17487/RFC9421, February 2024, . [RFC9651] Nottingham, M. and P. Kamp, "Structured Field Values for HTTP", RFC 9651, DOI 10.17487/RFC9651, September 2024, . 26.2. Informative References [AP2] Google, "Agent Payments Protocol (AP2)", 2025, . [ERC20] Vogelsteller, F. and V. Buterin, "EIP-20: Token Standard", 2015, . [I-D.hardt-aauth-r3] Hardt, D., "AAuth Rich Resource Requests (R3)", 2026, . [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, 23 May 2026, . [ISO4217] International Organization for Standardization, "ISO 4217:2015 Codes for the representation of currencies", 2015, . [OBIE.VRP] Open Banking Implementation Entity, "Variable Recurring Payments Profile, Read/Write Data API", 2024, . [ODRL] W3C, "ODRL Vocabulary and Expression 2.2", 2018, . [RFC7942] Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, July 2016, . [RFC9396] Lodderstedt, T., Richer, J., and B. Campbell, "OAuth 2.0 Rich Authorization Requests", RFC 9396, DOI 10.17487/RFC9396, May 2023, . [RFC9457] Nottingham, M., Wilde, E., and S. Dalal, "Problem Details for HTTP APIs", RFC 9457, DOI 10.17487/RFC9457, July 2023, . [Stripe.Issuing] Stripe, "Stripe Issuing: Card spending controls", 2026, . [TPX] Infinite Logic PBC, "TPX: Token Pony Express — An OAuth 2.0 Profile for Metered LLM Inference Grants", 2026, . [W3C.PaymentRequest] W3C, "Payment Request API", 2025, . [x402] x402 Foundation, "x402: HTTP 402 Payment Protocol", 2025, . 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 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.5. 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.6. 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. 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 are the precedent for the balance endpoint Section 13. Its separation of budget_exhausted from balance_exhausted is the same boundary Section 14.2 draws between re-authorization and payment. And its budget is "a damage cap, not a payment" — the hard-cap property Section 15.6 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 19 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. PeriodAlignment — Consent versus Calendar — names the choice the usage counters Section 16 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 16 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.4, 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ō Email: dick.hardt@gmail.com