HTTP API
Overview
The AWF HTTP API server lets you trigger, monitor, and manage workflow executions remotely without shelling out to the CLI. Perfect for integrating AWF into CI/CD pipelines, web dashboards, IDE extensions, or any external system that needs to orchestrate workflows over HTTP.
Key features:
- REST endpoints for workflow discovery, validation, and execution
- Server-Sent Events (SSE) for real-time step-by-step execution monitoring
- Auto-generated OpenAPI 3.1 specification at
/openapi.jsonwith Swagger UI at/docs - Async execution — start a workflow and check progress via polling or SSE stream
- Execution history and statistics for audit and analytics
Starting the Server
awf serveFlags:
--port <int>— Port to bind on (default:2511)--host <string>— Host to bind on (default:127.0.0.1)
Example:
# Start on default localhost:2511
awf serve
# Start on a specific port
awf serve --port 8080
# Bind to all interfaces (use at your own risk in production)
awf serve --host 0.0.0.0 --port 8080Once running:
- Swagger UI:
http://localhost:2511/docs - OpenAPI spec:
http://localhost:2511/openapi.json - API endpoints:
http://localhost:2511/api/workflows,/api/executions,/api/history
Endpoints
Workflow Discovery & Validation
List workflows
GET /api/workflowsResponse (200 OK):
{
"body": {
"workflows": [
{
"name": "code-review",
"version": "1.0.0",
"description": "Review code for bugs and security issues"
},
{
"name": "deploy-app",
"version": "2.1.0",
"description": "Deploy application to production"
}
]
}
}Get workflow details
GET /api/workflows/{name}Response (200 OK):
{
"body": {
"name": "code-review",
"version": "1.0.0",
"description": "Review code for bugs and security issues",
"states": {
"initial": "read",
"read": {
"type": "step",
"command": "cat {{inputs.file}}"
}
}
}
}Error (404 Not Found):
{
"status": 404,
"title": "Not Found",
"detail": "workflow not found: nonexistent"
}Validate workflow
POST /api/workflows/{name}/validateResponse (200 OK — valid workflow):
{
"body": {
"errors": []
}
}Response (200 OK — invalid workflow):
{
"body": {
"errors": [
"state 'invalid_ref' references undefined state"
]
}
}Workflow Execution
Run workflow (async)
POST /api/workflows/{name}/run
Content-Type: application/json
{
"inputs": {
"file": "main.go",
"model": "claude-opus-4-20250805"
}
}Response (202 Accepted):
{
"body": {
"execution_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "accepted"
}
}The workflow begins execution asynchronously. Use the execution_id to monitor progress via the events endpoint or polling.
Error (404 Not Found):
{
"status": 404,
"title": "Not Found",
"detail": "workflow not found: nonexistent"
}Error (422 Unprocessable Entity):
{
"status": 422,
"title": "Unprocessable Entity",
"detail": "failed to start execution: <reason>"
}List executions
GET /api/executionsResponse (200 OK):
{
"body": {
"executions": [
{
"execution_id": "550e8400-e29b-41d4-a716-446655440000",
"workflow_name": "code-review",
"status": "running",
"current_step": "analyze",
"started_at": "2026-05-17T20:39:00Z",
"updated_at": "2026-05-17T20:39:05Z"
}
]
}
}Get execution details
GET /api/executions/{id}Response (200 OK):
{
"body": {
"execution_id": "550e8400-e29b-41d4-a716-446655440000",
"workflow_name": "code-review",
"status": "running",
"current_step": "analyze",
"started_at": "2026-05-17T20:39:00Z",
"updated_at": "2026-05-17T20:39:05Z"
}
}Error (404 Not Found):
{
"status": 404,
"title": "Not Found",
"detail": "execution not found: <id>"
}Cancel execution
DELETE /api/executions/{id}Response (204 No Content)
Cancels the running workflow. The execution transitions to cancelled state and all running steps are terminated gracefully. Idempotent — returns 204 even if the execution does not exist.
Resume failed execution
POST /api/executions/{id}/resume
Content-Type: application/json
{
"input_overrides": {"model": "claude-sonnet-4-20250514"},
"from_step": "analyze"
}Response (200 OK):
{
"body": {
"execution_id": "661f9500-f30c-52e5-c827-557766551111",
"status": "accepted"
}
}A new execution ID is assigned to the resumed run. Monitor progress via the new ID.
Error (404 Not Found):
{
"status": 404,
"title": "Not Found",
"detail": "execution not found or cannot be resumed: <id>"
}Real-Time Event Streaming
Stream execution events (Server-Sent Events)
GET /api/executions/{id}/events
Accept: text/event-streamThis endpoint returns a Server-Sent Events stream. Keep the connection open to receive real-time step updates as the workflow executes. The stream closes automatically when the workflow reaches a terminal state (completed, failed, or cancelled).
Event stream example:
event: step.started
data: {"step_name":"read","status":"running","started_at":"2026-05-17T20:39:00Z"}
event: step.completed
data: {"step_name":"read","status":"completed","output":"package main...","completed_at":"2026-05-17T20:39:02Z"}
event: step.started
data: {"step_name":"analyze","status":"running","started_at":"2026-05-17T20:39:02Z"}
event: step.failed
data: {"step_name":"analyze","status":"failed","error":"timeout exceeded","completed_at":"2026-05-17T20:40:15Z"}
event: workflow.failed
data: {"workflow_name":"code-review","status":"failed","completed_at":"2026-05-17T20:40:15Z"}Event types:
| Event | Description | Fields |
|---|---|---|
step.started | Step execution began | step_name, status, started_at |
step.completed | Step finished successfully | step_name, status, output, completed_at |
step.failed | Step execution failed | step_name, status, error, completed_at |
workflow.completed | Workflow completed | workflow_name, status, completed_at |
workflow.failed | Workflow failed or cancelled | workflow_name, status, error, completed_at |
output | Incremental step output | step_name, output |
Polling interval: Events are emitted every ~200ms as state transitions occur.
Error (404 Not Found): Stream returns 404 before opening if the execution does not exist.
Execution History & Statistics
List historical executions
GET /api/history?workflow=code-review&status=failed&limit=50Query parameters:
workflow— Filter by workflow name (optional)status— Filter by status:success,failed,cancelled(optional)since— Start date, RFC 3339 (optional)until— End date, RFC 3339 (optional)limit— Max results (optional)
Response (200 OK):
{
"body": {
"entries": [
{
"id": "rec-abc123",
"workflow_name": "code-review",
"status": "failed",
"started_at": "2026-05-16T15:30:00Z",
"completed_at": "2026-05-16T15:31:00Z",
"duration_ms": 60000
},
{
"id": "rec-def456",
"workflow_name": "code-review",
"status": "success",
"started_at": "2026-05-16T14:20:00Z",
"completed_at": "2026-05-16T14:21:00Z",
"duration_ms": 60000
}
]
}
}Get execution statistics
GET /api/history/stats?workflow=code-reviewQuery parameters:
workflow— Filter by workflow name (optional)status— Filter by status (optional)since— Start date, RFC 3339 (optional)until— End date, RFC 3339 (optional)
Response (200 OK):
{
"body": {
"TotalExecutions": 142,
"SuccessCount": 128,
"FailedCount": 12,
"CancelledCount": 2,
"AvgDurationMs": 45000
}
}OpenAPI Specification
The API serves an auto-generated OpenAPI 3.1 specification:
# Download OpenAPI specification
curl http://localhost:2511/openapi.json
# Or YAML format
curl http://localhost:2511/openapi.yaml
# View Swagger UI in browser
open http://localhost:2511/docsClient Libraries & Integration
cURL
# Run a workflow
RESULT=$(curl -s -X POST http://localhost:2511/api/workflows/code-review/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"file": "main.go"}}')
EXEC_ID=$(echo $RESULT | jq -r '.body.execution_id')
# Stream events
curl -N http://localhost:2511/api/executions/$EXEC_ID/events
# Get status
curl http://localhost:2511/api/executions/$EXEC_IDJavaScript/TypeScript
// Start execution
const response = await fetch('http://localhost:2511/api/workflows/code-review/run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inputs: { file: 'main.go' } })
});
const { body } = await response.json();
const executionId = body.execution_id;
// Stream events
const eventSource = new EventSource(
`http://localhost:2511/api/executions/${executionId}/events`
);
eventSource.addEventListener('step.started', (event) => {
console.log('Step started:', JSON.parse(event.data));
});
eventSource.addEventListener('workflow.completed', (event) => {
console.log('Workflow done:', JSON.parse(event.data));
eventSource.close();
});Python
import requests
import json
# Start execution
response = requests.post(
'http://localhost:2511/api/workflows/code-review/run',
json={'inputs': {'file': 'main.go'}}
)
execution_id = response.json()['body']['execution_id']
# Stream events
response = requests.get(
f'http://localhost:2511/api/executions/{execution_id}/events',
stream=True
)
for line in response.iter_lines():
if line.startswith(b'event: '):
event_type = line.decode().split(': ', 1)[1]
print(f'Event: {event_type}')Error Handling
All error responses follow RFC 7807 Problem Details format (provided by Huma):
{
"status": 422,
"title": "Unprocessable Entity",
"detail": "Human-readable error description"
}Common HTTP status codes:
400— Bad request (missing required field, invalid JSON)404— Resource not found (unknown workflow or execution ID)422— Unprocessable entity (valid JSON but semantic error)500— Internal server error
Security Considerations
Default behavior:
- Server binds to
127.0.0.1:2511by default — localhost only - No authentication in v1 (assumes isolated network or reverse proxy)
- No HTTPS/TLS in the server (use a reverse proxy like nginx)
For production deployments:
- Run behind a reverse proxy (nginx, HAProxy, etc.) with:
- HTTPS/TLS termination
- Authentication (OAuth, API key, mutual TLS)
- Rate limiting
- Request logging
- Use
--host 127.0.0.1or--host [::1]to prevent accidental network exposure - Consider running in a container with restricted network access
- Monitor
/api/executionsfor long-running or stuck workflows - Configure appropriate timeouts for long-duration workflows
Graceful Shutdown
The server listens for SIGINT and SIGTERM signals. On shutdown:
- New requests return
503 Service Unavailable - Active SSE streams are drained (30-second timeout)
- Running workflows continue execution (separate from the HTTP server)
- Server exits cleanly
To stop the server:
kill -TERM $(pgrep -f "awf serve") # or Ctrl+C in foregroundExamples
Full workflow execution flow
# 1. Start server
awf serve --port 8080 &
# 2. List available workflows
curl http://localhost:8080/api/workflows
# 3. Validate a workflow before running
curl -X POST http://localhost:8080/api/workflows/code-review/validate
# 4. Start a workflow execution
RESPONSE=$(curl -s -X POST http://localhost:8080/api/workflows/code-review/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"file": "src/main.go"}}')
EXEC_ID=$(echo $RESPONSE | jq -r '.body.execution_id')
echo "Execution ID: $EXEC_ID"
# 5. Monitor execution in real-time via SSE
curl -N http://localhost:8080/api/executions/$EXEC_ID/events
# Or poll for status
curl http://localhost:8080/api/executions/$EXEC_ID | jq '.body | {status, current_step}'
# 6. Check execution history
curl "http://localhost:8080/api/history?workflow=code-review&limit=10"
# 7. Get statistics
curl http://localhost:8080/api/history/stats?workflow=code-reviewIntegrate with CI/CD (GitHub Actions)
name: Code Review Workflow
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run AWF code review via API
run: |
RESPONSE=$(curl -s -X POST http://awf-server:2511/api/workflows/code-review/run \
-H "Content-Type: application/json" \
-d '{"inputs": {"file": "src/main.go"}}')
EXEC_ID=$(echo $RESPONSE | jq -r '.body.execution_id')
# Poll until complete
while true; do
STATUS=$(curl -s http://awf-server:2511/api/executions/$EXEC_ID | jq -r '.body.status')
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 2
done
# Get final status
curl -s http://awf-server:2511/api/executions/$EXEC_ID | jq '.body'Performance & Limits
- Default port:
2511 - Event polling cadence: 200ms (real-time within ~200ms)
- Graceful shutdown timeout: 30 seconds
- Max concurrent SSE subscribers per execution: 50+ (tested)
- Response time: Most endpoints respond within 100ms
- Workflow execution: Runs asynchronously; HTTP response is immediate (≤100ms)
Troubleshooting
Server won’t start on port 2511
# Check if port is in use
lsof -i :2511
# Use a different port
awf serve --port 8080SSE stream closes unexpectedly
- Check network connectivity (firewall, proxy timeouts)
- Verify execution exists:
curl http://localhost:2511/api/executions/{id} - Check server logs for errors
API returns 404 for a workflow
- Verify workflow exists:
awf listorcurl http://localhost:2511/api/workflows - Check workflow YAML syntax:
awf validate <workflow>
404 on / or /api
- There is no root handler. Use specific endpoints:
/api/workflows,/api/executions,/api/history - Browse
/docsfor interactive Swagger UI
Slow response times
- Check server load:
curl http://localhost:2511/api/executions - Look for long-running workflows blocking execution
- Monitor reverse proxy (if using one) for bottlenecks