API Reference
Base URL: https://usertold.ai
Use REST when you are building a custom backend, scheduled job, or product integration. Use the CLI for terminal automation and MCP when an agent should work through project-aware tools and resources.
The shipped OpenAPI 3.1 document is the source of truth for its registered endpoint paths, request fields, response schemas, and enums:
This page covers the stable integration contract and a few common recipes. It intentionally does not copy the generated endpoint catalog. The multipart upload bodies and provider webhook routes are not yet represented completely in OpenAPI, so their required inputs are documented below.
Authentication
Authenticated REST requests use a user-delegated bearer token:
Authorization: Bearer <token>
The CLI can complete browser login and expose the token for local automation:
usertold auth login
export USERTOLD_TOKEN="$(usertold auth token)"
Keep bearer tokens server-side. The embedded widget uses a public SDK key (X-Project-Key: ut_pub_... or the widget's data-project-key) only on SDK routes designed for participant traffic; it is not a replacement for a user token. Provider webhooks use the signature verification inputs documented below.
Project scope
Most builder operations are scoped by organization and project handles:
/api/orgs/:orgHandle/projects/:projectHandle/...
Project responses include org_handle, project_handle, and project_ref (org/project). Study and intake routes accept their ID or project-scoped handle where the OpenAPI parameter description says so.
Integration recipes
Resolve the current workspace
Read the authenticated profile first. Its personal_org_handle identifies the default workspace for project discovery and creation.
curl -sS https://usertold.ai/api/user/profile \
-H "Authorization: Bearer $USERTOLD_TOKEN"
Create a project
curl -sS https://usertold.ai/api/orgs/acme/projects \
-H "Authorization: Bearer $USERTOLD_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Checkout research" }'
Use the returned project handle for later project-scoped calls. Create and activate the study and linked intake before embedding the widget; the exact write schemas live in OpenAPI.
Review evidence before creating work
curl -sS "https://usertold.ai/api/orgs/acme/projects/checkout/signals?type=struggling_moment&limit=10" \
-H "Authorization: Bearer $USERTOLD_TOKEN"
Inspect the source quote, transcript or playback context, and product area before grouping evidence into a review packet. Creating a packet does not make it verified delivery work.
curl -sS https://usertold.ai/api/orgs/acme/projects/checkout/tasks/from-signals \
-H "Authorization: Bearer $USERTOLD_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "title": "Clarify failed checkout payment state", "signal_ids": ["sig_1", "sig_2"] }'
Push the work item to Linear or GitHub only after source review confirms it is ready.
Import a transcript
Send multipart/form-data to /api/orgs/:orgHandle/projects/:projectHandle/sessions/import-transcript. The transcript file is required, must contain non-empty text, and must be no larger than 5 MiB. Optional fields are participant_name, participant_email, and study_id; when supplied, study_id must identify an active study.
curl -sS https://usertold.ai/api/orgs/acme/projects/checkout/sessions/import-transcript \
-H "Authorization: Bearer $USERTOLD_TOKEN" \
-F "transcript=@interview.txt;type=text/plain" \
-F "study_id=checkout-study"
A successful import returns 201 with the created interview and queued: true. Processing continues asynchronously.
Upload a recording
Send multipart/form-data to /api/orgs/:orgHandle/projects/:projectHandle/sessions/upload-video using one of these shapes:
media: one audio or video file, up to 25 MiB; oraudio: an audio file up to 25 MiB, plus optionalvideoplayback media up to 100 MiB.
Optional fields are participant_name, participant_email, and study_id; when supplied, study_id must identify an active study. Supported audio formats are MP3, M4A, WAV, OGG, FLAC, AAC, and WebM. Supported video formats are MP4, WebM, and MPEG.
curl -sS https://usertold.ai/api/orgs/acme/projects/checkout/sessions/upload-video \
-H "Authorization: Bearer $USERTOLD_TOKEN" \
-F "audio=@interview.ogg" \
-F "video=@screen.mp4" \
-F "study_id=checkout-study"
A successful upload returns 201 with the created interview and queued: true.
Receive provider webhooks
These signature-verified routes receive native provider payloads for connected UserTold integrations. They are not generic outgoing-webhook configuration endpoints, and they are not currently listed in OpenAPI.
| Provider route | Required signature input | Replay input |
|---|---|---|
POST /api/webhooks/github | Raw request body and X-Hub-Signature-256 (sha256= HMAC); X-GitHub-Event selects the event type | X-GitHub-Delivery when supplied |
POST /api/webhooks/linear | Raw request body and Linear-Signature HMAC; payload must include a current webhookTimestamp | Linear-Delivery when supplied |
POST /api/webhooks/polar | Raw request body plus webhook-id, webhook-timestamp, and webhook-signature | Signed timestamp must be within five minutes |
Configure these through the corresponding GitHub, Linear, or billing integration. Preserve the raw request body for signature verification; do not parse and reserialize it first.
Errors and retries
Domain errors that expose retry guidance use this JSON envelope:
{
"code": "OPTIONAL_CODE",
"retryable": false,
"error": "Description of what went wrong",
"action": "Optional next step for the caller"
}
- Treat
retryableas the primary retry signal when it is present. - Retry
429and retryable5xxresponses with bounded exponential backoff and jitter. - Do not automatically retry validation, authentication, permission, or payment errors; surface
errorandactionto the operator. - Do not turn an ambiguous write timeout into repeated unbounded writes.
- Upload and background-processing operations may succeed asynchronously. Poll the documented status operation instead of resubmitting the original write.
Common statuses are 400 validation, 401 authentication required, 402 payment required, 403 forbidden, 404 not found, 429 rate limited, and 5xx service failure.
Rate limits
Limits use independent 60-second buckets for each route. They are keyed by client IP unless the table says otherwise.
| Integration route | Limit |
|---|---|
| Google sign-in start | 10 requests per IP |
| OAuth authorize | 20 requests per IP |
| OAuth token exchange | 10 requests per IP |
| OAuth dynamic client create | 5 requests per IP |
| OAuth dynamic client read, update, or delete | 20 requests per IP for each route |
| Agent registration | 30 requests per IP; claim-producing registrations also share a 5-request bucket |
| Agent claim trigger, claim completion, and revocation | 5, 10, and 60 requests per IP, respectively |
| SDK intake list, detail, and response | 30, 60, and 10 requests per IP, respectively |
| SDK interview create, runtime token, and consent | 20 requests per IP for each route |
| SDK widget install beacon | 120 requests per IP |
| Legacy SDK audio form upload | 30 requests per interview |
| Conductor start | 10 requests per IP |
| Conductor transcription secret, STS secret, and realtime call | 5 requests per IP for each route |
| Project integration-key validation and health | 10 requests per IP for each route |
| Admin email delivery test | 6 requests per IP |
A limited request returns 429 with an error field. The limiter does not currently send Retry-After, remaining, or reset headers, so use bounded client-side backoff.
Continue
- OpenAPI contract — generated REST paths and schemas
- Quickstart — complete the first interview in the dashboard
- MCP Integration — connect an agent through MCP
- CLI Reference — automate through the terminal