Documentation Index
Fetch the complete documentation index at: https://mintlify.com/motiadev/motia/llms.txt
Use this file to discover all available pages before exploring further.
HTTP triggers allow you to expose workflows as HTTP endpoints, enabling you to build REST APIs, webhooks, and HTTP-based integrations.
Basic usage
import { step, http } from 'motia'
import { z } from 'zod'
export const config = step({
name: 'get-user',
triggers: [http('GET', '/users/:id')],
})
export const handler = async (input, ctx) => {
const userId = input.request.pathParams.id
return {
status: 200,
body: { id: userId, name: 'John Doe' },
}
}
from motia import step, http
config = step(
name='get-user',
triggers=[http('GET', '/users/:id')],
)
async def handler(input, ctx):
user_id = input['request']['pathParams']['id']
return {
'status': 200,
'body': {'id': user_id, 'name': 'John Doe'},
}
HTTP methods
HTTP triggers support all standard HTTP methods:
import { step, http } from 'motia'
export const config = step({
name: 'api-endpoints',
triggers: [
http('GET', '/items'),
http('POST', '/items'),
http('PUT', '/items/:id'),
http('DELETE', '/items/:id'),
http('PATCH', '/items/:id'),
],
})
from motia import step, http
config = step(
name='api-endpoints',
triggers=[
http('GET', '/items'),
http('POST', '/items'),
http('PUT', '/items/:id'),
http('DELETE', '/items/:id'),
http('PATCH', '/items/:id'),
],
)
Request handling
Path parameters
Extract dynamic values from the URL path:
import { step, http } from 'motia'
export const config = step({
name: 'get-item',
triggers: [http('GET', '/items/:id')],
})
export const handler = async (input, ctx) => {
const { id } = input.request.pathParams
return {
status: 200,
body: { id, name: 'Item' },
}
}
from motia import step, http
config = step(
name='get-item',
triggers=[http('GET', '/items/:id')],
)
async def handler(input, ctx):
item_id = input['request']['pathParams']['id']
return {
'status': 200,
'body': {'id': item_id, 'name': 'Item'},
}
Query parameters
Access query string parameters:
import { step, http } from 'motia'
export const config = step({
name: 'search-items',
triggers: [
http('GET', '/search', {
queryParams: [
{ name: 'q', description: 'Search query' },
{ name: 'limit', description: 'Results limit' },
],
}),
],
})
export const handler = async (input, ctx) => {
const { q, limit } = input.request.queryParams
return {
status: 200,
body: { query: q, limit: limit || '10' },
}
}
from motia import step, http
config = step(
name='search-items',
triggers=[
http(
'GET',
'/search',
query_params=[
{'name': 'q', 'description': 'Search query'},
{'name': 'limit', 'description': 'Results limit'},
],
),
],
)
async def handler(input, ctx):
q = input['request']['queryParams'].get('q')
limit = input['request']['queryParams'].get('limit', '10')
return {
'status': 200,
'body': {'query': q, 'limit': limit},
}
Request body
Handle POST/PUT/PATCH request bodies with schema validation:
import { step, http } from 'motia'
import { z } from 'zod'
const createItemSchema = z.object({
name: z.string(),
price: z.number(),
description: z.string().optional(),
})
export const config = step({
name: 'create-item',
triggers: [
http('POST', '/items', {
bodySchema: createItemSchema,
}),
],
})
export const handler = async (input, ctx) => {
const { name, price, description } = input.request.body
return {
status: 201,
body: {
id: 'item-123',
name,
price,
description,
created: true,
},
}
}
from motia import step, http
config = step(
name='create-item',
triggers=[
http(
'POST',
'/items',
body_schema={
'type': 'object',
'properties': {
'name': {'type': 'string'},
'price': {'type': 'number'},
'description': {'type': 'string'},
},
'required': ['name', 'price'],
},
),
],
)
async def handler(input, ctx):
body = input['request']['body']
return {
'status': 201,
'body': {
'id': 'item-123',
'name': body['name'],
'price': body['price'],
'created': True,
},
}
Access request headers:
export const handler = async (input, ctx) => {
const authHeader = input.request.headers['authorization']
const contentType = input.request.headers['content-type']
return {
status: 200,
body: { authenticated: !!authHeader },
}
}
async def handler(input, ctx):
auth_header = input['request']['headers'].get('authorization')
content_type = input['request']['headers'].get('content-type')
return {
'status': 200,
'body': {'authenticated': bool(auth_header)},
}
Response handling
Status codes
Return different HTTP status codes:
export const handler = async (input, ctx) => {
const { id } = input.request.pathParams
// Simulate item not found
if (id === 'missing') {
return {
status: 404,
body: { error: 'Item not found' },
}
}
return {
status: 200,
body: { id, name: 'Found Item' },
}
}
async def handler(input, ctx):
item_id = input['request']['pathParams']['id']
# Simulate item not found
if item_id == 'missing':
return {
'status': 404,
'body': {'error': 'Item not found'},
}
return {
'status': 200,
'body': {'id': item_id, 'name': 'Found Item'},
}
Set custom response headers:
export const handler = async (input, ctx) => {
return {
status: 200,
headers: {
'X-Custom-Header': 'value',
'Cache-Control': 'max-age=3600',
},
body: { message: 'Success' },
}
}
async def handler(input, ctx):
return {
'status': 200,
'headers': {
'X-Custom-Header': 'value',
'Cache-Control': 'max-age=3600',
},
'body': {'message': 'Success'},
}
Response schema
Define response schemas for type safety:
import { step, http } from 'motia'
import { z } from 'zod'
const successSchema = z.object({
id: z.string(),
name: z.string(),
})
const errorSchema = z.object({
error: z.string(),
})
export const config = step({
name: 'typed-response',
triggers: [
http('GET', '/items/:id', {
responseSchema: {
200: successSchema,
404: errorSchema,
},
}),
],
})
from motia import step, http
config = step(
name='typed-response',
triggers=[
http(
'GET',
'/items/:id',
response_schema={
200: {
'type': 'object',
'properties': {
'id': {'type': 'string'},
'name': {'type': 'string'},
},
},
404: {
'type': 'object',
'properties': {
'error': {'type': 'string'},
},
},
},
),
],
)
Middleware
Add middleware functions for cross-cutting concerns:
import { step, http } from 'motia'
import type { ApiMiddleware } from 'motia'
const authMiddleware: ApiMiddleware = async (req, ctx, next) => {
const token = req.request.headers['authorization']
if (!token) {
return {
status: 401,
body: { error: 'Unauthorized' },
}
}
return await next()
}
const loggingMiddleware: ApiMiddleware = async (req, ctx, next) => {
ctx.logger.info('Request received', {
method: req.request.method,
path: ctx.trigger.path,
})
return await next()
}
export const config = step({
name: 'protected-endpoint',
triggers: [
http('GET', '/protected', {
middleware: [loggingMiddleware, authMiddleware],
}),
],
})
from motia import step, http
async def auth_middleware(req, ctx, next):
token = req['request']['headers'].get('authorization')
if not token:
return {
'status': 401,
'body': {'error': 'Unauthorized'},
}
return await next()
async def logging_middleware(req, ctx, next):
ctx.logger.info('Request received', {
'method': req['request']['method'],
'path': ctx.trigger['path'],
})
return await next()
config = step(
name='protected-endpoint',
triggers=[
http(
'GET',
'/protected',
middleware=[logging_middleware, auth_middleware],
),
],
)
Conditional triggers
Use conditions to selectively execute handlers:
import { step, http } from 'motia'
export const config = step({
name: 'conditional-api',
triggers: [
http(
'POST',
'/webhooks',
undefined,
(input, ctx) => {
const signature = input.request.headers['x-webhook-signature']
return !!signature
},
),
],
})
from motia import step, http
def webhook_condition(input, ctx):
signature = input['request']['headers'].get('x-webhook-signature')
return bool(signature)
config = step(
name='conditional-api',
triggers=[
http('POST', '/webhooks', condition=webhook_condition),
],
)
Configuration options
HTTP method: GET, POST, PUT, DELETE, PATCH, OPTIONS, or HEAD
URL path pattern. Supports path parameters with :param syntax (e.g., /users/:id)
Schema for validating request body. Automatically validates incoming requests
Map of status codes to response schemas for type safety and documentation
Array of query parameter definitions:
name (string): Parameter name
description (string): Parameter description
Array of middleware functions executed before the handler. Each middleware can:
- Modify the request
- Return early with a response
- Call
next() to continue the chain
Optional function (input, ctx) => boolean to conditionally execute the handler
Use cases
REST API
Build a complete REST API:
import { step, http } from 'motia'
import { z } from 'zod'
const itemSchema = z.object({
name: z.string(),
price: z.number(),
})
export const config = step({
name: 'items-api',
triggers: [
http('GET', '/items'),
http('GET', '/items/:id'),
http('POST', '/items', { bodySchema: itemSchema }),
http('PUT', '/items/:id', { bodySchema: itemSchema }),
http('DELETE', '/items/:id'),
],
})
Webhook receiver
Receive webhooks from external services:
import { step, http } from 'motia'
import { z } from 'zod'
const webhookSchema = z.object({
event: z.string(),
data: z.record(z.unknown()),
})
export const config = step({
name: 'github-webhook',
triggers: [
http('POST', '/webhooks/github', {
bodySchema: webhookSchema,
}),
],
})
export const handler = async (input, ctx) => {
const { event, data } = input.request.body
ctx.logger.info('Webhook received', { event })
// Process webhook
await ctx.enqueue({
topic: 'webhook-events',
data: { event, data },
})
return {
status: 200,
body: { received: true },
}
}
File upload
Handle file uploads:
export const config = step({
name: 'upload-file',
triggers: [http('POST', '/upload')],
})
export const handler = async (input, ctx) => {
const file = input.request.body
// Process file
ctx.logger.info('File uploaded', {
size: file.length,
})
return {
status: 201,
body: { uploaded: true, fileId: 'file-123' },
}
}