Reference
Complete technical specification for Runhuman. Integration guides link here for shared details.
Job Lifecycle
Every test follows this sequence:
| Status | Description | Terminal |
|---|---|---|
| pending | Job created, queued for posting to testers | No |
| preparing | AI generating test plan from PR/issue data | No |
| waiting | Posted to Slack, awaiting tester claim | No |
| working | Tester claimed and is actively testing | No |
| completed | Test finished, results extracted | Yes |
| incomplete | Test finished but missing required data | Yes |
| abandoned | Tester abandoned before completing | Yes |
| rejected | Tester determined instructions were invalid or impossible | Yes |
| error | System error occurred | Yes |
Terminal statuses will not change. Non-terminal statuses should be polled until resolution.
Output Schema
Define the structure of data you want extracted from the tester’s response.
Format
{
[fieldName: string]: {
type: "boolean" | "string" | "number" | "array" | "object";
description: string;
example?: any;
}
}
Example
{
"loginWorks": {
"type": "boolean",
"description": "Does login work with valid credentials?"
},
"errorMessage": {
"type": "string",
"description": "What error appears for invalid password?"
},
"issuesFound": {
"type": "array",
"description": "List of any UI/UX issues discovered"
}
}
Keep schemas simple. Testers describe findings in natural language, and AI extracts structured data matching your schema.
Cost
Tests are billed per second at $0.0085/second.
| Duration | Cost |
|---|---|
| 60 seconds | $0.51 |
| 120 seconds | $1.02 |
| 300 seconds (5 min) | $2.55 |
| 600 seconds (10 min) | $5.10 |
Duration is rounded up using Math.ceil(). A test lasting 61 seconds costs the same as 62 seconds. Cost is stored at full precision, not rounded to cents.
Request Parameters
These parameters apply to all job creation endpoints (POST /api/jobs, POST /api/run).
| Parameter | Type | Required | Description |
|---|---|---|---|
| projectId | string | Conditional | Project ID. Required unless using an org-scoped API key with githubRepo for auto-creation |
| organizationId | string | No | Organization ID. Used with PATs when projectId is not provided |
| url | string | Conditional | URL for the tester to visit. Required for simple jobs; optional with prNumbers, issueNumbers, or template |
| description | string | Conditional | Instructions for the tester. Required for simple jobs; optional with prNumbers, issueNumbers, or template |
| outputSchema | object | No | JSON Schema defining data to extract. If omitted, only success/explanation returned |
| resultsTemplate | string | No | MDForm template for free-form text reports (alternative to outputSchema) |
| template | string | No | Template name to use as base configuration. See Templates. |
| templateContent | string | No | Raw template content (markdown with YAML frontmatter). See Templates. |
| targetDurationMinutes | number | No | Time limit in minutes. Default: 30. Range: 1-60 |
| allowDurationExtension | boolean | No | Allow tester to request more time. Default: true |
| maxExtensionMinutes | number or false | No | Maximum extension allowed. Default: false (unlimited) |
| additionalValidationInstructions | string | No | Custom instructions for AI result validation |
| deviceClass | string | No | Device class: "desktop" or "mobile" |
| githubRepo | string | No | GitHub repo ("owner/repo"). Required when using prNumbers or issueNumbers |
| githubToken | string | No | GitHub token for operations without GitHub App installation |
| prNumbers | number[] | No | PR numbers for AI test plan generation (async only) |
| issueNumbers | number[] | No | Issue numbers for AI test plan generation (async only) |
| checkTestability | boolean | No | Reject job early if not testable. Default: true when prNumbers/issueNumbers provided |
| attachments | array | No | Media attachments (max 10). Type auto-detected from URL |
| metadata | object | No | Custom metadata for tracking job source and context |
additionalValidationInstructions
Use this parameter to guide how AI interprets results:
{
"additionalValidationInstructions": "Ignore minor UI glitches in the header. Focus only on whether the order was placed and confirmation number displayed."
}
deviceClass
Control the browser viewport for testing different devices:
| Device Class | Dimensions |
|---|---|
| desktop | 1600x900 |
| mobile | 375x812 (portrait) |
Response Fields
Job status responses (GET /api/jobs/:jobId) return these fields:
| Field | Type | Description |
|---|---|---|
| id | string | Unique job identifier |
| status | string | Job status (see Job Lifecycle) |
| result | object | Extracted data matching your outputSchema |
| result.success | boolean | Whether extraction succeeded |
| result.explanation | string | AI’s interpretation of the test |
| result.data | object | Structured data matching your schema |
| error | string | Error message (if job failed) |
| reason | string | Failure reason category |
| claimedAt | string | Timestamp when tester claimed the job |
| completedAt | string | Timestamp when job completed |
| costUsd | number | Total cost in USD |
| testDurationSeconds | number | Time the tester spent |
| extractedIssues | array | AI-extracted issues with titles, descriptions, severity, reproduction steps, suggested labels, and related existing issues (see Extracted Issues below) |
| testerResponse | string | Raw natural language feedback before extraction |
| testerAlias | string | Anonymized tester name (e.g., “Phoenix”) |
| testerAvatarUrl | string | Avatar image URL for UI display |
| testerData | object | Captured testing artifacts |
| jobUrl | string | Full URL to view job on the dashboard |
| projectName | string | Name of the project this job belongs to |
| keyMoments | array | Key moments from test execution timeline |
| targetDurationMinutes | number | Configured time limit for the test |
| totalExtensionMinutes | number | Total extension time granted |
| responseDeadline | string | Timestamp when tester response is due |
Fields like result, costUsd, testerResponse, and testerData only appear when status is completed.
Tester Data
The testerData object contains artifacts captured during the test session:
{
testDurationSeconds: number;
consoleMessages: Array<{
type: string; // "log", "error", "warn", etc.
message: string;
timestamp: string;
}>;
networkRequests: Array<{
url: string;
method: string; // "GET", "POST", etc.
status?: number; // HTTP status code
timestamp: string;
}>;
clicks: Array<{
x: number;
y: number;
timestamp: string;
element?: string; // Element selector if available
}>;
screenshots: string[]; // URLs to captured screenshots
videoUrl?: string; // URL to session recording
}
Extracted Issues
The extractedIssues field contains AI-extracted issues from the tester’s findings. Each issue includes structured data and optional related issue detection (when a GitHub repo is linked).
interface ExtractedIssue {
title: string; // Short issue title
description: string; // Detailed description of the finding
severity: 'critical' | 'high' | 'medium' | 'low';
reproductionSteps: string[]; // Numbered steps to reproduce
suggestedLabels: string[]; // Suggested GitHub labels (e.g., ["bug", "ui"])
relatedIssues?: RelatedIssueInfo[]; // Existing issues that match this finding
}
interface RelatedIssueInfo {
issueNumber: number; // GitHub issue number
title: string; // Issue title
state: 'open' | 'closed'; // Current issue state
relation: 'duplicate' | 'related'; // How this issue relates
confidence: number; // 0.0–1.0 confidence score
reason: string; // Why this issue is related
}
Example
{
"extractedIssues": [
{
"title": "Checkout button unresponsive on mobile",
"description": "The 'Place Order' button does not respond to taps on mobile Safari.",
"severity": "high",
"reproductionSteps": [
"Open the site on mobile Safari",
"Add items to cart",
"Navigate to checkout",
"Tap 'Place Order' — nothing happens"
],
"suggestedLabels": ["bug", "mobile"],
"relatedIssues": [
{
"issueNumber": 42,
"title": "Checkout button broken on Safari",
"state": "closed",
"relation": "duplicate",
"confidence": 0.85,
"reason": "Same Safari checkout button issue, previously fixed but appears to have regressed"
},
{
"issueNumber": 99,
"title": "Mobile tap targets too small",
"state": "open",
"relation": "related",
"confidence": 0.45,
"reason": "Related mobile interaction issue with touch targets"
}
]
}
]
}
How Related Issues Work
When the project has a linked GitHub repository with access, the AI compares each extracted issue against existing GitHub issues:
duplicate(confidence > 70%): The finding matches an existing issue. Use this to comment on the existing issue instead of creating a duplicate.related(confidence 30–70%): The finding is similar but distinct. Mention these when creating a new issue for additional context.
Issues with confidence below 30% are not included.
API Endpoints
POST /api/run
Synchronous endpoint. Creates a job and waits for completion, blocking up to 60 minutes.
Request:
{
"url": "https://example.com",
"description": "Test the checkout flow",
"outputSchema": {
"checkoutWorks": { "type": "boolean", "description": "Order placed successfully?" }
}
}
Response (200):
{
"id": "job_abc123",
"status": "completed",
"result": {
"success": true,
"explanation": "Checkout completed successfully",
"data": { "checkoutWorks": true }
},
"costUsd": 0.396,
"testDurationSeconds": 220,
"testerResponse": "I completed the checkout...",
"testerAlias": "Phoenix",
"testerAvatarUrl": "https://...",
"testerData": { "..." : "..." }
}
Response (408): Test did not complete within 60 minutes.
POST /api/jobs
Asynchronous endpoint. Creates a job and returns immediately.
Response (201):
{
"jobId": "job_abc123",
"message": "Job created successfully. Use GET /api/jobs/:jobId to check status."
}
When prNumbers or issueNumbers are provided, the job starts in preparing status while AI generates the test plan:
{
"jobId": "job_abc123",
"message": "Job created with status preparing. AI is generating the test plan.",
"testability": { "testable": true, "reason": "..." }
}
GET /api/jobs/:jobId
Retrieves full job details including conversation history. Requires authentication.
Response:
{
"id": "job_abc123",
"status": "completed",
"result": {
"success": true,
"explanation": "Checkout completed successfully",
"data": { "checkoutWorks": true }
},
"costUsd": 0.54,
"testDurationSeconds": 300,
"testerResponse": "I completed the checkout flow...",
"testerAlias": "Phoenix",
"testerAvatarUrl": "https://...",
"testerData": { "..." : "..." },
"jobUrl": "https://runhuman.com/dashboard/proj_abc/jobs/job_abc123",
"projectName": "My Web App",
"keyMoments": []
}
See Response Fields for the complete field reference.
GET /api/jobs/:jobId/status
Lightweight status check. Returns the same fields as GET /api/jobs/:jobId but without conversation history. Does not require authentication — useful for webhooks and external polling.
MCP Tools
For full MCP documentation, see the MCP guide. Below is a summary of available tools.
Runhuman exposes 7 MCP tools:
| Tool | Description |
|---|---|
list_organizations | List organizations the user belongs to |
list_projects | List projects, optionally filtered by organization |
create_job | Create a custom QA test job |
run_template | Create a job from a pre-configured template |
wait | Poll for job completion (automatic retry) |
get_job | Quick status check without waiting |
list_templates | List available templates for a project |
Error Codes
| HTTP Status | Meaning |
|---|---|
| 400 | Bad request. Invalid parameters. |
| 401 | Unauthorized. Invalid or missing API key/PAT. |
| 403 | Forbidden. Insufficient permissions. |
| 404 | Not found. Resource does not exist. |
| 408 | Timeout. Synchronous request exceeded 60 minutes. |
| 500 | Server error. |
All errors return this format:
{
"error": "Error type",
"message": "Detailed description"
}
Authentication
Include your API key or Personal Access Token in the Authorization header:
Authorization: Bearer YOUR_API_KEY_OR_PAT
Two token types:
- API Keys — Organization-scoped. Get from your organization’s API Keys page in the Dashboard.
- Personal Access Tokens (PATs) — User-scoped. Create from Settings > Tokens.
See the REST API guide for details on when to use each type.