# registerApiRoute()

The `registerApiRoute()` function creates custom HTTP routes that integrate with the Mastra server. Routes can include OpenAPI metadata to appear in the Swagger UI documentation.

## Import

```typescript
import { registerApiRoute } from '@mastra/core/server'
```

## Parameters

### path

The URL path for the route. Supports path parameters using `:param` syntax.

```typescript
registerApiRoute("/items/:itemId", { ... })
```

**Note:** Custom route paths can't start with the server's configured `apiPrefix` (default: `/api`), as that prefix is reserved for built-in Mastra routes. If you set a custom `apiPrefix`, only that prefix is reserved — for example, with `apiPrefix: '/mastra/api'`, paths like `/api/my-endpoint` are allowed.

### options

**method** (`'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'ALL'`): HTTP method for the route

**handler** (`Handler`): Route handler function receiving Hono Context. Use either \`handler\` or \`createHandler\`, not both.

**createHandler** (`(c: Context) => Promise<Handler>`): Async factory function to create the handler. Use either \`handler\` or \`createHandler\`, not both.

**middleware** (`MiddlewareHandler | MiddlewareHandler[]`): Route-specific middleware functions

**cors** (`CorsOptions`): Route-specific CORS configuration. Use this when one custom route needs a different cross-origin policy from \`server.cors\`.

**openapi** (`DescribeRouteOptions`): OpenAPI metadata for Swagger UI documentation

## OpenAPI options

The `openapi` property accepts standard OpenAPI 3.1 operation fields from [hono-openapi](https://github.com/honojs/middleware/tree/main/packages/openapi). Routes without an `openapi` property aren't included in Swagger UI.

**summary** (`string`): Short summary of the operation

**description** (`string`): Detailed description of the operation

**tags** (`string[]`): Tags for grouping in Swagger UI. Defaults to \['custom'] if not specified.

**deprecated** (`boolean`): Mark the operation as deprecated

**parameters** (`ParameterObject[]`): Path, query, and header parameters

**requestBody** (`RequestBodyObject`): Request body specification

**responses** (`ResponsesObject`): Response specifications by status code

**security** (`SecurityRequirementObject[]`): Security requirements for the operation

## Return value

Returns an `ApiRoute` object to be passed to `server.apiRoutes` in the Mastra configuration.

## Handler context

The handler receives a Hono `Context` object with access to:

```typescript
handler: async c => {
  // Get the Mastra instance
  const mastra = c.get('mastra')

  // Get request context
  const requestContext = c.get('requestContext')

  // Access path parameters
  const itemId = c.req.param('itemId')

  // Access query parameters
  const filter = c.req.query('filter')

  // Access request body
  const body = await c.req.json()

  // Return JSON response
  return c.json({ data: 'value' })
}
```

## Examples

### Basic GET Route

```typescript
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'

export const mastra = new Mastra({
  server: {
    apiRoutes: [
      registerApiRoute('/health-check', {
        method: 'GET',
        handler: async c => {
          return c.json({ status: 'ok' })
        },
      }),
    ],
  },
})
```

### Route with Path Parameters

```typescript
registerApiRoute('/users/:userId/posts/:postId', {
  method: 'GET',
  handler: async c => {
    const userId = c.req.param('userId')
    const postId = c.req.param('postId')

    return c.json({ userId, postId })
  },
})
```

### POST Route with Body

```typescript
registerApiRoute('/items', {
  method: 'POST',
  handler: async c => {
    const body = await c.req.json()
    const mastra = c.get('mastra')

    // Process the request...

    return c.json({ id: 'new-id', ...body }, 201)
  },
})
```

### Route with Middleware

```typescript
registerApiRoute('/protected', {
  method: 'GET',
  middleware: [
    async (c, next) => {
      const token = c.req.header('Authorization')
      if (!token) {
        return c.json({ error: 'Unauthorized' }, 401)
      }
      await next()
    },
  ],
  handler: async c => {
    return c.json({ data: 'protected content' })
  },
})
```

### Route with CORS

Use route-specific CORS when one custom route needs cross-origin credentials, but the rest of the server should keep the global CORS policy.

```typescript
registerApiRoute('/customer-webhook', {
  method: 'POST',
  cors: {
    origin: ['https://customer-saas.example'],
    credentials: true,
  },
  handler: async c => {
    return c.json({ ok: true })
  },
})
```

### Route with OpenAPI Documentation

```typescript
import { z } from 'zod'

const ItemSchema = z.object({
  id: z.string(),
  name: z.string(),
  price: z.number(),
})

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: ItemSchema, // Zod schemas are converted to JSON Schema during OpenAPI generation
          },
        },
      },
      404: {
        description: 'Item not found',
      },
    },
  },
  handler: async c => {
    const itemId = c.req.param('itemId')
    return c.json({ id: itemId, name: 'Example', price: 9.99 })
  },
})
```

### Using `createHandler()`

For routes that need async initialization:

```typescript
registerApiRoute('/dynamic', {
  method: 'GET',
  createHandler: async c => {
    // Perform async setup
    const config = await loadConfig()

    return async c => {
      return c.json({ config })
    }
  },
})
```

## Error handling

Throw errors with status codes using Hono's `HTTPException`:

```typescript
import { HTTPException } from 'hono/http-exception'

registerApiRoute('/items/:itemId', {
  method: 'GET',
  handler: async c => {
    const itemId = c.req.param('itemId')
    const item = await findItem(itemId)

    if (!item) {
      throw new HTTPException(404, { message: 'Item not found' })
    }

    return c.json(item)
  },
})
```

## Related

- [Custom API Routes Guide](https://mastra.ai/docs/server/custom-api-routes): Usage guide with examples
- [Server Middleware](https://mastra.ai/docs/server/middleware): Global middleware configuration
- [createRoute()](https://mastra.ai/reference/server/create-route): Type-safe route creation for server adapters
- [Server Routes](https://mastra.ai/reference/server/routes): Built-in Mastra server routes