QUESTPIE
AgentsOpenapi

Generated paths

The exact operations a collection, a global, a route, Better Auth and search each contribute to the document, plus the component schemas and security schemes every document carries.

View markdown
SourceComes fromTag
Collectionsapp.getCollections()Collections: <name>
Globalsapp.getGlobals()Globals: <name>
Routesapp.config.routesRoutes: <segment>
AuthBetter Auth, through app.auth.apiAuth
SearchNothing. The two paths are hardcoded.Search

Every path below is shown with basePath: "/api". Swap in your own.

Collection and global paths use the registered name verbatim, with no kebab-casing. A collection registered as blogPosts documents as /api/blogPosts, which is the URL the runtime actually answers.

Collections

A collection called posts always produces these thirteen operations.

MethodPathoperationId
GET/api/postsposts_find
POST/api/postsposts_create
PATCH/api/postsposts_updateMany
GET/api/posts/countposts_count
POST/api/posts/delete-manyposts_deleteMany
POST/api/posts/update-batchposts_updateBatch
GET/api/posts/schemaposts_schema
GET/api/posts/metaposts_meta
GET/api/posts/{id}posts_findOne
PATCH/api/posts/{id}posts_update
DELETE/api/posts/{id}posts_delete
GET/api/posts/{id}/versionsposts_findVersions
POST/api/posts/{id}/revertposts_revertToVersion

Four more appear only when the collection opted in.

MethodPathAppears when
POST/api/posts/uploadthe collection has .upload()
POST/api/posts/{id}/restoreoptions.softDelete is on
POST/api/posts/{id}/purgeoptions.softDelete is on
POST/api/posts/{id}/transitionversioning.workflow is set

Versions and revert are always documented

Both paths are emitted whether or not you turned versioning on. The endpoints themselves decide at request time. The document does not gate them.

Query parameters

GET /api/posts takes limit (default 10), page (default 1), offset, where, orderBy, locale and stage. where and orderBy are documented as strings holding JSON.

GET /api/posts/{id} takes locale and stage. GET /api/posts/count takes only where.

Component schemas

Three per collection, named from the PascalCase form of the collection name. So blogPosts gives BlogPostsDocument, BlogPostsInsert and BlogPostsUpdate.

SchemaBuilt from
PostsInsertyour .validation() insert schema, or the field definitions
PostsUpdatethe same, made partial
PostsDocumentid and timestamps, merged with the field schema

PostsDocument carries createdAt and updatedAt unless timestamps is false, deletedAt when soft delete is on, and revision when optimistic concurrency is on.

A field marked .inputFalse() is dropped from the request schemas and turns up as readOnly in the document schema. .outputFalse() is the mirror image: dropped from the document, marked writeOnly in requests. date and datetime fields are rewritten to a string with a date or date-time format.

A collection with neither a validation schema nor usable field definitions gets three bare { "type": "object" } schemas instead.

With optimistic concurrency on

Optimistic concurrency changes six things at once.

ChangeWhere
revision added, requiredthe document schema
revision removedthe update schema
If-Match header parameterevery mutation
ETag response header on 200every read and write that returns a row
409 responseevery mutation
412 responsemutations that accept If-Match

The bodies change too. PATCH /api/posts/{id} stops taking the bare update schema and takes { data, expectedRevision } instead. PATCH /api/posts and POST /api/posts/delete-many require an expectedRevisions array covering every selected row. Each entry of update-batch requires its own expectedRevision.

Globals

A global called settings produces five operations, one of them conditional.

MethodPathoperationId
GET/api/globals/settingsglobal_settings_get
PATCH/api/globals/settingsglobal_settings_update
GET/api/globals/settings/schemaglobal_settings_schema
GET/api/globals/settings/versionsglobal_settings_findVersions
POST/api/globals/settings/revertglobal_settings_revertToVersion

POST /api/globals/settings/transition joins them when the global has versioning.workflow set.

Two component schemas per global: SettingsGlobal for the value and SettingsGlobalUpdate for the request. Optimistic concurrency changes the same six things it changes on a collection.

Routes

The generator walks app.config.routes and builds each path from the route key. Literal segments are kebab-cased, [param] becomes {param}, and [...slug] becomes {slug}. That matches the URL the HTTP adapter serves. A key ending in :METHOD splits into a path and a method.

Route keyDocumented pathMethod
createBooking/api/create-bookingfrom the builder
posts/[id]/api/posts/{id}from the builder
files/[...key]/api/files/{key}from the builder
auth/[...path]:GET/api/auth/{path}get, from the key

The operationId is route_ plus the key segments joined with underscores. A path serving more than one method gets the method appended, so sibling files on one path stay unique.

What each route contributes

The route hasThe operation gets
.schema(z…)A request body, $ref to <operationId>_Input
.outputSchema(z…)A 200 body, $ref to <operationId>_Output
no .outputSchema()A 200 body typed as a bare { "type": "object" }
.meta({ title })That string as the summary, instead of the route key
.meta({ description })That string as the description
.meta({ tags })Those tags, instead of Routes: <first segment>
.access(true)security: [], opting out of the document's scheme
.raw()A permissive body, and 200 plus 401 responses

A raw route accepts either application/json with no constraint or application/octet-stream as binary. It also gets a stock description saying so, unless your .meta({ description }) already set one.

Every JSON route documents 200, 400, 401 and 404. See Route metadata for the .meta() object in full.

Auth

This section is built two different ways. Which one you get depends on your Better Auth setup.

With the openAPI() plugin on. The generator calls app.auth.api.generateOpenAPISchema() and folds the whole result in. Every auth path is prefixed with <basePath>/auth. Every component schema is renamed with an Auth prefix, and every $ref to it is rewritten. So Better Auth's User cannot collide with yours. Every operation is retagged Auth, replacing the generic tags Better Auth emits. Its security schemes are merged in too.

Without it. Four hardcoded paths, enough to sign in and check a session.

MethodPath
POST/api/auth/sign-in/email
POST/api/auth/sign-up/email
GET/api/auth/get-session
POST/api/auth/sign-out

The fallback also covers the case where the plugin call throws. Generation never fails on auth.

Set auth: false and none of this is emitted.

Two paths, always the same, emitted unless you set search: false.

MethodPathNotes
POST/api/searchBody takes query, plus optional collections, limit and offset
POST/api/search/reindex/{collection}Documented as needing admin auth

Neither path checks whether you configured a search adapter. An app with no search still documents both.

In every document

Four component schemas are always present: ErrorResponse, SuccessResponse, CountResponse and DeleteManyResponse.

Two security schemes are always present, and both are declared at the document root as alternatives.

SchemeTypeCarries
bearerAuthHTTP bearerA token in the Authorization header
cookieAuthAPI key, in cookieThe better-auth.session_token cookie

Schemes derived from Better Auth are merged alongside these two. On a name clash, QUESTPIE's own definition wins.

Zod conversion

Route schemas go through z.toJSONSchema. Request bodies convert on Zod's input side so a transform keeps its pre-transform shape. Responses convert on the output side.

What happensWhat lands in the document
The schema convertsThe real JSON Schema
An input schema throwsA retry that renders unrepresentable parts as any
That retry throws tooA description-only schema, with no constraints
An output schema throwsA description-only schema, with no constraints

The description-only fallback says outright that runtime validation is still authoritative. Nothing silently claims a shape it could not derive.

Collection and global schemas take a shorter path. They convert with unrepresentable parts rendered as any, and fall back to a bare { "type": "object" } if that throws.

On this page