API Docs Endpoint
A running Kirak app describes its own HTTP API in two forms:
| For | Format | |
|---|---|---|
GET /docs |
AI agents (and people) calling the API | Markdown, one document |
GET /openapi.json |
API clients, SDK generators, gateways | OpenAPI 3.1 |
Both are built from the same description of the running app, so they never disagree. Both exist only on a running app: they describe the routes and models it has actually loaded.
Swagger UI and ReDoc are not served. Import openapi.json into an API client such as
Postman, Insomnia or Bruno for interactive use.
What /docs contains
Section titled “What /docs contains”An agent reads it once and can then call any endpoint correctly:
- Quick Reference – a map of the API first: the models, the CRUD and GraphQL paths, how
to authenticate, the enabled modules, where
openapi.jsonis, and the official SDKs and coding-agent integrations (listed inkirak/catalog/specs/resources.py; an entry without a URL is not shown). - Not Supported – what agents most often assume exists: REST relationship expansion, a get-by-id route, whole-table writes, GraphQL introspection and subscriptions, a guest fallback for a bad credential.
- Authentication – how to send a token or API key, what happens without one, when to refresh, how to read an access rule’s condition, and the session flow (register, login, me, refresh, logout) with what each step sends and returns.
- Responses – the success and error envelope, stated once, and the stable
errorcodes of the core and of each enabled module that lists them (see Response Envelope). - CRUD Operations – one table for every model (method, path, input, what
dataholds), followed by the filter operators, a complete fetch response with itspagination, sorting and rate limits, that REST does not expand relationships, and example requests on one of your models, written for a named role (tests check them against the validator and the query parser). - Models – one compact block per model: fields with their flags (
read-only,required,unique,searchable,max 200,one of: ...,-> usersfor relationships), access rules per operation, field-level rules and the model’s own rate limits.read-onlyfields (id, the timestamps, primary keys) are set by Kirak; values sent for them are ignored; every other field is writable. An ownership rule readsuser (own records: user_id = their user_id), and a create rule like that is stated as an owner field: create and upsert fill it from the caller. - GraphQL – the query, aggregate and mutation root fields, filter syntax, joins, and that several mutations in one request run in one transaction. GraphQL introspection is disabled, so this section is the schema. The examples are built from your models.
- Modules – for each enabled module: its summary, the things an agent gets wrong, the
configured provider instances (names and types, never secrets) and its endpoints,
grouped by their OpenAPI tag when a module uses several (auth: session, password, MFA,
…). An endpoint with a JSON body lists its fields (
Body:,*marks a required one), from the operation’s parameters in the module catalog (kirak modules <name> --json). Only what the configured providers use is listed: a field only some provider types read appears when one of them is configured, and a route whose operation none of them supports (the/payments/connect/*routes without astripe_connectinstance) lists no fields. - MCP servers – with the
mcpmodule on, under Modules: each server’s path, description, access level and tools (from its file and the Python tools registered on it). They are for MCP clients; the Quick Reference names them too. - Custom Endpoints – your own routes, grouped by tag.
- System APIs – last, and named only: admin, monitoring and scheduler serve operators
and tooling, not app clients, so their routes are not listed (
openapi.jsonhas them).
Models marked "internal": true are left out everywhere, as the CRUD routes return 404 for
them and GraphQL rejects them.
The document is built once at startup and cached; models and routes do not change after that.
Narrower views
Section titled “Narrower views”Two query parameters cut the document down, for a large app or a frontend for one kind of user:
GET /docs?role=user– only the models, operations, access rules, owner fields and fields that role can use (the same decision the access engine makes). Module and custom routes stay, since their access is not declared where/docscan read it.GET /docs?models=posts,comments– only those models. An unknown or internal model answers400 VALIDATION_ERROR.
?role only chooses what the document describes: it grants nothing, and every request is
still checked against the caller’s own role. Both combine. Names are letters, digits and _ . * - only; anything else is rejected, so no
other text from the URL reaches a document agents read. Views are rendered on first use and
cached.
Every response carries an ETag; send it back in If-None-Match to get 304 Not Modified
when nothing changed, e.g. to know whether a cached copy is stale.
What openapi.json contains
Section titled “What openapi.json contains”FastAPI’s own document, with the generic CRUD paths (/{model_name}/fetch, …) replaced by
one set per model (/posts/fetch, /posts/create, …), each tagged with its model and
carrying:
- request bodies from the model’s fields (
<Model>Create,<Model>Update) and the record shape (<Model>;passwordfields are left out of it) - filter, pagination and sorting parameters
security: a bearer token or API key, optional when the operation allowsguest- the success and error envelopes (
KirakSuccess,KirakError) and rate-limit headers
Kirak rules OpenAPI has no field for are in extensions, which standard tools ignore:
| Extension | Where | Content |
|---|---|---|
x-kirak-access |
each CRUD operation | the access rules: roles and row-level conditions |
x-kirak-rate-limit |
each CRUD operation with a limit | max_requests, window_seconds, per |
x-kirak-filter-operators |
read operations, /graphql |
the filter operators |
x-kirak-searchable |
search | the searchable fields |
x-kirak-soft-delete |
model tag | the model uses soft delete |
x-kirak-relationship |
foreign-key properties | the related model |
x-kirak-field-access |
record schema | field-level read and write rules |
x-kirak-graphql |
POST /graphql |
query and mutation root fields per model |
x-kirak-operation |
module routes | the module operation the route runs, e.g. payments.initiate_payment |
x-kirak-effect |
module routes | read, write or destructive |
x-kirak-providers |
module request body properties | the provider types that read the field, when not all do |
Auth, admin, module and custom routes appear as FastAPI renders them, except that a module route running a module operation gets that operation’s description and, for a JSON body, its parameters as the request body schema (types, required fields, allowed values); the parameters the route takes from its path or query string, or fills itself, are left out.
Settings
Section titled “Settings”In kirak.json (all default to on and public):
{ "docs": { "enabled": true, "path": "/docs", "public": true }, "openapi": { "enabled": true, "public": true }}docs.enabled: false– no/docsroute (404).openapi.jsonstill works.docs.public: false–/docsneeds a signed-in caller or an API key (401 otherwise).openapi.enabled: false– no/openapi.jsonroute (404)./docsstill works.openapi.public: false–/openapi.jsonneeds a signed-in caller or an API key (401 otherwise). API clients importing it by URL then have to send the token.
The two public settings are separate, so a tool can keep reading one while the other is
closed. Both documents describe the same models, fields and access rules: to keep them from
anonymous callers, set both docs.public and openapi.public to false.
Use kirak.local.json to differ per environment, for example public in development and
closed or off in production.
Writing custom routes that document well
Section titled “Writing custom routes that document well”/docs reads what FastAPI already knows about a route:
@app.get("/reports/monthly", summary="Monthly sales report", tags=["reports"])async def monthly_report(month: str): """Totals per product for one month. month is YYYY-MM.""" ...summary=(or the first sentence of the docstring) is the one-line description.- The rest of the docstring is shown under it; use it for body fields and rules.
tags=groups routes under a heading.openapi_extra={"x-agent-doc": "..."}replaces the text/docsshows for that route, without changingopenapi.json.include_in_schema=Falsehides a route from both.