The Voiceflow CLI now includes an HTTP API server that exposes test execution functionality as REST endpoints with auto-generated OpenAPI/Swagger documentation and WebSocket support for real-time log streaming.
- HTTP API: Execute test suites via REST endpoints
- WebSocket API: Execute, cancel, and monitor test suites over a persistent WebSocket connection with real-time log streaming
- Real-time Logging: Capture and return test execution logs in API responses
- OpenAPI/Swagger: Auto-generated API documentation at
/swagger/index.html - Asynchronous Execution: Non-blocking test execution with status tracking
- CORS Support: Enable cross-origin requests for web frontends
- Health Checks: Built-in health check endpoints
# Start server on default port (8080)
voiceflow server
# Start server on custom port
voiceflow server --port 9090
# Start server with debug mode
voiceflow server --debug
# Start server with custom host
voiceflow server --host 127.0.0.1 --port 8080| Flag | Short | Default | Description |
|---|---|---|---|
--port |
-p |
8080 |
Port to run the server on |
--host |
-H |
0.0.0.0 |
Host to bind the server to |
--debug |
-d |
false |
Enable debug mode |
--cors |
true |
Enable CORS middleware | |
--swagger |
true |
Enable Swagger documentation endpoint |
GET /healthPOST /api/v1/tests/execute
Content-Type: application/json
{
"api_key": "your_api_key (optional)",
"suite": {
"name": "Example Suite",
"description": "Suite used as an example",
"environment_name": "production",
"tests": [
{
"id": "test_1",
"test": {
"name": "Example test",
"description": "These are some tests",
"interactions": [
{
"id": "test_1_1",
"user": {
"type": "text",
"text": "hi"
},
"agent": {
"validate": [
{
"type": "contains",
"value": "hello"
}
]
}
}
]
}
}
]
}
}Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"status": "running",
"started_at": "2023-01-01T00:00:00Z",
"logs": ["Test execution started"]
}GET /api/v1/tests/status/{execution_id}Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"status": "completed",
"started_at": "2023-01-01T00:00:00Z",
"completed_at": "2023-01-01T00:05:00Z",
"logs": [
"Starting test suite execution...",
"Running Test ID: example_test",
"Test suite execution completed successfully"
]
}GET /api/v1/system/infoResponse:
{
"version": "1.0.0",
"go_version": "go1.20.0",
"os": "linux",
"arch": "amd64"
}Once the server is running, you can access the interactive API documentation at:
http://localhost:8080/swagger/index.html
Connect to ws://localhost:8080/ws for a persistent connection with real-time log streaming. The WebSocket endpoint exposes the same functionality as the REST API but pushes log lines as they happen instead of requiring polling.
All messages are JSON objects.
Client → Server:
| Action | Description | Fields |
|---|---|---|
execute |
Start a test suite | { "action": "execute", "data": <TestExecutionRequest> } |
cancel |
Cancel a running execution | { "action": "cancel", "id": "<execution-id>" } |
status |
Get execution status | { "action": "status", "id": "<execution-id>" } |
Server → Client:
| Type | Description |
|---|---|
log |
Real-time log line: { "type": "log", "id": "...", "message": "..." } |
status |
Status update: { "type": "status", "id": "...", "data": <TestStatusResponse> } |
result |
Final result when execution finishes: { "type": "result", "id": "...", "data": <TestStatusResponse> } |
error |
Error message: { "type": "error", "message": "..." } |
# Install: brew install websocat
websocat ws://localhost:8080/wsThen paste JSON messages:
{"action":"execute","data":{"suite":{"name":"Example Suite","description":"Test","environment_name":"production","tests":[{"id":"test_1","test":{"name":"Example test","description":"Test","interactions":[{"id":"t1","user":{"type":"text","text":"hi"},"agent":{"validate":[{"type":"contains","value":"hello"}]}}]}}]}}}Cancel a running execution:
{"action":"cancel","id":"<execution-id-from-status-message>"}const ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = () => {
// Execute a test suite
ws.send(JSON.stringify({
action: 'execute',
data: {
api_key: 'your_api_key',
suite: {
name: 'Example Suite',
description: 'Suite used as an example',
environment_name: 'production',
tests: [
{
id: 'test_1',
test: {
name: 'Example test',
description: 'These are some tests',
interactions: [
{
id: 'test_1_1',
user: { type: 'text', text: 'hi' },
agent: { validate: [{ type: 'contains', value: 'hello' }] }
}
]
}
}
]
}
}
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'log':
console.log(`[LOG] ${msg.message}`);
break;
case 'status':
console.log(`[STATUS] ${msg.id}: ${msg.message || msg.data.status}`);
// Cancel example: ws.send(JSON.stringify({ action: 'cancel', id: msg.id }));
break;
case 'result':
console.log('[RESULT]', msg.data);
break;
case 'error':
console.error('[ERROR]', msg.message);
break;
}
};import asyncio
import json
import websockets
async def run_tests():
async with websockets.connect('ws://localhost:8080/ws') as ws:
# Execute a test suite
await ws.send(json.dumps({
'action': 'execute',
'data': {
'api_key': 'your_api_key',
'suite': {
'name': 'Example Suite',
'description': 'Test',
'environment_name': 'production',
'tests': [{
'id': 'test_1',
'test': {
'name': 'Example test',
'description': 'Test',
'interactions': [{
'id': 't1',
'user': {'type': 'text', 'text': 'hi'},
'agent': {'validate': [{'type': 'contains', 'value': 'hello'}]}
}]
}
}]
}
}
}))
# Read messages until execution completes
async for message in ws:
msg = json.loads(message)
if msg['type'] == 'log':
print(f"[LOG] {msg['message']}")
elif msg['type'] == 'result':
print(f"[RESULT] {msg['data']}")
break
elif msg['type'] == 'error':
print(f"[ERROR] {msg['message']}")
break
asyncio.run(run_tests())curl -X POST http://localhost:8080/api/v1/tests/execute \
-H "Content-Type: application/json" \
-d '{
"api_key": "your_api_key (optional)",
"suite": {
"name": "Example Suite",
"description": "Suite used as an example",
"environment_name": "production",
"tests": [
{
"id": "test_1",
"test": {
"name": "Example test",
"description": "These are some tests",
"interactions": [
{
"id": "test_1_1",
"user": {
"type": "text",
"text": "hi"
},
"agent": {
"validate": [
{
"type": "contains",
"value": "hello"
}
]
}
}
]
}
}
]
}
}'curl http://localhost:8080/api/v1/tests/status/YOUR_EXECUTION_IDcurl http://localhost:8080/health// Execute a test suite
const response = await fetch('http://localhost:8080/api/v1/tests/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
api_key: "your_api_key (optional)",
suite: {
name: "Example Suite",
description: "Suite used as an example",
environment_name: "production",
tests: [
{
id: "test_1",
test: {
name: "Example test",
description: "These are some tests",
interactions: [
{
id: "test_1_1",
user: {
type: "text",
text: "hi"
},
agent: {
validate: [
{
type: "contains",
value: "hello"
}
]
}
}
]
}
}
]
}
})
});
const execution = await response.json();
console.log('Execution ID:', execution.id);
// Poll for status
const statusResponse = await fetch(`http://localhost:8080/api/v1/tests/status/${execution.id}`);
const status = await statusResponse.json();
console.log('Status:', status.status);
console.log('Logs:', status.logs);import requests
import time
# Execute a test suite
response = requests.post('http://localhost:8080/api/v1/tests/execute', json={
'api_key': 'your_api_key (optional)',
'suite': {
'name': 'Example Suite',
'description': 'Suite used as an example',
'environment_name': 'production',
'tests': [
{
'id': 'test_1',
'test': {
'name': 'Example test',
'description': 'These are some tests',
'interactions': [
{
'id': 'test_1_1',
'user': {
'type': 'text',
'text': 'hi'
},
'agent': {
'validate': [
{
'type': 'contains',
'value': 'hello'
}
]
}
}
]
}
}
]
}
})
execution = response.json()
print(f"Execution ID: {execution['id']}")
# Poll for completion
while True:
status_response = requests.get(f"http://localhost:8080/api/v1/tests/status/{execution['id']}")
status = status_response.json()
print(f"Status: {status['status']}")
if status['status'] in ['completed', 'failed']:
print("Logs:")
for log in status['logs']:
print(f" {log}")
break
time.sleep(1)The server respects all existing Voiceflow CLI environment variables:
VF_API_KEY: Voiceflow API KeyOPENAI_API_KEY: OpenAI API Key (for similarity validations)
CORS is enabled by default. To disable CORS:
voiceflow server --cors=falseEnable debug mode for detailed logging:
voiceflow server --debug- The server runs on all interfaces (
0.0.0.0) by default. In production, consider binding to specific interfaces. - There is no built-in authentication. Consider adding a reverse proxy with authentication if needed.
- Test executions are stored in memory. Consider implementing persistent storage for production use.
-
Check if the port is already in use:
lsof -i :8080
-
Try a different port:
voiceflow server --port 9090
Ensure you're using the correct base path /api/v1/ for API endpoints.
Enable debug mode to see more detailed logging:
voiceflow server --debug