For AI Agents & Developers

API reference, authentication, webhooks, MCP integration, and the auto-fix loop

Quick Start

Get your API key and submit your first test cycle in under 5 minutes.

1. Get an API Key

Sign in at clawqa.ai with GitHub, then go to Settings → API Keys and generate a key. Keys are prefixed with cqa_.

2. Submit a Test Cycle

curl -X POST https://clawqa.ai/api/v1/test-cycles \
  -H "Authorization: Bearer cqa_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": 1,
    "title": "Login flow regression test",
    "targetUrl": "https://staging.myapp.com",
    "steps": [
      {
        "title": "Login with valid credentials",
        "description": "Navigate to /login, enter valid email and password, click Submit",
        "expectedResult": "User is redirected to dashboard with welcome message"
      },
      {
        "title": "Login with invalid password",
        "description": "Navigate to /login, enter valid email but wrong password, click Submit",
        "expectedResult": "Error message shown: Invalid credentials. Form is not cleared."
      }
    ]
  }'
import requests

response = requests.post(
    "https://clawqa.ai/api/v1/test-cycles",
    headers={"Authorization": "Bearer cqa_your_api_key"},
    json={
        "projectId": 1,
        "title": "Login flow regression test",
        "targetUrl": "https://staging.myapp.com",
        "steps": [
            {
                "title": "Login with valid credentials",
                "description": "Navigate to /login, enter valid email and password, click Submit",
                "expectedResult": "User is redirected to dashboard with welcome message"
            }
        ]
    }
)
print(response.json())  # {"id": 42, "status": "submitted"}
const response = await fetch("https://clawqa.ai/api/v1/test-cycles", {
  method: "POST",
  headers: {
    "Authorization": "Bearer cqa_your_api_key",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    projectId: 1,
    title: "Login flow regression test",
    targetUrl: "https://staging.myapp.com",
    steps: [
      {
        title: "Login with valid credentials",
        description: "Navigate to /login, enter valid email and password, click Submit",
        expectedResult: "User is redirected to dashboard with welcome message"
      }
    ]
  })
});
const cycle = await response.json();
console.log(cycle); // { id: 42, status: "submitted" }

Authentication

All authenticated endpoints require a Bearer token in the Authorization header:

Authorization: Bearer cqa_your_api_key

API keys:

Unauthenticated requests to protected endpoints return 401 Unauthorized.

Creating Test Cycles

A test cycle represents a batch of test steps to be executed by human testers. Send a POST request to /api/v1/test-cycles:

POST /api/v1/test-cycles
Content-Type: application/json
Authorization: Bearer cqa_...

{
  "projectId": 1,
  "title": "Checkout flow — v2.3.1",
  "targetUrl": "https://staging.myapp.com",
  "steps": [
    {
      "title": "Add item to cart",
      "description": "Search for 'wireless headphones', click first result, click 'Add to Cart'",
      "expectedResult": "Cart badge shows 1 item. Toast notification confirms addition."
    },
    {
      "title": "Complete checkout",
      "description": "Click cart icon, click 'Checkout', fill in test payment details, click 'Place Order'",
      "expectedResult": "Order confirmation page shown with order number and estimated delivery date."
    }
  ]
}

Response:

{
  "id": 42,
  "projectId": 1,
  "title": "Checkout flow — v2.3.1",
  "status": "submitted",
  "stepsCount": 2,
  "createdAt": "2026-02-20T01:30:00Z"
}

The cycle is immediately routed to testers. You'll receive webhook events as results come in.

Receiving Results

Register a Webhook

POST /api/v1/webhooks
Authorization: Bearer cqa_...

{
  "url": "https://your-agent.example.com/clawqa-webhook",
  "events": ["bug_report.created", "bug_report.verified", "bug_report.fix_failed", "cycle.completed"],
  "secret": "whsec_your_signing_secret"
}

Webhook Payload: bug_report.created

{
  "event": "bug_report.created",
  "timestamp": "2026-02-20T03:15:00Z",
  "data": {
    "id": 101,
    "cycleId": 42,
    "title": "Checkout button unresponsive on iOS Safari",
    "severity": "critical",
    "description": "Tapping 'Place Order' does nothing on iPhone 15 Pro, iOS 17.3, Safari.",
    "stepsToReproduce": "1. Open staging URL on iPhone\n2. Add item to cart\n3. Proceed to checkout\n4. Fill payment details\n5. Tap 'Place Order'\n6. Nothing happens",
    "screenshotUrl": "https://clawqa.ai/uploads/bug-101-screenshot.png",
    "device": "iPhone 15 Pro",
    "os": "iOS 17.3",
    "browser": "Safari 17.3"
  }
}

Webhook Payload: bug_report.verified

{
  "event": "bug_report.verified",
  "timestamp": "2026-02-20T04:30:00Z",
  "data": {
    "id": 101,
    "cycleId": 42,
    "fixAttemptId": 5,
    "status": "verified",
    "verifiedBy": "human_tester",
    "notes": "Fix confirmed — Place Order button now works correctly on iOS Safari."
  }
}

The Auto-Fix Loop

When your agent receives a bug_report.created webhook, it enters the fix loop:

🐛 Receive Bug 🔍 Analyze ✍️ Write Fix 🚀 Deploy 📤 Submit Fix ✅ Verified? Pass → Done

Submit a fix:

POST /api/v1/bugs/101/fix
Authorization: Bearer cqa_...

{
  "commitUrl": "https://github.com/myorg/myapp/commit/abc123",
  "deployUrl": "https://staging.myapp.com",
  "notes": "Fixed iOS Safari touch event handler — was using click instead of pointerdown"
}

ClawQA will automatically request re-testing from crowd testers. If the fix passes, you receive a bug_report.verified webhook. If it fails, you receive bug_report.fix_failed with tester notes, and the loop continues.

MCP Integration

ClawQA exposes an MCP (Model Context Protocol) server, allowing AI agents to interact with the platform using the standard for AI tool integration.

MCP Server URL

https://clawqa.ai/api/mcp

Available Tools

── Project Setup ──
clawqa.create_project(name, targetUrl, repoUrl)        → {project, generatedPlans[]} (auto-generates plans!)

── Core ──
clawqa.list_projects()                                 → [{id, name, slug, url, plans, cycles}]
clawqa.list_plans(projectId?)                          → [{id, title, steps, version}]
clawqa.list_cycles(projectId?)                         → [{id, title, status, plan}]
clawqa.get_bugs(cycleId?, severity?)                   → [{id, title, severity, steps}]
clawqa.get_analytics(projectId?)                       → {totalCycles, totalBugs, ...}

── Plan Lifecycle ──
clawqa.generate_plans(projectId, focus?)               → {plans[], count}
clawqa.classify_steps(planId, classifications[])       → {classificationsCached}
clawqa.enrich_plan_actions(planId, steps[])            → {stepsEnriched}
clawqa.get_page_context(url)                           → {forms, buttons, links, headings}
clawqa.execute_plan(planId)                            → {testCycle, testRuns[]}

── Execution ──
clawqa.run_all_plans(projectId)                        → {results[]}
clawqa.rerun(cycleId, runId)                           → {rerun}

── Bug Fix ──
clawqa.submit_fix(bugId, commitUrl?, notes?)           → {fixAttempt}
clawqa.escalate(cycleId, reason?)                      → {externalCycleId}

MCP gives agents a structured way to discover and call ClawQA tools without needing to know the REST API details.

Enriching Test Plans (Recommended Workflow)

ClawQA generates test plans with deterministic action matching. For higher-quality Playwright tests, agents should enrich plans with structured actions and step classifications. Here's the recommended workflow:

1. clawqa.create_project(name, targetUrl, repoUrl)  → Project created + plans auto-generated!
2. clawqa.enrich_plan_actions(planId, steps)         → Improve test accuracy with structured actions
3. clawqa.execute_plan(planId)                       → Start testing

── Optional enrichment (for higher precision) ──
   clawqa.get_page_context(url)                      → Discover page elements (forms, buttons, links)
   clawqa.classify_steps(planId, classifications)     → Tell ClawQA which steps are automated vs manual

Steps 2-3 of the enrichment phase are optional. Without them, ClawQA uses deterministic rules (less precise but functional). With them, Playwright tests are significantly more accurate.

classify_steps — Step Classification

Tell ClawQA which steps should be automated (Playwright) vs manual (human testers). Classifications are cached and used during step partitioning when execute_plan is called.

// MCP: clawqa.classify_steps
// REST: POST /api/v1/test-plans/{planId}/classify-steps
{
  "planId": "plan_abc123",
  "classifications": [
    { "stepIndex": 0, "type": "automated", "reason": "Simple navigation + form submit" },
    { "stepIndex": 1, "type": "automated", "reason": "Page element verification" },
    { "stepIndex": 2, "type": "manual", "reason": "Visual layout check requires human judgment" },
    { "stepIndex": 3, "type": "manual", "reason": "Email verification — external system" }
  ]
}

enrich_plan_actions — Structured Actions

Provide structured TestAction[] for specific steps. Steps with actions produce precise Playwright code with real locators and assertions.

// MCP: clawqa.enrich_plan_actions
// REST: POST /api/v1/test-plans/{planId}/enrich-actions
{
  "planId": "plan_abc123",
  "steps": [
    {
      "stepIndex": 0,
      "actions": [
        {
          "type": "navigate",
          "url": "https://myapp.com/login",
          "description": "Open login page"
        },
        {
          "type": "fill",
          "target": { "strategy": "label", "value": "Email" },
          "value": "test@example.com",
          "description": "Fill email field"
        },
        {
          "type": "fill",
          "target": { "strategy": "label", "value": "Password" },
          "value": "testpass123",
          "description": "Fill password field"
        },
        {
          "type": "click",
          "target": { "strategy": "role", "value": "Sign in" },
          "description": "Click sign in button"
        }
      ]
    }
  ]
}

TestAction Reference

Action types: navigate, click, fill, select, check, expect_visible, expect_text, expect_url, expect_count, screenshot, wait

Locator strategies: role, text, label, placeholder, css, testid

Validation rules: 1-6 actions per step. fill requires value. navigate requires url. expect_text requires expectedText. expect_url requires expectedUrl. Click/fill/expect actions require target.

get_page_context — Page Discovery

Crawl a page to discover interactive elements. Use this data to generate accurate actions for enrich_plan_actions.

// MCP: clawqa.get_page_context({ url: "https://myapp.com/login" })
// REST: GET /api/v1/page-context?url=https://myapp.com/login
// Returns:
{
  "url": "https://myapp.com/login",
  "title": "Login — MyApp",
  "hasAuth": true,
  "forms": [{
    "method": "POST",
    "submitButton": "Sign in",
    "fields": [
      { "label": "Email", "type": "email", "name": "email", "required": true },
      { "label": "Password", "type": "password", "name": "password", "required": true }
    ]
  }],
  "buttons": [{ "text": "Sign in with GitHub", "role": "button" }],
  "headings": [{ "level": 1, "text": "Welcome back" }],
  "links": [{ "text": "Forgot password?", "href": "/reset-password" }]
}

OpenClaw Skill Example

Here's a complete example of an OpenClaw skill that integrates with ClawQA:

// clawqa-testing-skill.js — OpenClaw Skill
const CLAWQA_API = "https://clawqa.ai/api/v1";
const API_KEY = process.env.CLAWQA_API_KEY;

// Create a test cycle from a PR
async function createTestCycleFromPR(projectId, prData) {
  const steps = analyzePRForTestSteps(prData);
  const response = await fetch(`${CLAWQA_API}/test-cycles`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      projectId,
      title: `PR #${prData.number}: ${prData.title}`,
      targetUrl: prData.deployUrl,
      steps
    })
  });
  return response.json();
}

// Analyze PR diff to generate test steps
function analyzePRForTestSteps(prData) {
  return prData.changedFiles.map(file => ({
    title: `Verify changes in ${file.path}`,
    description: `Test the functionality affected by changes to ${file.path}. ` +
      `${file.additions} lines added, ${file.deletions} lines removed.`,
    expectedResult: "Feature works correctly with no regressions."
  }));
}

// Handle incoming bug webhook
async function handleBugWebhook(event) {
  if (event.event !== "bug_report.created") return;
  const bug = event.data;
  console.log(`Bug received: ${bug.title} (${bug.severity})`);

  const fix = await analyzeBugAndFix(bug);
  if (fix) {
    await fetch(`${CLAWQA_API}/bugs/${bug.id}/fix`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        commitUrl: fix.commitUrl,
        deployUrl: fix.deployUrl,
        notes: fix.description
      })
    });
    console.log(`Fix submitted for bug ${bug.id}`);
  }
}

module.exports = { createTestCycleFromPR, handleBugWebhook };