# Custom API Routes By default, Mastra automatically exposes registered agents and workflows via its server. For additional behavior you can define your own HTTP routes. Routes are provided with a helper `registerApiRoute()` from `@mastra/core/server`. Routes can live in the same file as the `Mastra` instance but separating them helps keep configuration concise. ```typescript import { Mastra } from "@mastra/core"; import { registerApiRoute } from "@mastra/core/server"; export const mastra = new Mastra({ server: { apiRoutes: [ registerApiRoute("/my-custom-route", { method: "GET", handler: async (c) => { const mastra = c.get("mastra"); const agent = await mastra.getAgent("my-agent"); return c.json({ message: "Custom route" }); }, }), ], }, }); ``` Once registered, a custom route will be accessible from the root of the server. For example: ```bash curl http://localhost:4111/my-custom-route ``` Each route's handler receives the Hono `Context`. Within the handler you can access the `Mastra` instance to fetch or call agents and workflows. ## Middleware To add route-specific middleware pass a `middleware` array when calling `registerApiRoute()`. ```typescript import { Mastra } from "@mastra/core"; import { registerApiRoute } from "@mastra/core/server"; export const mastra = new Mastra({ server: { apiRoutes: [ registerApiRoute("/my-custom-route", { method: "GET", middleware: [ async (c, next) => { console.log(`${c.req.method} ${c.req.url}`); await next(); }, ], handler: async (c) => { return c.json({ message: "Custom route with middleware" }); }, }), ], }, }); ``` ## OpenAPI Documentation Custom routes can include OpenAPI metadata to appear in the Swagger UI alongside Mastra server routes. Pass an `openapi` option with standard OpenAPI operation fields. ```typescript import { Mastra } from "@mastra/core"; import { registerApiRoute } from "@mastra/core/server"; import { z } from "zod"; export const mastra = new Mastra({ server: { apiRoutes: [ registerApiRoute("/items/:itemId", { method: "GET", openapi: { summary: "Get item by ID", description: "Retrieves a single item by its unique identifier", tags: ["Items"], parameters: [ { name: "itemId", in: "path", required: true, description: "The item ID", schema: { type: "string" }, }, ], responses: { 200: { description: "Item found", content: { "application/json": { schema: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, }, }, }, }, }, 404: { description: "Item not found", }, }, }, handler: async (c) => { const itemId = c.req.param("itemId"); return c.json({ id: itemId, name: "Example Item" }); }, }), ], }, }); ``` ### Using Zod Schemas Zod schemas in the `openapi` configuration are converted to JSON Schema when the OpenAPI document is generated: ```typescript import { Mastra } from "@mastra/core"; import { registerApiRoute } from "@mastra/core/server"; import { z } from "zod"; const ItemSchema = z.object({ id: z.string(), name: z.string(), price: z.number(), }); const CreateItemSchema = z.object({ name: z.string().min(1), price: z.number().positive(), }); export const mastra = new Mastra({ server: { apiRoutes: [ registerApiRoute("/items", { method: "POST", openapi: { summary: "Create a new item", tags: ["Items"], requestBody: { required: true, content: { "application/json": { schema: CreateItemSchema, }, }, }, responses: { 201: { description: "Item created", content: { "application/json": { schema: ItemSchema, }, }, }, }, }, handler: async (c) => { const body = await c.req.json(); return c.json({ id: "new-id", ...body }, 201); }, }), ], }, }); ``` ### Viewing in Swagger UI When running in development mode (`mastra dev`) or with `swaggerUI: true` in build options, your custom routes appear in the Swagger UI at `/swagger-ui`. ```typescript export const mastra = new Mastra({ server: { build: { swaggerUI: true, // Enable in production builds }, apiRoutes: [ // Your routes... ], }, }); ``` ## Authentication When authentication is configured on your Mastra server, custom API routes require authentication by default. To make a route publicly accessible, set `requiresAuth: false`: ```typescript import { Mastra } from "@mastra/core"; import { registerApiRoute } from "@mastra/core/server"; import { MastraJwtAuth } from "@mastra/auth"; export const mastra = new Mastra({ server: { auth: new MastraJwtAuth({ secret: process.env.MASTRA_JWT_SECRET, }), apiRoutes: [ // Protected route (default behavior) registerApiRoute("/protected-data", { method: "GET", handler: async (c) => { // Access authenticated user from request context const user = c.get("requestContext").get("user"); return c.json({ message: "Authenticated user", user }); }, }), // Public route (no authentication required) registerApiRoute("/webhooks/github", { method: "POST", requiresAuth: false, // Explicitly opt out of authentication handler: async (c) => { const payload = await c.req.json(); // Process webhook without authentication return c.json({ received: true }); }, }), ], }, }); ``` ### Authentication behavior - **No auth configured**: All routes (built-in and custom) are public - **Auth configured**: - Mastra-provided routes (`/api/agents/*`, `/api/workflows/*`, etc.) require authentication - Custom routes require authentication by default - Custom routes can opt out with `requiresAuth: false` ### Accessing user information When a request is authenticated, the user object is available in the request context: ```typescript registerApiRoute("/user-profile", { method: "GET", handler: async (c) => { const requestContext = c.get("requestContext"); const user = requestContext.get("user"); return c.json({ user }); }, }) ``` For more information about authentication providers, see the [Auth documentation](https://mastra.ai/docs/server/auth). ## Related - [registerApiRoute() Reference](https://mastra.ai/reference/server/register-api-route) - Full API reference - [Server Middleware](https://mastra.ai/docs/server/middleware) - Global middleware configuration - [Mastra Server](https://mastra.ai/docs/server/mastra-server) - Server configuration options