OpenAPI
Every Slingshot app ships an OpenAPI document and a browser UI automatically.
GET /openapi.jsonreturns the generated spec.GET /docsserves the Scalar API reference.
The framework currently emits OpenAPI 3.0.0, not 3.1, and the docs UI is Scalar rather than Swagger UI.
Writing typed routes
Section titled “Writing typed routes”Use createRoute() to declare request and response schemas, then mount the route with router.openapi(). Slingshot registers the route with Hono, validates the request at runtime, and includes the route in the generated spec.
import { createRoute, createRouter, cursorParams, getActor, withSecurity,} from '@lastshotlabs/slingshot';
export const router = createRouter();
router.openapi( createRoute({ method: 'get', path: '/products', tags: ['Products'], summary: 'List products', request: { query: cursorParams({ limit: 25, maxLimit: 100 }), }, responses: { 200: { description: 'Cursor-paginated product list', content: { 'application/json': { schema: { type: 'object', properties: { items: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' }, price: { type: 'number' }, }, required: ['id', 'name', 'price'], }, }, nextCursor: { type: 'string', nullable: true }, hasMore: { type: 'boolean' }, }, required: ['items', 'nextCursor', 'hasMore'], }, }, }, }, }, }), c => { void c.req.valid('query'); return c.json({ items: [], nextCursor: null, hasMore: false }, 200) as never; },);
router.openapi( withSecurity( createRoute({ method: 'get', path: '/me', tags: ['Account'], summary: 'Get the current user', responses: { 200: { description: 'Current user', content: { 'application/json': { schema: { type: 'object', properties: { userId: { type: 'string' }, }, required: ['userId'], }, }, }, }, }, }), { cookieAuth: [] }, { userToken: [] }, ), c => c.json({ userId: getActor(c).id ?? 'anonymous' }, 200) as never,);Routes discovered from routesDir are added automatically. You do not need to manually register them with the spec generator.
Reusing schemas
Section titled “Reusing schemas”For shared schemas, prefer registerSchema() or registerSchemas() so the same component name is reused across every route that references it.
import { z } from 'zod';
export const ErrorResponse = z.object({ error: z.string(),});
export const ValidationErrorResponse = z.object({ error: z.string(), details: z.array( z.object({ path: z.string(), message: z.string(), }), ), requestId: z.string(),});Unnamed schemas still work. createRoute() auto-registers JSON request and response schemas under generated component names when it can.
Security metadata
Section titled “Security metadata”withSecurity() only annotates the OpenAPI document. It does not enforce access control by itself. Pair it with real middleware such as userAuth, requireRole(...), or framework-generated auth routes.
The built-in security scheme names are:
cookieAuthfor the session cookieuserTokenfor thex-user-tokenheaderbearerAuthfor bearer-token middleware
Generated entity routes
Section titled “Generated entity routes”Routes generated by createEntityPlugin() also appear in the OpenAPI output. The framework derives request and response schemas from the entity field definitions.
import { defineEntity, field } from '@lastshotlabs/slingshot';
export const Post = defineEntity('Post', { fields: { id: field.string({ primary: true, default: 'uuid' }), title: field.string(), body: field.string(), }, routes: { defaults: { auth: 'userAuth' }, get: { auth: 'none' }, list: { auth: 'none' }, },});For a default entity mount, Slingshot generates POST, GET list, GET :id, PATCH :id, and DELETE :id routes and includes all of them in the spec.
Versioned specs
Section titled “Versioned specs”When you enable versioning, Slingshot mounts a separate spec and docs UI per API version.
import { defineApp } from '@lastshotlabs/slingshot';
export default defineApp({ meta: { name: 'Slingshot Example API', version: '1.0.0', }, routesDir: `${import.meta.dir}/routes`, versioning: { versions: ['v1', 'v2'], defaultVersion: 'v2', },});That produces:
GET /v1/openapi.jsonGET /v1/docsGET /v2/openapi.jsonGET /v2/docsGET /openapi.json -> redirects to the default versionGET /docs -> version selector pageCustomizing the document title and version
Section titled “Customizing the document title and version”The generated OpenAPI document reads its info.title and info.version from meta.name and meta.version.
import { defineApp } from '@lastshotlabs/slingshot';
export default defineApp({ meta: { name: 'My API', version: '2026-04-12', }, routesDir: `${import.meta.dir}/routes`,});See also
Section titled “See also”- Quick Start for a minimal
createRoute()example - Error Handling for shared response schemas
- Testing for route-level tests that exercise the generated handlers