For agents
API documentation
Everything an agent needs to order human testing and read the verdict. Humans can do the same things from the dashboard — the API is the product, the UI is a client of it.
export API=https://touchstone.grig-teo.space
export KEY=tst_…Authentication
Requesters (you, or your agent) authenticate with an API key: X-API-Key: tst_… Dashboard sessions use Authorization: Bearer <requester JWT> and can call the same endpoints. Testers use their own Bearer JWT from /v1/auth/tester/login.
curl -X POST $API/v1/auth/requester/register \
-H 'Content-Type: application/json' \
-d '{"name": "Acme Agents", "email": "ops@acme.dev", "password": "supersecret"}'
# 201 → {"requester": {...}, "api_key": "tst_…"}
# The raw key is shown exactly once. Store it; only its hash is kept server-side.Endpoints
| Method | Path | Auth | What it does |
|---|---|---|---|
POST | /v1/auth/requester/register | public | Create a requester account. Returns a bootstrap api_key, shown once. |
POST | /v1/auth/requester/login | public | Email/password → session JWT for the dashboard. |
GET | /v1/keys | requester | List active API keys (prefix, label, timestamps — never raw keys). |
POST | /v1/keys | requester | Generate a key. Raw key returned once. |
DELETE | /v1/keys/{id} | requester | Revoke a key (soft). |
POST | /v1/tasks | requester | Order a test. Body per task/v1; optional credentials field. |
GET | /v1/tasks?status= | requester | List your tasks, optionally filtered by status. |
GET | /v1/tasks/{id} | requester | Task detail as {task, report} envelope. |
GET | /v1/tasks/{id}/artifact-url | requester | Presigned GET for artifact:// uploads, else passthrough URL. |
POST | /v1/tasks/{id}/approve | requester | Accept the report and release payment. Optional note. |
POST | /v1/tasks/{id}/reject | requester | Reject the report. Note required. |
POST | /v1/tasks/{id}/cancel | requester | Cancel before resolution. |
POST | /v1/uploads/presign | either | kind=artifact (requester) or recording/screenshot with task_id (tester). |
POST | /v1/auth/tester/register | public | Create a tester account. |
POST | /v1/auth/tester/login | public | Email/password → tester JWT. |
GET | /v1/tester/me | tester | Profile, devices, locales, rating. |
PUT | /v1/tester/me/devices | tester | Replace device list. Body: {"devices": [...]}. |
GET | /v1/tester/tasks/available | tester | Open tasks matching the tester's devices/locales. |
GET | /v1/tester/tasks/mine | tester | Tasks assigned to me (in_progress through paid). |
GET | /v1/tester/tasks/{id} | tester | Instructions, credentials, artifact — if assigned or available to me. |
POST | /v1/tester/tasks/{id}/accept | tester | Claim an available task. |
POST | /v1/tester/tasks/{id}/report | tester | Submit a report/v1 body. recording_key required. |
GET | /v1/tester/tasks/{id}/artifact-url | tester | Download URL for the build under test. |
GET | /v1/tester/earnings | tester | {total_cents, withdrawn_cents, available_cents, entries[]} payout ledger. |
POST | /v1/tester/withdrawals | tester | Request a payout to a bank card. amount_cents > 0; available balance checked. |
GET | /v1/tester/withdrawals | tester | Your withdrawal history, newest first (card_last4 only). |
Ordering a test (task/v1)
instructions holds the structured payload, validated against the task/v1 schema: scenario_steps[], survey_questions[], focus_areas[] — at least one required. Upload the build first, then reference it as artifact://<key>, or pass any external URL.
# 1. Ask for an artifact upload slot
curl -X POST $API/v1/uploads/presign \
-H "X-API-Key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"kind": "artifact"}'
# → {"upload_url": "https://…", "key": "artifacts/ab12…", "expires_in": 900}
# 2. PUT the binary straight to the presigned URL
curl -X PUT --upload-file ./app-debug.apk "https://…"
# 3. Reference it on the task as "artifact_url": "artifact://artifacts/ab12…"curl -X POST $API/v1/tasks \
-H "X-API-Key: $KEY" \
-H 'Content-Type: application/json' \
-d '{
"artifact_type": "apk_url",
"artifact_url": "artifact://artifacts/ab12…",
"niche": "mobile_game",
"test_type": "gameplay",
"instructions": {
"scenario_steps": [
"Install and launch the game",
"Complete the tutorial",
"Play one match"
],
"survey_questions": ["Was the tutorial clear?"],
"focus_areas": ["onboarding", "controls"]
},
"targeting": {
"platforms": ["android"],
"locales": ["en-US"],
"device_classes": ["phone"]
},
"credentials": "tester@acme.dev / hunter2",
"time_limit_minutes": 45,
"budget_cents": 1500
}'
# 201 → the task object. Money is always integer cents.
# "credentials" (optional, ≤2000 chars) is shown only to the assigned tester.Reading the verdict (report/v1)
When the task reaches submitted, the report is attached to the task detail envelope. Reports are validated against report/v1:
- recording_key — session video, required (anti-fraud gate).
- bugs[] — severity (blocker/major/minor/trivial), title, steps, expected, actual, optional evidence keys.
- ux_findings[] — area, 1–5 rating, optional comment.
- metrics — completed, time_on_task_s, optional fun rating, onboarding clarity, perf issues.
# Poll until status is "submitted"
curl -H "X-API-Key: $KEY" $API/v1/tasks/$TASK_ID
# → {"task": {...}, "report": null | {...}}
# Approve (pays the tester) or reject with a note
curl -X POST -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
-d '{"note": "Great catch on the tutorial soft-lock."}' \
$API/v1/tasks/$TASK_ID/approveTask lifecycle
created → matched → in_progress → submitted → approved → paid. Rejection moves submitted → rejected; cancellation is possible from created, matched, or in_progress. Invalid transitions return HTTP 409.