OpenAPI
A partner team wants to call your task API from Python. A QA engineer wants the endpoints in their API client. Your gateway wants a contract to validate against. Writing that description by hand means it is wrong the week after someone adds a field.
Arc writes it for you. Arc can serve an OpenAPI 3.1 document at GET /openapi.json, built from the same @field declarations and Zod schemas that bind requests. When the code changes, the document changes with it.
Fetch the document
Section titled “Fetch the document”In Development, no credentials are required by default. Elsewhere /openapi.json follows the shared discovery access policy: authenticated callers only, or unmapped when no authentication is configured. The Tasks sample explicitly selects Development.
curl http://127.0.0.1:3000/openapi.jsonFor the Tasks sample, with its generated metadata registered, the RegisterTask command appears under paths like this (excerpt, responses left out):
"/api/tasks/registration/register-task": { "post": { "operationId": "Tasks.Registration.RegisterTask", "tags": ["Tasks.Registration"], "summary": "Register a task.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "id": { "type": "string", "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", "format": "uuid" }, "title": { "type": "string" } }, "required": ["id", "title"] } } } } }}The summary is the JSDoc comment on the RegisterTask class. TaskId and TaskTitle are concepts, so they appear as the UUID string and the string they wrap. Fetch the document from your running server to see the responses in full.
Arc generates the document without extra registration, but HTTP access follows the discovery access policy: anonymous only in Development by default, authenticated elsewhere, and unmapped outside Development without authentication configured. Arc does not bundle a Swagger or Scalar UI; point one you host at /openapi.json and configure its credentials if you want a browsable page.
What each operation contains
Section titled “What each operation contains”- Identity. A namespace-qualified
operationId, such asTasks.Registration.RegisterTask, a tag for the namespace, and a summary. Route paths follow endpoint mapping. Every command also has aPOST <route>/validateoperation, with:validateappended to its execute operationId (for example,Tasks.Registration.RegisterTask:validate), even if the command itself is namedValidate. - Input. Execute and validate share the same required JSON request body and security requirements. Queries expose their arguments as GET parameters, with
requiredtaken from the input schema. Queries with declared array orqueryPageresults also advertisepage,pageSize,sortBy, andsortDirection. Observable queries addwaitForFirstResultandwaitForFirstResultTimeout. - Responses. The Arc
CommandResultorQueryResultenvelope for 200, 400, 403, and 500. When default authentication handlers run (even on anonymous routes), a named scheme is selected, or a native principal is required, command execution, validation, and query GET also describe 401 with an untyped result envelope. The validation-only operation describes an untyped command result; it does not execute the handler or return a typedresponse. Observable queries also describe 202, 408, and 503, and atext/event-streamresponse. A paged result carriespagingwithpage,size,totalItems, andtotalPages. - Security. HTTP bearer security, when the operation authenticates with a
jwtBearer()handler.
Topics
Section titled “Topics”| Page | What it covers |
|---|---|
| Concepts | Concepts, GUIDs, dates, and models described as the JSON value they carry |
| Commands | The POST operation, request body, CommandResult envelope, and typed response |
| Queries | GET parameters, paging and sorting, observable options, and the QueryResult envelope |
| Enums | @enumeration values for numeric and string enums |
| Model-bound and low-level operations | Where each part of an operation comes from, for decorators and define* definitions |
| How types appear in the document | One worked example, plus optional, nullable, defaulted, and derived fields |
Arc on .NET also documents its C#-only [FromRequest] binding; Arc for TypeScript binds a command from the JSON body and a query from its arguments, so it has no counterpart.
Summaries and result types need generated metadata
Section titled “Summaries and result types need generated metadata”Arc reads your source only through the metadata it has at runtime. Two parts of the document depend on generated artifact metadata registered with useGeneratedMetadata():
- Summaries. JSDoc on a command class or query method becomes the operation summary. Without metadata the summary is empty. A low-level definition sets
summarydirectly. - Result types. The 200 envelope includes a typed
responseordataonly when the metadata declares the return type. Without it, the document omitsresponseordatarather than guess from the input schema or run the handler. The runtime result is the same; only its description is missing.
The Tasks sample registers its metadata, so its registerTask response is described as a UUID string and allTasks as an array of TaskItem. Without declared return cardinality, Arc cannot determine before execution whether a query will return an array, queryPage result, or scalar. The runtime still accepts paging and sorting on array or queryPage results, but the document conservatively omits those parameters for unknown results. Renderer-backed (QueryRenderer) queries aren’t automatically advertised as pageable: the renderer’s output is not known before execution. The four standard parameters use the same names and descriptions as the .NET OpenAPI integration. TypeScript additionally documents nonnegative page, positive pageSize, and the ascending/descending aliases accepted by its HTTP binder; both use int32 for the page parameters, while .NET lists only asc/desc and does not document these bounds.
For a low-level array query declared with defineQuery, put generatedReturn on the descriptor to advertise paging without generated artifact metadata:
import { defineQuery } from '@cratis/arc.core';import { z } from 'zod';
export const allTasks = defineQuery({ name: 'AllTasks', schema: z.object({}), generatedReturn: { cardinality: 'many', nullable: false }, perform: () => [] as string[]});The same field works on defineObservableQuery descriptors; use cardinality: 'paged' for a declared queryPage result instead of 'many'. GeneratedReturn is exported as a type from @cratis/arc.core if you need to declare the shape separately. Declaring cardinality is a contract for the result, not a way to turn an arbitrary scalar into a pageable query.
Set the advertised version
Section titled “Set the advertised version”info.version defaults to 0.1.0. Set your application’s version with generatedApis.openApiVersion, in code or as Cratis:Arc:GeneratedApis:OpenApiVersion in configuration:
const server = new ArcServer({ generatedApis: { openApiVersion: '2.3.0' } });Bearer security
Section titled “Bearer security”An operation that requires authentication advertises HTTP bearer security when it authenticates with a default jwtBearer() handler, or explicitly selects a named JWT scheme. Anonymous operations advertise none. A named-only handler does not authenticate operations that use the default handlers. If a named scheme is called bearer, the default bearer component is called arcBearer.
Arc cannot infer the protocol of a custom handler, so it never advertises one as bearer. A native principal is authenticated by the host and has no security scheme in this document.
What the document leaves out
Section titled “What the document leaves out”- HTTP
QUERY. OpenAPI path items cannot represent it, so queries appear as GET only. - The
/.cratisendpoints. Introspection describes those. pathBase. Paths are not rewritten for a standalone hostpathBase.- Every status the runtime can return. The document describes the common statuses, but does not list every possible HTTP response.
GET /openapi.jsonis generated from the same schemas that bind requests and follows the shared discovery access policy.- Register generated metadata to get summaries and typed results.
- Set
generatedApis.openApiVersionto advertise your version.
Next, see how concepts appear in the document.