Reference

Complete technical specification for Runhuman. Integration guides link here for shared details.


Job Lifecycle

Every test moves through these states, matching the chips on the Jobs dashboard:

StatusDescriptionTerminal
PendingJob created; the platform is preparing it for testersNo
QueuedReleased to testers, awaiting claimNo
WorkingA tester has claimed the job and is actively testingNo
CompletedTester submitted results and AI extraction succeededYes
CancelledTester or admin ended the job before submitting resultsYes
ExpiredNo tester claimed in time, or the claimed tester ran out the clockYes
FailedTest could not produce a result (AI extraction failed, platform error, or the target URL wasn’t reachable)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.

DurationCost
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).

ParameterTypeRequiredDescription
projectIdstringConditionalProject ID. Required unless using an org-scoped API key with githubRepos for auto-creation
organizationIdstringNoOrganization ID. Used with PATs when projectId is not provided
urlstringConditionalURL for the tester to visit. Required for simple jobs; optional with prNumbers, issueNumbers, or template
descriptionstringConditionalInstructions for the tester. Required for simple jobs; optional with prNumbers, issueNumbers, or template
outputSchemaobjectNoJSON Schema defining data to extract. If omitted, only success/explanation returned
resultsTemplatestringNoMDForm template for free-form text reports (alternative to outputSchema)
templatestringNoTemplate name to use as base configuration. See Templates.
templateContentstringNoRaw template content (markdown with YAML frontmatter). See Templates.
targetDurationMinutesnumberNoTime limit in minutes. Default: 30. Range: 1-120. Total with extensions must not exceed 120
maxExtensionMinutesnumberNoTotal extension time in minutes. Default: 15
maxExtensionCountnumberNoNumber of extensions. Default: 3. Set to 0 to disable extensions
additionalValidationInstructionsstringNoCustom instructions for AI result validation
deviceClassstringNoDevice class: "desktop" or "mobile"
githubReposstring[]NoGitHub repos (["owner/repo"]). Required when using prNumbers or issueNumbers
githubRepostringNoSingle GitHub repo ("owner/repo"). Use githubRepos for multiple

| 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 | | 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 ClassDimensions
desktop1600x900
mobile375x812 (portrait)

Response Fields

Job status responses (GET /api/jobs/:jobId) return these fields:

FieldTypeDescription
idstringUnique job identifier
statusstringJob status (see Job Lifecycle)
resultobjectExtracted data matching your outputSchema
result.successbooleanWhether extraction succeeded
result.explanationstringAI’s interpretation of the test
result.dataobjectStructured data matching your schema
errorstringError message (if job failed)
reasonstringFailure reason category
claimedAtstringTimestamp when tester claimed the job
completedAtstringTimestamp when job completed
costUsdnumberTotal cost in USD
testDurationSecondsnumberTime the tester spent
extractedIssuesarrayAI-extracted issues with titles, descriptions, severity, reproduction steps, suggested labels, and related existing issues (see Extracted Issues below)
testerResponsestringRaw natural language feedback before extraction
testerAliasstringAnonymized tester name (e.g., “Phoenix”)
testerAvatarUrlstringAvatar image URL for UI display
testerDataobjectCaptured testing artifacts
jobUrlstringFull URL to view job on the dashboard
projectNamestringName of the project this job belongs to
keyMomentsarrayKey moments from test execution timeline
targetDurationMinutesnumberConfigured time limit for the test
maxExtensionMinutesnumberTotal extension time in minutes
maxExtensionCountnumberNumber of extensions allowed
extensionsUsednumberNumber of extensions the tester has used
responseDeadlinestringTimestamp 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
  }>;
  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 ReproductionStep {
  text: string;                     // Step description
  videoOffsetSeconds?: number;      // Video offset in seconds for this step
}

interface ExtractedIssue {
  title: string;                    // Short issue title
  description: string;              // Detailed description of the finding
  severity: 'critical' | 'high' | 'medium' | 'low';
  expectedBehavior: string;         // What should have happened (grounded in the tester's task)
  actualBehavior: string;           // What actually happened (grounded in telemetry)
  affectedArea: string;             // Specific element + route, e.g. "Login button at /login"
  evidenceCitations: EvidenceCitation[];  // Timestamped citations from session telemetry
  reproductionSteps: ReproductionStep[];  // Numbered steps to reproduce with optional video timestamps
  videoOffsetSeconds?: number;      // Video offset when the bug is most visible
  suggestedLabels: string[];        // Suggested GitHub labels (e.g., ["bug", "ui"])
  relatedEvidence?: RelatedEvidence[];  // Stage-4 telemetry decorations grouped by category
  relatedSentryEventIds?: string[];  // IDs of Sentry events linked to this issue
  relatedIssues?: RelatedIssueInfo[];  // Existing issues that match this finding
}

interface EvidenceCitation {
  timestamp: string;                // ISO timestamp of the cited event
  source: 'console' | 'network' | 'sentry' | 'page_state' | 'voice' | 'chat' | 'crash' | 'interaction';
  summary: string;                  // One-line summary of what the citation shows
  videoOffsetSeconds: number | null;  // Video offset for this citation, or null if outside the recording
}

interface RelatedEvidence {
  category: 'network' | 'console' | 'crash' | 'page_state' | 'sentry';
  timestamp: string;                // ISO timestamp of the evidence
  summary: string;                  // One-line summary
  detail?: string;                  // Optional deeper detail (e.g. network failure reason)
}

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",
      "expectedBehavior": "Tapping 'Place Order' submits the cart and navigates to the order-confirmation page.",
      "actualBehavior": "Tapping 'Place Order' produces no visible response and the user stays on /checkout.",
      "affectedArea": "'Place Order' button (button[data-testid='checkout-submit']) at /checkout on mobile Safari",
      "evidenceCitations": [
        { "timestamp": "2026-04-20T15:02:11.400Z", "source": "console", "summary": "TypeError: submitOrder is not a function", "videoOffsetSeconds": 68 },
        { "timestamp": "2026-04-20T15:02:11.480Z", "source": "interaction", "summary": "click on 'Place Order' button, no navigation followed", "videoOffsetSeconds": 68 }
      ],
      "reproductionSteps": [
        { "text": "Open the site on mobile Safari", "videoOffsetSeconds": 12 },
        { "text": "Add items to cart", "videoOffsetSeconds": 35 },
        { "text": "Navigate to checkout", "videoOffsetSeconds": 52 },
        { "text": "Tap 'Place Order' — nothing happens", "videoOffsetSeconds": 68 }
      ],
      "videoOffsetSeconds": 68,
      "suggestedLabels": ["bug", "mobile"],
      "relatedEvidence": [
        { "category": "console", "timestamp": "2026-04-20T15:02:11.400Z", "summary": "TypeError: submitOrder is not a function", "detail": "Thrown at CheckoutPage.tsx:142" }
      ],
      "relatedSentryEventIds": ["b3f4c92d1a8e4f5c"],
      "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"
        }
      ]
    }
  ]
}

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:

ToolDescription
list_organizationsList organizations the user belongs to
list_projectsList projects, optionally filtered by organization
create_jobCreate a custom QA test job
run_templateCreate a job from a pre-configured template
waitPoll for job completion (automatic retry)
get_jobQuick status check without waiting
list_templatesList available templates for a project

Error Codes

HTTP StatusMeaning
400Bad request. Invalid parameters.
401Unauthorized. Invalid or missing API key/PAT.
403Forbidden. Insufficient permissions.
404Not found. Resource does not exist.
408Timeout. Synchronous request exceeded 60 minutes.
500Server 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.