curl --request POST \
--url https://app.testorim.com/api/runs/trigger \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"projectId": "3f1c2b90-7a5e-4c31-9f0d-2b8a6c4e1d55",
"procedureId": "8d4e7a12-90bb-4f6c-a1e3-5c7d9f2b4a06"
}
'{
"runId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "pending",
"pollUrl": "/api/runs/4c9e1f77-2a31-4e0b-8d6a-1f2b3c4d5e6f"
}{
"error": "Invalid request body",
"issues": [
{
"code": "<string>",
"path": [
"<string>"
],
"message": "<string>"
}
]
}{
"error": "Invalid or revoked API key"
}{
"error": "Viewers have read-only access to this workspace",
"code": "read_only_role"
}{
"error": "<string>"
}{
"error": "<string>",
"retryAfter": 123
}Start a test run headlessly
The primary CI endpoint. Starts a run in the background and
returns immediately with a run id. The HTTP request does not block
on test completion, because typical runs take 30-120 s (and may be
allowed up to 120 minutes) while CI providers time out sooner. Poll
GET /api/runs/{runId} until status is terminal.
Two mutually exclusive modes
The body must carry exactly one of procedureId or
description; the Zod schema has one .refine that rejects neither
and another that rejects both.
- Replay mode (
procedureId) re-executes a saved procedure’s stored steps. No LLM planning call. The procedure is looked up scoped toprojectId, so a procedure from another project 404s. - Ad-hoc mode (
description) plans steps from the prompt, then executes them. The description is truncated to the first 2000 characters when it is stored as the run’sinstruction(the schema itself allows up to 4000).
Base URL resolution
The browser opens, in priority order:
environment.baseUrl → baseUrl (this request) → project.baseUrl.
The resolved URL is then run through the SSRF guard
(assertSafeUrl); private, loopback, link-local and metadata hosts
are rejected with 400.
Duration
maxDurationMinutes overrides the procedure’s saved cap. When
omitted the run inherits procedure.maxDurationMinutes, and failing
that the platform default of 10 minutes. The effective value is
always clamped to 5..120.
Refusals
A pre-allocated runs row is created before setup. If setup then
fails (quota, unsafe URL, browser launch), that row is closed with a
reason rather than left dangling, so a 429 or 400 from this
endpoint does not leave a phantom pending run.
curl --request POST \
--url https://app.testorim.com/api/runs/trigger \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"projectId": "3f1c2b90-7a5e-4c31-9f0d-2b8a6c4e1d55",
"procedureId": "8d4e7a12-90bb-4f6c-a1e3-5c7d9f2b4a06"
}
'{
"runId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "pending",
"pollUrl": "/api/runs/4c9e1f77-2a31-4e0b-8d6a-1f2b3c4d5e6f"
}{
"error": "Invalid request body",
"issues": [
{
"code": "<string>",
"path": [
"<string>"
],
"message": "<string>"
}
]
}{
"error": "Invalid or revoked API key"
}{
"error": "Viewers have read-only access to this workspace",
"code": "read_only_role"
}{
"error": "<string>"
}{
"error": "<string>",
"retryAfter": 123
}Authorizations
Send Authorization: Bearer tst_live_….
Format (services/api-keys.ts): the literal prefix tst_live_
followed by 24 random bytes rendered as 32 base64url characters.
Only the SHA-256 hash is stored server-side. The shape check that
routes a token down the API-key path rather than the Clerk JWT path
requires the tst_live_ prefix and a total length of at least 25
characters.
Keys are minted in the dashboard at /settings/team. The same header
also accepts a Clerk session JWT, which is how the web app
authenticates, but the JWT path is out of scope for this document.
Body
Body of POST /api/runs/trigger, mirroring TriggerRunBody in routes/runs.ts. Exactly one of procedureId or description is required. The schema rejects both neither and both.
The project to run against. Always required, even in replay mode. Must be a live project in the key's organization.
Replay mode. A saved procedure belonging to projectId; its stored steps are executed with no LLM planning call. Mutually exclusive with description.
Ad-hoc mode. A plain-English description of the test; Claude plans the steps. Mutually exclusive with procedureId. Note the first 2000 characters are what get stored as the run's instruction.
1 - 4000How to frame the verdict.
pass(default): a positive test; every step should succeed.fail: a negative test; the run "passes" when the application correctly refuses the action. A safety net in the orchestrator appends a synthetic failed step if nothing actually failed.unknown: exploratory; no strong framing.
The server coerces anything other than fail/unknown to pass.
pass, fail, unknown Override the project's base URL for this run only. The usual way to point CI at a PR preview deployment. Beaten by the environment's base URL when environmentId is also set. Validated only for length here; the SSRF guard checks the resolved URL and rejects private hosts with 400.
2048Apply a saved project environment (base URL, extra headers, cookies). Its base URL takes priority over baseUrl and over the project default. An environment id that does not resolve for this project is silently ignored rather than erroring.
Per-run time cap. Omit to inherit the procedure's saved value, then the platform default of 10 minutes.
5 <= x <= 120Response
Accepted. The run row exists in pending and execution has started in the background.
The pre-allocated run id. Poll it to observe completion.
Always the literal string pending. This is the state the row was created in, not a live read.
"pending"Relative path to poll, e.g. /api/runs/<runId>. Join it to the server base URL.
"/api/runs/4c9e1f77-2a31-4e0b-8d6a-1f2b3c4d5e6f"

