Pricing Docs MCP

REST API

Integrate Runhuman directly from your backend, scripts, or any HTTP client.


Authentication

Include your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Get your key from the API Keys dashboard.


Synchronous Endpoint

POST /api/run creates a test and waits for completion. The request blocks until a human tester finishes (up to 10 minutes).

If the test does not complete within 10 minutes, the request returns 408 Timeout.


Asynchronous Endpoints

For longer tests or parallel testing, use the async pattern:

Step 1: Create a job

POST /api/jobs creates a test and returns immediately with a job ID.

const response = await fetch('https://runhuman.com/api/jobs', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://myapp.com/checkout',
    description: 'Complete the full checkout flow',
    targetDurationMinutes: 10,
    outputSchema: {
      checkoutWorks: { type: 'boolean', description: 'Order placed successfully?' }
    }
  })
});

const { jobId } = await response.json();
console.log('Job created:', jobId);

Step 2: Poll for results

GET /api/job/:jobId retrieves the job status and results.

async function pollJob(jobId) {
  while (true) {
    const response = await fetch(`https://runhuman.com/api/job/${jobId}`, {
      headers: { 'Authorization': `Bearer ${API_KEY}` }
    });
    const job = await response.json();

    if (job.status === 'completed') {
      return job;
    }

    if (['incomplete', 'abandoned', 'error'].includes(job.status)) {
      throw new Error(`Job failed: ${job.status}`);
    }

    await new Promise(resolve => setTimeout(resolve, 30000));
  }
}

const result = await pollJob(jobId);
console.log(result.result.data);

Request Parameters

ParameterTypeRequiredDefaultDescription
urlstringYes-URL for the tester to visit
descriptionstringYes-Instructions for the tester
outputSchemaobjectNo-Schema defining data to extract. If omitted, only success/explanation returned
targetDurationMinutesnumberNo5Time limit (1-60 minutes)
allowDurationExtensionbooleanNotrueAllow tester to request more time
maxExtensionMinutesnumber/falseNofalseMaximum extension allowed
additionalValidationInstructionsstringNo-Custom instructions for AI validation
screenSizestring/objectNodesktop”desktop”, “laptop”, “tablet”, “mobile”, or custom object
repoNamestringNo-GitHub repo (“owner/repo”) for better AI context
canCreateGithubIssuesbooleanNofalseAuto-create GitHub issues from bugs. Requires repoName

See Reference for full details on output schema format and response fields.


Handling Responses

A completed test returns:

{
  "status": "completed",
  "result": {
    "success": true,
    "explanation": "Login worked correctly. User was redirected to dashboard.",
    "data": {
      "loginWorks": true,
      "redirectsToHome": true
    }
  },
  "costUsd": 0.18,
  "testDurationSeconds": 100,
  "testerResponse": "I entered the credentials and clicked login...",
  "testerAlias": "Alex",
  "testerAvatarUrl": "https://images.subscribe.dev/uploads/.../phoenix.png",
  "testerColor": "#FF6B35",
  "testerData": {
    "screenshots": ["https://..."],
    "videoUrl": "https://..."
  }
}

Tester Identity Fields:

  • testerAlias: Anonymized tester name (e.g., “Alex”, “Jordan”, “Sam”)
  • testerAvatarUrl: Avatar image URL for displaying tester identity
  • testerColor: Hex color code for UI theming (e.g., “#FF6B35”)

These fields allow you to differentiate between testers while protecting their privacy.

Check status before accessing result. Only completed jobs have result data.


Testing a Checkout Flow

import requests

response = requests.post(
    'https://runhuman.com/api/run',
    headers={
        'Authorization': f'Bearer {API_KEY}',
        'Content-Type': 'application/json'
    },
    json={
        'url': 'https://myapp.com/products',
        'description': 'Add a product to cart, go to checkout, fill shipping info, verify the total is correct',
        'targetDurationMinutes': 10,
        'outputSchema': {
            'checkoutCompletes': { 'type': 'boolean', 'description': 'Checkout flow completes without errors?' },
            'totalCorrect': { 'type': 'boolean', 'description': 'Order total displays correctly?' },
            'issues': { 'type': 'array', 'description': 'Any issues found' }
        }
    }
)

result = response.json()
print(f"Checkout works: {result['result']['data']['checkoutCompletes']}")
print(f"Issues: {result['result']['data']['issues']}")

Custom Validation Instructions

Use additionalValidationInstructions to guide how GPT-4o interprets results:

const response = await fetch('https://runhuman.com/api/run', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://myapp.com/checkout',
    description: 'Complete checkout with test card 4242424242424242',
    outputSchema: {
      orderPlaced: { type: 'boolean', description: 'Order was placed?' },
      confirmationShown: { type: 'boolean', description: 'Confirmation number displayed?' }
    },
    additionalValidationInstructions: `
      Ignore minor UI glitches in the header.
      Focus only on whether the order was placed and confirmation shown.
      If tester mentions payment errors, mark orderPlaced as false even if they eventually succeeded.
    `
  })
});

Running Tests in Parallel

Create multiple jobs and poll them concurrently:

const urls = [
  'https://myapp.com/page1',
  'https://myapp.com/page2',
  'https://myapp.com/page3'
];

const jobIds = await Promise.all(
  urls.map(async (url) => {
    const res = await fetch('https://runhuman.com/api/jobs', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        url,
        description: 'Check if page loads and has no broken images',
        outputSchema: {
          pageLoads: { type: 'boolean', description: 'Page loads?' },
          brokenImages: { type: 'boolean', description: 'Any broken images?' }
        }
      })
    });
    const data = await res.json();
    return data.jobId;
  })
);

const results = await Promise.all(jobIds.map(pollJob));

Wrapper Function

A reusable function for your codebase:

async function runHumanTest(url, description, outputSchema, options = {}) {
  const response = await fetch('https://runhuman.com/api/run', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.RUNHUMAN_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url,
      description,
      outputSchema,
      targetDurationMinutes: options.targetDurationMinutes || 5
    })
  });

  if (!response.ok) {
    if (response.status === 408) {
      throw new Error('Test timed out after 10 minutes');
    }
    const error = await response.json();
    throw new Error(`API error: ${error.message}`);
  }

  return await response.json();
}

const result = await runHumanTest(
  'https://myapp.com/checkout',
  'Test the checkout flow',
  {
    checkoutWorks: { type: 'boolean', description: 'Checkout completed?' }
  }
);

Error Handling

HTTP StatusMeaning
400Invalid request parameters
401Invalid or missing API key
404Job not found
408Synchronous request timed out (10 minutes)
500Server error

All errors return:

{
  "error": "Error type",
  "message": "Detailed description"
}

Next Steps

TopicLink
Full technical specificationReference
Practical recipesCookbook
CI/CD integrationGitHub Actions