# API generation (/architecture/api-generation)



Chelabase's APIs describe themselves from the Rust code. Three OpenAPI documents are committed under `openapi/`, typed clients are generated from them, and this site renders them as the API reference. Nothing here is edited by hand.

## The documents [#the-documents]

OpenAPI documents are generated from the Rust route handlers with utoipa 6 and utoipa-axum, and the paths come from the endpoint registry in `crates/chelabase-routes`.

| Document                  | Describes                                                                                                | Built in                               |
| ------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| `openapi/management.json` | The Management API: the orchestrator's `/v1`                                                             | `services/orchestrator/src/openapi.rs` |
| `openapi/login.json`      | The login API: auth's `/auth/v1`, Studio's sign-in pages and app users' sign-in flows (`/auth/v1/app/*`) | `services/auth/src/openapi.rs`         |
| `openapi/data.json`       | The data API: `rest`'s `/data/v1`                                                                        | `services/rest/src/openapi.rs`         |

The [management](/reference/management/databases/createDatabase) and [data](/reference/data/runQuery) references are published under `/reference/` and the [login API](/architecture/login-api/login/signIn) under `/architecture/login-api`; each page is generated from its document when the site is built.

### Tests that keep them honest [#tests-that-keep-them-honest]

Each service has tests in its `openapi` module that fail when a document is stale or a route isn't documented:

* `document_is_current`: the committed file equals what the code generates (set `CHELABASE_BLESS` to rewrite it);
* `document_matches_the_registry`: the document's method and path set equals the registry's public endpoints for that service;
* `operation_ids_are_the_contract`: operation ids are stable, because generated clients take their function names from them;
* `every_response_has_a_description_and_errors_use_the_envelope`: every response is described, and every error status references the shared error envelope;
* `schema_names_are_api_facing` (or `schema_names_are_specific_and_unique` for the login document): component names are meaningful in the generated clients.

### The data API document [#the-data-api-document]

The data API document is built differently, because the request body is one JSON tree that `rest` parses by hand rather than through serde types. `chelabase_query::openapi` (behind the `openapi` feature) holds marker types for the query language's schemas: `QueryRequest` (one of the seven top-level operations: select, count, insert, update, delete, call and batch, with `CountRequest` for count), the batch members (`BatchCount` among them), and the shared `Filter`, `TableName`, `SelectItem`, `OrderBy`, `Returning` and `QueryResult`. One helper builds each operation twice, once for the top level (`v` required, `database` allowed) and once as a batch member (`v` optional, no `database`).

`services/rest/src/openapi.rs` assembles the document: `POST /data/v1/query` (`runQuery`), `GET /data/v1/schema` (`getDataSchema`) and `GET /data/v1/types` (`getDataTypes`), the `bearer` and `user` security schemes (the `Chelabase-User` header), and the data API's own error envelope (`code`, `message`, and optionally `detail`, `hint` and `sqlstate`) for every status of 400 and above.

Because the schema is written by hand next to a hand-written parser, a fidelity test keeps the two together. `crates/chelabase-query/tests/openapi_fidelity.rs` reads the committed `openapi/data.json` and checks that:

* every contract fixture of the TypeScript client (`packages/client/fixtures`) is valid against the request schema;
* the schema and the parser reject the same invalid inputs, and accept the same edge inputs;
* the known gaps (inputs the schema accepts but the parser rejects, such as keys that differ between insert rows or an alias of 32 multi-byte characters) are exactly the listed ones.

## Regenerating [#regenerating]

After changing any route, its request or response types, or an error code, run:

```bash
just api-gen
```

This command:

* rewrites the OpenAPI documents with `CHELABASE_BLESS=1` on the document tests;
* runs orval 8 to regenerate the TypeScript clients.

Commit the result. `just check` fails if the documents or the generated code are out of date.

## Generated clients [#generated-clients]

Two packages are generated from the documents with orval 8:

* `packages/management-client` (`@chelabase/management-client`, private for now): TanStack Query hooks (`useGetDatabase`, `useCreateBranch`), plain functions (`getDatabase(id)`) and query key helpers (`getGetDatabaseQueryKey(id)`). Configure it once with `configureManagementClient({ baseUrl, getToken, onUnauthorized })`.
* `apps/studio/src/lib/login-client`: Studio's internal login client, generated the same way.

Generated files are never edited by hand. Errors from these clients are thrown as `ApiError { status, code, message, extra, signInRestarted }`, so a caller can switch on the stable `code` rather than the message.

The application-facing data API client is a separate, hand-written package (`@chelabase/client`), checked against the data API document by the fidelity test above.

## Error shape [#error-shape]

All management, login and gateway errors use one shape:

```json
{ "error": { "code": "<code>", "message": "<sentence>" } }
```

Some codes add extra keys (for example `keys` on `role_in_use`). The `code` is stable snake_case (for example `not_found`, `branch_not_ready`, `has_children`, `name_taken`, `invalid_request`, `too_large`, `rate_limited`, `unavailable`, `worker_unavailable`, `not_applied`, `sign_in_expired`, `invalid_credentials`). The full list is the `ErrorCode` enum in `crates/chelabase-types/src/api_error.rs` and the OpenAPI documents; see also [Error codes](/reference/errors). Another tenant's database answers 404 `not_found`, exactly like a missing one.

The data API keeps its own codes and extra fields (`detail`, `hint`, `sqlstate`).

## API reference UI [#api-reference-ui]

Studio's **API reference** page (`/app/api`, in the left rail) renders the Management, Data and Auth API documents with "Try it out"; only the Management document gets the signed-in user's token. The docs site has the reference pages for all three APIs. The orchestrator serves no page of its own; `GET /v1/openapi.json` stays public behind the gateway, so any tool can load the document.

Studio's page uses `@scalar/api-reference-react`, pinned exactly in Studio and configured never to contact scalar.com: telemetry off, default fonts off, no request proxy, external URLs empty, agent and MCP off, developer tools never. Update it with pnpm (the minimum release age applies).
