API Reference
Complete reference for the CFFBRW API.
Base URL: https://api.cffbrw.com
Authentication
All API endpoints (except webhooks) require authentication. Include your API key in the Authorization header:
Authorization: Bearer wfk_your-api-key-here
API keys are managed in the dashboard under Settings → API Keys.
Note: API keys can only trigger published workflows. To run draft workflows, use the dashboard.
Workflows
POST /v1/workflows
Create a new workflow.
Request body:
{
"name": "HN Trend Analyzer",
"markdown": "# HN Trend Analyzer\n\n## Steps\n\n### 1. Fetch top stories\n..."
}
Response:
{
"id": "wf_abc123",
"name": "HN Trend Analyzer",
"created_at": "2026-04-03T10:00:00Z",
"updated_at": "2026-04-03T10:00:00Z"
}
GET /v1/workflows
List all workflows in your workspace.
Query parameters:
| Param | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20, max: 100) |
GET /v1/workflows/:id
Get a single workflow by ID.
PUT /v1/workflows/:id
Update a workflow's name or markdown content.
DELETE /v1/workflows/:id
Delete a workflow.
Compilation
POST /v1/workflows/:id/compile
Compile workflow markdown into an execution plan.
Request body:
{
"markdown": "# My Workflow\n\n## Steps\n\n### 1. Fetch data\n..."
}
Response:
{
"plan": {
"steps": [
{
"id": "step_0",
"type": "http_request",
"config": {
"method": "GET",
"url": "https://api.example.com/data"
},
"dependsOn": []
}
]
},
"planHash": "sha256:abc123...",
"aiCost": 10
}
Credits: Compilation costs 10 credits. Cached plans (same markdown content) cost 0.
Runs
POST /v1/workflows/:id/run
Execute a workflow. The workflow must be published when using API keys.
Request body:
{
"variables": {
"API_KEY": "sk-...",
"REPO": "owner/repo"
}
}
Response:
{
"runId": "run_xyz789",
"status": "running",
"planHash": "sha256:abc123..."
}
GET /v1/runs/:id
Get run status and results.
Response:
{
"id": "run_xyz789",
"workflowId": "wf_abc123",
"status": "completed",
"startedAt": "2026-04-03T10:00:00Z",
"completedAt": "2026-04-03T10:00:05Z",
"steps": [
{
"id": "step_0",
"status": "completed",
"duration": 234,
"output": {}
}
]
}
GET /v1/runs
List all runs.
Query parameters:
| Param | Type | Description |
|---|---|---|
workflowId | string | Filter by workflow |
status | string | Filter: running, completed, failed, pending_approval |
page | number | Page number |
limit | number | Items per page |
POST /v1/runs/:runId/approve/:stepIndex
Approve, reject, or retry a human approval gate. Accepts Clerk JWT auth or a valid signed review token (from the public approval page).
Request body:
{
"action": "approve",
"comment": "Looks good, proceed"
}
| Field | Type | Description |
|---|---|---|
action | string | "approve", "reject", or "retry_with_hint" |
comment | string? | Optional comment (available to downstream steps) |
reviewToken | string? | Signed review token (for public page auth bypass) |
GET /v1/approve/:token
Signed token approval link. Three token types:
- review — Renders a public approval page with step context
- approve — One-click approve, returns confirmation page
- reject — One-click reject, returns confirmation page
POST /v1/approve/:token
Form submission from the public review page. Accepts action (approve/reject) and optional comment.
Triggers
POST /v1/trigger/:workflowId
Trigger a published workflow via webhook. Accepts wfk_ API key or API_SECRET auth.
Request body:
{
"variables": {
"message": "Hello from webhook"
}
}
POST /v1/workflows/:id/schedule
Set a cron schedule for a workflow.
Request body:
{
"cron": "0 9 * * 1-5"
}
GET /v1/workflows/:id/schedule
Get the current cron schedule.
DELETE /v1/workflows/:id/schedule
Remove the cron schedule.
Queue Trigger
POST /v1/enqueue/:workflowId
Enqueue a message onto a queue-triggered workflow. The platform batches messages and fires one run per drain when either the time window elapses or the batch size is reached, whichever is first.
Auth: optional per-workflow X-Webhook-Secret header (set on the trigger config). If the workflow has a secret configured, requests without it return 401. If blank, the endpoint is public.
Request body: any JSON under 64 KB. Two accepted shapes:
{ "event": "lead.created", "email": "alice@example.com" }
or wrapped with optional metadata:
{
"payload": { "event": "lead.created", "email": "alice@example.com" },
"source": "webhook-A",
"dedupeKey": "order-123"
}
dedupeKey is optional. When provided, the platform rejects any subsequent enqueue with the same key while the message is still in the queue (not yet drained). Use it to prevent duplicate processing when your producer retries on network errors. The key is scoped to the queue — the same key can be reused after the message is drained.
Response 200:
{ "enqueued": true, "queueDepth": 4, "msgId": 42 }
Errors:
401 UNAUTHORIZED— wrong or missingX-Webhook-Secret404 NOT_FOUND— workflow not found or not published409 NOT_QUEUE_TRIGGER— workflow's trigger type is notqueue409 DEDUPE— a message with the samededupeKeyis already in the queue413 PAYLOAD_TOO_LARGE— body over 64 KB429 QUEUE_FULL— queue atmaxQueueDepth. Response includesdetails.queueDepthanddetails.maxQueueDepth.
GET /v1/queues/:workflowId
Inspect queue state. Clerk-authenticated (dashboard session).
Response:
{
"depth": 3,
"inFlightRunId": "run_abc...",
"nextFireAt": 1777030154222,
"oldestMsgAt": 1777029800000,
"deadLetterCount": 0
}
GET /v1/queues/:workflowId/messages?limit=50
List queued (not yet drained) messages. Returns up to 200 via limit query param.
DELETE /v1/queues/:workflowId/messages/:id
Remove a single queued message. :id must be a positive integer.
DELETE /v1/queues/:workflowId
Purge all queued messages. Returns { "purged": N }. Admin escape hatch.
GET /v1/queues/:workflowId/dead-letter?limit=50
List dead-letter entries, messages whose dispatched run terminally failed or whose batch exceeded the platform's per-run size cap. Each entry includes batch_id, run_id, failed_at, failure_reason.
POST /v1/queues/:workflowId/dead-letter/replay
Re-enqueue dead-letter messages. Request body:
{ "ids": [5, 9, 11] }
or
{ "all": true }
Replay preserves each message's original received_at. If replaying would push currentDepth + replayed > maxQueueDepth, returns 409 REPLAY_EXCEEDS_CAPACITY with details, paginate instead of silently truncating.
DELETE /v1/queues/:workflowId/dead-letter/:id
Permanently purge a single dead-letter entry. :id must be a positive integer.
MCP Servers
POST /v1/mcp/servers
Register an MCP server.
Request body:
{
"name": "my-slack-server",
"url": "https://mcp.example.com/slack",
"headers": {
"Authorization": "Bearer xoxb-..."
}
}
GET /v1/mcp/servers
List registered MCP servers.
GET /v1/mcp/servers/:id/tools
List available tools from an MCP server.
DELETE /v1/mcp/servers/:id
Unregister an MCP server.
Usage & Plans
GET /v1/usage
Get current period usage summary.
GET /v1/plan
Get the current workspace's plan and limits.
Response:
{
"tier": "pro",
"credits": { "used": 320, "total": 1000, "remaining": 680 },
"workflows": { "used": 12, "total": 50 },
"runs": { "used": 150, "total": 500 },
"mcpServers": { "used": 3, "total": 10 }
}
Error Responses
All errors follow this format:
{
"error": "Error message describing what went wrong",
"code": "ERROR_CODE"
}
| HTTP Status | Meaning |
|---|---|
| 400 | Bad request — invalid input |
| 401 | Unauthorized — missing or invalid API key |
| 403 | Forbidden — insufficient permissions or unpublished workflow |
| 404 | Not found — resource doesn't exist |
| 429 | Rate limited — too many requests |
| 500 | Internal server error |