# OpenAPI and Scalar (/docs/agents/openapi)

---
title: OpenAPI and Scalar
description: One module reads the collections, globals and routes you already wrote, emits an OpenAPI 3.1 document, and serves a browsable Scalar reference beside it.
kind: guide
package: "@questpie/openapi"
---

Someone wants to call your API. A partner, a colleague, an agent. This page is
how you hand them the whole surface without writing a spec by hand.

## It is probably already on

The starters put `openApiModule` in `modules.ts` for you. Open yours and check.

```ts title="src/questpie/server/modules.ts"
import { openApiModule } from "@questpie/openapi";

export default [openApiModule] as const;
```

Add it if it is missing, then run `questpie generate`. Codegen has to see the
module before its two routes exist.

## Point it at your base path

The starters mount the fetch handler at `/api`. The generator does not know
that. It prefixes every documented path with its own `basePath`, which defaults
to `/`. The scaffolded `config/openapi.ts` never sets it. So a fresh app
documents `//posts`, not `/api/posts`.

One line fixes it.

```ts title="src/questpie/server/config/openapi.ts"
import { openApiConfig } from "@questpie/openapi";

export default openApiConfig({
	info: { title: "My API", version: "1.0.0" },
	basePath: "/api",
	scalar: { theme: "purple" },
});
```

`openApiConfig()` hands your object straight back. It exists for the types.
Codegen discovers the file, and the spec route reads it when it builds the
document. If your handler sits at the root, write `basePath: ""`, not `"/"`.

## What you get

Two endpoints, under whatever base path your fetch handler uses.

| Endpoint                | Serves                                               |
| ----------------------- | ---------------------------------------------------- |
| `GET /api/openapi.json` | The OpenAPI 3.1 document, as JSON                    |
| `GET /api/docs`         | The Scalar reference UI, rendered from that document |

Start the app and open `http://localhost:3000/api/docs`. Every collection,
global and route is listed, each with a try-it panel. The page pulls Scalar
from jsDelivr, so it needs network access to render.

<Callout type="warn" title="`/api/docs` ignores `config/openapi.ts`">
	Only the spec route reads that file. The Scalar route always renders the
	defaults: title `QUESTPIE API`, theme `purple`, paths under `/`. Mount your
	own docs route to get your config back. See [Serving it
	yourself](/docs/agents/openapi/serving).
</Callout>

## What lands in the document

Five sources merge into one file.

| Source      | What it contributes                                         | Tag                   |
| ----------- | ----------------------------------------------------------- | --------------------- |
| Collections | The CRUD set, plus count, batch writes, versions and revert | `Collections: <name>` |
| Globals     | Get, update, schema, versions and revert                    | `Globals: <name>`     |
| Routes      | One operation per method, typed by your Zod schemas         | `Routes: <segment>`   |
| Better Auth | Its own document, or four fallback endpoints                | `Auth`                |
| Search      | `POST /search` and `POST /search/reindex/{collection}`      | `Search`              |

Every document also carries four shared component schemas and two security
schemes. [Generated paths](/docs/agents/openapi/paths) lists each one.

## The document is built once

`/api/openapi.json` builds the document on the first request and caches it for
the life of the app instance. It answers with an `ETag` and a one-hour
`Cache-Control`, and returns `304` when your `If-None-Match` matches. So a
change to `config/openapi.ts` needs a restart, not just another request.

## Listing an endpoint does not open it

Both routes are ordinary QUESTPIE routes with no access rule. Anyone who can
reach them can read your whole API shape. That does not make the endpoints
callable. Every collection, global and route in the document is still checked
against its own [access rules](/docs/schema/access-control) when the call
arrives.

The two security schemes say how to authenticate. They say nothing about what
you are allowed to do.

<Callout type="info" title="A public route says so in the spec">
	`.access(true)` on a route emits `security: []` on that operation. That opts
	it out of the document's security scheme. A function rule, `false`, or no rule
	at all inherits the scheme instead.
</Callout>

## Where each topic lives

| Topic                                                       | Page                                                |
| ----------------------------------------------------------- | --------------------------------------------------- |
| Every config option and its default                         | [Configuration](/docs/agents/openapi/configuration) |
| The exact paths a collection, global or route emits         | [Generated paths](/docs/agents/openapi/paths)       |
| Mounting the routes yourself, or generating in a build step | [Serving it yourself](/docs/agents/openapi/serving) |
| Naming and tagging one operation                            | [Route metadata](/docs/code/routes/metadata)        |
| Who may call what the document lists                        | [Access control](/docs/schema/access-control)       |

## Next

**[MCP](/docs/agents/mcp)** is the other half. OpenAPI describes your API to
whoever reads it. MCP hands the same surface to an agent as callable tools.
