Tool Pricing
Lower a service’s published price and follow the charge from the signed offer, through the agent’s grant, to one signed receipt.
On this page
A provider signs its price into the tool manifest. An issuer signs how much an agent may spend. The kernel admits each call within the grant, and the receipt records what the call cost.
Continue the metered discount service from Budgets & Metering. Lower its price from 20 to 15 USD minor units, and the same 50-unit allowance buys three discount checks.
1. Lower the price to 15 units
Before you begin
You need the pricing-metered project from Budgets & Metering and the Chio CLI. The project supplies the service and the key that signs its manifest, an issuer that sets monetary limits on the grant, a connection that reports realized cost, durable admission, and a local-credit payment adapter. Amounts are minor currency units: 100 USD units is one dollar.
In the service, replace the price constant. The service uses it both in its signed manifest and in the cost its connection reports.
pub const PRICE_UNITS: u64 = 15;In the host, lower the per-call ceiling to match and keep the total allowance at 50. The local adapter captures exactly the authorized amount, so price and ceiling move together.
pub const CALL_CEILING: u64 = 15;
pub const TOTAL_BUDGET: u64 = 50;2. Run three calls under the same allowance
Run the service again from the project directory. The run issues a fresh capability with the new ceiling.
cargo run --release --locked -- sku-204 15call-1: ALLOW
result: {"currency":"USD","percent":15,"price":"49.30","sku":"sku-204"}
charge: 15 USD; settlement: settled
budget: 35 available; 15 spent (USD)
reserved: 0
handler invocations: 1
call-2: ALLOW
result: {"currency":"USD","percent":15,"price":"49.30","sku":"sku-204"}
charge: 15 USD; settlement: settled
budget: 20 available; 30 spent (USD)
reserved: 0
handler invocations: 2
call-3: ALLOW
result: {"currency":"USD","percent":15,"price":"49.30","sku":"sku-204"}
charge: 15 USD; settlement: settled
budget: 5 available; 45 spent (USD)
reserved: 0
handler invocations: 3
evidence: runs/3750e077-fe09-4160-a5dd-ccee3ba7c0cdYou should see three calls admitted, 45 units spent, and 5 available. Editing the constants never mutates a capability already issued; the next run signs a new one. The local ledger records credits and moves no external money.
3. Read the offer the provider signed
Open manifest.json in the printed evidence directory and find the tool's pricing block.
{
"pricing_model": "per_invocation",
"unit_price": {
"units": 15,
"currency": "USD"
},
"billing_unit": "invocation"
}You should see per_invocation at 15 USD units. Three values now agree because this application connects them: the manifest advertises 15, the grant permits 15 per call, and the connection reports 15. Each comes from a different actor at a different moment.
Spending authority comes from the grant
max_cost_per_invocation and max_total_cost bound what the agent may commit, independently of any price the provider names. Budgets and metering.
The kernel enforces the grant. An issuer reads the provider's price and decides what budget to sign, so widening an agent's spending takes that issuer. After execution, the connection reports cost, the payment module reconciles it, and the receipt records the result.
Read more in Pricing models.
4. Verify the charge in the signed receipt
The first receipt of the run carries this financial block. It records the charge, the remaining allowance, and the identifiers that join the authorization to its completed local capture.
{
"budget_remaining": 35,
"budget_total": 50,
"cost_breakdown": {
"payment": {
"authorization_id": "hold:d6ffa273e4882e6a0454ef6f25bf01582dd488a368ca09015c9ea989e6b2ec1e",
"preauthorized_units": 15,
"rail": "finding-operator-ledger",
"rail_mode": "reversible_hold",
"recorded_units": 15,
"transaction_id": "capture:d6a78ca355ecb41e407513f49efe01fcea77bdefd253cfd0b511c16364584340"
}
},
"cost_charged": 15,
"currency": "USD",
"delegation_depth": 0,
"grant_index": 0,
"payment_reference": "capture:d6a78ca355ecb41e407513f49efe01fcea77bdefd253cfd0b511c16364584340",
"root_budget_holder": "4806f7a942076d27d3652d6d74b861455fce7eaeb7e235957ea26d8e87cb075d",
"settlement_status": "settled"
}In the evidence directory, verify the receipt against the host key you selected before any receipt arrived.
cargo run --release --locked --bin verify-receipt -- call-1-receipt.json kernel-public-key.txt{
"authorized": true,
"crypto_error": null,
"crypto_floor": "allow_classical",
"decision": {
"verdict": "allow"
},
"ok": true,
"parameter_hash_valid": true,
"receipt_id": "607a245ba9f85470eb7277a30754527deeb47e8233880f583d0f8c53b50169ed",
"receipt_id_valid": true,
"signature_valid": true,
"signer_trusted": true
}You should see signature_valid and signer_trusted both true, and the receipt's recorded decision. For a remote operator, take its expected key from trusted configuration. Settlement reads the payment identifiers in this block.
A price reaches the kernel only through the grant
The kernel reads the grant at admission and the reported cost at finalization; it reads the manifest price at neither. Raise only the advertised price to 30 while the grant and the reported charge stay at 20, and the two admitted calls still charge 20 each.
The service reported more than the grant authorized
A higher report from the service is a different event: reporting 30 against an authorization of 20 fails during finalization, after the service ran.
Error: durable admission failed: reported cost exceeds the durable payment authorizationThe process returns no tool response and no billable receipt, and the local payment stays held. Keep the run's stores for diagnosis and resolve the hold before a new run; a retry runs the service again. Align the charge and the ceiling, or use a host and adapter built for variable charges.
The call is unpayable
A governed intent that mandates prepayment with no payment adapter configured is refused before the tool runs. The kernel signs a deny receipt for the refusal and charges nothing.
$ jq '{decision, financial: .metadata.financial}' ./bundle-deny.json{
"decision": {
"verdict": "deny",
"reason": "governed transaction denied: governed intent mandates prepayment (settlement_mode=MustPrepay) but no payment adapter is configured",
"guard": "kernel"
},
"financial": {
"attempted_cost": 100,
"budget_remaining": 1000,
"budget_total": 1000,
"cost_charged": 0,
"currency": "USD",
"delegation_depth": 0,
"grant_index": 0,
"root_budget_holder": "95dd6964b2c519849a0f53ded5c810fc87791bb622ace9b887537de4c51a2c59",
"settlement_status": "not_applicable"
}
}Pricing models, manifests, quotes, and currencies
Search for per_unit, billing_unit, metered_billing, or oracle_evidence.
Pricing models
Manifests support four models, spelled Flat, PerInvocation, PerUnit, and Hybrid in the PricingModel enum. Each has a builder method on NativeTool in Rust. Every price is declared in minor currency units, matching MonetaryAmount.
| Model | Rust builder | Behavior |
|---|---|---|
flat | flat_price(units, currency) | One fixed base price; no parameter sensitivity |
per_invocation | per_(units, currency) | Same fixed price per call; billing unit "invocation" |
per_unit | per_unit_price(units, currency, billing_unit) | Scales with a declared unit such as 1k_tokens or MB |
hybrid | hybrid_price(base_units, unit_units, currency, billing_unit) | Fixed base plus a per-unit component |
Manifest pricing fields
Every ToolDefinition may carry a pricing object. The four models share one shape with different fields populated.
{
"name": "search",
"description": "Search competitor prices for a SKU",
"input_schema": { "...": "..." },
"pricing": {
"pricing_model": "per_invocation",
"unit_price": { "units": 25, "currency": "USD" },
"billing_unit": "invocation"
}
}A hybrid block also carries base_price:
{
"pricing": {
"pricing_model": "hybrid",
"base_price": { "units": 100, "currency": "USD" },
"unit_price": { "units": 5, "currency": "USD" },
"billing_unit": "1k_tokens"
}
}For flat, the amount lives in base_price with no unit_price and no billing_unit. For per_invocation, it lives in unit_price with billing_unit set to "invocation". For per_unit, it lives in unit_price and billing_unit names the scaling dimension (1k_tokens, MB, GB, row). The manifest validator requires base_price for flat, unit_price and billing_unit for per_invocation and per_unit, and both amounts plus billing_unit for hybrid.
Declare pricing in Rust
A native service publishes pricing directly from NativeTool, as the pricing tools do for quote. .per_invocation_price(25, "USD") declares 25 minor units, USD 0.25 a call, and .tool(...) takes the definition together with its handler.
pub fn build_service(
public_key_hex: String,
) -> Result<NativeChioService, chio_manifest::ManifestError> {
NativeChioServiceBuilder::new("srv-hello", public_key_hex)
.server_name("Hello Tool Server")
.server_version("0.1.0")
.server_description("A tiny native Chio service that exposes a tool, resource, prompt, and priced manifest")
.tool(
NativeTool::new(
"greet",
"Returns a personalized greeting",
serde_json::json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name to greet"
}
},
"required": ["name"]
}),
)
.output_schema(serde_json::json!({
"type": "object",
"properties": {
"greeting": { "type": "string" }
}
}))
.read_only()
.per_invocation_price(25, "USD")
.latency_hint(chio_manifest::LatencyHint::Instant),
|arguments| {
let name = greeting_name(&arguments)?;
Ok(serde_json::json!({
"greeting": format!("Hello, {name}! This greeting was served by a native Chio service.")
}))
},
)
.static_resource(The other three models use a different builder method:
use chio_mcp_adapter::native::NativeTool;
// Price by token count: USD 0.05 per 1k tokens.
let summarize = NativeTool::new("summarize", "Condense input text", schema.clone())
.per_unit_price(5, "USD", "1k_tokens");
// Fixed USD 1.00 base plus USD 0.05 per MB.
let archive = NativeTool::new("archive", "Compress and store data", schema.clone())
.hybrid_price(100, 5, "USD", "MB");
// One fixed price with no parameter sensitivity.
let ping = NativeTool::new("ping", "Liveness probe", schema)
.flat_price(1, "USD");NativeChioServiceBuilder::build() validates the assembled manifest and returns the service unsigned. The public-key hex passed to NativeChioServiceBuilder::new(server_id, public_key) is a manifest field, and sign_manifest refuses when it differs from the signing key. After build(), call chio_manifest::sign_manifest(service.manifest(), &keypair), which validates again and signs the whole manifest. The signature covers the pricing block, so a price change means re-signing, and callers can check the signature before they trust a price.
Flat and per-invocation shapes
flat_price and per_invocation_price can advertise the same amount, yet they emit different manifest shapes. Use flat_price when the manifest should record that the tool has no parameter sensitivity, and per_invocation_price for a fixed per-call unit. A caller branches on pricing_model to read the amount from the right field.
// flat_price: pricing_model Flat, amount in base_price, no billing_unit.
tool.flat_price(25, "USD");
// pricing = { pricing_model: "flat", base_price: { units: 25, currency: "USD" } }
// per_invocation_price: pricing_model PerInvocation, amount in unit_price,
// billing_unit "invocation".
tool.per_invocation_price(25, "USD");
// pricing = { pricing_model: "per_invocation",
// unit_price: { units: 25, currency: "USD" },
// billing_unit: "invocation" }Other authoring languages
A server in any language can publish the same signed manifest structure if it produces the canonical bytes and signature the manifest verifier expects; the Rust builder is a convenience. The TypeScript, Python, and Go SDKs parse manifests and check their invariants.
Read an offer as a caller
Before an agent, or an authority acting for it, delegates a capability for a tool, it fetches the signed manifest during discovery and reads the tool's pricing block. Once parsed, the block is plain data.
// signedManifest is what verifySignedManifest accepted. Pricing hangs off each
// tool: a top-level "pricing" key fails the structure check.
const tool = signedManifest.manifest.tools.find((entry) => entry.name === "search");
const pricing = tool?.pricing;
if (pricing?.pricing_model === "per_invocation" && pricing.unit_price) {
const { units, currency } = 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.`);
}# signed_manifest is what the invariants verifier accepted; a parsed
# manifest is a plain dict. Pricing hangs off each tool.
tool = next(t for t in signed_manifest["manifest"]["tools"] if t["name"] == "search")
pricing = tool.get("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.")MANIFEST_FIELD_SET excludes pricing and TOOL_FIELD_SET includes it, so a manifest with a top-level pricing key returns structure_valid: false. Planning a budget from the offer leaves issuance as a separate, explicit host step.
Plan a budget from a quote
| Model | Per-call allowance | Total budget |
|---|---|---|
flat | Flat quote | flat * allowed_ |
per_invocation | Quoted unit price | unit_price * allowed_ |
per_unit | Conservative estimate from the expected unit ceiling | per_ |
hybrid | base + unit * expected_ | per_call * allowed_ |
For a search tool on web at per_invocation 25 USD units and an expected 40 calls:
expected_total = 40 * 25 = 1000
safety_margin = 200
grant_total = 1200
per_call_cap = 25use chio_core::capability::scope::{MonetaryAmount, Operation, ToolGrant};
let grant = ToolGrant {
server_id: "web".to_string(),
tool_name: "search".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),
};When a tool may exceed its quote, set the ceiling from the worst case you will authorize. dpop_required binds the spend-bearing grant to its subject.
Set the budget at issuance
The monetary fields belong to the signed capability grant. Populate them in issuance or authority code, alongside the guard policy and host configuration:
- Publish. The tool server puts its pricing in the signed manifest.
- Read. Operator or authority code reads the verified manifest.
- Issue. The issuer signs a budgeted capability explicitly.
A price change therefore never widens a grant: an agent holds the budget its issuer signed.
Admission and cost reconciliation
For an ordinary monetary grant, admission reserves max_cost_per_invocation against the available total. The durable authority coordinates that hold with the operation and any invocation quota, and execution starts only after admission and payment authorization succeed. In the 15-unit run:
- Issue. The issuer permits 15 per call and 50 in total.
- Reserve. The host reserves and authorizes 15 before invoking the discount service.
- Report. The connection returns the discounted price and reports 15 USD units.
- Capture. The local-credit adapter captures 15, and the kernel records the charge and the available balance.
A connection that reports no cost is charged the authorized ceiling on this durable path. A report above the authorization fails during finalization, after the service has run.
Governed quotes
To bind a pre-execution quote into a governed request, put a typed governed_intent.metered_billing block on the intent:
settlement_mode:must_prepay,hold_capture, orallow_then_settle.quote.quote_id: a stable identifier from the metering or billing system.quote.provider: the billing authority that issued the quote.quote.billing_unit: the unit name, such asinvocationor1k_tokens.quote.quoted_unitsandquote.quoted_cost: the estimated units and amount.quote.issued_atandexpires_at: the quote's validity window.max_billed_units: an explicit upper bound for the governed request.
The quote and settlement configuration live on the governed intent, the charged cost lives in the receipt's metadata.financial, and post-execution usage reconciles through a mutable trust-control sidecar keyed by receipt_id. The signed receipt stays immutable, so a report can show quote, charge, and external metering side by side.
Cross-currency transactions
A tool may quote in one currency while the agent's budget is in another. A configured oracle resolves the rate when the payment path converts the reported cost, and the receipt's metadata.financial.oracle_evidence records an OracleConversionEvidence: the rate as an integer rate_numerator / rate_denominator pair, with the feed source, address, and timestamp. The Settlement guide covers the same record from the resolver side. This fragment is illustrative, with the feed address elided.
{
"grant_index": 0,
"cost_charged": 250,
"currency": "USD",
"budget_remaining": 950,
"budget_total": 1200,
"delegation_depth": 0,
"settlement_status": "settled",
"oracle_evidence": {
"schema": "chio.oracle-conversion-evidence.v1",
"base": "JPY",
"quote": "USD",
"authority": "chio_link_runtime_v1",
"rate_numerator": 1096,
"rate_denominator": 100000,
"source": "chainlink",
"feed_address": "0x...",
"updated_at": 1700000125,
"max_age_seconds": 3600,
"cache_age_seconds": 12,
"converted_cost_units": 250,
"original_cost_units": 228,
"original_currency": "JPY",
"grant_currency": "USD"
}
}The validator requires the authority label chio_link_runtime_v1; configured degraded resolution can append :degraded to the source. Conversion uses each currency's minor-unit scale and rounds up: USD, EUR, and GBP use 100 minor units, JPY uses 1, and USDC and USDT use 1,000,000.
228 × 1096 × 100 / (1 × 100000) = 249.888
round up → 250 USD minor unitsKeep the grant and pricing currencies identical unless the capability needs a conversion. With no oracle configured, a cross-currency call fails closed.
Change a published offer
A tool's manifest is signed, so a price change takes three steps:
- Edit. Update the pricing fields on the
ToolDefinition. - Sign. Re-sign the manifest with the server's key.
- Publish. Publish the new manifest version with an updated
versionandpublished_at.
Callers keep the offer they fetched until they refresh it. The authority inspects the new offer and decides whether to issue a new grant. Configure the host to reject an unaffordable offer before invoking it.
FX evidence that does not reconcile
A receipt whose converted_cost_units differs from the recomputed value fails conversion-evidence validation, whatever its signature. Recompute the conversion with both minor-unit scales, then check the oracle policy and evidence. Where the error surfaces depends on which integration runs the validator.
A manifest that will not sign
sign_manifest validates before signing, so a pricing block missing a required field never reaches a caller: Flat needs base_price, PerInvocation and PerUnit need unit_price and billing_unit, and Hybrid needs all three. The builder methods set these; a hand-written manifest is where they go missing.
Operating rules
- Treat manifest pricing as operator input. Receipts and the metered sidecar record billed usage.
- Coordinate spending through the deployment's durable budget authority; the overrun bound depends on that authority and its consistency model.
- Require DPoP on spend-bearing grants so quoted authority stays bound to its subject.
- Payment rails plug in as optional modules. Holds and caps stop spend before dispatch, and the receipt records the rail outcome the kernel observes.