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

MethodPathAuthWhat it does
POST/v1/auth/requester/registerpublicCreate a requester account. Returns a bootstrap api_key, shown once.
POST/v1/auth/requester/loginpublicEmail/password → session JWT for the dashboard.
GET/v1/keysrequesterList active API keys (prefix, label, timestamps — never raw keys).
POST/v1/keysrequesterGenerate a key. Raw key returned once.
DELETE/v1/keys/{id}requesterRevoke a key (soft).
POST/v1/tasksrequesterOrder a test. Body per task/v1; optional credentials field.
GET/v1/tasks?status=requesterList your tasks, optionally filtered by status.
GET/v1/tasks/{id}requesterTask detail as {task, report} envelope.
GET/v1/tasks/{id}/artifact-urlrequesterPresigned GET for artifact:// uploads, else passthrough URL.
POST/v1/tasks/{id}/approverequesterAccept the report and release payment. Optional note.
POST/v1/tasks/{id}/rejectrequesterReject the report. Note required.
POST/v1/tasks/{id}/cancelrequesterCancel before resolution.
POST/v1/uploads/presigneitherkind=artifact (requester) or recording/screenshot with task_id (tester).
POST/v1/auth/tester/registerpublicCreate a tester account.
POST/v1/auth/tester/loginpublicEmail/password → tester JWT.
GET/v1/tester/metesterProfile, devices, locales, rating.
PUT/v1/tester/me/devicestesterReplace device list. Body: {"devices": [...]}.
GET/v1/tester/tasks/availabletesterOpen tasks matching the tester's devices/locales.
GET/v1/tester/tasks/minetesterTasks assigned to me (in_progress through paid).
GET/v1/tester/tasks/{id}testerInstructions, credentials, artifact — if assigned or available to me.
POST/v1/tester/tasks/{id}/accepttesterClaim an available task.
POST/v1/tester/tasks/{id}/reporttesterSubmit a report/v1 body. recording_key required.
GET/v1/tester/tasks/{id}/artifact-urltesterDownload URL for the build under test.
GET/v1/tester/earningstester{total_cents, withdrawn_cents, available_cents, entries[]} payout ledger.
POST/v1/tester/withdrawalstesterRequest a payout to a bank card. amount_cents > 0; available balance checked.
GET/v1/tester/withdrawalstesterYour 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/approve

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