Skip to content

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.

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.

Terminal window
curl http://127.0.0.1:3000/openapi.json

For 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.

  • Identity. A namespace-qualified operationId, such as Tasks.Registration.RegisterTask, a tag for the namespace, and a summary. Route paths follow endpoint mapping. Every command also has a POST <route>/validate operation, with :validate appended to its execute operationId (for example, Tasks.Registration.RegisterTask:validate), even if the command itself is named Validate.
  • Input. Execute and validate share the same required JSON request body and security requirements. Queries expose their arguments as GET parameters, with required taken from the input schema. Queries with declared array or queryPage results also advertise page, pageSize, sortBy, and sortDirection. Observable queries add waitForFirstResult and waitForFirstResultTimeout.
  • Responses. The Arc CommandResult or QueryResult envelope 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 typed response. Observable queries also describe 202, 408, and 503, and a text/event-stream response. A paged result carries paging with page, size, totalItems, and totalPages.
  • Security. HTTP bearer security, when the operation authenticates with a jwtBearer() handler.
PageWhat it covers
ConceptsConcepts, GUIDs, dates, and models described as the JSON value they carry
CommandsThe POST operation, request body, CommandResult envelope, and typed response
QueriesGET parameters, paging and sorting, observable options, and the QueryResult envelope
Enums@enumeration values for numeric and string enums
Model-bound and low-level operationsWhere each part of an operation comes from, for decorators and define* definitions
How types appear in the documentOne 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 summary directly.
  • Result types. The 200 envelope includes a typed response or data only when the metadata declares the return type. Without it, the document omits response or data rather 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:

allTasks.ts
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.

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' } });

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.

  • HTTP QUERY. OpenAPI path items cannot represent it, so queries appear as GET only.
  • The /.cratis endpoints. Introspection describes those.
  • pathBase. Paths are not rewritten for a standalone host pathBase.
  • Every status the runtime can return. The document describes the common statuses, but does not list every possible HTTP response.
  • GET /openapi.json is 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.openApiVersion to advertise your version.

Next, see how concepts appear in the document.