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

Guides

Errors

Every non-2xx response carries a structured body: a stable snake_case error code an authoring loop can branch on, a human message, and — for some codes — the extra fields a client needs to act. Branch on error. Ignore fields you do not know.

The error body

1{
2 "error": "stale_policy",
3 "message": "The pinned policy is not the one in force.",
4 "pinned": "pol_2s5479gmf7bhrsavd5awacb0fg",
5 "active": "pol_9k18tzq4c7m2ha055xvbn31rjd"
6}

The error code is never an internal type or variant name. It is a contract, and it is stable.

The code vocabulary

CodeStatusMeaning
invalid_transaction_id400The path segment is not a transaction id.
invalid_account_id400The path segment is not an account id.
invalid_usage_id400The path segment is not a usage-balance id.
unknown_agent401The agent:// URI is not bound to a party.
attestation403The PASETO attestation was rejected.
unknown_transaction404No such transaction.
unknown_account404No such account.
unknown_usage404No such usage balance, or it is not yours. The two are indistinguishable.
stale_policy409The pinned policy is not the one in force (pre-check). Carries pinned, active.
policy_pin_mismatch409The policy changed between check and record. Nothing was written. Carries pinned, active.
no_active_policy409No policy is in force at all.
duplicate_binding409The agent:// URI is already bound. Carries uri.
not_primary421Cluster mode: a mutation reached a non-primary. Carries primary_client_addr.
invalid_agent_uri422The agent:// URI is malformed. Carries uri.
capability_mapping422The action maps to no capability the attestation grants. Carries action.
multi_currency_mandate_charge422A mandate-bound spend moved outflow in more than one currency. Carries currencies.
substrate_rejected422The ledger refused it — unbalanced legs, unknown account, a breached ceiling. Carries kind: "validation".
gateway_error422A gateway rejection with no more specific code.
commit_unavailable503Cluster mode: the commit could not be confirmed. Carries reason.
substrate_rejected507A ledger capacity bound was breached. Carries kind: "capacity".

What to retry

Re-pin, then resubmit

stale_policy, policy_pin_mismatch, no_active_policy. Nothing was written. Re-read the policy in force and resubmit the same transaction id against it. Bound the loop at three attempts.

Retry as-is

commit_unavailable. The mutation may or may not have landed. Because the transaction id is the idempotency key, resubmitting is safe: a commit that did land replays its stored outcome. Alternatively, read the transaction and find out.

Surface, never follow

not_primary. The body names the primary in primary_client_addr, and the SDKs surface that address rather than silently re-sending to it. Following a redirect for a mutation is a decision about where your money goes, and that decision is yours to make.

Fix the request

invalid_agent_uri, capability_mapping, multi_currency_mandate_charge, substrate_rejected, unknown_agent, attestation, invalid_usage_id. These will fail identically on every retry.

Surface, and infer nothing

unknown_usage. A usage balance owned by another party, one opened with no owner, and one that was never opened all answer this same 404. That is deliberate: a usage id is never an existence oracle across a tenant boundary. It means you cannot read the balance, and nothing more. Note that a 403 on a usage read is a rejected attestation, never an ownership verdict.

Handle the codes

1use axorum_client::AxorumError;
2
3match client.submit_pinned(draft).await {
4 Ok(outcome) => outcome, // includes a recorded refusal
5 Err(AxorumError::CommitUnavailable { .. }) => {
6 // Idempotent by transaction id — safe to resubmit.
7 client.submit_pinned(draft_again).await?
8 }
9 Err(AxorumError::NotPrimary { primary, .. }) => {
10 // Surfaced, never auto-followed.
11 return Err(anyhow!("re-send to the primary at {primary:?}"));
12 }
13 Err(error) => return Err(error.into()),
14};
1{
2 "error": "not_primary",
3 "message": "This replica is not the primary; re-send the mutation to the named address.",
4 "reason": "view change",
5 "primary_client_addr": "10.0.4.12:8080"
6}