OpenTester REST API Documentation
March 8, 2026 · View on GitHub
OpenTester provides a REST API for Web UI usage. MCP tools are the primary interface, REST API is auxiliary.
Basic Information
- Base URL:
http://localhost:8000 - API Prefix:
/api(all routes prefixed with/api) - Content-Type:
application/json - CORS: Allow
http://localhost:5173
Health Check
GET /health
Check service health status.
Response:
{
"status": "ok",
"service": "opentester"
}
Project Management
GET /api/projects
List all projects.
Response:
[
{
"id": "uuid",
"name": "Project Name",
"target_type": "cli",
"case_count": 5,
"group_count": 0,
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
]
POST /api/projects
Create a new project.
Request Body:
{
"name": "New Project",
"target_type": "cli",
"prd_content": "Optional PRD content"
}
Response (201 Created):
{
"id": "uuid",
"name": "New Project",
"target_type": "cli",
"case_count": 0,
"group_count": 0,
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
GET /api/projects/{project_id}
Get a project by ID.
Response:
{
"id": "uuid",
"name": "Project Name",
"target_type": "cli",
"case_count": 5,
"group_count": 0,
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
PUT /api/projects/{project_id}
Update a project.
Request Body:
{
"name": "Updated Name",
"prd_content": "Updated PRD content"
}
Response:
{
"id": "uuid",
"name": "Updated Name",
"target_type": "cli",
"case_count": 5,
"group_count": 0,
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:30:00"
}
DELETE /api/projects/{project_id}
Delete a project.
Response: 204 No Content
Test Case Management
GET /api/cases/project/{project_id}
List all test cases for a project.
Response:
[
{
"id": "case-uuid",
"name": "Test Case",
"description": "Test description",
"execution_mode": "screen",
"step_count": 5,
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
]
POST /api/cases/project/{project_id}
Create a new test case.
Request Body:
{
"name": "New Test Case",
"description": "Test description",
"execution_mode": "screen",
"dsl_content": "version: '1.0'\nmeta:\n name: Test\nsteps: []"
}
Response:
{
"id": "case-uuid",
"name": "New Test Case",
"description": "Test description",
"execution_mode": "screen",
"step_count": 0,
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
GET /api/cases/{case_id}
Get a test case by ID.
Response:
{
"id": "case-uuid",
"name": "Test Case",
"description": "Test description",
"execution_mode": "screen",
"step_count": 5,
"dsl_content": "version: '1.0'\n...",
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
PUT /api/cases/{case_id}
Update a test case.
Request Body:
{
"name": "Updated Name",
"description": "Updated description",
"execution_mode": "headless",
"dsl_content": "updated dsl content"
}
Response:
{
"id": "case-uuid",
"name": "Updated Name",
"description": "Updated description",
"execution_mode": "headless",
"step_count": 5,
"dsl_content": "updated dsl content",
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:30:00"
}
DELETE /api/cases/{case_id}
Delete a test case.
Response:
{
"message": "Case deleted",
"id": "case-uuid"
}
POST /api/cases/batch-delete
Batch delete test cases.
Request Body:
{
"case_ids": ["case-1", "case-2", "case-3"]
}
Response:
{
"message": "Deleted 2 cases",
"deleted": ["case-1", "case-2"],
"not_found": ["case-3"]
}
POST /api/cases/validate-dsl
Validate DSL content without creating a case. Returns detailed validation results with line numbers.
Request Body:
{
"dsl_content": "version: '1.0'\nmeta:\n name: Test\nsteps: []"
}
Response:
{
"valid": true,
"errors": [],
"error_details": [],
"warnings": [],
"warning_details": [],
"step_count": 0
}
Error Response Example:
{
"valid": false,
"errors": ["Step 1: Missing 'action' field."],
"error_details": [
{
"message": "Step 1: Missing 'action' field.",
"line": 5,
"column": null,
"severity": "error"
}
],
"warnings": ["Step 1: Consider adding a 'name' for better readability."],
"warning_details": [
{
"message": "Step 1: Consider adding a 'name' for better readability.",
"line": 5,
"column": null,
"severity": "warning"
}
],
"step_count": 1
}
DSL Parser
POST /api/parser/validate
Validate DSL YAML syntax and schema using the Pydantic DSLScript model.
Request Body:
{
"dsl_yaml": "version: '1.0'\nmeta:\n name: Test\nsteps: []"
}
Success Response:
{
"valid": true,
"message": "DSL is valid",
"details": {
"case_name": "Test",
"step_count": 0,
"variables": []
}
}
Error Response (YAML syntax error):
{
"valid": false,
"message": "YAML syntax error: mapping values are not allowed here",
"error_type": "yaml_syntax",
"line": 3,
"hint": "Check YAML indentation and syntax"
}
Error Response (Schema validation error):
{
"valid": false,
"message": "DSL schema validation failed",
"error_type": "dsl_schema",
"details": [
{
"field": "steps.0.action",
"error": "field required",
"type": "missing"
}
],
"hint": "Check field types and required fields. See DSL_SPEC.md for reference."
}
Note: PRD parsing has been removed from backend and frontend runtime surfaces.
/api/parser/parse-prdis not part of the supported API.
Test Execution
POST /api/execution/run
Execute a single test case.
Request Body:
{
"project_id": "project-uuid",
"case_id": "case-uuid"
}
Response:
{
"execution_id": "exec-uuid",
"project_id": "project-uuid",
"case_id": "case-uuid",
"status": "pending",
"message": "Execution started",
"current_step": 0,
"total_steps": 5,
"progress_percentage": 0
}
POST /api/execution/stop/{execution_id}
Stop a running execution.
Response:
{
"message": "Execution stopped",
"id": "exec-uuid"
}
GET /api/execution/status/{execution_id}
Get execution status.
Response:
{
"execution_id": "exec-uuid",
"project_id": "project-uuid",
"case_id": "case-uuid",
"status": "completed",
"message": "Execution status retrieved",
"current_step": 5,
"total_steps": 5,
"progress_percentage": 100
}
GET /api/execution/{execution_id}/results
Get detailed execution results including all step results.
Response:
{
"execution_id": "exec-uuid",
"project_id": "project-uuid",
"case_id": "case-uuid",
"status": "completed",
"current_step": 5,
"total_steps": 5,
"start_time": "2026-02-27T10:00:00",
"end_time": "2026-02-27T10:01:00",
"error_message": null,
"results": [
{
"step_index": 0,
"action": "exec",
"status": "passed",
"message": "Executed: echo hello",
"duration_ms": 100,
"timestamp": "2026-02-27T10:00:01"
}
]
}
GET /api/execution/project/{project_id}/history
Get execution history for a project.
Query Parameters:
limit- Maximum number of records (default: 10)
Response:
{
"executions": [
{
"execution_id": "exec-uuid",
"case_id": "case-uuid",
"status": "completed",
"current_step": 5,
"total_steps": 5,
"start_time": "2026-02-27T10:00:00",
"end_time": "2026-02-27T10:01:00"
}
]
}
POST /api/execution/batch/run
Execute multiple test cases in batch.
Request Body:
{
"project_id": "project-uuid",
"case_ids": ["case-1", "case-2"]
}
Response:
{
"batch_id": "batch-uuid",
"project_id": "project-uuid",
"case_count": 2,
"status": "running",
"message": "Batch execution started for 2 test cases"
}
GET /api/execution/batch/{batch_id}
Get batch execution status.
Response:
{
"batch_id": "batch-uuid",
"project_id": "project-uuid",
"status": "completed",
"current_index": 2,
"total_cases": 2,
"start_time": "2026-02-27T10:00:00",
"end_time": "2026-02-27T10:02:00",
"executions": [
{
"case_id": "case-1",
"execution_id": "exec-1",
"status": "completed"
},
{
"case_id": "case-2",
"execution_id": "exec-2",
"status": "completed"
}
]
}
GET /api/execution/{execution_id}/ai-pending-state
Get AI pending payload when execution is paused waiting for selector input.
Response:
{
"execution_id": "exec-uuid",
"status": "paused_waiting_for_ai",
"ai_pending_state": {
"step_index": 2,
"action": "click",
"description": "Submit button",
"context": "Form footer",
"dom_snapshot": {
"url": "https://example.com/login",
"title": "Login",
"viewport": {"width": 1280, "height": 720},
"elements": []
},
"screenshot_base64": "...",
"requested_at": "2026-03-07T10:00:00",
"timeout_seconds": 300
}
}
POST /api/execution/{execution_id}/submit-selector
Submit AI-generated selector and resume execution.
Note: selector is consumed as a Playwright selector string. If using XPath, pass Playwright XPath form in selector (for example xpath=//button[@type='submit']). selector_type is currently metadata for tracking/display.
Request Body:
{
"selector": "#submit-button",
"selector_type": "css",
"reasoning": "Unique button id"
}
Response:
{
"success": true,
"message": "Selector submitted, execution resumed",
"execution_id": "exec-uuid",
"selector": "#submit-button",
"selector_type": "css"
}
GET /api/execution/paused/list
List all executions waiting for AI selector input.
Response:
{
"count": 1,
"executions": [
{
"execution_id": "exec-uuid",
"project_id": "project-uuid",
"case_id": "case-uuid",
"status": "paused_waiting_for_ai",
"description": "Submit button",
"action": "click",
"requested_at": "2026-03-07T10:00:00"
}
]
}
WebSocket
WS /api/execution/ws/{execution_id}
WebSocket connection for real-time execution updates.
Connection URL: ws://localhost:8000/api/execution/ws/{execution_id}
Initial Message (sent on connection):
{
"type": "initial",
"execution_id": "exec-uuid",
"status": "running",
"current_step": 2,
"total_steps": 5,
"progress_percentage": 40,
"results": [
{
"index": 0,
"action": "exec",
"status": "passed",
"message": "Executed: echo hello",
"duration_ms": 100,
"timestamp": "2026-02-27T10:00:01",
"data": {}
}
]
}
Update Messages (sent during execution):
{
"type": "step_update",
"execution_id": "exec-uuid",
"step": {
"index": 2,
"action": "assert",
"status": "passed",
"message": "Assertion (stdout_contains): Stdout contains 'ok': true",
"duration_ms": 42,
"timestamp": "2026-02-27T10:00:03",
"data": {}
},
"progress": {
"current": 3,
"total": 5,
"percentage": 60
}
}
Status transition messages:
{
"type": "status_update",
"execution_id": "exec-uuid",
"status": "completed",
"error_message": null,
"end_time": "2026-02-27T10:02:00"
}
Client Commands:
Send commands to control the execution:
{"command": "stop"}
Response:
{
"type": "command_result",
"command": "stop",
"success": true
}
Keep connection alive:
{"command": "ping"}
Response:
{"type": "pong"}
Error Message:
{
"type": "error",
"message": "Execution not found"
}
AI Pause Message:
{
"type": "paused_for_ai",
"execution_id": "exec-uuid",
"ai_pending_state": {
"action": "click",
"description": "Submit button"
}
}
Note: actual runtime payload includes the full ai_pending_state object (for example step_index, context, dom_snapshot, screenshot_base64, requested_at, timeout_seconds).
AI Selector Submitted Message:
{
"type": "ai_selector_submitted",
"execution_id": "exec-uuid",
"selector": "#submit-button",
"selector_type": "css",
"reasoning": "Unique button id"
}
During AI pause/resume, status_update messages are also emitted (paused_waiting_for_ai when paused, then running when resumed).
Template Management
GET /api/templates
List all templates with optional filtering.
Query Parameters:
category- Filter by category (e.g., "cli", "api", "auth")target_type- Filter by target type (e.g., "cli", "api", "tui")tag- Filter by tag
Response:
[
{
"id": "template-uuid",
"name": "API Health Check",
"description": "Template for API health check tests",
"target_type": "api",
"category": "api",
"tags": ["health", "api"],
"variables": [
{
"name": "base_url",
"description": "API base URL",
"required": true,
"default_value": "http://localhost:8000"
}
],
"metadata": {
"category": "api",
"tags": ["health"],
"author": "",
"version": "1.0"
},
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
]
POST /api/templates
Create a new template.
Request Body:
{
"name": "New Template",
"description": "Template description",
"target_type": "cli",
"dsl_template": "version: '1.0'\nmeta:\n name: ${test_name}\nsteps: []",
"variables": [
{
"name": "test_name",
"description": "Name of the test",
"required": true,
"default_value": "Default Test"
}
],
"metadata": {
"category": "cli",
"tags": ["example"]
}
}
Response (201 Created):
{
"id": "template-uuid",
"name": "New Template",
"description": "Template description",
"target_type": "cli",
"variables": [...],
"metadata": {...},
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00"
}
GET /api/templates/{template_id}
Get template details.
Response:
{
"id": "template-uuid",
"name": "Template Name",
"description": "Description",
"target_type": "cli",
"dsl_template": "version: '1.0'\nmeta:\n name: ${test_name}\nsteps: []",
"variables": [
{
"name": "test_name",
"description": "Name of the test",
"required": true,
"default_value": "Default Test"
}
],
"metadata": {...},
"created_at": "2026-02-27T10:00:00",
"updated_at": "2026-02-27T10:00:00",
"usage_count": 5
}
PUT /api/templates/{template_id}
Update an existing template.
Request Body:
{
"name": "Updated Template Name",
"description": "Updated description",
"dsl_template": "updated dsl content",
"variables": [],
"metadata": {}
}
Response:
{
"id": "template-uuid",
"name": "Updated Template Name",
"description": "Updated description",
"target_type": "cli",
"variables": [],
"metadata": {},
"updated_at": "2026-02-27T10:30:00"
}
DELETE /api/templates/{template_id}
Delete a template.
Response: 204 No Content
POST /api/templates/{template_id}/instantiate
Create a test case from a template by replacing variables.
Request Body:
{
"project_id": "project-uuid",
"case_name": "My API Health Test",
"description": "Created from template",
"variables": {
"base_url": "http://localhost:3000"
}
}
Response:
{
"case_id": "case-uuid",
"name": "My API Health Test",
"project_id": "project-uuid",
"template_id": "template-uuid",
"template_name": "API Health Check",
"step_count": 3,
"dsl_content": "version: '1.0'\nmeta:\n name: My API Health Test\nsteps: [...]"
}
POST /api/templates/from-case/{project_id}/{case_id}
Create a template from an existing test case.
Request Body:
{
"template_name": "Login Test Template",
"description": "Reusable login test",
"category": "auth",
"variable_mappings": {
"http://localhost:3000": "base_url",
"test@example.com": "email"
}
}
Response:
{
"id": "template-uuid",
"name": "Login Test Template",
"description": "Reusable login test",
"target_type": "cli",
"variable_count": 2,
"source_case": "Original Case Name",
"source_project": "Project Name"
}
GET /api/templates/categories/list
List all available template categories.
Response:
["cli", "api", "tui", "gui", "auth", "database", "workflow", "other"]
Settings Management
GET /api/settings
Get all application settings.
Response:
{
"app": {
"name": "OpenTester",
"version": "0.1.0",
"data_dir": "/path/to/data"
},
"execution": {
"default_timeout": 30,
"parallel_limit": 4
},
"ui": {
"theme": "system",
"language": "en"
},
"features": {
"websocket_enabled": true,
"batch_execution": true
}
}
POST /api/settings
Update application settings.
Request Body:
{
"app": {},
"execution": {
"default_timeout": 60
},
"ui": {
"theme": "dark"
},
"features": {}
}
Response:
{
"message": "Settings updated successfully",
"settings": {...}
}
POST /api/settings/reset
Reset settings to defaults.
Query Parameters:
section- Optional section to reset ("app", "execution", "ui", "features")
Request Body:
{
"section": "ui"
}
Response:
{
"message": "Settings reset to defaults for section: ui",
"settings": {...}
}
Error Handling
Error Response Format
All errors follow this format:
{
"detail": "Error message"
}
HTTP Status Codes
| Status Code | Description |
|---|---|
| 200 | Success |
| 201 | Created successfully |
| 204 | Deleted successfully |
| 400 | Bad request parameters |
| 404 | Resource not found |
| 422 | Validation error |
| 500 | Internal server error |
Relationship with MCP
REST API is mainly for Web UI, MCP tools are for Agent integration.
| Feature | REST API | MCP Tool |
|---|---|---|
| Project Management | ✅ | ✅ |
| Test Case Management | ✅ | ✅ |
| Test Execution | ✅ | ✅ |
| DSL Validation | ✅ (parser/validate + cases/validate-dsl) | ✅ |
| Template Management | ✅ | ✅ |
| Settings Management | ✅ | ❌ |
| PRD Parsing | ❌ | ❌ |
Recommendations:
- Agents use MCP tools
- Web UI uses REST API
- Complex operations (e.g., DSL validation) use either MCP or REST API