API Reference
All automation endpoints are mounted at /api/automations/* and require authentication via Bearer token.
Base Path: /api/automations
Authentication: All endpoints require Authorization: Bearer <access_token> header.
Authorization: Most endpoints require owner or admin role on the podcast (not member).
Templates Endpoints
List Templates
Retrieves all templates for a podcast.
GET /api/automations/templates?podcast_id={id}Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
podcast_id | UUID | Yes | Podcast ID to filter by |
limit | number | No | Max results (default: 100) |
offset | number | No | Pagination offset (default: 0) |
category | string | No | Filter by category |
Response:
{
"templates": [
{
"id": "uuid",
"podcast_id": "uuid",
"name": "Booking Confirmation",
"description": "Sent when a booking is confirmed",
"category": "booking",
"subject": "Recording Confirmed: {episode_title}",
"body_html": "<p>Hi {guest_first_name}...</p>",
"body_text": "Hi {guest_first_name}...",
"created_at": "2025-01-07T10:00:00Z",
"updated_at": "2025-01-07T10:00:00Z"
}
],
"total": 5
}Create Template
Creates a new email template.
POST /api/automations/templatesRequest Body:
{
"podcast_id": "uuid",
"name": "Booking Confirmation",
"description": "Sent when a booking is confirmed",
"category": "booking",
"subject": "Recording Confirmed: {episode_title}",
"body_html": "<p>Hi {guest_first_name}...</p>",
"body_text": "Hi {guest_first_name}..."
}Response: 201 Created
{
"template": {
/* template object */
}
}Get Template
Retrieves a single template by ID.
GET /api/automations/templates/:idResponse:
{
"template": {
/* template object */
}
}Update Template
Updates an existing template.
PUT /api/automations/templates/:idRequest Body: Same as create (all fields optional).
Response:
{
"template": {
/* updated template object */
}
}Delete Template
Deletes a template.
DELETE /api/automations/templates/:idResponse: 200 OK
{
"success": true
}Preview Template
Renders a template with sample data without saving. This endpoint does not require a template ID - it previews arbitrary content.
POST /api/automations/templates/previewRequest Body:
{
"subject": "Recording Confirmed: {episode_title}",
"body_html": "<p>Hi {guest_first_name}...</p>",
"sample_data": {
"guest_first_name": "John",
"episode_title": "My Great Episode"
}
}Response:
{
"preview": {
"subject": "Recording Confirmed: My Great Episode",
"body_html": "<p>Hi John...</p>",
"body_text": "Hi John..."
},
"validation": {
"is_valid": true,
"used_tags": ["guest_first_name", "episode_title"],
"invalid_tags": [],
"missing_context": [],
"errors": []
}
}Rules Endpoints
List Rules
Retrieves all automation rules for a podcast.
GET /api/automations/rules?podcast_id={id}Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
podcast_id | UUID | Yes | Podcast ID to filter by |
is_enabled | boolean | No | Filter by enabled status |
Response:
{
"rules": [
{
"id": "uuid",
"podcast_id": "uuid",
"name": "Booking Confirmation Workflow",
"description": "Send confirmation when booking is confirmed",
"trigger_type": "booking.confirmed",
"trigger_config": {},
"workflow_data": { "nodes": [...], "connections": [...] },
"is_enabled": true,
"is_paused": false,
"created_at": "2025-01-07T10:00:00Z",
"updated_at": "2025-01-07T10:00:00Z",
"automation_actions": [
{
"id": "uuid",
"action_type": "send_email",
"action_config": { "template_id": "uuid" }
}
]
}
],
"total": 3
}Create Rule
Creates a new automation rule.
POST /api/automations/rulesRequest Body:
{
"podcast_id": "uuid",
"name": "Booking Confirmation Workflow",
"description": "Send confirmation when booking is confirmed",
"trigger_type": "booking.confirmed",
"trigger_config": {},
"workflow_data": {
"nodes": [...],
"connections": [...]
},
"is_enabled": true,
"actions": [
{
"action_type": "send_email",
"action_config": {
"template_id": "uuid",
"to": "guest"
},
"execution_order": 0
}
]
}Response: 201 Created
{
"rule": {
/* rule object with actions */
}
}Get Rule
Retrieves a single rule with its actions.
GET /api/automations/rules/:idResponse:
{
"rule": {
/* rule object with automation_actions */
}
}Update Rule
Updates an existing rule.
PUT /api/automations/rules/:idRequest Body: Same as create (all fields optional).
Response:
{
"rule": {
/* updated rule object */
}
}Delete Rule
Deletes a rule and all its actions/scheduled jobs.
DELETE /api/automations/rules/:idResponse: 200 OK
{
"success": true
}Toggle Rule
Enables or disables a rule.
POST /api/automations/rules/:id/toggleRequest Body (optional):
{
"is_enabled": true
}If no body provided, toggles the current state.
Response:
{
"rule": {
/* rule with updated is_enabled */
}
}Executions Endpoints
List Executions
Retrieves execution history for a podcast.
GET /api/automations/executions?podcast_id={id}Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
podcast_id | UUID | Yes | Podcast ID to filter by |
rule_id | UUID | No | Filter by rule |
status | string | No | Filter by status |
limit | number | No | Max results (default: 50) |
offset | number | No | Pagination offset (default: 0) |
Response:
{
"executions": [
{
"id": "uuid",
"rule_id": "uuid",
"podcast_id": "uuid",
"trigger_type": "booking.confirmed",
"status": "completed",
"context_data": {
"guest_first_name": "John",
"episode_title": "Great Episode"
},
"action_results": [
{
"action_type": "send_email",
"status": "completed",
"result": { "message_id": "..." }
}
],
"error_message": null,
"started_at": "2025-01-07T10:00:00Z",
"completed_at": "2025-01-07T10:00:05Z",
"created_at": "2025-01-07T10:00:00Z",
"automation_rules": {
"name": "Booking Confirmation Workflow"
}
}
],
"total": 100
}Get Execution
Retrieves a single execution with full details.
GET /api/automations/executions/:idResponse:
{
"execution": {
/* execution object */
}
}Retry Execution
Retries a failed execution.
POST /api/automations/executions/:id/retryResponse:
{
"execution": {
/* new execution object with status: pending */
}
}Get Execution Stats
Retrieves aggregated execution statistics.
GET /api/automations/executions/stats?podcast_id={id}Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
podcast_id | UUID | Yes | Podcast ID |
days | number | No | Days to look back (default: 30) |
Response:
{
"stats": {
"total": 150,
"completed": 142,
"failed": 5,
"pending": 3,
"by_trigger": {
"booking.confirmed": 80,
"time.before_recording": 50,
"episode.published": 20
},
"by_action": {
"send_email": 145,
"send_webhook": 5
}
}
}Error Responses
All endpoints return errors in a consistent format:
{
"error": "Error message description"
}Common HTTP Status Codes:
| Code | Meaning |
|---|---|
400 | Bad Request - Invalid input or missing required fields |
401 | Unauthorized - Missing or invalid auth token |
403 | Forbidden - User lacks permission for this podcast |
404 | Not Found - Resource doesn't exist |
409 | Conflict - Duplicate resource (e.g., idempotency key) |
500 | Internal Server Error - Server-side error |
TypeScript Client Usage
Using the Hono RPC client:
import { createApiClient } from '$api/client';
import { createClient } from '$lib/supabase/client';
// Get auth token
const supabase = createClient();
const {
data: { session }
} = await supabase.auth.getSession();
// Create client
const client = createApiClient(fetch);
// List templates
const response = await client.api.automations.templates.$get(
{ query: { podcast_id: 'uuid' } },
{ headers: { Authorization: `Bearer ${session.access_token}` } }
);
// Create rule
const response = await client.api.automations.rules.$post(
{
json: {
podcast_id: 'uuid',
name: 'My Automation',
trigger_type: 'booking.confirmed',
workflow_data: { nodes: [], connections: [] }
}
},
{ headers: { Authorization: `Bearer ${session.access_token}` } }
);
// Toggle rule
const response = await client.api.automations.rules[':id'].toggle.$post(
{ param: { id: 'rule-uuid' } },
{ headers: { Authorization: `Bearer ${session.access_token}` } }
);Rate Limiting
The API implements rate limiting per podcast:
| Action Type | Limit | Window |
|---|---|---|
send_email | 100 | 1 hour |
send_webhook | 500 | 1 hour |
update_field | 200 | 1 hour |
When rate limited, the API returns:
{
"error": "Rate limit exceeded for send_email. Try again in X minutes."
}