NEWWatch an agent's payment get refused, and read the reason, in the live browser demo.Open the demo
AxorumAxorum
SDK language

Resources

Usages

A usage balance is the ledger's memory of a metered resource: what an agent consumed, what a meter measured, and what a bill recognized. The three totals are held in order at every commit, so metering usage that never happened is not a policy someone has to remember to write. It is refused.

The conservation chain

Every usage balance carries three running totals in minor units, and the ledger admits a commit only if the chain still holds afterward:

10 <= billed <= metered <= consumed
  • consumed is the ground-truth event. The agent used the resource.
  • metered is the measurement recognition of that consumption. Metering more than was consumed is phantom usage, and it is refused.
  • billed is the financial recognition of that metering. Billing more than was metered is billing with no meter basis, and it is refused.

The three are recognized at different moments, so the chain is an ordering, not an equality. Under-recognition (billed < metered < consumed) is a lawful transient: consumption in flight, waiting to be measured and billed. reconciled is true only when all three are equal, and reaching that steady state is reconciliation's job, not an admission rule. Do not treat an unreconciled balance as a fault to fix.

Corrections run backwards. A negative delta (a metering correction, a credit memo) is admitted exactly when the post-state still satisfies the chain, so a counter never goes below zero.

1consumed $100.00 the agent used it
2metered $65.00 a meter measured it
3billed $40.00 an invoice recognized it
4
5unmetered $35.00 consumed, not yet metered
6unbilled $25.00 metered, not yet billed
7reconciled false the three are not equal yet

Metered spend is one intent

The three moves ride the intent draft, beside the journal legs, and they are judged in the same commit under the same law. Consuming a metered resource, measuring it, and billing for it is one intent, not three.

The moves

consume_usageUsageMove | optional

A signed delta on the balance's consumed total: the agent used the resource.

meter_usageUsageMove | optional

A signed delta on the balance's metered total: the consumption was measured.

bill_usageUsageMove | optional

A signed delta on the balance's billed total: the metering was billed for.

A UsageMove is a usage id plus an Amount, denominated in the balance's own currency. A move in any other currency is refused. An absent move is omitted from the envelope entirely, never sent as null.

All three at once is the lawful case, not an edge case: the moves of one intent are validated together against the combined post-state, so a single intent may consume, meter, and bill the same amount. Any subset works too. Metering something an earlier intent consumed is one move, not three.

A violating move refuses the whole intent

A move that breaks the chain is a 422 substrate_rejected with kind: "validation", and nothing posts: not the usage moves, and not the journal legs that rode with them. The ledger is left byte-identical. All the checks run before any mutation, so there is no partial commit to unwind.

The refusal arrives in compliance English with the exact figures, which is what you show a human.

Consume, meter, and bill in one intent

POST/api/v1/intents
1use axorum_client::{AxorumClient, IntentDraft, UsageMove};
2use axorum_substrate::{Amount, Entry, Side};
3
4// The usage id was handed to the agent. The admin plane opened the balance.
5let spend = Amount::new(1_000, "USD");
6
7let draft = IntentDraft::new(agent, attestation, transaction, action)
8 .entries(vec![
9 Entry { account: expense, side: Side::Debit, amount: spend.clone() },
10 Entry { account: cash, side: Side::Credit, amount: spend.clone() },
11 ])
12 .consume_usage(UsageMove { usage: usage.clone(), amount: spend.clone() })
13 .meter_usage(UsageMove { usage: usage.clone(), amount: spend.clone() })
14 .bill_usage(UsageMove { usage: usage.clone(), amount: spend })
15 .justification("1,000 inference calls, metered and billed at $10.00");
16
17// One commit. The money and the usage chain are judged together.
18let outcome = client.submit_pinned(draft).await?;
1{
2 "transaction": "txn_7w5b4trnc7b2ja027jyyb64395",
3 "entries": [
4 {
5 "account": "acc_2n4kqx21g5bty927z2tj955css",
6 "side": "Debit",
7 "amount": { "minor": 1000, "currency": "USD" }
8 },
9 {
10 "account": "acc_5j8dmv93k1nzq047x8ph622bkt",
11 "side": "Credit",
12 "amount": { "minor": 1000, "currency": "USD" }
13 }
14 ],
15 "consume_usage": {
16 "usage": "usage_2n4kqx21g5bty927z2tj955css",
17 "amount": { "minor": 1000, "currency": "USD" }
18 },
19 "meter_usage": {
20 "usage": "usage_2n4kqx21g5bty927z2tj955css",
21 "amount": { "minor": 1000, "currency": "USD" }
22 },
23 "bill_usage": {
24 "usage": "usage_2n4kqx21g5bty927z2tj955css",
25 "amount": { "minor": 1000, "currency": "USD" }
26 },
27 "action": { "action": "post", "bindings": {} },
28 "evidence": [],
29 "justification": "1,000 inference calls, metered and billed at $10.00",
30 "policy": "pol_2s5479gmf7bhrsavd5awacb0fg"
31}
1{
2 "error": "substrate_rejected",
3 "kind": "validation",
4 "message": "billing exceeds metered usage for usage balance 'usage_2n4kqx21g5bty927z2tj955css': attempted billed total 6200 exceeds metered total 5000 (already billed 4000; 1000 metered but not yet billed)"
5}

GET/api/v1/usages/{id}Read a usage balance

The three totals, the two headrooms that follow from them, whether the chain has reconciled, and the balance narrated in plain English. This is a party-scoped read: the client presents its attestation, and a client holding none fails locally before a request is sent.

Path parameters

idstring

The usage-balance id (usage_…). A malformed id is a 400 invalid_usage_id.

Query parameters

formatstring | optional

json (the default), or text for the compliance-English narration on its own.

Response

usagestring

The usage-balance id, echoed back.

currencystring

The single currency every move against this balance must match.

consumedinteger

The running total consumed, in minor units.

meteredinteger

The running total metered. Never above consumed.

billedinteger

The running total billed. Never above metered.

unmeteredinteger

The un-metered headroom (consumed − metered): what may still be metered.

unbilledinteger

The un-billed headroom (metered − billed): what may still be billed.

reconciledboolean

Whether the three totals are equal.

textstring

The balance narrated in compliance English, with exact figures. Prefer it whenever you have a human to show.

Decode the counters as big integers

Every counter is a 128-bit integer, emitted as a bare JSON number, the same rule as Amount.minor. It matters more here than anywhere else on the plane: consumed takes no ceiling, so a busy meter's total can lawfully outgrow u64. bigint in TypeScript, int in Python, i128 in Rust.

Read a usage balance

GET/api/v1/usages/{id}
1use axorum_client::AdminClient;
2
3// The agent-plane Rust client carries usage moves on the draft; the usage
4// read is the admin plane's, where the balance was opened.
5let admin = AdminClient::builder("http://127.0.0.1:8080")
6 .token(admin_token)
7 .build()?;
8
9let report = admin.usage_balance(&usage).await?;
10
11// The ledger's own narration, in compliance English with exact figures.
12println!("{}", report.text);
1{
2 "usage": "usage_2n4kqx21g5bty927z2tj955css",
3 "currency": "USD",
4 "consumed": 10000,
5 "metered": 6500,
6 "billed": 4000,
7 "unmetered": 3500,
8 "unbilled": 2500,
9 "reconciled": false,
10 "text": "Usage balance 'usage_2n4kqx21g5bty927z2tj955css', denominated in USD.\nConsumed to date: $100.00. Metered to date: $65.00. Billed to date: $40.00.\n$35.00 consumed but not yet metered; $25.00 metered but not yet billed.\nThe conservation law holds: billing does not exceed metering, and metering does not exceed consumption. The balance reconciles when the three totals are equal.\n"
11}
1{
2 "error": "unknown_usage",
3 "message": "no such usage balance"
4}

Ownership, and the refusal that tells you nothing

A usage balance is bound to an owning party when it is opened, and only that party may move it or read it.

The read is a 404, and it is deliberately blind

A balance owned by another party, a balance opened with no owner at all, and a balance that was never opened all answer the same 404 unknown_usage, with the same message. The cases are indistinguishable on purpose: splitting them would let any attested agent probe candidate usage ids across a tenant boundary and learn which ones are real, and whose. A usage id is never an existence oracle.

So a 404 means one thing only: you cannot read this balance. Surface it as it stands. There is nothing further in it to infer, and in particular it is not evidence that the balance is absent.

A 403 on this route is not an ownership answer. It means the attestation itself was rejected: a bad signature, an expired token, an untrusted issuer. Ownership never produces a 403 here.

The write is refused by the law, not by the boundary

A consume, meter, or bill move against a balance the acting party does not own is refused by the substrate law itself, as a 422 substrate_rejected. The refusal names no owner and discloses nothing about the balance, and it is checked before the currency comparison, so a non-owner cannot provoke a currency mismatch and learn what the balance is denominated in.

Errors

400invalid_usage_id

The path segment is not a usage-balance id.

401unknown_agent

No attestation was presented, or the attested agent:// URI is not bound to a party.

403attestation

The attestation was rejected. Not an ownership verdict.

404unknown_usage

No such balance, or it is not yours. The two are indistinguishable.

422substrate_rejected

On the intent path: a move broke the chain, moved the wrong currency, or landed on a balance the acting party does not own. Carries kind: "validation". Nothing posted.

1{
2 "error": "substrate_rejected",
3 "kind": "validation",
4 "message": "the usage balance 'usage_2n4kqx21g5bty927z2tj955css' is not available to the acting party: a usage balance may be consumed, metered, or billed only by the party that owns it"
5}