Tool Pricing
Every tool call in chio is a priced transaction. This guide is for the people who operate tool servers and need to declare what each tool costs, and for the people who budget capability tokens against those prices. We walk through the four supported pricing models, how price is advertised in the signed ToolDefinition manifest, how the kernel validates budget capacity against declared price at dispatch time, and how cross-currency settlement is handled through oracle evidence on the receipt.
Read Budgets & Metering first
Two Sources of Truth
Chio keeps pricing and budgeting conceptually separate. A tool server publishes a price. An authority issues a budget. The kernel runs at dispatch time and checks whether the budget covers at least one more invocation at the advertised price. Those are distinct concerns and should not be collapsed.
| Surface | Source | Role |
|---|---|---|
| Advertised price | Signed tool manifest | Operator input, not enforcement |
| Issued budget | Capability grant | Kernel-enforced ceiling |
| Kernel-charged cost | Receipt metadata | What actually got deducted |
| Post-execution usage | Trust-control sidecar | Reconciliation evidence |
The practical flow: the tool server publishes a signed manifest with pricing. An operator or authority reads that quote. The authority issues a capability whose monetary budget is consistent with the advertised price plus a local safety margin. The kernel enforces the issued budget at invocation time.
The Four Pricing Models
Chio supports four pricing models in manifests. Each corresponds to a builder method on NativeTool in Rust and to equivalent declarations in the TypeScript and Python SDKs.
| Model | Rust builder | Behavior |
|---|---|---|
flat | flat_price(units, currency) | One fixed base price; no parameter sensitivity |
per_invocation | per_invocation_price(units, currency) | Same fixed unit price on every call; billing unit is "invocation" |
per_unit | per_unit_price(unit_price, billing_unit) | Price scales with a declared unit such as 1k_tokens or MB |
hybrid | hybrid_price(base, unit_price, billing_unit) | Fixed base plus a per-unit variable component |
All prices are declared in minor currency units (cents for USD, the smallest denomination for other currencies), matching the MonetaryAmount type used throughout chio.
Manifest Pricing Fields
Every ToolDefinition may carry a pricing object. The shape is the same across the four models; only which fields are populated varies.
{
"name": "greet",
"description": "Returns a personalized greeting",
"input_schema": { "...": "..." },
"pricing": {
"pricing_model": "per_invocation",
"unit_price": { "units": 25, "currency": "USD" },
"billing_unit": "invocation"
}
}For hybrid, a base_price field is also present:
{
"pricing": {
"pricing_model": "hybrid",
"base_price": { "units": 100, "currency": "USD" },
"unit_price": { "units": 5, "currency": "USD" },
"billing_unit": "1k_tokens"
}
}For flat and per_invocation, the billing_unit is always "invocation" and the base_price field is absent. For per_unit, the billing_unit names the scaling dimension (common values: 1k_tokens, MB, GB, row).
Declaring Pricing in Rust
The maintained native example in examples/hello-tool publishes pricing directly from NativeTool. The builder is small and composable.
use chio_mcp_adapter::{NativeChioServiceBuilder, NativeTool};
// Flat $0.25 per call; no variable component.
let greet = NativeTool::new(
"greet",
"Returns a personalized greeting",
serde_json::json!({
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}),
)
.read_only()
.per_invocation_price(25, "USD");
// Price by token count; $0.005 per 1k tokens.
let summarize = NativeTool::new(
"summarize",
"Condense input text",
serde_json::json!({
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
}),
)
.per_unit_price(5, "1k_tokens");
// Fixed $1.00 setup fee plus $0.05 per MB.
let archive = NativeTool::new(
"archive",
"Compress and store data",
serde_json::json!({
"type": "object",
"properties": { "data": { "type": "string" } },
"required": ["data"]
}),
)
.hybrid_price(100, 5, "MB");
let server = NativeChioServiceBuilder::new("srv-hello")
.tool(greet)
.tool(summarize)
.tool(archive)
.build()?;NativeChioServiceBuilder::build() signs the manifest with the server's key. The signature covers the pricing block, so any post-hoc change to price requires re-signing. Callers can verify the signature before extending trust to the declared prices.
Shorthand Builders
Two builders are aliases for clarity. They produce the same manifest output:
// These two produce identical manifest output.
// per_invocation_price is preferred; flat_price is retained for
// cases where a tool genuinely has no parameter sensitivity and
// the operator wants to signal that intent explicitly.
tool.flat_price(25, "USD");
tool.per_invocation_price(25, "USD");Declaring Pricing in TypeScript
import { ChioToolServer, tool, pricing } from "@chio-protocol/sdk-server";
const greet = tool({
name: "greet",
description: "Returns a personalized greeting",
inputSchema: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
},
pricing: pricing.perInvocation({ units: 25, currency: "USD" }),
handler: async ({ name }) => ({ greeting: `hello, ${name}` }),
});
const summarize = tool({
name: "summarize",
description: "Condense input text",
inputSchema: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
},
pricing: pricing.perUnit({
unitPrice: { units: 5, currency: "USD" },
billingUnit: "1k_tokens",
}),
handler: async ({ text }) => ({ summary: await summarize(text) }),
});
const archive = tool({
name: "archive",
description: "Compress and store data",
inputSchema: {
type: "object",
properties: { data: { type: "string" } },
required: ["data"],
},
pricing: pricing.hybrid({
basePrice: { units: 100, currency: "USD" },
unitPrice: { units: 5, currency: "USD" },
billingUnit: "MB",
}),
handler: async ({ data }) => ({ handle: await writeArchive(data) }),
});
const server = ChioToolServer.from({
id: "srv-hello",
tools: [greet, summarize, archive],
});
await server.listen({ port: 7000 });The TS SDK's pricing helpers mirror the Rust builders one-to-one. Internally they produce the same JSON as the Rust server, so a manifest fetched from either server is byte-identical (modulo field ordering, which canonical JSON normalizes before signing).
Declaring Pricing in Python
from chio.server import ChioToolServer, tool
from chio.pricing import per_invocation, per_unit, hybrid
@tool(
name="greet",
description="Returns a personalized greeting",
input_schema={
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
},
pricing=per_invocation(units=25, currency="USD"),
)
def greet(name: str) -> dict:
return {"greeting": f"hello, {name}"}
@tool(
name="summarize",
description="Condense input text",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
pricing=per_unit(
unit_price={"units": 5, "currency": "USD"},
billing_unit="1k_tokens",
),
)
def summarize(text: str) -> dict:
return {"summary": _summarize(text)}
@tool(
name="archive",
description="Compress and store data",
input_schema={
"type": "object",
"properties": {"data": {"type": "string"}},
"required": ["data"],
},
pricing=hybrid(
base_price={"units": 100, "currency": "USD"},
unit_price={"units": 5, "currency": "USD"},
billing_unit="MB",
),
)
def archive(data: bytes) -> dict:
return {"handle": _write_archive(data)}
ChioToolServer(
id="srv-hello",
tools=[greet, summarize, archive],
).listen(port=7000)Budget Planning from a Quote
Reading pricing is only half the job. You still have to translate a quote into a budget on the capability grant. The planning rules depend on the pricing model:
| Model | Per-call cap | Total budget |
|---|---|---|
flat | Flat quote | flat * allowed_invocations + margin |
per_invocation | Quoted unit price | unit_price * allowed_invocations + margin |
per_unit | Conservative per-call estimate from expected unit ceiling | per_call_estimate * allowed_invocations + margin |
hybrid | base + unit * expected_units_per_call | per_call * allowed_invocations + margin |
A worked example with the greet tool above: the manifest advertises per_invocation at 25 USD minor units. The expected workload is 40 calls. A straightforward planning pass:
expected_total = 40 * 25 = 1000
safety_margin = 200
grant_total = 1200
per_call_cap = 25The corresponding capability grant:
use chio_core::capability::{MonetaryAmount, Operation, ToolGrant};
let grant = ToolGrant {
server_id: "srv-hello".to_string(),
tool_name: "greet".to_string(),
operations: vec![Operation::Invoke],
constraints: vec![],
max_invocations: None,
max_cost_per_invocation: Some(MonetaryAmount {
units: 25,
currency: "USD".to_string(),
}),
max_total_cost: Some(MonetaryAmount {
units: 1200,
currency: "USD".to_string(),
}),
dpop_required: Some(true),
};The quote is not the enforcement boundary
Dispatch-Time Validation
At dispatch, the kernel calls try_charge_cost(). This is an atomic operation that checks three things together, in a single transaction on the budget store:
- Invocation count. Does the grant have any
max_invocationsleft? - Per-invocation cap. Is this call's planned cost at or below
max_cost_per_invocation? - Total budget. Does the grant have at least the planned cost remaining under
max_total_cost?
If any check fails the call is denied with a specific reason code. If all three pass, the planned cost is deducted and the call is dispatched. The deduction is visible on the receipt as metadata.financial.cost_charged.
# The planned cost that the kernel deducts:
#
# flat -> flat price
# per_invocation -> unit price
# per_unit -> unit price * expected_units_per_call (from intent)
# hybrid -> base price + unit price * expected_units_per_call
#
# The grant's max_cost_per_invocation should be at least this value.
# Post-execution, observed units may differ from expected. That
# reconciliation happens in the trust-control sidecar, not in the
# signed receipt.Metered Billing and Governed Quotes
Manifest pricing is advisory discovery data. When an operator wants to bind a concrete pre-execution quote into a governed request, chio supports a typed governed_intent.metered_billing block. It carries:
| Field | Meaning |
|---|---|
settlement_mode | must_prepay, hold_capture, or allow_then_settle |
quote.quote_id | Stable identifier from the metering or billing system |
quote.provider | Billing authority that issued the quote |
quote.billing_unit | Unit name (invocation, 1k_tokens, ...) |
quote.quoted_units | Estimated billable units |
quote.quoted_cost | Estimated monetary amount |
quote.issued_at / expires_at | Quote validity window |
max_billed_units | Explicit upper bound for the governed request |
With metered_billing in place, chio has a truthful two-source contract: pre-execution quote and settlement posture live on the governed intent; kernel-charged cost lives in metadata.financial on the receipt; post-execution usage is reconciled through a mutable trust-control sidecar keyed by receipt_id. The signed receipt itself is immutable. Reports present quote, kernel-observed cost, and external metered evidence side by side instead of flattening them into one mutable field.
Cross-Currency Transactions
Tools may quote in one currency while agents hold budgets in another. Chio handles cross-currency settlement through oracle evidence attached to the receipt. A Chainlink feed (or other policy-configured oracle) provides the conversion rate at dispatch time; the kernel records the rate, the source, and the timestamp in receipt metadata so an auditor can reconstruct the math.
{
"metadata": {
"financial": {
"cost_charged": 250,
"currency": "USD",
"source_amount": { "units": 22800, "currency": "JPY" },
"fx_oracle": {
"provider": "chainlink",
"feed": "JPY/USD",
"rate": 0.01096,
"as_of": 1700000125,
"round_id": "0x1c8d..."
}
}
}
}Keep currencies aligned until you actually need FX
Changing Prices
A tool's manifest is signed. Changing the price of an existing tool requires:
- Updating the pricing fields on the
ToolDefinition. - Re-signing the manifest with the server's key.
- Publishing the new manifest version (with an updated
versionandpublished_at).
Callers who fetched the previous manifest still see the old price until they re-fetch. When they do re-fetch, they will see the new price before they attempt any delegation or capability issuance. The kernel does not silently adopt new prices: the authority that mints a grant must read the current manifest and issue a budget that is consistent with the current quote. If the price has risen and the grant budget no longer covers a call, the kernel denies at dispatch.
Price increases need operator action
max_cost_per_invocation. This is working as intended. Operators should watch the receipt query API for a spike in budget-cap denies after a price change and re-issue grants with a higher ceiling where appropriate.Advertising Price to Callers
Before an agent (or an authority on its behalf) delegates a capability for a tool, it can fetch the manifest and inspect the pricing block. The SDK makes this straightforward:
import { ChioClient } from "@chio-protocol/sdk";
const client = ChioClient.fromEnv();
const manifest = await client.getToolManifest({
serverId: "srv-hello",
toolName: "greet",
});
if (manifest.pricing?.pricing_model === "per_invocation") {
const { units, currency } = manifest.pricing.unit_price;
const plannedCalls = 40;
const margin = 200; // minor units
const totalBudget = units * plannedCalls + margin;
console.log(`Quoted at ${units} ${currency} per call.`);
console.log(`Planning budget of ${totalBudget} ${currency} for ${plannedCalls} calls.`);
}from chio import Client
client = Client.from_env()
manifest = client.get_tool_manifest(server_id="srv-hello", tool_name="greet")
pricing = manifest.pricing
if pricing and pricing["pricing_model"] == "per_invocation":
units = pricing["unit_price"]["units"]
currency = pricing["unit_price"]["currency"]
planned_calls = 40
margin = 200
total_budget = units * planned_calls + margin
print(f"Quoted at {units} {currency} per call.")
print(f"Planning budget of {total_budget} {currency} for {planned_calls} calls.")Current Limitations
The policy YAML and HushSpec authoring path does not yet encode monetary default-capability issuance. Monetary planning happens in the capability issuance or authority layer, not as a declarative policy shortcut. The truthful flow today:
- Publish pricing in the manifest.
- Read the manifest in operator or authority code.
- Issue a budgeted capability explicitly.
Do not document or rely on a YAML-based pricing-to-budget pipeline that does not exist yet. It is on the roadmap, and when it ships this guide will call it out.
Safety Notes
- Keep pricing currency and grant currency identical until multi-currency support ships.
- Size
max_total_costwith HA overrun headroom in clustered deployments. Per-kernel budget state does not coordinate across replicas. - Require DPoP on spend-bearing grants so quoted authority stays bound to the intended subject.
- Treat manifest pricing as operator input, not a billing truth source. The billing truth lives in receipts and the metered sidecar.
- When the real tool may overrun the advisory quote, set the grant from the worst-case amount you are willing to authorize, not from the optimistic quote.
Related Guides
- Budgets & Metering walks through the budget side of the same transaction.
- Settlement covers how observed costs reconcile against quoted costs after the tool runs.
- Economics is the conceptual overview of how money moves through chio.