ReferenceSDKs
Python SDK
Two pure-Python distributions for Python >=3.11: one talks to a colocated sidecar kernel, the other to a hosted MCP edge.
Source
This page reflects two directories in the Chio source at the pinned commit: sdks/python/chio-sdk-python/ (the module chio_sdk, its client.py, cognition_market.py, finding.py, errors.py, models_approvals.py, and testing.py) and sdks/python/chio-py/ (the module chio, its client.py, session.py, invariants/, receipt_query.py, and auth.py). Distribution names, module names, versions, the Python floor, and the declared dependencies render from the sdks dataset, which reads every pyproject.toml under sdks/. The worked application is examples/hello-fastapi. A Python package states what it states; the normative protocol text lives in the Protocol Reference.
Synopsis
One install line and one entry-point import per distribution.
$ pip install chio-sdk-python==0.1.0
$ pip install chio-sdk==0.1.0from chio_sdk import ChioClient # async, colocated sidecar kernel
from chio import ChioClient # synchronous, hosted MCP edgeThe two distributions
The distribution name and the module name differ, and they differ in opposite directions between the two packages. Read this table before you type an install line.
| Install | Import | Version | Dependencies | Talks to |
|---|---|---|---|---|
pip install chio-sdk-python | import chio_sdk | 0.1.0 | httpx, pydantic | A colocated sidecar kernel over localhost HTTP, default http://127.0.0.1:9090. Async. The FastAPI, Django, LangChain, and Temporal companions build on it. |
pip install chio-sdk | import chio | 0.1.0 | httpx, pure25519 | A hosted chio MCP edge over Streamable HTTP: sessions, tools, OAuth, and receipt queries. Synchronous. |
There is no pip install chio
chio and the distribution that ships it is called chio-sdk. The name chio on PyPI belongs to an unrelated project, so installing it gets you someone else's library.Sidecar client (chio_sdk)
chio_sdk is an async HTTP client to the sidecar kernel plus the typed models for capabilities, scopes, verdicts, receipts, and approvals. It signs and evaluates nothing itself: it forwards to the sidecar and returns typed results. Its declared dependencies are httpx, pydantic.
$ pip install chio-sdk-python
$ uv pip install chio-sdk-pythonfrom chio_sdk import ChioClient
from chio_sdk.errors import ChioDeniedError
async def main() -> None:
# Defaults to the local sidecar at http://127.0.0.1:9090.
async with ChioClient() as client:
await client.health()
advisory = await client.evaluate_tool_call_advisory(
capability_id="cap-123",
tool_server="search-srv",
tool_name="search_documents",
parameters={"query": "capability-based security"},
)
print(f"advisory receipt: {advisory.id}")
try:
await client.evaluate_tool_call(
capability_id="cap-123",
tool_server="search-srv",
tool_name="search_documents",
parameters={"query": "capability-based security"},
)
except ChioDeniedError as error:
print(f"not authorized: {error}")evaluate_tool_call always raises
evaluate_tool_call runs advisory evaluation for the audit trail and then raises ChioDeniedError every time. It returns no receipt. Use evaluate_tool_call_advisory for a non-authoritative observation, or evaluate_tool_call_mediated with a full signed token for authoritative enforcement. Wrappers gate on the raise.chio_sdk.ChioClient
The constructor takes a base URL (default http://127.0.0.1:9090) and a keyword-only timeout in seconds, default 5. The class is an async context manager, and close() shuts the underlying httpx.AsyncClient down. Every method is a coroutine except the static collect_evidence.
| Method | Route | Returns and behavior |
|---|---|---|
health() | GET /chio/health | The raw health document. |
create_capability(*, subject, scope, ttl_seconds=3600) | POST /v1/capabilities | CapabilityToken bound to the hex Ed25519 subject key. |
validate_capability(token) | POST /v1/capabilities/validate | bool, from the response's valid field. |
attenuate_capability(token, *, new_scope) | POST /v1/capabilities/attenuate | Checks the subset locally, posts, then raises ChioDeniedError with reason code chio_attenuation_requires_subject_signer. Minting a child token needs the parent subject signer, which the sidecar must not hold, so the route fails closed. |
verify_receipt(receipt) | POST /v1/receipts/verify | bool, true only when the structured VerifyReceiptResponse authorizes the presented receipt. A bare {"valid": true} is not authoritative. |
verify_http_receipt(receipt) | POST /chio/verify | The whole VerifyReceiptResponse for an HttpReceipt. |
verify_receipt_chain(receipts) | local | bool: each receipt's content_hash is the SHA-256 of the canonical JSON of the one before it. A list shorter than two is true. |
evaluate_tool_call(...) | POST /v1/evaluate/advisory | Records the advisory evaluation, then always raises ChioDeniedError. |
evaluate_tool_call_advisory(...) | POST /v1/evaluate/advisory | A non-authoritative ChioReceipt for the audit trail. |
evaluate_tool_call_mediated(...) | POST /v1/evaluate | Authoritative enforcement against a full signed capability token, with optional governed intent, approval token, and dpop_proof. |
reconcile_mediated_authorization(*, control_token, execution_nonce, arguments, realized_cost) | POST /v1/reconcile | Called by the trusted tool server, not the agent. Sends control_token as a bearer header, settles the reserved hold at the lesser of realized and reserved cost, frees the difference, and returns {"status", "receipt"}. The nonce is single use. |
evaluate_http_request(...) | POST /chio/evaluate | EvaluateResponse. A capability token travels on the X-Chio-Capability header. This is the route every ASGI and framework companion calls. |
list_pending_approvals() | GET /approvals/pending | list[PendingApproval]. The response count is dropped; a bare array is tolerated. |
get_approval(approval_id) | GET /approvals/{id} | Approval, carrying either pending or resolution. An empty id raises ChioValidationError. |
respond_approval(approval_id, verdict, reason=None) | POST /approvals/{id}/operator-respond | ApprovalResponse. The sidecar signs a GovernedApprovalToken with its own keypair. Use the signed /respond route directly when an external approver keypair is required. |
submit_for_approval(*, capability_id, tool_name, tool_args, ...) | POST /approvals/submit | The new approval_id. The parameter hash comes from the canonical JSON of tool_args, so a later response binds to the exact arguments proposed. |
collect_evidence(receipts) | local, static | list[GuardEvidence], flattened from every receipt's evidence. |
The approvals channel has its own models: PendingApproval (the held call, its policy, subject, capability, tool, parameter hash, expiry, and the triggers that held it), ResolvedApproval (outcome, resolution time, approver key, token id), Approval (one or the other), PendingApprovalList, ApprovalResponse, and SubmitApprovalResult. The verdict enum ApprovalVerdict has the wire values approved and denied, and ApprovalVerdict.from_action accepts the shorthands approve, allow, deny, and reject.
Testing without a sidecar
chio_sdk.testing ships a drop-in MockChioClient with allow_all(), deny_all(), and with_policy(...), so capability-checked code paths run with no sidecar. It records calls as RecordedCall and takes a MockVerdict.
async def test_allow_all_permits_every_tool_call() -> None:
async with allow_all() as chio:
receipt = await chio.evaluate_tool_call(
capability_id="cap-1",
tool_server="srv",
tool_name="read",
parameters={"path": "/tmp"},
)
assert receipt.is_allowed
assert receipt.decision.verdict == "allow"
assert receipt.tool_name == "read"Protecting a FastAPI application
chio-asgi puts the sidecar in front of any ASGI framework. It evaluates every request before it reaches the application, returns the receipt id on X-Chio-Receipt, and denies rather than passes when the sidecar is unreachable.
from chio_asgi import ChioASGIMiddleware, ChioASGIConfig
# Starlette / FastAPI
app.add_middleware(
ChioASGIMiddleware,
config=ChioASGIConfig(sidecar_url="http://127.0.0.1:9090"),
)The worked application is examples/hello-fastapi. It excludes /healthz from evaluation, allows GET /hello, and requires a capability on POST /echo.
def build_chio_config(sidecar_url: str | None = None) -> ChioASGIConfig:
return ChioASGIConfig(
sidecar_url=sidecar_url
or os.environ.get("CHIO_SIDECAR_URL", "http://127.0.0.1:9090"),
exclude_paths=frozenset({"/healthz"}),
)Running examples/run-hello-smokes.sh hello-fastapi starts the trust control plane, the application, and a chio api protect sidecar, then walks the three routes. On a refusal the middleware sends a JSON body of error (the guard name, or ChioGuard when the verdict names none), message (the verdict reason, or denied), and status, with the receipt id on the configured receipt header. The status is the verdict's own HTTP status when it carries one and 403 otherwise.
$ cat hello.headers hello.jsonHTTP/1.1 200 OK
date: Sat, 05 Sep 2026 11:49:07 GMT
server: uvicorn
content-length: 32
content-type: application/json
x-chio-receipt: 1dba0f38326d8eaba47d2b8e50a54fdeb0da056639cbaa20fca0100e0dc67f64
{"message":"hello from fastapi"}examples/hello-fastapi/smoke.shat fe56570$ cat deny.headers deny.jsonHTTP/1.1 403 Forbidden
date: Sat, 05 Sep 2026 11:49:08 GMT
server: uvicorn
content-type: application/json
content-length: 103
x-chio-receipt: 5d31d1463fe3819b3a5ffae0fb89e01c92339c3e4da455e6701511509690aaa3
{"error": "CapabilityGuard", "message": "side-effect route requires a capability token", "status": 403}examples/hello-fastapi/smoke.shat fe56570Cognition market
chio_sdk.cognition_market carries the Finding market clients, and chio_sdk.finding the buyer-local bid ceiling. Both are re-exported from chio_sdk. Verification is not reimplemented in Python: the buyer shells out to the chio binary and parses its JSON, so the Rust verifier stays the only judge of a bundle. Each client is an async context manager over its own httpx.AsyncClient.
CognitionMarketBuyer
Constructed from a buyer profile path plus keyword-only chio_binary, status_floor_path, timeout (default 30 seconds), and transport. The profile must carry the schema chio.finding.buyer-client.v1, and the bearer token on it goes on every request. The status floor defaults to the profile path with .status-floor.json appended.
| Method | Behavior |
|---|---|
search(*, topic_prefix, limit=20, cursor=None) | GET /v1/findings/search. An empty or untrimmed prefix raises before the request. |
proof(finding_id) | GET /v1/findings/{id}/proof, returning the bundle bytes. The id must be lowercase 64-hex, and the response is bounded at 24 MiB. |
verify_proof(proof) | Pipes the bundle into chio finding verify-bundle and returns a VerifiedFindingProof: the Finding id, the bytes, and the verifier report. |
verified_proof(finding_id) | Fetch then verify, refusing a bundle that names a different Finding. |
purchase(verified, *, max_price_units, currency="USD", deadline_secs=3600) | POST /v1/findings/{id}/purchase with the canonical request, verifying the terminal before returning it. |
purchase_verified_fix(verified, *, max_price_units, ...) | The same purchase, returning a PurchasedVerifiedFix: repository, base and candidate revisions, and the patch text. Nothing is written to a workspace. |
status(finding_id) | Runs chio finding status against the profile's feed, operator authorization, service bond, rollback floor, and epoch age bound, then maps non_inclusion to live and inclusion to retracted. |
challenge(finding_id, signed_challenge) | POST /v1/findings/{id}/challenges with caller-signed bytes, bounded at 1 MiB. |
challenge_evidence_invalid(verified, purchased, *, filed_at=None) | Re-verifies the purchase terminal, builds the evidence document, and files it with chio finding challenge --class evidence-invalid. |
from chio_sdk import (
CognitionMarketBuyer,
CognitionMarketError,
PurchasedVerifiedFix,
VerifiedFindingProof,
)
async def buy(finding_id: str) -> PurchasedVerifiedFix:
async with CognitionMarketBuyer("./buyer-profile.json") as buyer:
verified: VerifiedFindingProof = await buyer.verified_proof(finding_id)
report = await buyer.status(finding_id)
if report["status"] != "live":
raise CognitionMarketError("finding is retracted")
return await buyer.purchase_verified_fix(
verified,
max_price_units=300,
currency="USD",
)CognitionMarketSeller
Constructed from a credential path carrying the schema chio.finding.seller-client.v1. The default deadline is 720 seconds, sized for the operator's bounded packaging sandbox.
package_verified_fix(*, repository, base, candidate, tests, topic, price=300, output=None)builds the submission identity and derives itsrequestIdas SHA-256 over the domain-separated canonical JSON. The price must fall between 1 and the verified-fix sale exposure of 450 units, the repository must be an absolute normalized operator-side path, and a caller-suppliedoutputis refused because the package is operator-owned.admit(package)posts toPOST /v1/findings/operator/verified-fixes.retract(finding_id)postschio.finding.voluntary-retraction-request.v1toPOST /v1/findings/operator/retractions, with a request id derived from the retraction domain and the Finding id.
HostedCognitionMarketClient
The multi-tenant transport. The constructor is positional in endpoint, tenant_id, api_key_id, and api_key_secret, with keyword-only timeout and transport. The endpoint must be an https: URL with no embedded credentials.
publish(finding, request_id, idempotency_key):POST /v1/findings/publish.mutate(operation, mutation, request_id):POST /v1/findings/events/{operation}, where the operation is one oflisting,delivery,challenge,verified-fix,retraction, andpenalty. The mutation accepts onlyaggregateId,eventId,expectedRevision,expectedEventSha256,artifactSignerKey, andpayload; any other key is refused, and the event id doubles as the idempotency key.finding(finding_id, request_id):GET /v1/findings/{id}.findings(request_id, *, after=None, limit=None):GET /v1/findings, with a limit from 1 through 100.
Every failure on all three clients raises CognitionMarketError, a RuntimeError subclass outside the ChioError tree: a non-2xx response, an oversized body, a timeout, a profile whose schema does not match, and a verifier answer that names a different Finding.
finding_bid_ceiling
finding_bid_ceiling computes a buyer-local ceiling with exact integer arithmetic and returns it as a decimal string. It authenticates nothing. What it does is bind one caller-carried estimate to the buyer's own expected source, context, and replay-recipe digests, to the buyer's currency, and to a validity window, then discount the estimate by three basis-point factors and cap the result at the remaining budget. It rounds down once, after the combined product.
from chio_sdk import (
BuyerFindingEstimate,
FindingBidCeilingError,
FindingBidCeilingInput,
FindingBidCeilingPolicy,
finding_bid_ceiling,
)
estimate: BuyerFindingEstimate = {
"units": "1200",
"currency": "USD",
"provenance": "buyer_metering_history_v1",
"sourceSha256": expected_source_sha256,
"contextSha256": expected_context_sha256,
"replayRecipeSha256": expected_replay_recipe_sha256,
"observedAtUnixMs": "1744500000000",
"validUntilUnixMs": "1744503600000",
}
policy: FindingBidCeilingPolicy = {
"budgetRemainingUnits": "5000",
"currency": "USD",
"wouldHaveRunBps": "9000",
"siblingRedundancyBps": "1500",
"guaranteeClassBps": "10000",
}
payload: FindingBidCeilingInput = {
"estimate": estimate,
"policy": policy,
"expectedSourceSha256": expected_source_sha256,
"expectedContextSha256": expected_context_sha256,
"expectedReplayRecipeSha256": expected_replay_recipe_sha256,
"nowUnixMs": "1744501000000",
}
try:
ceiling = finding_bid_ceiling(payload)
except FindingBidCeilingError as error:
print("refused:", error.code)FindingBidCeilingError is a ValueError subclass carrying a code: invalid_decimal, u64_overflow, basis_points_out_of_range, currency_mismatch, provenance_unsupported, source_substituted, context_substituted, replay_recipe_substituted, digest_malformed, invalid_validity_window, or stale_estimate. Only two provenance values are accepted: buyer_metering_history_v1 and buyer_fresh_metered_quote_v1.
Companion distributions
Framework adapters, orchestrator operators, agent-framework bindings, the AWS Lambda runtime, and the WASM guard guest SDK ship as separate distributions so a base install stays small. Each uses a distinct top-level module (for example import chio_fastapi, not chio.fastapi). Every distribution the sdks dataset reads is below, with the version and the dependencies its own manifest declares.
| Distribution | Import | Version | Declared dependencies |
|---|---|---|---|
chio-adapter-base | import chio_adapter_base | 0.2.0 | chio-sdk-python |
chio-airflow | import chio_airflow | 0.1.1 | apache-airflow, chio-adapter-base, chio-sdk-python, pydantic |
chio-asgi | import chio_asgi | 0.1.0 | chio-sdk-python |
chio-autogen | import chio_autogen | 0.1.1 | chio-adapter-base, chio-sdk-python, pyautogen, pydantic |
chio-bedrock | import chio_bedrock | 0.1.0 | boto3, cryptography, pytest |
chio-code-agent | import chio_code_agent | 0.1.0 | chio-sdk-python, pydantic, pyyaml |
chio-crewai | import chio_crewai | 0.1.1 | chio-adapter-base, chio-sdk-python, crewai, pydantic |
chio-dagster | import chio_dagster | 0.1.1 | chio-adapter-base, chio-sdk-python, dagster, pydantic |
chio-django | import chio_django | 0.1.0 | chio-sdk-python, django |
chio-fastapi | import chio_fastapi | 0.1.0 | chio-asgi, chio-sdk-python, fastapi |
chio-guard-py | import chio_guard | 0.2.0 | none |
chio-hermes | import chio_hermes | 0.1.1 | chio-adapter-base, chio-code-agent, chio-sdk-python, pyyaml |
chio-iac | import chio_iac | 0.1.1 | chio-adapter-base, chio-sdk-python, pydantic |
chio-lambda-python | import chio_lambda | 0.1.0 | chio-sdk-python, httpx |
chio-langchain | import chio_langchain | 0.1.1 | chio-adapter-base, chio-sdk-python, langchain-core |
chio-langgraph | import chio_langgraph | 0.1.1 | chio-adapter-base, chio-sdk-python, langgraph, pydantic |
chio-llamaindex | import chio_llamaindex | 0.1.1 | chio-adapter-base, chio-sdk-python, llama-index-core, pydantic |
chio-observability | import chio_observability | 0.1.0 | chio-sdk-python, pydantic |
chio-prefect | import chio_prefect | 0.1.2 | chio-adapter-base, chio-sdk-python, prefect, pydantic |
chio-ray | import chio_ray | 0.1.1 | chio-adapter-base, chio-sdk-python |
chio-streaming | import chio_streaming | 0.2.1 | chio-adapter-base, chio-sdk-python, pydantic |
chio-temporal | import chio_temporal | 0.1.1 | chio-adapter-base, chio-sdk-python, pydantic, temporalio |
The dependency column decides which of these are Chio clients at all. Every companion declares chio-sdk-python except chio-bedrock, chio-guard-py. None of them declares chio-sdk: the hosted MCP client is for a caller that opens a session, not for a server that governs one. chio-adapter-base is the shared adapter base the orchestrator and agent-framework packages build on, and chio-asgi is the one chio-fastapi builds on.
Each companion evaluates the request against a Chio sidecar, attaches the signed receipt, and raises a typed deny error on a policy violation. chio_fastapi exposes route decorators (chio_requires, chio_approval, chio_budget) plus dependency-injection helpers (get_chio_client, get_chio_receipt, get_caller_identity).
from fastapi import FastAPI, Request
from chio_fastapi import chio_requires
app = FastAPI()
@app.post("/tools/deploy")
@chio_requires("deploy-server", "deploy", ["Invoke"])
async def deploy(request: Request):
# request.state.chio_receipt is an HttpReceipt attached on success.
receipt = request.state.chio_receipt
return {"status": "deployed", "receipt_id": receipt.id}Hosted MCP client (chio)
The rest of this page documents pip install chio-sdk, the module chio. Its declared dependencies are httpx, pure25519.
$ pip install chio-sdk
$ uv add chio-sdk
$ poetry add chio-sdkfrom chio import ChioClient, ChioSession, ReceiptQueryClient
from chio.invariants import verify_receipt, verify_capability
from chio.auth import discover_oauth_metadata, perform_authorization_code_flowThe chio package exposes the submodules auth, client, errors, invariants, models, nested, receipt_query, session, transport, and version. Its __all__ re-exports the client and session types (ChioClient, ChioSession, SessionHandshake, TransportResponse, initialize_session, rpc_result), the receipt query types (ReceiptQueryClient, ReceiptQueryParams, ReceiptQueryResponse), the five errors, the OAuth helpers (StaticBearerAuth, static_bearer_auth, authorization_server_metadata_url, discover_oauth_metadata, exchange_access_token, get_json, perform_authorization_code_flow, pkce_challenge, resolve_oauth_access_token), the nested-callback router (NestedCallbackRouter, elicitation_accept_result, roots_list_result, sampling_text_result), parse_json_text, and __version__.
chio.ChioClient
chio.ChioClient opens authenticated chio MCP HTTP sessions. Both distributions export a class called ChioClient, and they are different classes: this one takes a base URL and returns a session, chio_sdk.ChioClient takes a sidecar URL and returns receipts. The with_static_bearer classmethod is the shortest path for development; a deployment constructs the client with an OAuth token source. The base URL is the edge origin: the transport appends /mcp to it on every request. initialize() is keyword-only in capabilities, client_info, on_message, and protocol_version, which defaults to 2025-11-25.
from chio import ChioClient
client = ChioClient.with_static_bearer(
base_url="https://edge.example.com",
auth_token="dev-token",
)
session = client.initialize(
client_info={"name": "my-app", "version": "1.0.0"},
)ChioSession
ChioSession exposes the MCP method set plus lower-level entry points for custom JSON-RPC work. It numbers its own request ids.
list_tools(),call_tool(name, arguments=None)list_resources(),read_resource(uri),subscribe_resource(uri),unsubscribe_resource(uri),list_resource_templates()list_prompts(),get_prompt(name, arguments=None)complete(params)forcompletion/complete, andset_log_level(level)forlogging/setLevellist_tasks(),get_task(task_id),get_task_result(task_id),cancel_task(task_id)request(),request_result(),notification(),send_envelope()close(), which returns the HTTP status of the session delete
Every method here returns the terminal JSON-RPC message rather than the unwrapped result, so the MCP payload is under result. The TypeScript session unwraps it; this one does not.
tools_result = session.list_tools()
for tool in tools_result.get("result", {}).get("tools", []):
print(tool["name"], tool.get("description"))
call = session.call_tool("read_file", {"path": "./README.md"})
print(call.get("result", {}))
session.close()Invariants
chio.invariants holds the pure verification primitives. Every function is synchronous, dependency-light, and safe to call from any thread or async context. This is its whole __all__.
from chio.invariants import (
canonicalize_json,
canonicalize_json_string,
sha256_hex_bytes,
sha256_hex_utf8,
is_valid_public_key_hex,
is_valid_signature_hex,
public_key_hex_matches,
sign_utf8_message_ed25519,
verify_utf8_message_ed25519,
sign_json_string_ed25519,
verify_json_string_signature_ed25519,
verify_chio_signature,
parse_receipt_json,
receipt_body_canonical_json,
receipt_signing_body_canonical_json,
verify_receipt,
verify_receipt_json,
verify_receipt_with_trusted_signers,
parse_capability_json,
capability_body_canonical_json,
capability_signing_body_canonical_json,
verify_capability,
verify_capability_json,
parse_signed_manifest_json,
signed_manifest_body_canonical_json,
verify_signed_manifest,
verify_signed_manifest_json,
)Canonical JSON
from chio.invariants import canonicalize_json_string
canonical = canonicalize_json_string('{"b":2,"a":1}')
assert canonical == '{"a":1,"b":2}'Receipts and capabilities
import time
from chio.invariants import (
parse_capability_json,
parse_receipt_json,
verify_capability,
verify_receipt,
verify_receipt_with_trusted_signers,
)
receipt = parse_receipt_json(json_string)
result = verify_receipt(receipt)
assert result["signature_valid"]
assert result["parameter_hash_valid"]
# Pass the kernel keys you accept when you want an authorization answer.
trusted = verify_receipt_with_trusted_signers(receipt, [kernel_key_hex])
cap = parse_capability_json(json_string)
status = verify_capability(cap, int(time.time()))
assert status["signature_valid"]
assert status["delegation_chain_shape_valid"]
assert status["time_status"] in ("valid", "not_yet_valid", "expired")Ed25519
Ed25519 signing and verification use pure25519, a pure-Python implementation chosen so the distribution installs on a restricted runner (Lambda, App Engine, a serverless container) without a native toolchain. Ed25519 is the one algorithm this build verifies. verify_chio_signature dispatches on the signature prefix and raises ChioInvariantError with code invalid_signature for a p256:, p384:, or hybrid: signature, so a receipt this build cannot check fails closed rather than passing.
sign_json_string_ed25519(input_text, seed_hex) canonicalizes the input first and returns a dict with canonical_json, public_key_hex, and signature_hex.
from chio.invariants import sign_json_string_ed25519, verify_json_string_signature_ed25519
sig = sign_json_string_ed25519('{"key":"value"}', seed_hex)
ok = verify_json_string_signature_ed25519(
'{"key":"value"}',
sig["public_key_hex"],
sig["signature_hex"],
)
assert okReceiptQueryClient
ReceiptQueryClient wraps GET /v1/receipts/query, injects the bearer token, and exposes both a one-shot and an iterator API. ReceiptQueryParams is a total-false TypedDict in camelCase.
from chio import ReceiptQueryClient
client = ReceiptQueryClient(
base_url="https://receipts.example.com",
auth_token="service-token",
)
response = client.query({
"capabilityId": "cap_7f3a",
"agentSubject": agent_public_key_hex,
"toolServer": "srv-files",
"toolName": "read_file",
"outcome": "deny",
"since": 1744500000,
"until": 1744600000,
"minCost": 1,
"maxCost": 1000,
"costCurrency": "USD", # three uppercase letters; required with a cost bound
"limit": 50,
})
print(response["totalCount"], response.get("nextCursor"))Use paginate() to drive the numeric cursor automatically. The wire contract, including the values outcome accepts and the server's pagination limits, is on the Receipt Query API page.
for page in client.paginate({"toolServer": "srv-files"}):
for receipt in page:
# decision is present only on mediated_decision receipts; trace and
# advisory receipts omit it, and it is a tagged union when present.
print(receipt["id"], receipt.get("decision"))OAuth and PKCE
chio.auth ships the helpers that discover a chio edge's OAuth configuration, drive an authorization-code flow with PKCE, and run a token exchange. Both flow helpers are keyword-only and return a bare access-token string. discover_oauth_metadata returns a dict carrying protected_resource_metadata and authorization_server_metadata.
from chio.auth import (
discover_oauth_metadata,
perform_authorization_code_flow,
exchange_access_token,
)
metadata = discover_oauth_metadata("https://edge.example.com")
access_token = perform_authorization_code_flow(
base_url="https://edge.example.com",
auth_scope="chio.read chio.invoke",
authorization_server_metadata=metadata["authorization_server_metadata"],
client_id="chio-cli",
redirect_uri="http://127.0.0.1:53682/callback",
)
exchanged = exchange_access_token(
base_url="https://edge.example.com",
auth_scope="chio.read chio.invoke",
authorization_server_metadata=metadata["authorization_server_metadata"],
access_token=access_token,
)Error types
The two distributions carry two error trees with the same base name and no shared class.
In chio:
ChioError: base SDK exception.ChioTransportError: network or transport-level failure.ChioQueryError: non-2xx response from the receipt query endpoint.ChioRpcError: JSON-RPC error returned by the hosted MCP edge.ChioInvariantError: parsing or verification failure in the invariants layer.
In chio_sdk.errors, a different set. All five fail closed: a denial, a timeout, an unreachable sidecar, or a model that will not validate raises rather than allowing the call through.
ChioError: base SDK exception.ChioConnectionError: the sidecar could not be reached.ChioDeniedError: the kernel refused. This is the expected outcome of an id-onlyevaluate_tool_call.ChioTimeoutError: the sidecar did not answer in time.ChioValidationError: the request or the response failed a model invariant.
CognitionMarketError and FindingBidCeilingError sit outside both trees: the first extends RuntimeError, the second ValueError.
from chio import ChioError, ChioQueryError, ChioTransportError
try:
response = client.query({"capabilityId": "cap_abc"})
except ChioQueryError as err:
print("query failed", err.status, err)
except ChioTransportError as err:
print("network failed", err)
except ChioError as err:
print("chio error", err)Conformance
The Python SDK runs against the cross-language conformance vectors: canonical JSON output, SHA-256 digests, Ed25519 signatures, receipt verification, capability verification, and manifest verification all produce byte-identical results against the Rust reference.
$ cd sdks/python/chio-py && pytest
$ cd sdks/python/chio-sdk-python && pytestTwo worked examples run the two paths. sdks/python/chio-py/examples/governed_hello.py initializes a session against a hosted edge, lists tools, calls one, and queries the receipt store for the resulting governed receipt. examples/hello-fastapi is the sidecar path: a three-route FastAPI application, a policy, and a smoke script that walks an allow and a deny.
Related
- SDK Overview: the same contract in the other five languages, and the platform SDKs.
- Bindings API: the invariants contract both distributions conform to.
- Receipt Query API: the wire contract behind
ReceiptQueryClient. - Receipt Format: the fields
ChioReceiptandHttpReceiptcarry. - Finding Artifacts: the proof bundle, the purchase terminal, and the retraction feed the market clients speak to.
- Python worked applications: FastAPI and Django end to end.