API reference
Most projects never touch anything below init(). The rest of this page exists for the ones that script their own submissions or wire the widget into a test pipeline.
init(options)
Call once after the script tag. Idempotent: repeated calls are no-ops while a widget is mounted.
Testosaurus.init({
endpoint: string, // required — your Worker origin
apiKey: string, // required — app token; identifies the project
position?: 'bottom-right' | 'bottom-left' // default 'bottom-right'
});
If endpoint or apiKey is missing, init logs a warning and does not mount the widget. The queue stub also accepts calls made before the bundle loads, so load order doesn't matter.
open()
Open the mounted widget from your own application control with Testosaurus.open(), or import open from testosaurus-sdk. Call it after initialization completes. The return value is true when a mounted widget receives the request, or false when none is mounted.
This enters the same flow as the launcher. It does not grant capture consent, change the selected project or submit a report. See the custom-button example.
Report shape
A submitted report carries:
- message — the tester's text.
- context — page URL, browser, platform.
- screenshot key — optional, only with screenshot consent.
- replay key — optional, only with replay consent.
- distilled fields — title, category, severity, summary, and next step, added asynchronously. See Reports & distillation.
Endpoints
POST /api/submit
Stores a report and enqueues distillation. Header x-api-key must be a valid app token for the project, and the page's origin must be registered in the project's allowed origins.
PUT /api/screenshot
Uploads a screenshot (≤ 2 MB) to storage and returns { key }. Pass the key on the submit call to attach it.
PUT /api/replay
Uploads session-replay JSON and returns { key }. Same attach-on-submit rule.
Status codes
- 2xx — stored. The widget shows its success state.
- 4xx — rejected (bad token, disallowed origin, oversized attachment). The widget fails without queueing a retry.
- 5xx / timeout / offline — the payload is queued locally and retried on the next
init. See Offline queue.
Dashboard API
Endpoints the widget never calls. All of them need your signed-in dashboard session, not an app token.
GET /api/projects/:id/export
Everything Testosaurus holds for one project in a single JSON download: settings, every submission with its distilled fields, and a manifest of the stored media. Whatever you put in comes back out, in full, whenever you want — no lock-in. Owner only; the response carries content-disposition: attachment, so a plain browser link triggers the download.
{
"version": 1,
"exportedAt": "2026-09-12T09:30:00.000Z",
"project": {
"id": "…",
"name": "Web app",
"createdAt": "…",
"domains": ["app.example.com"],
"archived": false,
"notifications": { "notifyNew": true, "notifyHighSeverity": true, "notifyFailedDistill": true },
"settings": {
"distillProvider": null,
"distillEnabled": true,
"showBranding": true,
"widgetTheme": null,
"webhookUrl": null,
"turnstile": { "sitekey": "…", "source": "auto", "mode": "invisible" },
"githubRepo": null,
"githubLabels": [],
"linearTeamId": null
}
},
"submissions": [
{
"id": "…",
"clientKey": "…",
"raw": "Checkout button does nothing on Safari",
"context": { "url": "/checkout", "browser": "Safari 18.2" },
"distillation": { "title": "…", "category": "bug", "severity": "high" },
"distillState": "done",
"distillError": null,
"status": "new",
"raisedRef": null,
"createdAt": "…"
}
],
"media": [
{
"key": "…",
"kind": "screenshot",
"size": 84213,
"contentType": "image/png",
"createdAt": "…",
"submissionId": "…",
"fetchPath": "/api/screenshot/…",
"available": true
}
]
}
Each media entry is a stored screenshot (image/png) or replay (application/json). Fetch the bytes through fetchPath with your dashboard session; it is null for uploads with no submission attached. available is false once the blob has been purged, and size and contentType come from the stored object when it still exists.
PATCH /api/usage/settings/:resource
Sets the overage policy and alert threshold for one metered resource: submissions, storage, ai, or seats. Workspace owner only; unknown resources get a 422. Both body fields are optional:
{ "overageAllowed": true, "alertThresholdPct": 90 }
alertThresholdPct is an integer from 10 to 100 — the share of the included allowance where usage alert emails fire. overageAllowed lets usage continue past the included limit at the published rate, drawn from your prepaid balance. It takes effect only when your plan supports overage (Pro or Enterprise) and you've opted in for that resource; otherwise usage stops at the included limit. The response is the updated usage row for the resource — the same shape the Usage page renders.
GET /api/models
The model catalog behind the Settings → AI picker: every text-capable model on the Vercel AI Gateway with its per-1M-token input and output prices and capability flags (vision, structured output, reasoning, free), plus the server default model — the one used when a project leaves its Default role unset.
PATCH /api/projects/:id
Updates project settings. AI model control rides on the aiSettings object: an optional default model (null falls back to the server default), an optional vision model, custom roles — each a slug, a label, and a model — and categories, mapping the four distilled categories (bug, confusing, idea, other) to role slugs. Every model id is validated against the model catalog; an unknown id is rejected with a 400.
{
"aiSettings": {
"default": "openai/gpt-4o-mini",
"vision": null,
"roles": [
{ "slug": "bug-investigator", "label": "Bug investigator", "model": "anthropic/claude-sonnet-4" }
],
"categories": { "bug": "bug-investigator" }
}
}