qaitai docs

API reference

The qaitai REST API (/api/v1), generated from its OpenAPI 3.1 document.

Start runs from CI or scripts, follow them and read the findings. Every instance serves this description at /api/v1/openapi.json (OpenAPI 3.1): feed it to your client generator. For GitHub Actions use qaitai/action@v1, for other CI qaitai run --instance.

Authentication

Create a workspace API key in Settings → API keys (admins only) and send it as a bearer token:

curl "$QAITAI_INSTANCE/api/v1/runs?limit=5" -H "Authorization: Bearer $QAITAI_API_KEY"
  • A key belongs to one workspace and only ever sees that workspace's data. Keys are shown once and stored as SHA-256 hashes.
  • Each operation needs one scope (listed below). A key without it gets 403; a missing, unknown or revoked key gets 401.
  • Runs started with a key are ordinary runs: the same domain verification, plan concurrency, allowance, spend caps and BYOK rules as in the app. They show api_key:<id> as their creator and in the audit log.
  • The API is for servers and CI: responses carry no CORS headers, so browsers on other sites can't call it with your key.

Conventions

  • Errors are JSON: { "error": { "code": "run_rejected", "message": "…", "reason": "domain_unverified" } }. message is safe to show; issues lists field problems for invalid_request.
  • Lists are newest first. Pass limit (1–100, default 20) and the previous page's nextCursor as cursor; nextCursor is null on the last page.
  • Idempotency: send Idempotency-Key on POST /runs and POST /suites/{suiteId}/run. A retry with the same key within 24 hours returns the first run (200, Idempotent-Replayed: true) instead of starting and billing another; the same key with a different request is 409.
  • Rate limits per key and minute: Free 60, Pro 300, Team 600, Enterprise and self-hosted 1,200 (self-hosters can set QAITAI_API_RATE_LIMIT). Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; 429 comes with Retry-After. Limits are counted per web replica.
CodeHTTPMeaning
invalid_request400Malformed JSON, invalid field, bad cursor or limit
unauthorized401Missing, unknown or revoked key
forbidden403The key lacks the operation's scope
not_found404No such resource in the key's workspace
conflict409Run already finished, or Idempotency-Key reused for another request
run_rejected422Refused by policy; reason is domain_unverified, concurrency_limit, plan_limit, private_host, byok_required, no_url, queue_unavailable or rejected
rate_limited429Too many requests; wait Retry-After seconds

Scopes

ScopeAllows
issues:readGET /issues, GET /issues/{issueId}
projects:readGET /projects
runs:readGET /runs, GET /runs/{runId}, GET /runs/{runId}/events
runs:writePOST /runs, POST /runs/{runId}/cancel, POST /suites/{suiteId}/run
suites:readGET /suites, GET /suites/{suiteId}
suites:writePOST /suites

Runs

Start, follow and cancel runs.

Start a run

POST /api/v1/runs · scope runs:write

Same checks as starting a run in the app: the project must be in the key's workspace, cloud runs on qaitai Cloud need a verified domain, and plan concurrency, allowance and spend caps apply. Policy refusals are 422 run_rejected with a reason.

ParameterInTypeDescription
Idempotency-Keyheaderstring ≤255Retries with the same key within 24 hours return the first run (200, Idempotent-Replayed: true) instead of starting another. Reusing a key for a different request is a 409.

Body (application/json):

FieldTypeDescription
urlstring ≤2048 (uri)Start URL. Cloud runs need a verified domain on qaitai Cloud.
promptstring ≤8000The journey to test, in plain English (10-8000 characters).
projectId (optional)string ≤64Project to file the run under. Optional when the workspace has one project, or one whose app URL has the same host as url.
workers (optional)integer 1–10Parallel workers (default 1).
model (optional)string ≤100Model id (default claude-sonnet-5).
keyMode (optional)"managed" | "byok"Default: byok when the workspace has a model key connected, else managed.
runtime (optional)"cloud" | "self_hosted"Default: self_hosted for private/localhost URLs, else cloud.
gitRef (optional)string ≤200e.g. "main@a41e2d" or "pr/412".

Response 201: Run. The queued run. 200 when an Idempotency-Key replay returns an earlier run.

Errors: 400, 401, 403, 404, 409, 422, 429.

curl -X POST "$QAITAI_INSTANCE/api/v1/runs" \
  -H "Authorization: Bearer $QAITAI_API_KEY" \
  -H "Idempotency-Key: $GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://staging.acme.dev","prompt":"Sign up, create a project and invite a teammate"}'

List runs

GET /api/v1/runs · scope runs:read

ParameterInTypeDescription
projectIdquerystringOnly this project's runs.
suiteIdquerystringOnly this suite's runs.
statusqueryRunStatusOnly runs with this status.
cursorquerystringnextCursor from the previous page.
limitqueryinteger 1–100Page size, 1-100 (default 20).

Response 200: RunPage. Newest first.

Errors: 400, 401, 403, 429.

curl "$QAITAI_INSTANCE/api/v1/runs" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Get a run

GET /api/v1/runs/{runId} · scope runs:read

The run with its workers and findings. Poll this or stream /events until status is terminal.

ParameterInTypeDescription
runIdpathstring ≤64The run id.

Response 200: RunDetail. The run.

Errors: 401, 403, 404, 429.

curl "$QAITAI_INSTANCE/api/v1/runs/<runId>" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Cancel a run

POST /api/v1/runs/{runId}/cancel · scope runs:write

ParameterInTypeDescription
runIdpathstring ≤64The run id.

Response 200: Run. The cancelled run.

Errors: 401, 403, 404, 409, 429.

curl -X POST "$QAITAI_INSTANCE/api/v1/runs/<runId>/cancel" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Stream run events (SSE)

GET /api/v1/runs/{runId}/events · scope runs:read

Server-sent events until the run finishes: run {id, status, startedAt, finishedAt} on change, worker {id, label, status, startedAt, finishedAt} on change, step {id, runWorkerId, index, kind, summary, at} (the SSE id is its timestamp), issue {id, severity, title, stepId, createdAt}, then end {}. The server closes the stream after 10 minutes; reconnect with Last-Event-ID to resume without repeating steps.

ParameterInTypeDescription
runIdpathstring ≤64The run id.
Last-Event-IDheaderstringResume after this step.

Response 200: text/event-stream. An event stream.

Errors: 401, 403, 404, 429.

curl -N "$QAITAI_INSTANCE/api/v1/runs/<runId>/events" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Suites

Saved journeys you can run on demand or on a schedule.

List suites

GET /api/v1/suites · scope suites:read

ParameterInTypeDescription
projectIdquerystringOnly this project's suites.
cursorquerystringnextCursor from the previous page.
limitqueryinteger 1–100Page size, 1-100 (default 20).

Response 200: SuitePage. Newest first.

Errors: 400, 401, 403, 429.

curl "$QAITAI_INSTANCE/api/v1/suites" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Create a suite

POST /api/v1/suites · scope suites:write

Body (application/json):

FieldTypeDescription
projectIdstring ≤64a string at most 64 character(s) long
namestring ≤80a string at most 80 character(s) long
promptstring ≤8000The journey to test, in plain English.
triggers (optional)"pr" | "deploy" | "nightly" | "weekly" | "manual"[]Default ["manual"].
mode (optional)"exploratory" | "replay"Default exploratory.
workerCount (optional)integer 1–10a number between 1 and 10
scheduleHour (optional)integer 0–23a number between 0 and 23
scheduleWeekday (optional)integer 0–6a number between 0 and 6
timezone (optional)string
rerunOnFailure (optional)boolean
enabled (optional)boolean

Response 201: Suite. The suite.

Errors: 400, 401, 403, 404, 429.

curl -X POST "$QAITAI_INSTANCE/api/v1/suites" \
  -H "Authorization: Bearer $QAITAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"<projectId>","name":"<name>","prompt":"Sign up, create a project and invite a teammate"}'

Get a suite

GET /api/v1/suites/{suiteId} · scope suites:read

ParameterInTypeDescription
suiteIdpathstring ≤64The suite id.

Response 200: Suite. The suite.

Errors: 401, 403, 404, 429.

curl "$QAITAI_INSTANCE/api/v1/suites/<suiteId>" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Run a suite

POST /api/v1/suites/{suiteId}/run · scope runs:write

Starts the suite now, against url or the project's app URL: a replay when the suite is compiled, else an exploratory run. Needs runs:write because it starts a run.

ParameterInTypeDescription
suiteIdpathstring ≤64The suite id.
Idempotency-Keyheaderstring ≤255Retries with the same key within 24 hours return the first run (200, Idempotent-Replayed: true) instead of starting another. Reusing a key for a different request is a 409.

Body (application/json):

FieldTypeDescription
url (optional)string ≤2048 (uri)Run against this URL instead of the project's app URL (e.g. a preview deploy).
gitRef (optional)string ≤200a string at most 200 character(s) long

Response 201: Run. The queued run.

Errors: 400, 401, 403, 404, 409, 422, 429.

curl -X POST "$QAITAI_INSTANCE/api/v1/suites/<suiteId>/run" \
  -H "Authorization: Bearer $QAITAI_API_KEY" \
  -H "Idempotency-Key: $GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" \
  -H "Content-Type: application/json" \
  -d '{}'

Issues

Findings reported by workers.

List issues

GET /api/v1/issues · scope issues:read

ParameterInTypeDescription
runIdquerystringOnly findings of this run.
projectIdquerystringOnly this project's findings.
severityquerySeverityOnly this severity.
statusquerystringe.g. "open".
cursorquerystringnextCursor from the previous page.
limitqueryinteger 1–100Page size, 1-100 (default 20).

Response 200: IssuePage. Newest first.

Errors: 400, 401, 403, 429.

curl "$QAITAI_INSTANCE/api/v1/issues" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Get an issue

GET /api/v1/issues/{issueId} · scope issues:read

ParameterInTypeDescription
issueIdpathstring ≤64The issue id.

Response 200: Issue. The issue.

Errors: 401, 403, 404, 429.

curl "$QAITAI_INSTANCE/api/v1/issues/<issueId>" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Projects

Apps under test.

List projects

GET /api/v1/projects · scope projects:read

ParameterInTypeDescription
cursorquerystringnextCursor from the previous page.
limitqueryinteger 1–100Page size, 1-100 (default 20).

Response 200: ProjectPage. Newest first.

Errors: 400, 401, 403, 429.

curl "$QAITAI_INSTANCE/api/v1/projects" \
  -H "Authorization: Bearer $QAITAI_API_KEY"

Schemas

Error

FieldTypeDescription
errorobject

ErrorCode

"invalid_request" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "run_rejected" | "payload_too_large" | "rate_limited" | "internal_error" | "unavailable"

Run

FieldTypeDescription
idstring
projectIdstring
suiteIdstring | null
kind"agent" | "replay"
statusRunStatus
promptstring
targetUrlstring
gitRefstring | null
workerCountnumber
maxStepsnumberStep limit per worker.
maxMinutesnumberTime limit per worker.
runtime"cloud" | "self_hosted"
keyMode"managed" | "byok"
modelstring
createdBystringMember id, "github:<login>", "schedule:<trigger>" or "api_key:<id>".
rerunOfstring | null
createdAtstring (date-time)
startedAtstring (date-time) | null
finishedAtstring (date-time) | null
urlstringThe run in the qaitai app.

RunStatus

"queued" | "running" | "passed" | "failed" | "flaky" | "cancelled" | "errored"

CreateRunRequest

FieldTypeDescription
urlstring ≤2048 (uri)Start URL. Cloud runs need a verified domain on qaitai Cloud.
promptstring ≤8000The journey to test, in plain English (10-8000 characters).
projectId (optional)string ≤64Project to file the run under. Optional when the workspace has one project, or one whose app URL has the same host as url.
workers (optional)integer 1–10Parallel workers (default 1).
model (optional)string ≤100Model id (default claude-sonnet-5).
keyMode (optional)"managed" | "byok"Default: byok when the workspace has a model key connected, else managed.
runtime (optional)"cloud" | "self_hosted"Default: self_hosted for private/localhost URLs, else cloud.
gitRef (optional)string ≤200e.g. "main@a41e2d" or "pr/412".

RunPage

FieldTypeDescription
dataRun[]
nextCursorstring | nullPass as cursor for the next page; null on the last page.

RunDetail

FieldTypeDescription
idstring
projectIdstring
suiteIdstring | null
kind"agent" | "replay"
statusRunStatus
promptstring
targetUrlstring
gitRefstring | null
workerCountnumber
maxStepsnumberStep limit per worker.
maxMinutesnumberTime limit per worker.
runtime"cloud" | "self_hosted"
keyMode"managed" | "byok"
modelstring
createdBystringMember id, "github:<login>", "schedule:<trigger>" or "api_key:<id>".
rerunOfstring | null
createdAtstring (date-time)
startedAtstring (date-time) | null
finishedAtstring (date-time) | null
urlstringThe run in the qaitai app.
workersWorker[]
issuesIssueSummary[]
issueCountsobject

Worker

FieldTypeDescription
idstring
labelstring
statusRunStatus
startedAtstring (date-time) | null
finishedAtstring (date-time) | null

IssueSummary

FieldTypeDescription
idstring
severitySeverity
titlestring
statusstring
createdAtstring (date-time)
urlstring

Severity

"critical" | "high" | "medium" | "low"

SuitePage

FieldTypeDescription
dataSuite[]
nextCursorstring | nullPass as cursor for the next page; null on the last page.

Suite

FieldTypeDescription
idstring
projectIdstring
namestring
promptstring
triggers"pr" | "deploy" | "nightly" | "weekly" | "manual"[]
mode"exploratory" | "replay"
workerCountnumber
enabledboolean
scheduleHournumber
scheduleWeekdaynumber
timezonestring
rerunOnFailureboolean
compiledbooleanHas compiled replay steps.
lastResultobject | null
createdAtstring (date-time)
urlstring

CreateSuiteRequest

FieldTypeDescription
projectIdstring ≤64a string at most 64 character(s) long
namestring ≤80a string at most 80 character(s) long
promptstring ≤8000The journey to test, in plain English.
triggers (optional)"pr" | "deploy" | "nightly" | "weekly" | "manual"[]Default ["manual"].
mode (optional)"exploratory" | "replay"Default exploratory.
workerCount (optional)integer 1–10a number between 1 and 10
scheduleHour (optional)integer 0–23a number between 0 and 23
scheduleWeekday (optional)integer 0–6a number between 0 and 6
timezone (optional)string
rerunOnFailure (optional)boolean
enabled (optional)boolean

RunSuiteRequest

FieldTypeDescription
url (optional)string ≤2048 (uri)Run against this URL instead of the project's app URL (e.g. a preview deploy).
gitRef (optional)string ≤200a string at most 200 character(s) long

IssuePage

FieldTypeDescription
dataIssue[]
nextCursorstring | nullPass as cursor for the next page; null on the last page.

Issue

FieldTypeDescription
idstring
projectIdstring
runIdstring | null
severitySeverity
titlestring
descriptionstring
reproStepsstring[]
likelyCauseobject | null
statusstring
externalUrlstring | null
createdAtstring (date-time)
urlstring

ProjectPage

FieldTypeDescription
dataProject[]
nextCursorstring | nullPass as cursor for the next page; null on the last page.

Project

FieldTypeDescription
idstring
namestring
repoFullNamestring | null
defaultUrlstring | null
createdAtstring (date-time)

On this page