API Reference

POST a URL, get a human QA report. Integrate TestMyVibes into your CI/CD โ€” tests run async, results come back fast.

Base URL https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1

Authentication

Every request needs a Bearer token. Grab one from Dashboard → API Keys.

Authorization: Bearer tmv_live_abc123...

Rate limit: 60 req/min per key. Keys available on Pro and Scale plans.

Errors

All errors return JSON with error and code fields.

StatusCodeMeaning
400INVALID_REQUESTMissing or malformed fields
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENKey doesn't have access to this resource
404NOT_FOUNDJob, project, or resource doesn't exist
429RATE_LIMITEDToo many requests โ€” slow down
500INTERNAL_ERRORSomething broke on our end
{
  "error": "Insufficient credits",
  "code": "INSUFFICIENT_CREDITS"
}

Jobs

POST /v1/jobs Submit a test job. Returns a jobId immediately โ€” tests run async.

Request Body

FieldTypeRequiredDescription
urlstringYesURL to test
titlestringYesJob title
descriptionstringYesWhat should the checker test?
jobTypestringYesform_submission, auth_flow, payment_flow, mobile_responsive, full_user_story, first_impression, custom
checklistItemsarrayNoCustom checklist items [{description, expectedOutcome, severity}]. If omitted, AI generates them.
prioritystringNo"normal" (default) or "urgent" (2x credits, jumps the queue)
projectIdstringNoAssociate with a project
slaMinutesnumberNo15, 30, or 60 (default 60)
credentialsobjectNo{username, password, notes} โ€” AES-256-GCM encrypted at rest
generateCheckliststringNo"sync" or "async" (default). Sync waits for AI generation before responding.
curl -X POST https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://myapp.com",
    "title": "Test checkout flow",
    "description": "Complete a purchase with a test card",
    "jobType": "payment_flow",
    "priority": "normal"
  }'
const res = await fetch('https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer tmv_live_abc123',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://myapp.com',
    title: 'Test checkout flow',
    description: 'Complete a purchase with a test card',
    jobType: 'payment_flow',
    priority: 'normal'
  })
});
const data = await res.json();
import requests

res = requests.post(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs",
    headers={"Authorization": "Bearer tmv_live_abc123"},
    json={
        "url": "https://myapp.com",
        "title": "Test checkout flow",
        "description": "Complete a purchase with a test card",
        "jobType": "payment_flow",
        "priority": "normal"
    }
)
data = res.json()
{
  "jobId": "j_abc123",
  "status": "pending",
  "estimatedCompletionMinutes": 15,
  "creditsDeducted": 3,
  "checklistItemCount": 8
}
GET /v1/jobs List your jobs. Filter by status, project, or date.

Query Parameters

ParamTypeDefaultDescription
statusstringallpending, claimed, completed, failed
projectIdstringโ€”Filter by project
limitnumber201โ€“100
offsetnumber0Pagination offset
sincestringโ€”ISO 8601 date โ€” only jobs created after this
curl "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs?status=completed&limit=10" \
  -H "Authorization: Bearer tmv_live_abc123"
const res = await fetch('https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs?status=completed&limit=10', {
  headers: { 'Authorization': 'Bearer tmv_live_abc123' }
});
const data = await res.json();
import requests

res = requests.get(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs",
    headers={"Authorization": "Bearer tmv_live_abc123"},
    params={"status": "completed", "limit": 10}
)
data = res.json()
{
  "jobs": [
    {
      "id": "j_abc123",
      "status": "completed",
      "title": "Test checkout flow",
      "url": "https://myapp.com",
      "jobType": "payment_flow",
      "createdAt": "2026-03-14T20:30:00Z",
      "reportId": "r_def456"
    }
  ],
  "total": 42,
  "limit": 10,
  "offset": 0
}
GET /v1/jobs/:id Get a single job's status and metadata.

Path Parameters

ParamTypeDescription
idstringJob ID (e.g. j_abc123)
curl https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123 \
  -H "Authorization: Bearer tmv_live_abc123"
const res = await fetch('https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123', {
  headers: { 'Authorization': 'Bearer tmv_live_abc123' }
});
const data = await res.json();
import requests

res = requests.get(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123",
    headers={"Authorization": "Bearer tmv_live_abc123"}
)
data = res.json()
{
  "id": "j_abc123",
  "status": "completed",
  "title": "Test checkout flow",
  "url": "https://myapp.com",
  "jobType": "payment_flow",
  "creditsUsed": 3,
  "slaMinutes": 60,
  "createdAt": "2026-03-14T20:30:00Z",
  "claimedAt": "2026-03-14T20:31:00Z",
  "completedAt": "2026-03-14T20:42:00Z",
  "reportId": "r_def456"
}
GET /v1/jobs/:id/report Get the full QA report. Only available once the job is complete.
curl https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123/report \
  -H "Authorization: Bearer tmv_live_abc123"
const res = await fetch('https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123/report', {
  headers: { 'Authorization': 'Bearer tmv_live_abc123' }
});
const data = await res.json();
import requests

res = requests.get(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123/report",
    headers={"Authorization": "Bearer tmv_live_abc123"}
)
data = res.json()
{
  "id": "r_def456",
  "overallStatus": "fail",
  "passCount": 5,
  "failCount": 2,
  "skipCount": 0,
  "checkerSummary": "Checkout flow has two critical issues...",
  "items": [
    {
      "id": "ci_001",
      "description": "Submit form with empty email",
      "status": "fail",
      "checkerNote": "No validation shown โ€” form submits silently",
      "screenshotUrl": "https://storage.example.com/ss_001.png"
    },
    {
      "id": "ci_002",
      "description": "Complete purchase with test card",
      "status": "pass",
      "checkerNote": "Worked perfectly, confirmation page shown"
    }
  ],
  "recordingUrl": "https://storage.example.com/rec_001.webm",
  "checkerRating": 4.8,
  "createdAt": "2026-03-14T20:42:00Z"
}
POST /v1/jobs/:id/retest Re-run the same test. No body needed โ€” we clone the original job.
curl -X POST https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123/retest \
  -H "Authorization: Bearer tmv_live_abc123"
import requests

res = requests.post(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/j_abc123/retest",
    headers={"Authorization": "Bearer tmv_live_abc123"}
)
data = res.json()
{
  "jobId": "j_xyz789",
  "parentJobId": "j_abc123",
  "status": "pending",
  "creditsDeducted": 3
}
POST /v1/jobs/bulk Fire up to 20 jobs at once. Perfect for post-deploy smoke tests.
curl -X POST https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/bulk \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "jobs": [
      { "url": "https://myapp.com/login", "title": "Test login", "description": "Sign in with test creds", "jobType": "auth_flow" },
      { "url": "https://myapp.com/checkout", "title": "Test checkout", "description": "Complete a purchase", "jobType": "payment_flow" }
    ]
  }'
import requests

res = requests.post(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/bulk",
    headers={"Authorization": "Bearer tmv_live_abc123"},
    json={
        "jobs": [
            {"url": "https://myapp.com/login", "title": "Test login", "description": "Sign in", "jobType": "auth_flow"},
            {"url": "https://myapp.com/checkout", "title": "Test checkout", "description": "Buy something", "jobType": "payment_flow"}
        ]
    }
)
data = res.json()
{
  "batchId": "batch_abc123",
  "jobIds": ["j_001", "j_002"],
  "totalCreditsDeducted": 5,
  "estimatedCompletionMinutes": 20
}
GET /v1/jobs/bulk/:batchId Check the status of a bulk job batch.
curl https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/jobs/bulk/batch_abc123 \
  -H "Authorization: Bearer tmv_live_abc123"
{
  "batchId": "batch_abc123",
  "overallStatus": "in_progress",
  "totalJobs": 2,
  "completedJobs": 1,
  "passCount": 1,
  "failCount": 0,
  "jobs": [
    { "id": "j_001", "status": "completed" },
    { "id": "j_002", "status": "claimed" }
  ]
}

Projects

GET /v1/projects List all your projects with stats.
curl https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/projects \
  -H "Authorization: Bearer tmv_live_abc123"
import requests

res = requests.get(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/projects",
    headers={"Authorization": "Bearer tmv_live_abc123"}
)
data = res.json()
{
  "projects": [
    {
      "id": "p_abc123",
      "name": "My SaaS App",
      "defaultUrl": "https://myapp.com",
      "jobCount": 42,
      "passRate": 0.85,
      "createdAt": "2026-01-15T10:00:00Z"
    }
  ]
}
POST /v1/projects Create a new project. Gets you a deploy hook URL for CI integration.

Request Body

FieldTypeRequiredDescription
namestringYesProject name
descriptionstringNoWhat you're building
defaultUrlstringNoDefault URL to test
defaultJobTypestringNoDefault job type for new tests
defaultSlaMinutesnumberNoDefault SLA: 15, 30, or 60
curl -X POST https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/projects \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My SaaS App",
    "defaultUrl": "https://myapp.com",
    "defaultJobType": "full_user_story"
  }'
import requests

res = requests.post(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/projects",
    headers={"Authorization": "Bearer tmv_live_abc123"},
    json={
        "name": "My SaaS App",
        "defaultUrl": "https://myapp.com",
        "defaultJobType": "full_user_story"
    }
)
data = res.json()
{
  "projectId": "p_new123",
  "name": "My SaaS App",
  "deployHookUrl": "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/hooks/deploy/dhk_xxxxxxxx"
}

Credits

GET /v1/credits Check your credit balance, usage, and expiring credits.
curl https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/credits \
  -H "Authorization: Bearer tmv_live_abc123"
import requests

res = requests.get(
    "https://a41fb9b8-17b6-4c0b-93e5-d95854134fbb-00-2wp9ocxvv9nql.picard.replit.dev/api/v1/credits",
    headers={"Authorization": "Bearer tmv_live_abc123"}
)
data = res.json()
{
  "available": 47,
  "used": 28,
  "total": 75,
  "plan": "pro",
  "expiringCredits": [
    { "amount": 10, "expiresAt": "2026-06-14T00:00:00Z" }
  ]
}

Webhooks

Get real-time notifications when stuff happens. Set your webhook URL in Project Settings.

Events

EventFires when
job.createdA new test job is submitted
job.claimedA checker picks up the job
job.completedReport is ready โ€” go read it
job.failed_slaJob exceeded SLA without completion โ€” it's been re-queued

Payload Example

{
  "event": "job.completed",
  "timestamp": "2026-03-14T20:42:00Z",
  "data": {
    "jobId": "j_abc123",
    "status": "completed",
    "reportId": "r_def456",
    "overallStatus": "fail",
    "passCount": 5,
    "failCount": 2
  }
}

Verifying Signatures

Every webhook payload is signed with HMAC-SHA256 using your project's webhook secret. Check the X-TMV-Signature header.

const crypto = require('crypto');
const expected = crypto
  .createHmac('sha256', process.env.TMV_WEBHOOK_SECRET)
  .update(rawBody)
  .digest('hex');

if (expected === req.headers['x-tmv-signature']) {
  // legit โ€” process it
}

Retry Policy

3 attempts with exponential backoff (1s, 10s, 60s) on non-2xx responses. After 3 failures, the webhook is disabled โ€” re-enable it from project settings.

Changelog

March 2026 v1.0

Initial API release. 9 endpoints: submit jobs, list jobs, get status, reports, retest, bulk submit, bulk status, projects, credits. Webhook events for full job lifecycle.