Chio/Docs
LOGIN · JOIN

Add API Middleware

Add Chio middleware to a FastAPI, Express, Go, Spring Boot, or ASP.NET Core service so the kernel decides each request first.

Guide chio@5f63e6e

On this page

Chio's framework middleware asks the kernel about each request before your route handler runs. A refused request returns 403 from inside your service; an admitted one continues with its receipt id attached.

The middleware lives in your application and sends each request to a local Chio evaluator over HTTP. Larkfield's pricing service in FastAPI comes first: refuse a quote, authorize it, and read the three decisions. Then wire the same middleware into your own framework.

Start

The middleware holds each request until the kernel answers

The middleware sends the method, route, headers, and a hash of the body to the evaluator. The evaluator's kernel checks the capability and policy, signs the decision, and the middleware verifies that receipt before it lets the request continue.

Framework middlewareChoose an outcome.

κcapabilityσscopegguardsBbudget

Admission runs before your handler

The middleware sends the method, route, headers, and body hash to the kernel before the handler runs. In Python and Node it calls a separate evaluator and verifies the decision receipt; the Rust tower layer embeds the evaluator. An allow continues to the handler, which validates and saves the note. HTTP Framework Middleware.

One write through framework middleware to the kernel and the notes handler. The decision receipt records admission before the handler runs; the handler’s validation and storage are left out.HTTP Framework Middleware

Python, TypeScript, Go, JVM, and .NET middleware all call a separate evaluator. The Rust chio-tower layer embeds the evaluator in your process instead; HTTP Framework Middleware runs that path.

1. Start the governed FastAPI service

Starts from chio new pricing-python --example pricing-python, the project Python HTTP Frameworks walks through. Every terminal below starts in its directory.

Terminal 1 · the FastAPI servicebash
chio new pricing-python --example pricing-python && cd pricing-python
uv sync
uv run uvicorn fastapi_app:app --host 127.0.0.1 --port 8091

The service listens on port 8091. Its ASGI middleware sends requests to the evaluator on port 9098 and excludes /healthz.

Before you begin

You need the chio CLI (curl -fsSL https://chio.computer/install.sh | sh), Python 3.11 or newer, uv, and jq.

2. Start the evaluator beside it

In a second terminal, start chio api protect on port 9098. The middleware calls it for each decision.

Terminal 2 · the evaluatorbash
chio --authority-seed-file .pricing/authority.hex api protect \
  --upstream http://127.0.0.1:8091 \
  --spec openapi.yaml --listen 127.0.0.1:9098 \
  --receipt-store .pricing/receipts.db

Chio creates the signing seed and receipt store and reuses them on restart. Send application requests to port 8091, where the middleware intercepts them; port 9098 hosts the evaluator and the local operator endpoints.

3. Read, then watch a quote stop

In a third terminal, check the service's health and read its quotes.

Terminal 3 · health bypasses the evaluatorbash
curl -sS -i http://127.0.0.1:8091/healthz
Output
HTTP/1.1 200 OK
content-type: application/json

{"status":"ready","framework":"fastapi"}
Terminal 3 · a read with its HTTP receipt IDbash
curl -sS -i http://127.0.0.1:8091/v1/quotes
Output
HTTP/1.1 200 OK
content-type: application/json
x-chio-receipt: 01a12ef0-d1a4-7c3e-9b7d-5f0c2a61e8b4

{"quotes":[]}

You should see the health check answer directly and the read carry an x-chio-receipt header. Now ask for a quote. POST /v1/quote has side effects, so the middleware refuses it before the quote handler runs.

Terminal 3 · the quote is refusedbash
curl -sS -i http://127.0.0.1:8091/v1/quote \
  -H 'Content-Type: application/json' -d '{"sku":"sku-301","percent":80}'
Output
HTTP/1.1 403 Forbidden
content-type: application/json

{"error":"CapabilityGuard","message":"side-effect route requires a capability token","status":403}

4. Grant a capability and repeat the write

As the local operator, issue the five-minute grant http-grant.json describes for Basalt's buying agent, and keep the capability in this directory.

Terminal 3 · issue the grantbash
curl -sS http://127.0.0.1:9098/v1/capabilities/mint \
  -H 'Content-Type: application/json' --data-binary @http-grant.json \
  | jq .capability > capability.json

The grant covers the evaluator's HTTP operation on any route, with no count of quotes. Keep this operator endpoint on loopback; a shared deployment puts it behind its own authenticated control boundary. Send the quote again with the capability.

Terminal 3 · the authorized quote reaches FastAPIbash
curl -sS -i http://127.0.0.1:8091/v1/quote \
  -H 'Content-Type: application/json' \
  -H "X-Chio-Capability: $(jq -c . capability.json)" \
  -d '{"sku":"sku-301","percent":80}'
Output
HTTP/1.1 201 Created
content-type: application/json
x-chio-receipt: 01a12ef0-d2f7-7a10-8c44-0e93b5d17a62

{"id":1,"sku":"sku-301","percent":80.0,"price":"28.00","currency":"USD"}

You should see 201 with USD 28.00 and a new receipt id in x-chio-receipt.

5. Read the three decisions

Read the evaluator's store. On a fresh store these requests produce three receipts: the allowed read, the refused quote, and the authorized quote. The health check produces none.

Terminal 3 · decisions from this servicebash
python3 receipts.py
Output
01a12ef0-d1a4-7c3e-9b7d-5f0c2a61e8b4  ALLOW      GET    /v1/quotes  HTTP 200
01a12ef0-d22b-7f06-a1c5-7e48d93b20aa  DENY       POST   /v1/quote  HTTP 403
01a12ef0-d2f7-7a10-8c44-0e93b5d17a62  ALLOW      POST   /v1/quote  HTTP 200

You should see each decision with its route. The authorized quote's row shows HTTP 200 although FastAPI answered 201: the evaluator signs before the handler runs, so the receipt records the admission decision. Pass a receipt id to receipts.py to print the complete signed record; HTTP receipt lookup explains how the HTTP receipt links to its kernel record.

Your state is .pricing/receipts.db, .pricing/authority.hex, and capability.json. Stop both services with Ctrl-C when you finish, and keep the signing seed private. python3 run.py check runs an isolated copy of this flow and shuts down its services.

6. Wire the middleware into your own service

The pricing service runs in five more frameworks against one contract: GET /healthz outside Chio, GET /v1/quotes allowed with a receipt, and POST /v1/quote refused without a capability. Each delegates evaluation to a local evaluator. Add the interception point for your framework.

express.mjs · Expressfastapi_app.py · FastAPImain.go · in mainPricingApplication.kt · Spring BootProgram.cs · ASP.NET Core
const app = express();
app.use(chio({ sidecarUrl: process.env.CHIO_SIDECAR_URL ?? 'http://127.0.0.1:9099', skip: ['/healthz'] }));
app.use(express.json());
// ...routes...
app.use(chioErrorHandler);

You should see your framework's health route answer directly and a quote without a capability return 403. Set the sidecar URL explicitly or supply CHIO_SIDECAR_URL, and give the evaluator your OpenAPI document when you start it. The complete services, with their dependencies and launchers, are on the Node, Python, Go and C++, and JVM and .NET pages.

Middleware and the gateway refuse the same requests in different places

In both modes a refused request never reaches your business logic: the middleware returns 403 with the same structured ChioErrorResponse body the gateway emits. The difference is where the interception point lives and what you deploy.

Aspectchio api protect gatewayFramework middleware
Deploy shapeSelf-contained process in front of your serviceOne package in your service
Extra hopThe proxy forwards to the upstreamAn evaluation call to the evaluator; none for embedded Rust
LanguageAny service that speaks HTTPAn SDK for your framework
Policy sourceOpenAPI plus x-chio-* extensionsThe same policy through the evaluator, plus route decorators
Best forServices you cannot modify; edge gatewaysServices that can take a Chio dependency

Importing middleware keeps the evaluator and its network hop, except on the Rust path. Protect an API runs the gateway mode end to end.

Middleware packages and receipts

Where the receipt lands

Each adapter exposes the receipt through a response header or the framework's request object.

PackageEntry pointReceipt access
chio-asgi
ChioASGIMiddleware
Response header named by receipt_header, default X-Chio-Receipt
chio-django
ChioDjangoMiddleware
request.chio_receipt, a dict whose id is the receipt id
@chio-protocol/express
chio(), chioErrorHandler
req.chioResult.receipt; narrow the tagged verdict with isAllowed()
@chio-protocol/fastify
fastify.register(chio, ...)
request.chioResult, the EvaluateResponse; caller identity is the receipt's caller_identity_hash
chio-go-http
chio.Protect
Response header X-Chio-Receipt-Id
chio-spring-boot
ChioFilter
Response header X-Chio-Receipt-Id; configure under the chio prefix in application.yaml
Backbay.Chio.Middleware
AddChioProtection(), UseChioProtection()
Response header X-Chio-Receipt-Id; namespace Backbay.Chio

chio.Protect wraps any http.Handler, so chi, gorilla/mux, Echo, and Gin wire through their adapters. Spring Boot uses the world.chio:chio-spring-boot filter artifact; the JVM client lives in world.chio:chio-sdk-jvm.

Per-route enforcement in FastAPI

The ASGI middleware governs the whole app. chio-fastapi adds decorators and dependency injection for a route that names the capability it needs: chio_requires declares the grant, and get_caller_identity and get_chio_receipt hand the handler what the evaluator computed. The excerpt assumes your application defines run_search and installs the middleware that supplies the evaluated request context.

sdks/python/chio-fastapi/README.md:21-30python
from fastapi import FastAPI, Request
from chio_fastapi import chio_requires

app = FastAPI()


@app.post("/search")
@chio_requires(server_id="search-srv", tool_name="search_documents")
async def search(request: Request, query: str) -> dict:
    return {"results": run_search(query)}

A request with no capability gets HTTP 401 and the body error code CHIO_CAPABILITY_REQUIRED, asserted in sdks/python/chio-fastapi/tests/test_decorators.py. Stack chio_approval above chio_requires to hold a write above a monetary threshold for an approval token that a person issues.

Other frameworks

  • TypeScript. @chio-protocol/fastify, @chio-protocol/elysia, and @chio-protocol/node-http, the framework-agnostic base the other three build on.
  • Python. chio-django; chio-asgi for Starlette, Litestar, and other ASGI frameworks; chio-fastapi for per-route decorators.
  • C++. chio-drogon, linked as a CMake subdirectory and attached per route as chio::drogon::ChioMiddleware.
  • Rust. chio-tower ships a tower::Layer, so axum and anything built on tower::Service wires the same way, with the evaluator in your process.

Each SDK reference covers its full API: TypeScript, Python, Go, JVM, .NET, and Rust.

Install the middleware

Each middleware installs from its language registry: go get github.com/backbay-labs/chio/sdks/go/chio-go-http, implementation("world.chio:chio-spring-boot:0.1.0") in build.gradle.kts, and dotnet add package Backbay.Chio.Middleware. Install an SDK lists every package.

Next: verify the HTTP receipt