BREAKING:Axorum is a founding member of the A-Comm Evidence Protocol (AEP) to build trust and auditability in agentic commerceRead more here →
Axorum
Guides›SDKs

Guides

SDKs

Three clients, one contract. Each is held to the same conformance suite, run against a real service — the same routes, the same policy evaluation, the same error bodies — rather than a mock that would only assert what its author believed the service emits.

  • RustAvailableaxorum-client

    The reference implementation. Pins the policy, re-pins on a 409, and surfaces every structured error field the contract makes load-bearing.

    Read more
  • TypeScriptAvailable@axorum/client

    The same contract for Node and the edge. Decodes minor units as a big integer, so a large balance does not silently lose precision.

    Read more
  • PythonAvailableaxorum-client

    The same contract, sync and async. Held to the Rust client’s conformance suite against a real service, not a mock.

    Read more

What an SDK does for you

The agent plane is plain HTTP and you can speak it with curl — carrying your attestation as Authorization: Bearer <attestation> on the party-scoped reads, and reaching the service over TLS, since it refuses plaintext anywhere but loopback. What the clients add is the three places a hand-rolled caller gets it wrong.

The pin loopsubmit_pinned

Reads the policy in force, stamps the pin, and re-pins on a 409 — carrying the same client-minted transaction id through every attempt, bounded at three.

Lossless errorsstructured fields

The contract makes pinned, active, primary_client_addr, kind, currencies, uri, and action load-bearing. A client that collapses non-2xx responses into a generic error shape destroys exactly the fields you need to recover. The SDKs decode the body themselves and keep every one.

A refusal is not an errorOk / resolve / return

ForbiddenRejected comes back as a successful outcome with posted: false, not as a thrown exception. You branch on the verdict.

The usage surface

The usage-law surface was introduced with agent-plane contract 4.0.0. The table below records that historical feature release; these are not current installation versions. It was additive: an intent with no usage move retained its earlier wire shape.

PackageVersion that introduced usageWhat it gained
axorum-client (crates.io)0.4.0consume_usage, meter_usage and bill_usage on IntentDraft. The balance read is AdminClient::usage_balance, on the admin plane.
@axorum/client (npm)0.3.0usage(usageId), returning a UsageBalanceReport whose counters are bigint. The three moves on the draft. The invalid_usage_id and unknown_usage wire codes.
axorum-client (PyPI, imported as axorum)0.3.0usage(usage) on both the sync and async clients. The three moves on IntentDraft and IntentEnvelope, omitted from the dict when absent. UsageMove and UsageBalanceReport.
@axorum/mcp (npm)0.3.0A sixth tool, get_usage, and three optional submit_intent inputs.

A usage balance is opened on the admin plane, which the agent-plane clients do not speak. An agent receives a usage id; it never mints one. That is why there is no newUsageId anywhere in these packages.

Install

The selector above, or any tab below, sets your language for the whole reference. These commands select the registry's latest release; use your package manager's lockfile to retain the version you tested. For a pinned, verified local workflow, see Run your first agent.

Install
1cargo add axorum-client

The draft/envelope split is deliberate: an IntentDraft holds everything the agent knows, and pin — a pure function — stamps the one thing only the service knows. That is what makes envelope construction testable without a service running.

Your first intent

A client, and one intent
1use axorum_client::{AxorumClient, AxorumError, IntentDraft};
2
3let client = AxorumClient::builder("https://ledger.example.com")
4 .timeout(std::time::Duration::from_secs(5))
5 .build()?;
6
7let outcome = client.submit_pinned(draft).await?;
8
9// A refusal is an `Ok`. Branch on the verdict, never the status.
10if outcome.posted {
11 println!("posted: {:?}", outcome.verdict);
12} else {
13 println!("refused, and the refusal is on the record: {:?}", outcome.verdict);
14}

Rust

The reference implementation. axorum-client re-exports the wire types, so building an envelope and reading an outcome needs no second dependency. The client is cheap to clone — it shares one connection pool — so one instance can be held by many tasks.

TypeScript

@axorum/client targets Node 20+ and the edge, with no runtime dependencies: the platform's fetch carries the request. Wire types are generated from the frozen contract and re-exported. Every method accepts an AbortSignal; failures are instanceof-able error classes with a stable code. The one thing it will never do is hand you a number for a monetary amount: minor is a 128-bit integer and decodes as bigint.

Python

The axorum-client distribution installs the axorum package: AxorumClient and AsyncAxorumClient over the same contract, both context managers, fully typed. Failures raise an exception hierarchy rooted at AxorumError — with is_retriable and requires_repin telling you what to do next — while a refusal returns like any other outcome, with posted false.

Counters on a usage report are plain ints, so they are exact at any magnitude. That matters here more than anywhere else on the plane: consumed takes no ceiling, and a busy meter's total can lawfully outgrow u64.

MCP

@axorum/mcp exposes the agent plane to an MCP-capable model as six tools: submit_intent, get_active_policy, get_transaction, get_obligations, get_balance, and get_usage. It is built on @axorum/client, so it inherits the pin loop, the typed errors, and the refusal-is-not-an-error rule.

Each tool description is written as a prompt, not as documentation: it is the text the model reads when deciding what to call and what to pass. So submit_intent teaches the law rather than restating the schema. Metered spend is one intent, 0 <= billed <= metered <= consumed, metering past consumption is phantom usage, billing past metering has nothing behind it, and breaking either refuses the entire intent, journal legs included. It also says the thing a model would otherwise get wrong: an unreconciled chain is a lawful transient, not a fault to correct.

get_usage{ usage: string }

One usage balance. The result leads with the ledger's own compliance-English narration, then states the headroom that bounds the next lawful move, so a model learns its limit from a sentence rather than by subtracting figures out of a JSON blob. Counters are decimal strings: consumption takes no ceiling, so a total can outgrow what a JSON number holds exactly.

submit_intentconsume_usage / meter_usage / bill_usage

Three optional inputs, each a usage id plus an amount whose minor is a decimal string, like every other amount this server accepts. A refused usage move is a genuine isError result, unlike a policy refusal: the intent never reached a verdict. It carries the ledger's plain-English refusal with its exact figures, and the advice not to retry it unchanged.

A model that gets unknown_usage back is told to conclude one thing only: it cannot read that balance. Never that the balance is absent. See usages for why the refusal is deliberately blind.

1pnpm add @axorum/mcp