Chio/Docs
LOGIN · JOIN

BuildEconomics

Tool Pricing

Tool servers publish signed prices; the kernel checks each call against the capability budget at dispatch.

Read Budgets & Metering first

This guide assumes you already understand how budgets are shaped on capability grants. Start with Budgets & Metering for the budget side, then come back here for the pricing side. The two are independent layers that meet at dispatch time.

Prerequisites

  • A native tool server built with chio-mcp-adapter. Pricing is declared on the tool at build time and signed into the manifest; there is no runtime price API.
  • A keypair to sign the manifest with. The public-key hex you pass to the builder has to be that same key, or sign_manifest refuses.
  • An issuing authority that reads the manifest and mints a budgeted capability. Publishing a price does not create a budget.
  • Every amount on this page is in minor currency units: 100 units is one US dollar (crates/core/chio-core-types/src/capability/scope.rs:80-91).

Price, Budget, and Charged Cost

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 another invocation at the advertised price. Keep those values separate: they come from different actors and are used at different times.

ValueSourceRole
Advertised priceSigned tool manifestOperator input; the kernel does not enforce it directly
Issued budgetCapability grantKernel-enforced ceiling
Kernel-charged costReceipt metadataWhat actually got deducted
Post-execution usageTrust-control sidecarReconciliation record

A tool server publishes pricing in a signed manifest. 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.


Pricing models

Chio supports four pricing models in manifests, spelled Flat, PerInvocation, PerUnit, and Hybrid in the PricingModel enum. Each corresponds to a builder method on NativeTool in Rust, which is where a priced tool is authored.

ModelRust builderBehavior
flatflat_price(units, currency)One fixed base price; no parameter sensitivity
per_invocationper_invocation_price(units, currency)Each invocation uses the same fixed price; billing unit is "invocation"
per_unitper_unit_price(units, currency, billing_unit)Price scales with a declared unit such as 1k_tokens or MB
hybridhybrid_price(base_units, unit_units, currency, 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 four models use the same shape with different populated fields.

tool-manifest-fragment.jsonjson
{
  "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:

hybrid-pricing.jsonjson
{
  "pricing": {
    "pricing_model": "hybrid",
    "base_price":   { "units": 100, "currency": "USD" },
    "unit_price":   { "units": 5,   "currency": "USD" },
    "billing_unit": "1k_tokens"
  }
}

The populated fields differ by model. For flat, the advertised amount lives in base_price; unit_price is absent and no billing_unit is set. For per_invocation, the amount lives in unit_price, billing_unit is "invocation", and base_price is absent. For per_unit, the amount lives in unit_price and billing_unit names the scaling dimension (common values: 1k_tokens, MB, GB, row). The manifest validator enforces this split: it requires base_price for flat, unit_price plus a billing_unit for per_invocation and per_unit, and both amounts plus a billing_unit for hybrid.


Declaring Pricing in Rust

The maintained native example in examples/hello-tool publishes pricing directly from NativeTool. The builder adds pricing directly to a tool definition.

examples/hello-tool/src/lib.rs29-67rust
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 price rides on the tool, not on the server: .per_invocation_price(25, "USD") declares 25 minor units, which is $0.25 a call. Note the shape of .tool(...): it takes the tool definition and its handler, so a priced tool and the code that serves it are declared together (crates/protocol/chio-mcp-adapter/src/native.rs:265). The other three models are the same call with a different builder method:

rust
use chio_mcp_adapter::native::NativeTool;

// Price by token count; $0.05 per 1k tokens.
let summarize = NativeTool::new("summarize", "Condense input text", schema.clone())
    .per_unit_price(5, "USD", "1k_tokens");

// Fixed $1.00 base plus $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; it does not sign. The public-key hex passed to NativeChioServiceBuilder::new(server_id, public_key) is a manifest metadata field, and sign_manifest refuses when it does not equal the signing key (crates/platform/chio-manifest/src/lib.rs:266,293-303). After build(), call chio_manifest::sign_manifest(service.manifest(), &keypair), which re-validates and signs the whole manifest. 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.

Flat vs. Per-Invocation

flat_price and per_invocation_price can advertise the same per-call amount, but they are not aliases: they emit different manifest shapes and are not interchangeable.

rust
// 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" }

Reach for flat_price when a tool has no parameter sensitivity and the manifest should record that; use per_invocation_price when the price is a fixed per-call unit. A caller reading the manifest must branch on pricing_model and read the amount from the correct field.


Where Pricing Is Authored

Priced-tool authoring lives on NativeTool in chio-mcp-adapter, shown above, and nowhere else. No other SDK carries a pricing builder: a search for the word across the .NET, Swift, JVM, Kubernetes, Lambda and guard SDKs returns nothing, and the TypeScript, Python and Go SDKs carry pricing only on the read side. @chio-protocol/sdk exports the PricingModel and ToolPricing types for verifying a fetched manifest (sdks/typescript/chio-ts/src/invariants/manifest.ts:10-17), the Python chio-sdk package keeps them as private validation sets (sdks/python/chio-py/src/chio/invariants/manifest.py:16,36), and the Go SDK does the same (sdks/go/chio-go/invariants/manifest.go:18-23).

A tool server written in another language still advertises pricing the same way: it serves the signed ToolManifest with the pricing block described above, authored with the native builder. Callers in every language read that block from a fetched manifest (see Advertising Price to Callers); they do not author it.


Budget Planning from a Quote

Translate the quote into a capability-grant budget. The planning rules depend on the pricing model:

ModelPer-call capTotal budget
flatFlat quoteflat * allowed_invocations + margin
per_invocationQuoted unit priceunit_price * allowed_invocations + margin
per_unitConservative per-call estimate from expected unit ceilingper_call_estimate * allowed_invocations + margin
hybridbase + unit * expected_units_per_callper_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:

bash
expected_total = 40 * 25 = 1000
safety_margin  = 200
grant_total    = 1200
per_call_cap   = 25

The corresponding capability grant:

grant.rsrust
use chio_core::capability::scope::{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

The manifest quote tells the authority what budget to issue. The grant budget tells the kernel what to enforce. Do not collapse those concepts. A quoted price with no matching grant budget is unenforceable.

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:

  1. Invocation count. Does the grant have any max_invocations left?
  2. Per-invocation cap. Is this call's planned cost at or below max_cost_per_invocation?
  3. Total budget. Does the grant have at least the planned cost remaining under max_total_cost?

The dispatch path calls authorize_budget_hold(crates/kernel/chio-kernel/src/kernel/validation.rs:1650), whose default implementation delegates to try_charge_cost_with_ids_and_authority(crates/kernel/chio-kernel/src/budget_store.rs:910-933). 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 kernel never reads manifest pricing

ToolPricing is discovery data. Its only consumers in the workspace are the native builder, the manifest crate, and the manifest types (crates/protocol/chio-mcp-adapter/src/native.rs, crates/platform/chio-manifest/src/lib.rs, validation.rs, crates/core/chio-core-types/src/manifest.rs); nothing in the kernel or the economy crates reads it. The cost the kernel authorizes comes from the tool server's reported ToolInvocationCost(crates/kernel/chio-kernel/src/runtime.rs:386-394) or from metered.quote.quoted_cost on a governed intent (crates/kernel/chio-kernel/src/kernel/validation.rs:2698-2713). Translating a published price into a planned cost is the issuing authority's job, which is what makes the manifest advisory and the grant enforcing.

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:

FieldMeaning
settlement_modemust_prepay, hold_capture, or allow_then_settle
quote.quote_idStable identifier from the metering or billing system
quote.providerBilling authority that issued the quote
quote.billing_unitUnit name (invocation, 1k_tokens, ...)
quote.quoted_unitsEstimated billable units
quote.quoted_costEstimated monetary amount
quote.issued_at / expires_atQuote validity window
max_billed_unitsExplicit upper bound for the governed request

With metered_billing, the pre-execution quote and settlement configuration 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 is immutable. Reports can show the quote, kernel-charged cost, and external metering separately.


Cross-Currency Transactions

Tools may quote in one currency while agents hold budgets in another. Chio handles cross-currency settlement through an OracleConversionEvidence record on the receipt's metadata.financial.oracle_evidence field. A Chainlink feed (or other policy-configured oracle) provides the conversion rate at dispatch time; the kernel records the rate as an integer rate_numerator / rate_denominator pair, along with the feed source, address, and timestamp, so an auditor can reconstruct the math. The Settlement guide's Oracle Price Verification section documents the same record from the resolver side.

metadata.financial with oracle_evidence (abridged)json
{
  "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 two string fields are not free text. authority is always the constant chio_link_runtime_v1, and a receipt carrying anything else is rejected (crates/economy/chio-web3/src/anchors.rs:25,174-179). source is the backend label: chainlink or pyth, each of which gains a :degraded suffix when resolution relaxes its ceilings (crates/economy/chio-link/src/lib.rs:140-150,877). A Solidity method name such as AggregatorV3Interface.latestRoundData belongs in prose about how the rate was read, not in this field. The block is abridged: a real receipt also carries root_budget_holder, a bare 64-character lowercase hex issuer key.

Check the arithmetic yourself, because the kernel does. The conversion rule is original_cost_units x rate_numerator x quote_minor_units, divided by base_minor_units x rate_denominator, rounded up (crates/economy/chio-web3/src/anchors.rs:283-309), with the minor-unit table at :311-322: 100 for USD, EUR and GBP, 1 for JPY, 1,000,000 for USDC and USDT. Here that is 228 x 1096 x 100 / (1 x 100000) = 249.888, rounded up to 250. A converted_cost_units that does not equal the recomputed value is refused with oracle conversion evidence converted_cost_units must equal (anchors.rs:210-215), so an FX receipt whose numbers do not reconcile never verifies.

Use one currency unless you need FX

Multi-currency deployments are more complex than single-currency ones. Keep the grant currency and tool-pricing currency identical unless the capability needs a conversion. This avoids conversion and reconciliation work.

Changing Prices

A tool's manifest is signed. Changing the price of an existing tool requires:

  1. Updating the pricing fields on the ToolDefinition.
  2. Re-signing the manifest with the server's key.
  3. Publishing the new manifest version (with an updated version and published_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

A price increase on a tool will cause existing grants to start denying at dispatch once the per-call cost exceeds the grant's 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 fetches the signed manifest during tool discovery and inspects the pricing block. Once parsed, the block is plain structured data in any language:

plan-delegation.tstypescript
// `signedManifest` is what verifySignedManifest accepted. Pricing hangs off
// each tool, not off the manifest: a top-level "pricing" key fails the
// structure check outright.
const tool = signedManifest.manifest.tools.find((entry) => entry.name === "greet");
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.`);
}
plan_delegation.pypython
# `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"] == "greet")
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.")

The nesting matters more than it looks. MANIFEST_FIELD_SET excludes pricing while TOOL_FIELD_SET includes it (sdks/typescript/chio-ts/src/invariants/manifest.ts:71-90), so a manifest carrying a top-level pricing key comes back with structure_valid: false rather than merely reading as empty.


Where the Budget Is Set

A tool publishes its price; an issuer grants a budget. The policy authoring path, YAML and HushSpec alike, governs what an agent may call rather than what the call costs, so the monetary step lives in capability-issuance or authority code:

  1. Publish pricing in the manifest.
  2. Read the manifest in operator or authority code.
  3. Issue a budgeted capability explicitly.

Keeping the two apart means a price change never silently widens a grant: the budget an agent holds is the one an issuer signed for it.


Verify the result

A published price is only real once it lands on a receipt as a charged cost. The shortest way to see that end to end without standing up a priced server is chio mcp governed-sim, which drives one governed prepaid call through an ephemeral kernel and writes the signed bundle:

budget-receipts · allowtranscript
$ chio --session-db ./s.sqlite mcp governed-sim \
    --governed-mustprepay --payment-adapter sim --out ./bundle.json
$ jq '.metadata.financial' ./bundle.json
{
  "budget_remaining": 900,
  "budget_total": 1000,
  "cost_breakdown": {
    "payment": {
      "adapter_metadata": {
        "adapter": "sim",
        "commerce": null,
        "governed": {
          "approvalTokenId": "governed-sim-approval-1",
          "intentHash": "f57e0f00b78fe5267f81f92696a0fe9a32339cb02d57b81c9e15f59996f3062d",
          "intentId": "governed-sim-intent-1",
          "purpose": "no-key CI smoke",
          "serverId": "governed-sim-srv",
          "toolName": "compute"
        },
        "mode": "prepaid_no_broadcast"
      },
      "authorization_id": "sim-268afdc9129342029708eb795c87da08",
      "preauthorized_units": 100,
      "recorded_units": 100
    }
  },
  "cost_charged": 100,
  "currency": "USD",
  "delegation_depth": 0,
  "grant_index": 0,
  "payment_reference": "sim-268afdc9129342029708eb795c87da08",
  "root_budget_holder": "0473a15a01ca05a11b0558cd9fd1edd8bddd26f3f598f1c168aaec97dd7f031b",
  "settlement_status": "settled"
}
exit 0
A quoted 100 minor units authorized, charged and reconciled. preauthorized_units and recorded_units agree, so nothing was released, and payment_reference is the adapter authorization id.
sourcecrates/core/chio-core-types/src/receipt/economics.rs:77-106at fe56570

Read cost_charged against the price you published. If they disagree, the tool server is reporting a cost that its manifest does not justify, and the receipt is the record that says so.


Failures and recovery

The price rose past a grant's cap

Existing grants start denying at dispatch once the per-call cost exceeds max_cost_per_invocation. That is the design: a price change never silently widens a budget somebody already signed for. Re-issue the affected grants with a ceiling that covers the new price, and watch for a spike in monetary denies after any price change.

The call is unpayable

A governed intent that mandates prepayment with no adapter configured is refused before the tool runs, and the refusal is durable rather than merely returned:

budget-receipts · denytranscript
$ chio --session-db ./s2.sqlite mcp governed-sim \
    --governed-mustprepay --payment-adapter none --out ./bundle-deny.json
exit 1
A prepaid intent with no payment adapter. The nonzero exit is the visible half; the signed deny receipt written to the bundle is the durable half.
sourcecrates/kernel/chio-kernel/src/kernel/responses/deny_responses.rs:31-90at fe56570

The FX receipt does not reconcile

A cross-currency receipt whose converted_cost_units does not equal the recomputed value is refused at verification, not at dispatch, so the symptom is a proof that will not verify rather than a call that will not run. Recompute the conversion by hand before trusting a hand-authored fixture, and check the minor-unit column: a figure that looks 100 times too small is almost always a dollar amount written into a cents field.

The manifest will not sign

sign_manifest re-validates before signing, so a pricing block missing its required field never reaches a caller: Flat needs base_price, PerInvocation and PerUnit need unit_price and billing_unit, and Hybrid needs all three (crates/platform/chio-manifest/src/validation.rs:87-100). The builder methods set these for you; a hand-written manifest is where the omission happens.


Safety Notes

  • Keep pricing currency and grant currency identical unless the capability genuinely needs a conversion. Cross-currency resolution works, and it is one more thing that can fail closed: with no oracle configured the kernel denies rather than guessing a rate (crates/kernel/chio-kernel/src/kernel/validation.rs:2437-2465).
  • Size max_total_cost with HA overrun headroom in clustered deployments. Replicas do coordinate: the bound is max_cost_per_invocation x node_count, the window in which each node may approve one invocation at the cap before the merge propagates (crates/kernel/chio-kernel/src/budget_store.rs:636-641).
  • Require DPoP on spend-bearing grants so quoted authority stays bound to the intended subject.
  • Use manifest pricing as operator input. Receipts and the metered sidecar record billed usage.
  • When a tool may exceed the advisory quote, set the grant from the worst-case amount you are willing to authorize, not from the optimistic quote.

  • 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.