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-prd is 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 CodeDescription
200Success
201Created successfully
204Deleted successfully
400Bad request parameters
404Resource not found
422Validation error
500Internal server error

Relationship with MCP

REST API is mainly for Web UI, MCP tools are for Agent integration.

FeatureREST APIMCP 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