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.
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:
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.
MiddlewareDirect link to Middleware
To add route-specific middleware pass a middleware array when calling registerApiRoute().
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 DocumentationDirect link to 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.
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 SchemasDirect link to Using Zod Schemas
Zod schemas in the openapi configuration are converted to JSON Schema when the OpenAPI document is generated:
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 UIDirect link to 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.
export const mastra = new Mastra({
server: {
build: {
swaggerUI: true, // Enable in production builds
},
apiRoutes: [
// Your routes...
],
},
});
AuthenticationDirect link to 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:
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 behaviorDirect link to 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
- Mastra-provided routes (
Accessing user informationDirect link to Accessing user information
When a request is authenticated, the user object is available in the request context:
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.
RelatedDirect link to Related
- registerApiRoute() Reference - Full API reference
- Server Middleware - Global middleware configuration
- Mastra Server - Server configuration options