Skip to main content

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' },
  }
}

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'),
  ],
})

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' },
  }
}

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' },
  }
}

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,
    },
  }
}

Headers

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 },
  }
}

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' },
  }
}

Response headers

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' },
  }
}

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,
      },
    }),
  ],
})

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],
    }),
  ],
})

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
      },
    ),
  ],
})

Configuration options

method
string
required
HTTP method: GET, POST, PUT, DELETE, PATCH, OPTIONS, or HEAD
path
string
required
URL path pattern. Supports path parameters with :param syntax (e.g., /users/:id)
bodySchema
ZodSchema | JsonSchema
Schema for validating request body. Automatically validates incoming requests
responseSchema
Record<number, Schema>
Map of status codes to response schemas for type safety and documentation
queryParams
QueryParam[]
Array of query parameter definitions:
  • name (string): Parameter name
  • description (string): Parameter description
middleware
ApiMiddleware[]
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
condition
function
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' },
  }
}