Review API compatibility
Tenchi derives OpenAPI 3.1 from composed contracts. The document describes the same types, headers, errors, media types, examples, visibility, retry guarantees, and successful response definitions enforced by the server and client.
Generate a document
openapi_schema() is a pure function:
from tenchi.openapi import openapi_schema
document = openapi_schema(
api_routes,
title="Todos",
version="1.0.0",
description="Todo service API",
security={
"bearerAuth": {"type": "http", "scheme": "bearer"},
},
)Pass only the application route group you want documented. Public contracts
are exempted from global security through the same contract.public metadata
used by authentication hooks.
Discover error behavior
Every error response includes the stable Tenchi envelope and narrows code to
the exact values that status may return. It also documents the required
x-tenchi-error-source header as app, framework, or both. A generated
client or agent can therefore distinguish declared application outcomes from
framework failures without parsing response descriptions.
Every operation documents 500 with the framework-owned
INTERNAL_SERVER_ERROR code. Tenchi uses that response for unexpected
exceptions and undeclared application errors.
Security schemes describe how to authenticate; they cannot determine which authentication errors an application uses. Declare authentication failures on the protected route group so their statuses and codes appear on each affected operation:
private_routes = route_group(
project_routes,
task_routes,
errors=(unauthorized,),
)Teach clients and agents how to call an operation
Named request_examples= and response_examples= values flow from a contract
to the corresponding OpenAPI Media Type Object. Tenchi runs them through the
declared Pydantic validator and serializer, revalidates the serialized wire
payload through the receiving boundary, and checks the resulting JSON against
the published schema. This keeps aliases, custom validators, and custom
serializers honest while giving external clients and agents concrete payloads
they can adapt.
create_todo_contract = contract(
method="POST",
path="/todos",
request=CreateTodo,
response=Todo,
status=201,
request_examples={"create": CreateTodo(title="Buy milk")},
response_examples={
"created": Todo(id="todo_123", title="Buy milk", completed=False)
},
)Changing examples is metadata-only for compatibility. Do not put secrets, personal data, or production payloads in them because the OpenAPI document is often public.
Contracts marked idempotency_key=True or webhook=True publish Tenchi
extensions that describe caller-visible guarantees. Their compatibility rules
are listed under Constraints.
Serve OpenAPI
from tenchi.openapi import openapi_route, swagger_ui_route
from tenchi.routes import route_group
routes = route_group(
api_routes,
openapi_route(
api_routes,
title="Todos",
version="1.0.0",
),
swagger_ui_route(title="Todos API"),
)The default path is /openapi.json. The serving route is public by default and
does not include itself in the document. Swagger UI defaults to /docs and
loads that document through a relative URL, so ASGI root_path mounting keeps
both routes aligned.
The default Swagger JavaScript and stylesheet are pinned CDN assets with
subresource integrity metadata. Override swagger_js_url=,
swagger_css_url=, and swagger_favicon_url= to self-host them. Set
public=False on both routes when the API description requires authentication.
Store a canonical snapshot
uv run tenchi openapi --write openapi.json
uv run tenchi openapi --check openapi.json--write produces deterministic, key-sorted JSON. --check is an exact
equality check suitable for a repository test. By default the command loads
app.server.routes:api_routes and discovers literal OPENAPI_TITLE,
OPENAPI_VERSION, OPENAPI_DESCRIPTION, and OPENAPI_SECURITY declarations
from that module. Use the corresponding flags only to override this convention.
Classify changes
uv run tenchi openapi --diff openapi-baseline.jsonThe analyzer classifies changes as:
- Additive: compatible expansion, such as a new optional operation.
- Metadata: descriptions and other non-wire changes.
- Breaking: removal or tightening that rejects existing valid traffic.
- Unknown: JSON Schema behavior that cannot be proven safe.
Breaking and unknown changes return a failing exit status. Use
--diff-format json for automation.
Try a breaking and additive change
In a generated application, open app/features/todos/schemas.py and tighten
the title constraint:
class CreateTodo(BaseModel):
- title: str = Field(min_length=1)
+ title: str = Field(min_length=3)Compare the current contracts with the checked-in snapshot:
uv run tenchi openapi --diff openapi.jsonThe command fails because existing callers can send titles that the new
constraint rejects. Restore min_length=1, then add an optional field:
class CreateTodo(BaseModel):
title: str = Field(min_length=1)
notes: str | None = NoneThe same diff command now reports an additive change. After reviewing it, update the snapshot and check the application:
uv run tenchi openapi --write openapi.json
uv run tenchi checkUse tenchi verify --base-ref <historical-ref> when the repository also needs
one receipt for its checks, application
map, verification policy, and every versioned boundary.
A compatibility gate needs a snapshot from the pull-request base, previous push, or previous release. If a breaking change and its new snapshot are committed together, comparing the generated document with that same snapshot proves only equality.
CI pattern
uv run tenchi openapi --diff-ref "$BASE_SHA" --snapshot openapi.json
uv run tenchi openapi --check openapi.json--diff-ref resolves the snapshot inside the current Git repository and reads
REF:PATH without changing the working tree. Missing refs and snapshots fail
the command. Generated CI uses the pull request's base SHA; push checks retain
exact snapshot validation without assuming a previous commit exists.
Programmatic consumers can import analyze_openapi_compatibility() from
tenchi.compatibility and inspect its structured CompatibilityReport.
Constraints
- Keep the route module's literal
OPENAPI_*declarations stable across generation, comparison, and snapshot checks. Explicit CLI flags take precedence when an application uses a nonstandard target or metadata source. - An unsafe contract marked
idempotency_key=Truemust declare a required, non-empty stringIdempotency-Keyheader. It then emitsx-tenchi-idempotency-key: Idempotency-Key. Adding the guarantee is additive when the required header already exists; adding that header to an established operation is breaking; removing the guarantee is breaking because callers may rely on safe logical retries. The extension describes behavior and does not replace the idempotency store or use case. - Contracts marked
webhook=Trueemitx-tenchi-webhook: true. Adding that requirement is breaking because existing callers must begin signing deliveries; removing it is additive. - Changing
request_examples=orresponse_examples=is metadata-only. - Security schemes describe how to authenticate. They never add authentication error responses; declare those on the protected contract or route group.