Record one controlled UI or tool task attempt
POST /api/v1/workflow-trials · operationId recordWorkflowTrial
For developers: your server sends this request with a server key; it is not a button in the dashboard.
Requires a live session with server-associated identity. The authenticated application backend attests matching task and test conditions; no independent success or time-savings guarantee. UI and tool runs share pairId, taskId, protocolId, scenarioId and appRevision. An identical eventId retry is idempotent while the record and session remain available; conflicting facts or a duplicate mode are 409. Keep opaque non-personal IDs, never inputs or outputs. Stored for 90 days with a 10,000-attempt per-Site limit and erased with its subject. See Measured workflow comparisons.
Authentication
Send Authorization: Bearer YOUR_SERVER_KEY (siteServerKey: Site server key in Authorization: Bearer <key> (the scheme is case-insensitive and required; a bare key is 401). Shown once on creation or rotation. Publishable keys cannot authenticate). Keep the server key in server-only configuration; never send it to a browser.
Request body
Content-Type: application/json (required)
sessionToken(string, required) · minLength 1, maxLength 250: Opaque browser session token; do not log.eventId(string, required) · pattern^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}$pairId(string, required) · pattern^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}$taskId(string, required) · pattern^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}$protocolId(string, required) · pattern^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}$scenarioId(string, required) · pattern^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}$appRevision(string, required) · pattern^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}$controlled(true, required)mode("ui" | "tool", required)outcome("succeeded" | "failed" | "cancelled", required)durationMs(integer, required) · minimum 1, maximum 86400000retries(integer, required) · minimum 0, maximum 10000
{
"sessionToken": "PASTE_BROWSER_SESSION_TOKEN",
"eventId": "trial_export_ui_01",
"pairId": "pair_export_01",
"taskId": "task_export_report",
"protocolId": "protocol_2026_11",
"scenarioId": "scenario_monthly_export",
"appRevision": "v2.3.1",
"controlled": true,
"mode": "ui",
"outcome": "succeeded",
"durationMs": 42000,
"retries": 0
}Responses
- 200: Success · body
object - 400: bad_request: the request is invalid; message says what to fix and field names the request field · body
Error - 401: unauthorized (missing or invalid server key, missing Bearer scheme, or a malformed or unsigned session token) or session_reinit_required (a token this collector issued whose session expired, was revoked or was removed); use the code to distinguish · body
Error - 403: forbidden: the server key and session token belong to different Sites, or the call came from a browser page · body
Error - 404: not_found: unknown path, or an ID in the path that is not on this Site · body
Error - 409: conflict: the eventId was used with different facts, or this pair already has a run in this mode · body
Error - 413: payload_too_large: the request body exceeds 131072 bytes · body
Error - 429: rate_limited: wait for Retry-After · body
Error· headersRetry-After - 500: internal_error: fail the protected action; do not retry automatically · body
Error - 503: service_unavailable: temporarily unavailable; only verify retries · body
Error
Failures are not verdicts. Error bodies follow the Error schema: code is one of bad_request, unauthorized, session_reinit_required, forbidden, not_found, conflict, identity_required, payload_too_large, rate_limited, internal_error, service_unavailable, request_failed, approval_invalid, approval_used, approval_expired, approval_mismatch, step_up_not_held, step_up_mismatch, step_up_settled, step_up_expired, step_up_superseded, agent_tokens_disabled, tool_not_approved, tool_not_read_only. Errors and retries explains each code and what to do.
Example
/api/v1/workflow-trialsServer key · run it from your backendEdit the JSON and the examples below update. Nothing is sent from this page.
curl -sS -X POST 'https://app.trytelltale.dev/api/v1/workflow-trials' \
-H "Authorization: Bearer $TELLTALE_SERVER_KEY" \
-H 'Content-Type: application/json' \
--data '{"sessionToken":"PASTE_BROWSER_SESSION_TOKEN","eventId":"trial_export_ui_01","pairId":"pair_export_01","taskId":"task_export_report","protocolId":"protocol_2026_11","scenarioId":"scenario_monthly_export","appRevision":"v2.3.1","controlled":true,"mode":"ui","outcome":"succeeded","durationMs":42000,"retries":0}'const response = await fetch("https://app.trytelltale.dev/api/v1/workflow-trials", {
method: "POST",
headers: {
Authorization: 'Bearer ' + process.env.TELLTALE_SERVER_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"sessionToken": "PASTE_BROWSER_SESSION_TOKEN",
"eventId": "trial_export_ui_01",
"pairId": "pair_export_01",
"taskId": "task_export_report",
"protocolId": "protocol_2026_11",
"scenarioId": "scenario_monthly_export",
"appRevision": "v2.3.1",
"controlled": true,
"mode": "ui",
"outcome": "succeeded",
"durationMs": 42000,
"retries": 0
}),
signal: AbortSignal.timeout(5000)
});
const result = await response.json();Machine-readable contract: openapi.json. Request builder: the API explorer shows every operation as curl; it sends requests only on a local dashboard.