API Reference
The REST reference is rendered from apps/docs/openapi.yaml at /api/.
How the spec is maintained
The spec is partially generated and drift-gated — not fully generated, and not purely handwritten either:
- The
/projects*and/runs*paths are generated from route code: the zod-openapi route definitions inapps/api/src/routes/projects.tsandapps/api/src/routes/runs.tsare the same objects the runtime handlers register against, so those paths cannot drift from the implementation. - Every other path is maintained by hand in
apps/docs/openapi.base.yaml(the schemas are derived from the Zod schemas inpackages/shared/src/schemasand the route modules). pnpm --filter @qa-clone/api openapi:exportmerges both halves deterministically intoapps/docs/openapi.yaml.- CI drift gate: the vitest suite (
apps/api/__tests__/openapi.test.ts) re-assembles the spec from its sources and fails when the committedopenapi.yamlis stale, when a mounted public route has no path entry, or when the spec documents a route that is not mounted. Seedocs/runbooks/openapi-drift.md.
Envelope
All JSON error responses use:
{ "ok": false, "error": "human-readable message" }
Success bodies are either the documented resource shape or:
{ "ok": true, "data": { } }
Authentication
Two bearer credentials are accepted on Authorization:
1. Clerk session JWT (browser / dashboard)
Org-scoped endpoints require a Clerk JWT of a user with an active organization — org and role are resolved from the verified token, never from headers.
curl https://api.testsbot.com/projects \
-H "Authorization: Bearer <clerk-session-jwt>"
2. API keys (tb_live_…) — CI and integrations
Create a key under Settings → API Keys (or POST /api-keys, admin-only).
The plaintext key is shown exactly once at creation. Scopes:
read— GET requests onlyread_write— full access (acts as admin)
# Trigger a run from CI with an API key
curl -X POST https://api.testsbot.com/runs \
-H "Authorization: Bearer tb_live_0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{ "test_case_id": "00000000-0000-0000-0000-000000000000" }'
Public, unauthenticated endpoints: GET /health, GET /status/incidents,
GET /legal/{doc}.
See the interactive reference at /api/ for request/response schemas and try-it examples.