API Reference
The RalphFlow dashboard exposes a REST API and WebSocket interface on http://127.0.0.1:4242 (default port, customizable with -p).
App Management
List Apps
GET /api/appsReturns all apps with their loop metadata.
Response 200
[
{
"appName": "code-implementation",
"appType": "code-implementation",
"description": "Three-loop pipeline for code projects",
"loops": [
{
"key": "story-loop",
"name": "Story",
"order": 0,
"stages": ["analyze", "prioritize"],
"multiAgent": false,
"model": "claude-sonnet-4-6"
}
]
}
]Create App
POST /api/appsCreate a new app from a template (built-in or custom).
Request Body
{ "template": "code-implementation", "name": "my-project" }Response 201
{
"ok": true,
"appName": "my-project",
"warning": null,
"commands": ["npx ralphflow run story-loop -f my-project"]
}| Status | Meaning |
|---|---|
201 | App created |
400 | Invalid name or template |
409 | App already exists |
Delete App
DELETE /api/apps/:appDeletes the app directory and cleans up SQLite loop_state rows.
Response 200
{ "ok": true, "appName": "my-project" }Archive App
POST /api/apps/:app/archiveSnapshots the full app directory to .ralph-flow/.archives/<app>/<timestamp>/, then resets the app in place:
- Tracker files revert to template state
- Data files (stories.md, tasks.md) reset to headers only
.agents/directories and lock files are cleaned up- SQLite
loop_staterows are deleted - Prompt files and
ralphflow.yamlare preserved
Timestamps use the format YYYY-MM-DD_HH-mm. Same-minute collisions append a sequence suffix (e.g., 2026-03-14_15-30-2).
Response 200
{ "ok": true, "archivePath": ".ralph-flow/.archives/my-project/2026-03-14_15-30", "timestamp": "2026-03-14_15-30" }List Archives
GET /api/apps/:app/archivesReturns all archived snapshots, sorted newest-first.
Response 200
[
{
"timestamp": "2026-03-14_15-30",
"summary": { "storyCount": 5, "taskCount": 12 },
"fileCount": 18
}
]Returns an empty array (not an error) when no archives exist.
List Archive Files
GET /api/apps/:app/archives/:timestamp/filesReturns a recursive file listing within a specific archive.
Response 200
[
{ "path": "ralphflow.yaml", "isDirectory": false },
{ "path": "00-story-loop/prompt.md", "isDirectory": false }
]Read Archive File
GET /api/apps/:app/archives/:timestamp/files/*Reads a specific file's content from an archive. Append the relative file path after /files/.
Response 200
{ "path": "00-story-loop/prompt.md", "content": "# Story Loop\n..." }All archive endpoints validate paths against directory traversal.
Status & Configuration
Get Loop Status
GET /api/apps/:app/statusReturns parsed tracker status for all loops in the app.
Response 200
[
{
"key": "tasks-loop",
"loop": "Tasks",
"stage": "development",
"active": "TASK-3",
"completed": 5,
"total": 12,
"agents": [
{
"agent": "agent-1",
"active_task": "TASK-6",
"stage": "execute",
"last_heartbeat": "2026-03-14T10:45:30Z"
}
]
}
]Get App Config
GET /api/apps/:app/configReturns the raw parsed ralphflow.yaml configuration plus _rawYaml (the original YAML string).
Update Loop Model
PUT /api/apps/:app/config/modelUpdate a loop's Claude model in ralphflow.yaml.
Request Body
{ "loop": "tasks-loop", "model": "claude-opus-4-6" }Set model to null or "" to remove the field (revert to default).
Response 200
{ "ok": true, "loop": "tasks-loop", "model": "claude-opus-4-6" }Get Database State
GET /api/apps/:app/dbReturns SQLite loop_state rows for the app.
Loop File Endpoints
Read Prompt
GET /api/apps/:app/loops/:loop/promptResponse 200
{ "path": "01-tasks-loop/prompt.md", "content": "# Tasks Loop\n..." }Update Prompt
PUT /api/apps/:app/loops/:loop/promptRequest Body
{ "content": "# Updated prompt\n..." }Response 200
{ "ok": true }Read Tracker
GET /api/apps/:app/loops/:loop/trackerReturns the raw tracker markdown content.
Response 200
{ "path": "01-tasks-loop/tracker.md", "content": "- stage: development\n..." }List Loop Files
GET /api/apps/:app/loops/:loop/filesResponse 200
{ "files": [{ "name": "prompt.md", "isDirectory": false }, { "name": ".agents", "isDirectory": true }] }Template Management
List Templates
GET /api/templatesReturns all templates (built-in and custom) with metadata.
Response 200
[
{ "name": "code-implementation", "type": "built-in", "description": "Three-loop pipeline...", "loopCount": 3 },
{ "name": "my-pipeline", "type": "custom", "description": "Custom pipeline", "loopCount": 2 }
]Create Custom Template
POST /api/templatesRequest Body
{
"name": "my-pipeline",
"description": "Custom pipeline",
"loops": [
{
"name": "analyze",
"stages": ["research", "plan"],
"completion": "ANALYSIS COMPLETE",
"model": "claude-sonnet-4-6",
"multi_agent": false,
"data_files": ["data.md"],
"entities": ["ITEM"],
"prompt": "# Analyze Loop\n\nYour instructions here..."
}
]
}Loop keys are auto-suffixed with -loop. The prompt field is optional — if omitted, a default placeholder is written.
| Status | Meaning |
|---|---|
201 | Template created |
400 | Invalid name or configuration |
409 | Template already exists |
Delete Custom Template
DELETE /api/templates/:nameDeletes a custom template. Built-in templates return 403.
| Status | Meaning |
|---|---|
200 | Deleted |
403 | Cannot delete built-in template |
404 | Template not found |
Clone Built-in Template
POST /api/templates/:name/cloneCopies a built-in template into a custom template.
Request Body
{ "newName": "my-custom-pipeline" }Response 201
{ "ok": true, "source": "code-implementation", "templateName": "my-custom-pipeline", "message": "..." }| Status | Meaning |
|---|---|
201 | Cloned successfully |
400 | Source is not built-in, or invalid name |
409 | Target name already exists |
Get Template Config
GET /api/templates/:name/configReturns the parsed ralphflow.yaml for any template (built-in or custom).
Read Template Prompt
GET /api/templates/:name/loops/:loopKey/promptReads a template loop's prompt file content.
Response 200
{ "path": "loops/01-tasks-loop/prompt.md", "content": "# Tasks Loop\n..." }Update Template Prompt
PUT /api/templates/:name/loops/:loopKey/promptWrites prompt content to a custom template's prompt file. Built-in templates return 403.
Request Body
{ "content": "# Updated prompt\n..." }Notifications
Post Notification
POST /api/notification?app=my-project&loop=tasks-loopReceives attention notifications from the Claude Code hook. The app and loop query params identify the source. The request body is the JSON payload from Claude.
Response 200
{ "id": "abc123", "timestamp": "2026-03-14T10:45:30Z", "app": "my-project", "loop": "tasks-loop", "payload": {} }List Notifications
GET /api/notificationsReturns all active (undismissed) notifications.
Dismiss Notification
DELETE /api/notification/:idDismisses a notification. Broadcasts a notification:dismissed WebSocket event.
Context
Get Dashboard Context
GET /api/contextResponse 200
{ "cwd": "/Users/user/project", "projectName": "my-project", "port": 4242 }WebSocket Events
Connect to ws://localhost:4242 for real-time updates. All events are JSON messages with a type field.
status:full
Full status update. Sent on initial connection, on database state changes (polled every 2 seconds), and on tracker file changes.
{
"type": "status:full",
"apps": [
{
"appName": "my-project",
"appType": "code-implementation",
"description": "...",
"loops": [
{
"key": "tasks-loop",
"name": "Tasks",
"order": 1,
"stages": ["development", "testing"],
"status": {
"stage": "development",
"active": "TASK-3",
"completed": 5,
"total": 12
}
}
]
}
]
}tracker:updated
Sent when a loop's tracker.md file changes. Debounced by 300ms.
{
"type": "tracker:updated",
"app": "my-project",
"loop": "tasks-loop",
"status": {
"key": "tasks-loop",
"stage": "development",
"active": "TASK-3",
"completed": 5,
"total": 12
}
}file:changed
Sent when any .md or .yaml file changes in the .ralph-flow/ directory.
{
"type": "file:changed",
"app": "my-project",
"path": "01-tasks-loop/tasks.md"
}notification:attention
Broadcast when the hook POST delivers a Claude notification.
{
"type": "notification:attention",
"notification": {
"id": "abc123",
"timestamp": "2026-03-14T10:45:30Z",
"app": "my-project",
"loop": "tasks-loop",
"payload": {}
}
}notification:dismissed
Broadcast when a notification is dismissed via the API.
{
"type": "notification:dismissed",
"id": "abc123"
}