OpenAPI Integration
Chio ingests OpenAPI specifications and enforces capability-based access control over HTTP APIs. This page covers the OpenAPI to manifest pipeline, the x-chio-* extension vocabulary, the default deny-by-method policy, and the chio api protect reverse proxy contract.
Source
This page normatively reflects spec/OPENAPI-INTEGRATION.md in the chio repository. Status: Normative. Version 1.0.
RFC 2119
Pipeline Overview
The chio-openapi crate parses OpenAPI specifications and produces Chio ToolDefinition values conforming to chio.manifest.v1. Each HTTP operation (method + path pair) becomes one tool definition. The output drives both the OpenAPI-to-MCP bridge and the chio api protect reverse proxy.
- Parse the OpenAPI document (JSON or YAML).
- Extract operations from each path entry.
- Resolve
$refpointers and merge parameters. - Read
x-chio-*extensions for per-route policy hints. - Generate
ToolDefinitionvalues and assign default policies.
Supported OpenAPI Versions
The parser MUST accept specifications declaring version 3.0.x or 3.1.x in the top-level openapi field. The value MUST begin with 3..
The parser MUST reject specifications with any other version prefix with an UnsupportedVersion error. OpenAPI 2.0 (Swagger) is not supported.
Supported Formats
The parser MUST accept both JSON and YAML input. Format detection is automatic: if the input (after leading whitespace) begins with {, the parser treats it as JSON. Otherwise, it treats the input as YAML. No explicit format flag is required from the caller.
Required Top-Level Fields
| Field | Error if absent |
|---|---|
openapi | MissingField("openapi") |
info | MissingField("info") |
paths | MissingField("paths") |
When info.title is absent, the parser MUST default to "Untitled API". When info.version is absent, the parser MUST default to "0.0.0".
Operation to Tool Mapping
Route Extraction
For each entry in paths, the parser MUST extract operations for the following HTTP methods, in this order: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. Unrecognized method keys MUST be ignored.
Parameter Extraction
Each parameter MUST have a name and in field. The in field MUST be one of path, query, header, or cookie. Unknown values default to query.
Path parameters are always required, regardless of the required field value. For other locations, if required is absent, the parser defaults to false. Header and cookie parameters are parsed but MUST NOT appear in the generated ToolDefinition input schema. Only path and query parameters are promoted to tool input properties.
Path-level parameters and operation-level parameters MUST be merged before tool generation. If an operation-level parameter has the same name and location as a path-level parameter, the operation level wins.
Request Body Schema
When a requestBody is present, the parser MUST look for a content["application/json"].schema entry first. If absent, the parser MUST fall back to the first available content type. If the schema is a $ref, the parser resolves it before returning.
Response Schema Selection
The output schema for a ToolDefinition is derived from response schemas. The parser MUST prefer the 200 response, then 201, then any 2xx response that includes a schema. If no successful response includes a schema, the output schema is None.
$ref Resolution
The parser MUST resolve JSON Reference pointers that begin with #/. This covers #/components/schemas, #/components/parameters, and other #/components/ namespaces. The parser MUST reject:
- External references (URIs not beginning with
#/) with anUnresolvedReferror. - Internal references that point to nonexistent paths within the document with an
UnresolvedReferror.
ToolDefinition Derivation
| ToolDefinition field | Source |
|---|---|
name | operationId if present; otherwise "{METHOD} {path}" |
description | summary if present, else description, else "{METHOD} {path}" |
input_schema | JSON Schema object built from path + query parameters as properties, plus body property from request body schema |
output_schema | Selected 2xx response schema |
annotations.read_only | true if the operation has no side effects |
annotations.destructive | true if method is DELETE |
annotations.idempotent | true if method is GET, PUT, or DELETE |
annotations.requires_approval | Value of x-chio-approval-required, defaulting to false |
pricing | None (reserved for future use) |
The input_schema MUST be a JSON Schema object with type: "object". Path and query parameters become top-level properties. If a parameter has no explicit schema, the generator defaults to { "type": "string" }. Required parameters appear in the required array. If a request body schema is present, it appears as a required property named "body".
Generator Configuration
| Option | Type | Default | Effect |
|---|---|---|---|
server_id | string | "openapi-server" | Identifier for the generated manifest body |
include_output_schemas | boolean | true | Whether to derive output schemas from response definitions |
respect_publish_flag | boolean | true | Whether to honor x-chio-publish: false |
x-chio-* Extension Vocabulary
Chio defines five vendor extension fields for OpenAPI operation objects. They provide per-route policy hints that the manifest generator and the chio api protect proxy consume. All x-chio-* fields are optional; absent values use the defaults below.
| Extension | Scope | Type | Default | Meaning |
|---|---|---|---|---|
x-chio-sensitivity | operation | enum string | internal | One of public, internal, sensitive, restricted. Metadata classification consumed by the guard pipeline for logging level and audit granularity. Does not change policy directly. |
x-chio-side-effects | operation | boolean | method-driven | Overrides the method-based side-effect default. true forces deny-by-default; false forces session-scoped allow. |
x-chio-approval-required | operation | boolean | false | When true, forces deny-by-default regardless of method or x-chio-side-effects. Sets annotations.requires_approval. |
x-chio-budget-limit | operation | u64 | none | Per-invocation cost cap in minor currency units. Consumed by the budget guard. |
x-chio-publish | operation | boolean | true | Controls whether the operation appears in the generated manifest. Useful for internal or health-check endpoints. |
Sensitivity Levels
| Level | Meaning |
|---|---|
public | Publicly available data, no special handling |
internal | Internal data, logged but not restricted beyond defaults |
sensitive | Sensitive data, may trigger additional approval in the guard pipeline |
restricted | Highly restricted data, always flagged for elevated scrutiny |
If the value does not match one of the four allowed strings, the parser MUST ignore it (treat as absent).
Approval Precedence
Approval wins
x-chio-approval-required: true MUST take precedence over all other policy inputs. Even if x-chio-side-effects is false and the method is GET, the operation is deny-by-default when approval is required.Extension Precedence Summary
| Method | x-chio-side-effects | x-chio-approval-required | Resulting policy |
|---|---|---|---|
| GET | absent | absent | SessionAllow |
| GET | absent | true | DenyByDefault |
| GET | true | absent | DenyByDefault |
| GET | false | true | DenyByDefault |
| POST | absent | absent | DenyByDefault |
| POST | false | absent | SessionAllow |
| POST | false | true | DenyByDefault |
| POST | absent | true | DenyByDefault |
Schema Preservation
OpenAPI schemas project as JSON Schema in MCP tools. The generator preserves the original schema shape for parameters and request bodies, with three normalizations:
- All parameters are merged into a single
{ type: "object" }input schema. - Path and query parameters become top-level properties; required ones land in the
requiredarray. - Request body content (preferring
application/json) appears under a top-levelbodyproperty.
Internal $ref pointers are resolved before the schema is embedded in the tool definition; consumers see fully expanded schemas.
Example: Petstore
For the Petstore spec the pipeline produces four ToolDefinition values. Resulting policies:
| Operation | Method | Policy | Reason |
|---|---|---|---|
listPets | GET | SessionAllow | Safe method, no overrides |
createPet | POST | DenyByDefault | Side-effect method |
showPetById | GET | SessionAllow | Safe method, no overrides |
deletePet | DELETE | DenyByDefault | Side-effect method + x-chio-approval-required: true |
Default Deny-by-Method Policy
Method Classification
| Category | Methods | Default policy |
|---|---|---|
| Safe (read-only) | GET, HEAD, OPTIONS | SessionAllow |
| Side-effect (mutating) | POST, PUT, PATCH, DELETE | DenyByDefault |
SessionAllow
Operations classified as SessionAllow are permitted by default within an active session. No capability token is required. The proxy MUST still generate a signed HttpReceipt for every SessionAllow request (audit receipt).
DenyByDefault
Operations classified as DenyByDefault require the caller to present a valid capability token. Without a token, the proxy MUST return a structured 403 response.
The caller presents a capability token via the X-Chio-Capability HTTP header or the chio_capability query parameter. When a valid token is present, the request proceeds to the upstream.
Extension Overrides
- If
x-chio-approval-requiredistrue, the result is always DenyByDefault. This check takes highest precedence. - If
x-chio-side-effectsis explicitly set, it overrides the method default:trueforces DenyByDefault,falseforces SessionAllow. - If neither extension is set, the method classification applies.
Unmatched Routes
If a request path does not match any route in the loaded OpenAPI spec, the proxy MUST fall back to method-based default policy. Safe methods receive SessionAllow; side-effect methods receive DenyByDefault.
Auth Pass-Through
The proxy extracts caller identity from request headers using the following precedence; only SHA-256 hashes are retained, never the raw credential.
| Priority | Header | Identity format |
|---|---|---|
| 1 | Authorization: Bearer <token> | bearer:<truncated-sha256> |
| 2 | X-Api-Key / X-API-Key / x-api-key | apikey:<truncated-sha256> |
| 3 | (none) | anonymous |
When forwarding allowed requests, the proxy passes through Content-Type, Accept, and User-Agent headers and the request body verbatim. The maximum permitted body size is 10 MiB; the proxy MUST reject larger requests.
Error Mapping
Structured 403 Response
When a request is denied (DenyByDefault policy without a valid capability token), the proxy MUST return an HTTP 403 response with a JSON body conforming to this schema:
{
"error": "chio_access_denied",
"message": "<human-readable denial reason>",
"receipt_id": "<receipt ID for the denial>",
"suggestion": "provide a valid capability token in the X-Chio-Capability header or chio_capability query parameter"
}| Field | Type | Description |
|---|---|---|
error | string | Always "chio_access_denied" |
message | string | Reason for denial from the guard evaluation |
receipt_id | string | ID of the signed receipt that records this denial |
suggestion | string | Actionable guidance for the caller |
The Content-Type on the 403 response MUST be application/json.
Upstream Failure
If the upstream returns an error or the connection fails, the proxy MUST return HTTP 502 (Bad Gateway). The receipt for that request records the original allow verdict; the upstream failure does not change the access-control decision.
Error Catalog
chio-openapi errors:
| Error | Condition |
|---|---|
InvalidJson | Input detected as JSON but failed to parse |
InvalidYaml | Input detected as YAML but failed to parse |
MissingField | A required top-level field (openapi, info, or paths) is absent |
UnsupportedVersion | The openapi version does not begin with 3. |
UnresolvedRef | A $ref pointer could not be resolved (external URI or nonexistent internal path) |
chio-api-protect errors:
| Error | Condition |
|---|---|
SpecLoad | The OpenAPI spec file cannot be read or auto-discovery failed |
SpecParse | The loaded spec failed OpenAPI parsing (wraps chio-openapi errors) |
Config | Configuration error (e.g., cannot bind listen address) |
ReceiptSign | Receipt signing or content hashing failed |
Io | IO error during server operation |
HttpClient | HTTP client error during upstream communication |
chio api protect Contract
chio api protect is a zero-code reverse proxy that interposes Chio capability-based access control between callers and an existing HTTP API. It requires no code changes to the upstream.
Command Interface
chio api protect --upstream <URL> [--spec <path>] [--listen <addr>]| Flag | Required | Default | Description |
|---|---|---|---|
--upstream | Yes | -- | Base URL of the upstream API to proxy to |
--spec | No | Auto-discover | Path to a local OpenAPI spec file (JSON or YAML) |
--listen | No | 127.0.0.1:9090 | Address and port for the proxy to bind |
Spec Auto-Discovery
When --spec is not provided, the proxy MUST attempt to auto-discover the OpenAPI specification from the upstream server. The proxy probes the following well-known paths in order:
| Priority | Path |
|---|---|
| 1 | /openapi.json |
| 2 | /openapi.yaml |
| 3 | /swagger.json |
| 4 | /api-docs |
For each path, the proxy issues an HTTP GET request to {upstream}{path}. The first response that returns a successful HTTP status (2xx) with a non-empty body is used as the spec. If none of the probes succeed, the proxy MUST fail with a SpecLoad error directing the operator to use --spec.
Receipt Generation
The proxy MUST generate a signed HttpReceipt for every request, regardless of whether the request is allowed or denied.
| Receipt field | Source |
|---|---|
id | Unique receipt ID (UUIDv7) |
request_id | Unique request ID (UUIDv7) |
route_pattern | Matched OpenAPI path pattern (e.g., /pets/{petId}) |
method | HTTP method |
caller_identity_hash | SHA-256 hash of the caller identity |
verdict | Allow or Deny with reason and guard name |
evidence | Guard evaluation evidence chain |
response_status | Chio evaluation-time HTTP status (403 for denied; typically 200 for allowed sidecar/proxy evaluations before the upstream response exists) |
timestamp | Unix timestamp of the request |
content_hash | Hash of the request content |
policy_hash | SHA-256 hash of the loaded OpenAPI spec |
kernel_key | Public key of the proxy's ephemeral keypair |
The receipt MUST be signed using the proxy's ephemeral keypair, generated at startup. The signature MUST be verifiable using the kernel_key field embedded in the receipt.
Receipt Header
For proxied responses (allowed requests forwarded upstream), the proxy MUST include an X-Chio-Receipt-Id header in the response. For 403 responses, the receipt ID appears in the JSON body as receipt_id and the header is not required.
Startup Behavior
- Load the OpenAPI spec (from
--specor via auto-discovery). - Parse the spec using
chio-openapi. - Build a route table mapping (method, path pattern) to policy decisions.
- Generate an ephemeral signing keypair.
- Compute the SHA-256 hash of the spec content for use as
policy_hashin receipts. - Bind to the
--listenaddress and begin accepting requests.
The proxy logs the number of routes loaded and the upstream URL at startup.
Implementation References
| Component | Crate | Entry point |
|---|---|---|
| OpenAPI parser | chio-openapi | crates/chio-openapi/src/parser.rs |
| Manifest generator | chio-openapi | crates/chio-openapi/src/generator.rs |
| Extension vocabulary | chio-openapi | crates/chio-openapi/src/extensions.rs |
| Default policy | chio-openapi | crates/chio-openapi/src/policy.rs |
| Reverse proxy | chio-api-protect | crates/chio-api-protect/src/proxy.rs |
| Request evaluator | chio-api-protect | crates/chio-api-protect/src/evaluator.rs |
| Spec discovery | chio-api-protect | crates/chio-api-protect/src/spec_discovery.rs |
| Convenience function | chio-openapi | chio_openapi::tools_from_spec() |
Related
- Bridges: how the OpenAPI bridge sits alongside the A2A, ACP, and OpenAI surfaces.
- Bridge OpenAPI to MCP: walkthrough of the pipeline.
- OpenAPI sidecar example.
- Protocol:
chio.manifest.v1contract. - CLI reference: complete
chiosurface.