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://testmyvibes.com/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. Generate keys from your dashboard.

Errors

Every error response from /v1, /api, and /hooks uses a single JSON envelope. Branch on code, log requestId, and surface hint to your users.

{
  "error": "Insufficient credits",
  "code": "INSUFFICIENT_CREDITS",
  "hint": "Top up credits in Dashboard → Billing, then retry.",
  "requestId": "k9c3xQ8nY7Pa"
}
FieldTypeNotes
errorstringHuman-readable summary. May change wording across versions — do not pattern-match it.
codestringStable machine-readable identifier. Branch on this. Values are SCREAMING_SNAKE_CASE and never renamed without a deprecation notice.
hintstring?Optional. Actionable next step (e.g. "Top up credits in Dashboard → Billing"). Safe to show to end-users.
requestIdstringShort server-issued id, also echoed in the x-request-id response header. Include this when contacting support — we look it up in our logs.

Some responses also include endpoint-specific extras at the top level (e.g. creditsRequired on a 402, details on a Zod validation failure). These are additive — your client can ignore unknown keys.

Standard codes

StatusCodeMeaning
400INVALID_REQUESTMissing, malformed, or schema-invalid fields. details carries the offending paths for Zod failures.
401UNAUTHORIZEDMissing, invalid, expired, or revoked API key.
402INSUFFICIENT_CREDITSAccount balance too low for this operation. Includes creditsRequired, creditsAvailable, creditsShort.
403FORBIDDENKey is valid but lacks access to this resource.
403ACCOUNT_SUSPENDEDAccount is suspended. Contact support.
404NOT_FOUNDJob, project, session, or resource doesn't exist for this account.
409CONFLICTResource is in a state that doesn't allow the requested operation (e.g. session already closed).
409WEBHOOK_SECRET_CONFLICTProject already has a different webhook secret. Rotate via the dashboard or send the existing value.
413PAYLOAD_TOO_LARGERequest body exceeded the 50 MB limit.
429RATE_LIMITEDMore than 60 requests/min for this API key. Slow down and retry.
500INTERNAL_ERRORSomething broke on our end. Retry with backoff; if it persists, share the requestId with support.

Request correlation

Every request — success or error — gets an x-request-id response header. Save it alongside the response in your logs so you can quote it back to support if you ever need to investigate. You can also send your own x-request-id request header (1–80 chars, A-Z a-z 0-9 . _ -) and we'll honor and echo it back. When you contact us, paste the id into the Request ID field on our support form and we'll find the matching log line in seconds.

curl -i https://testmyvibes.com/v1/credits \
  -H "Authorization: Bearer tmv_live_abc123"

HTTP/1.1 200 OK
x-request-id: k9c3xQ8nY7Pa
content-type: application/json
...

Recommended client pattern

const res = await fetch(`${BASE}/v1/replit/test`, { method: 'POST', headers, body });
const requestId = res.headers.get('x-request-id'); // log this on every call
const data = await res.json();
if (!res.ok) {
  // Branch on `code`, never on `error` text.
  switch (data.code) {
    case 'INSUFFICIENT_CREDITS': return showTopupModal(data);
    case 'RATE_LIMITED':         return retryAfter(60_000);
    case 'UNAUTHORIZED':         return promptForKey();
    default:                     throw new Error(`${data.code}: ${data.error} (req ${data.requestId || requestId})`);
  }
}

Replit Integration

The Replit integration endpoints are designed for one-command testing. Post a URL and optionally describe what to test — the AI auto-generates a targeted test scope, links it to a project, and queues a human checker.

Works with *.replit.dev (dev) and *.replit.app (published) URLs. Projects are auto-created and linked so regression testing works across deploys.

POST /v1/replit/test Submit a Replit app for human QA testing

One-shot endpoint: give it a URL, get back a job tracking handle. The AI auto-scopes the test based on the URL, optional feature description, and any previous failures for the same project.

urlrequired
URL to test. Replit dev/published domains auto-detected.
featureoptional
What to focus on, e.g. "test the signup flow" or "checkout is broken on mobile". AI tailors the checklist to this.
webhookUrloptional
URL to receive webhook events (job.created, job.completed). Saved to the auto-created project.
webhookSecretoptional
Bring your own HMAC signing secret. 32–256 printable ASCII characters. Pass on first call alongside webhookUrl and we'll sign every callback to your URL with this exact value — store it as an env var on your side, no dashboard round-trip needed. Re-passing the same value on later calls is a no-op; passing a different value to a project that already has a secret returns 409 WEBHOOK_SECRET_CONFLICT. Omit to have us generate one server-side (you'll then need to copy it from the dashboard).
subscribedWebhookEventsoptional
Array of event names to subscribe to. Allowed: job.created, job.completed, job.ai_report_ready, job.session_ready, job.session_completed, loop_session.ended. Omit to receive every event. An empty array disables delivery for the project. Unknown event names are rejected with 400 INVALID_REQUEST. Only applied when webhookUrl is also set on a brand new project; for existing projects, this updates the saved subscription list.
priorityoptional
"normal" (default) or "urgent" (2x credits, faster pickup).
jobTypeoptional
Override auto-detected job type. Options: First Impression, General QA, Payment Flow, Custom, AI Review.

Example — basic test

curl -X POST https://testmyvibes.com/v1/replit/test \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://my-app.replit.app"}'

Example — focused test

curl -X POST https://testmyvibes.com/v1/replit/test \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://my-app.replit.app",
    "feature": "test the new checkout flow",
    "webhookUrl": "https://my-server.com/webhook",
    "priority": "urgent"
  }'

Response 201

{
  "jobId": "j_abc123",
  "projectId": "p_xyz789",
  "status": "pending",
  "estimatedCompletionMinutes": 60,
  "creditsDeducted": 2,
  "creditsCharged": 2,
  "creditsRemaining": 18,
  "checklistItemCount": 7,
  "jobType": "Payment Flow",
  "isReplitApp": true,
  "regressionItemsIncluded": 3,
  "statusUrl": "https://testmyvibes.com/v1/replit/status/j_abc123",
  "reportUrl": "https://testmyvibes.com/v1/jobs/j_abc123/report",
  "dashboardReportUrl": "https://testmyvibes.com/projects/p_xyz789/jobs/j_abc123"
}

creditsDeducted and creditsCharged always carry the same value — pick whichever name you prefer. reportUrl is the JSON API endpoint for fetching the report; dashboardReportUrl is the operator-facing page to deep-link humans to.

Errors

// 402 — out of credits
{
  "error": "Insufficient credits",
  "code": "INSUFFICIENT_CREDITS",
  "creditsRequired": 4,
  "creditsAvailable": 1,
  "creditsShort": 3
}

// 401 — bad/missing API key
{ "error": "Invalid API key", "code": "UNAUTHORIZED" }

Branch on the code field, not error. Code values are stable across versions.

POST /v1/replit/sessions Launch a synchronized multi-checker session

Create a session where 2–10 human checkers test the same app in parallel and at the same moment. Useful for multi-role flows (buyer + seller, host + guest, admin + member) and for stress-testing across devices/browsers. Credits = perSlot × slot count, deducted up front. Each slot becomes its own job — poll any slot's jobId via /v1/replit/status/:jobId, or watch the session via the dashboard URL.

urlrequired
URL to test. Same rules as /v1/replit/test.
slotsrequired
Array of 2–10 slot objects. Each slot accepts roleLabel (e.g. "Buyer") and optional targeting fields: targetDevice, targetOS, targetBrowser, targetScreenSize, targetNetwork. Defaults to "Checker N" if no role label.
scenarioBriefoptional
Shared briefing (max 2000 chars) every checker sees in the session lobby — describe the multi-role scenario.
feature, jobType, priority, webhookUrl, subscribedWebhookEvents, accessInstructionsoptional
Same semantics as /v1/replit/test. Apply to every slot.
voiceEnabledoptional
Enable voice chat in the session room. Default true.

Example — buyer + seller flow

curl -X POST https://testmyvibes.com/v1/replit/sessions \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://my-marketplace.replit.app",
    "feature": "test the listing + offer + accept flow end-to-end",
    "scenarioBrief": "Seller posts a listing. Buyer makes an offer. Seller accepts. Both confirm checkout.",
    "slots": [
      { "roleLabel": "Seller", "targetDevice": "desktop" },
      { "roleLabel": "Buyer",  "targetDevice": "mobile" }
    ]
  }'

Response 201

{
  "sessionId": "ses_abc123",
  "projectId": "p_xyz789",
  "status": "lobby",
  "checkerCount": 2,
  "creditsDeducted": 4,
  "creditsCharged": 4,
  "creditsRemaining": 16,
  "checklistItemCount": 7,
  "jobType": "General QA",
  "isReplitApp": true,
  "readyDeadline": "2026-04-19T15:30:00.000Z",
  "slots": [
    {
      "slot": 1, "roleLabel": "Seller", "jobId": "j_seller01",
      "targetDevice": "desktop",
      "statusUrl": "https://testmyvibes.com/v1/replit/status/j_seller01"
    },
    {
      "slot": 2, "roleLabel": "Buyer", "jobId": "j_buyer02",
      "targetDevice": "mobile",
      "statusUrl": "https://testmyvibes.com/v1/replit/status/j_buyer02"
    }
  ],
  "sessionUrl": "https://testmyvibes.com/dashboard/sessions/ses_abc123"
}
GET /v1/replit/status/:jobId Poll job progress with rich status info

Real-time progress with percentage, item-level counts, and the full report inline when complete. Poll this every 30-60 seconds or use webhooks for push-based updates.

Example

curl https://testmyvibes.com/v1/replit/status/j_abc123 \
  -H "Authorization: Bearer tmv_live_abc123"

Response — in progress

{
  "jobId": "j_abc123",
  "status": "in_progress",
  "statusMessage": "Testing in progress — 4/7 items checked",
  "progressPercent": 58,
  "url": "https://my-app.replit.app",
  "jobType": "Payment Flow",
  "checklist": {
    "totalItems": 7,
    "checkedItems": 4,
    "passedItems": 3,
    "failedItems": 1,
    "skippedItems": 0
  }
}

Response — completed (includes full report)

{
  "jobId": "j_abc123",
  "status": "completed",
  "statusMessage": "Testing complete",
  "progressPercent": 100,
  "result": {
    "overallStatus": "fail",
    "passCount": 5,
    "failCount": 2,
    "skipCount": 0,
    "summary": "Checkout flow mostly works but...",
    "items": [
      {
        "id": "ci_1",
        "description": "Complete checkout with valid card",
        "status": "pass",
        "note": "Works correctly"
      }
    ]
  }
}
POST /v1/replit/prepare-next Prepare regression tests for next deploy

After fixing bugs, call this to prepare a focused regression test. The next deploy hook or test submission will auto-include verification items for the fixed bugs.

Request Body

{
  "projectId": "p_abc123",
  "fixedBugIds": ["ci_3", "ci_5"],
  "fixedDescriptions": ["Login button is unresponsive on mobile"]
}

fixedBugIds references item IDs from the last test's failed items. fixedDescriptions lets you describe fixes in plain text. Provide at least one.

Response

{
  "message": "Regression test prepared...",
  "regressionItems": 2,
  "descriptions": ["Login button is unresponsive on mobile", "Cart total shows wrong currency"],
  "projectId": "p_abc123"
}

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?
jobTypestringYesFirst Impression, General QA, Payment Flow, Custom, AI Review
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://testmyvibes.com/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://testmyvibes.com/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://testmyvibes.com/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://testmyvibes.com/v1/jobs?status=completed&limit=10" \
  -H "Authorization: Bearer tmv_live_abc123"
const res = await fetch('https://testmyvibes.com/v1/jobs?status=completed&limit=10', {
  headers: { 'Authorization': 'Bearer tmv_live_abc123' }
});
const data = await res.json();
import requests

res = requests.get(
    "https://testmyvibes.com/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://testmyvibes.com/v1/jobs/j_abc123 \
  -H "Authorization: Bearer tmv_live_abc123"
const res = await fetch('https://testmyvibes.com/v1/jobs/j_abc123', {
  headers: { 'Authorization': 'Bearer tmv_live_abc123' }
});
const data = await res.json();
import requests

res = requests.get(
    "https://testmyvibes.com/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://testmyvibes.com/v1/jobs/j_abc123/report \
  -H "Authorization: Bearer tmv_live_abc123"
const res = await fetch('https://testmyvibes.com/v1/jobs/j_abc123/report', {
  headers: { 'Authorization': 'Bearer tmv_live_abc123' }
});
const data = await res.json();
import requests

res = requests.get(
    "https://testmyvibes.com/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"
}
GET /v1/jobs/:id/ai-report Get the AI-generated analysis report for a completed job. Includes health score, bugs, strengths, and recommendations.
curl https://testmyvibes.com/v1/jobs/j_abc123/ai-report \
  -H "Authorization: Bearer tmv_live_abc123"
import requests

res = requests.get(
    "https://testmyvibes.com/v1/jobs/j_abc123/ai-report",
    headers={"Authorization": "Bearer tmv_live_abc123"}
)
data = res.json()
{
  "id": "ar_abc123",
  "jobId": "j_abc123",
  "status": "completed",
  "executiveSummary": "The app has 2 critical issues...",
  "healthScore": 62,
  "bugs": [
    {
      "title": "Login form submit fails silently",
      "severity": "critical",
      "description": "Clicking submit with valid credentials shows no feedback",
      "reproductionSteps": ["Navigate to /login", "Enter valid credentials", "Click submit"],
      "suggestedFix": "Add error handling to the fetch call in login handler"
    }
  ],
  "passCount": 4,
  "failCount": 2,
  "skipCount": 0,
  "strengths": ["Clean responsive layout", "Fast page load times"],
  "recommendations": ["Add form validation feedback", "Implement loading states"]
}

Returns 202 if the AI report is still being generated. Returns 404 if the job is not yet completed.

POST /v1/jobs/:id/retest Re-run the same test. No body needed — we clone the original job.
curl -X POST https://testmyvibes.com/v1/jobs/j_abc123/retest \
  -H "Authorization: Bearer tmv_live_abc123"
import requests

res = requests.post(
    "https://testmyvibes.com/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://testmyvibes.com/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://testmyvibes.com/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://testmyvibes.com/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://testmyvibes.com/v1/projects \
  -H "Authorization: Bearer tmv_live_abc123"
import requests

res = requests.get(
    "https://testmyvibes.com/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,
      "webhookSecretRotatedAt": "2026-04-12T09:00:00.000Z",
      "webhookSecretRotations": [
        { "at": "2026-04-12T09:00:00.000Z", "userId": "u_123" },
        { "at": "2026-03-01T17:30:00.000Z", "userId": "u_123" }
      ],
      "createdAt": "2026-01-15T10:00:00Z"
    }
  ]
}

webhookSecretRotations lists up to the 5 most recent webhook-secret rotations (most recent first). Each entry has an at ISO timestamp and the userId who triggered the rotation (null for legacy entries). The field is null when no webhook is configured.

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
webhookUrlstringNoHTTPS URL to receive event notifications for this project. Loopback, private, and internal hostnames are rejected with 400 INVALID_REQUEST. When set, a webhookSecret is generated for HMAC signing.
subscribedWebhookEventsstring[]NoArray of event names to subscribe to. Allowed: job.created, job.completed, job.ai_report_ready, job.session_ready, job.session_completed, loop_session.ended. Omit to receive every event. An empty array disables delivery for the project. Unknown event names are rejected with 400 INVALID_REQUEST. Only applied when webhookUrl is also set.
curl -X POST https://testmyvibes.com/v1/projects \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My SaaS App",
    "defaultUrl": "https://myapp.com",
    "defaultJobType": "General QA"
  }'
import requests

res = requests.post(
    "https://testmyvibes.com/v1/projects",
    headers={"Authorization": "Bearer tmv_live_abc123"},
    json={
        "name": "My SaaS App",
        "defaultUrl": "https://myapp.com",
        "defaultJobType": "General QA"
    }
)
data = res.json()
{
  "projectId": "p_new123",
  "name": "My SaaS App",
  "deployHookUrl": "https://testmyvibes.com/v1/hooks/deploy/dhk_xxxxxxxx"
}
PATCH /v1/projects/:id Update project defaults and webhook configuration. All fields optional; only provided fields change.

Request Body

FieldTypeRequiredDescription
namestringNoNew project name (must be non-empty if provided).
descriptionstringNoProject description. Pass empty string or null to clear.
defaultUrlstringNoDefault URL for new tests. Pass empty string or null to clear.
defaultJobTypestringNoDefault job type. Pass empty string or null to clear.
defaultSlaMinutesnumberNoDefault SLA: 15, 30, or 60. Pass null to clear.
webhookUrlstringNoHTTPS URL for event notifications. Same validation as POST /v1/projects. Rotating to a new URL preserves the existing webhookSecret, or generates one if none was set. Pass an empty string to clear the webhook (also clears subscribedWebhookEvents).
subscribedWebhookEventsstring[]NoReplace the saved subscription list. Same validation as POST /v1/projects. Only applied when a webhookUrl is configured (either already on the project or set in this same request). Empty array disables delivery for the project; omit the field to leave the existing list untouched.
curl -X PATCH https://testmyvibes.com/v1/projects/p_abc123 \
  -H "Authorization: Bearer tmv_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://hooks.example.com/tmv",
    "subscribedWebhookEvents": ["job.completed", "job.ai_report_ready"]
  }'
{
  "projectId": "p_abc123",
  "name": "My SaaS App",
  "description": null,
  "defaultUrl": "https://myapp.com",
  "defaultJobType": "General QA",
  "defaultSlaMinutes": 60,
  "webhookUrl": "https://hooks.example.com/tmv",
  "webhookSecretRotatedAt": "2026-04-19T12:00:00.000Z",
  "webhookSecretRotations": [
    { "at": "2026-04-19T12:00:00.000Z", "userId": "u_123" },
    { "at": "2026-03-01T17:30:00.000Z", "userId": "u_123" }
  ],
  "subscribedWebhookEvents": ["job.completed", "job.ai_report_ready"],
  "updatedAt": "2026-04-19T12:00:00.000Z"
}

Returns 404 NOT_FOUND if the project doesn't exist or belongs to a different API key. The webhookSecret itself is never returned by this endpoint. webhookSecretRotations lists up to the 5 most recent webhook-secret rotations (most recent first); each entry has an at ISO timestamp and the userId who triggered it (null for legacy entries). The field is null when no webhook is configured.

Project History

GET /v1/projects/:id/history Get test history, trends, regressions, and prepared regression tests for a project.
curl https://testmyvibes.com/v1/projects/p_abc123/history \
  -H "Authorization: Bearer tmv_live_abc123"
{
  "projectId": "p_abc123",
  "projectName": "My SaaS App",
  "totalTests": 12,
  "passCount": 9,
  "failCount": 3,
  "passRate": 75,
  "averageHealthScore": 72,
  "latestFailures": [
    { "id": "ci_3", "description": "Login form shows error on submit", "severity": "Critical" }
  ],
  "regressions": ["Cart total calculates wrong with discount"],
  "trend": [
    { "jobId": "j_abc", "overallStatus": "FAIL", "healthScore": 62, "passCount": 5, "failCount": 2, "testedAt": "..." }
  ],
  "preparedRegressions": [
    { "bugIds": ["ci_3"], "descriptions": ["Login form shows error on submit"], "preparedAt": "..." }
  ]
}

trend shows the last 10 tests. regressions lists items that passed in the previous test but failed in the latest. preparedRegressions shows bug fixes queued for verification on the next deploy.

Credits

GET /v1/credits Check your credit balance, usage, and expiring credits.
curl https://testmyvibes.com/v1/credits \
  -H "Authorization: Bearer tmv_live_abc123"
import requests

res = requests.get(
    "https://testmyvibes.com/v1/credits",
    headers={"Authorization": "Bearer tmv_live_abc123"}
)
data = res.json()
{
  "available": 47,
  "used": 28,
  "total": 75,
  "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 and AI analysis ready — includes full AI report with health score, bugs, and fix suggestions
job.ai_report_readyAI analysis finished processing — fires separately after the async AI generation completes
job.failed_slaJob exceeded SLA without completion — it's been re-queued

Payload Example

{
  "event": "job.completed",
  "type": "job.completed",
  "timestamp": "2026-03-14T20:42:00Z",
  "projectId": "p_xyz789",
  "jobId": "j_abc123",
  "dashboardReportUrl": "https://testmyvibes.com/projects/p_xyz789/jobs/j_abc123",
  "data": {
    "projectId": "p_xyz789",
    "jobId": "j_abc123",
    "dashboardReportUrl": "https://testmyvibes.com/projects/p_xyz789/jobs/j_abc123",
    "reportId": "r_def456",
    "overallStatus": "fail",
    "passCount": 5,
    "failCount": 2,
    "skipCount": 0,
    "summary": "Login flow has critical issues...",
    "aiReport": {
      "id": "ar_xyz789",
      "status": "completed",
      "healthScore": 62,
      "executiveSummary": "The app has 2 critical issues...",
      "bugCount": 2,
      "bugs": [{ "title": "Login fails silently", "severity": "critical", "suggestedFix": "Add error handling..." }],
      "strengths": ["Clean responsive layout"],
      "recommendations": ["Add form validation feedback"]
    }
  }
}

The event name is available as both event (original) and type (Stripe-style alias). jobId, projectId, and dashboardReportUrl appear at the top level for easy routing and also inside data for permissive parsers. dashboardReportUrl is only present when the event is about a job.

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.1

Replit Integration API: POST /v1/replit/test one-shot testing endpoint with AI auto-scoping, feature-focused checklists, project auto-linking, regression detection. GET /v1/replit/status/:jobId rich progress polling. POST /v1/replit/prepare-next regression test preparation. GET /v1/projects/:id/history test history with trends, regressions, and health scores. Smart deploy hooks auto-include regression items from previous failures and prepared fixes. job.ai_report_ready webhook event.

March 2026 v1.0

Initial API release. 10 endpoints: submit jobs, list jobs, get status, reports, AI report, retest, bulk submit, bulk status, projects, credits. AI-powered report analysis with health scores, bug detection, and actionable fix suggestions. job.completed webhook includes full AI report with bugs, health score, and recommended fixes.