API Reference
Base URL: https://app.usertold.ai
Authentication
Three auth methods depending on the endpoint:
| Method | Header | Used By |
|---|---|---|
| JWT | Authorization: Bearer <token> or auth_token cookie | Dashboard |
| SDK Key | X-Project-Key: ut_pub_... or ?key=ut_pub_... | Embedded widget |
| Webhook | HMAC signature verification | GitHub, Polar webhooks |
Error Format
{
"code": "OPTIONAL_CODE",
"retryable": false,
"message": "Description of what went wrong",
"action": "Optional next step for user"
}
Common status codes: 400 validation, 401 auth required, 402 payment required, 403 forbidden, 404 not found, 429 rate limited.
Project-scoped routes use canonical handles in the path:
- frontend route params:
:organd:project(for/o/:org/p/:project/...) - API path params remain
:orgHandleand:projectHandle
Scoped resolver support:
:studyIdaccepts studyidorhandle(within project scope):screenerIdaccepts screeneridorhandle(within project scope)
MCP for Agentic Workflows
Use MCP when your agents need discovery and project-aware execution in one protocol surface.
- Endpoint:
POST /mcp - Auth: OAuth 2.1 for remote MCP clients (Authorization Code + PKCE). MCP accepts bearer tokens after OAuth issuance.
- Methods:
initialize,initialized,tools/list,tools/call,prompts/list,prompts/get,resources/list,resources/templates/list,resources/read - Discovery:
GET /.well-known/openid-configuration,GET /.well-known/oauth-protected-resource - Dynamic client registration: supported via
POST /api/oauth/register(RFC 7591). Management endpoints:GET/PUT/DELETE /api/oauth/register/:clientId. Static allowlist clients are also supported for pre-registered integrations.
Initialize:
{
"jsonrpc": "2.0",
"id": "init-1",
"method": "initialize",
"params": { "protocolVersion": "2025-11-25", "capabilities": { "tools": {}, "prompts": {}, "resources": {} } }
}
Call a tool:
{
"jsonrpc": "2.0",
"id": "tools-1",
"method": "tools/call",
"params": {
"name": "tasks.list",
"arguments": { "projectRef": "prj_..." }
}
}
For non-agent automation workflows, continue using the REST routes in this doc.
Health
GET /api/health
Public. Returns service status.
{ "status": "ok", "environment": "production" }
Auth
POST /api/auth/google
Exchange Google OAuth token for a session. Rate limited: 10 req/min.
POST /api/auth/logout
Clear session.
GET /api/user/profile
Auth: JWT. Returns current user profile.
PATCH /api/user/profile
Auth: JWT. Update user name.
Projects
Canonical project metadata fields on project-bearing responses:
organization_idorg_handleproject_handleproject_ref(org/project)canonical_path(/o/:org/p/:project)
GET /api/orgs/:orgHandle/projects
Auth: JWT. List all projects for the authenticated user.
POST /api/orgs/:orgHandle/projects
Auth: JWT. Create a project.
{ "name": "My Product", "description": "optional", "github_repo_url": "owner/repo" }
GET /api/orgs/:orgHandle/projects/:projectHandle
Auth: JWT. Get project details and members.
PATCH /api/orgs/:orgHandle/projects/:projectHandle
Auth: JWT (owner/admin). Update project fields.
DELETE /api/orgs/:orgHandle/projects/:projectHandle
Auth: JWT (owner only). Delete project.
Sessions
All session routes require JWT + Project membership.
GET /api/orgs/:orgHandle/projects/:projectHandle/sessions
List sessions. Query params: status, processing_status, limit, offset.
POST /api/orgs/:orgHandle/projects/:projectHandle/sessions
Create a session. Requires billing check.
{
"participant_name": "Jane",
"participant_email": "jane@example.com",
"interview_mode": "voice",
"screener_id": "scr_...",
"screener_response_id": "resp_..."
}
GET /api/orgs/:orgHandle/projects/:projectHandle/sessions/:sessionId
Get session with messages, events, and audio chunks.
PATCH /api/orgs/:orgHandle/projects/:projectHandle/sessions/:sessionId
Update session fields (status, summary, consent flags).
POST /api/orgs/:orgHandle/projects/:projectHandle/sessions/:sessionId/reprocess
Reprocess a completed session (re-run signal extraction).
DELETE /api/orgs/:orgHandle/projects/:projectHandle/sessions/:sessionId
Delete a session.
Signals
All signal routes require JWT + Project membership.
GET /api/orgs/:orgHandle/projects/:projectHandle/signals
List signals. Query params: type, session_id, task_id, search, min_confidence, dismissed, limit, offset.
GET /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId
Get signal details.
PATCH /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId
Update signal fields (type, confidence, quote, analysis, context).
POST /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId/annotate
Add human annotation. Body: { "text": "..." } (max 2000 chars).
POST /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId/dismiss
Soft-exclude signal. Body: { "reason": "..." }.
POST /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId/undismiss
Restore dismissed signal.
POST /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId/link
Link signal to task. Body: { "task_id": "..." }.
POST /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId/unlink
Unlink signal from task.
DELETE /api/orgs/:orgHandle/projects/:projectHandle/signals/:signalId
Delete a signal.
Tasks
All task routes require JWT + Project membership.
GET /api/orgs/:orgHandle/projects/:projectHandle/tasks
List tasks. Query params: status, type, limit, offset.
GET /api/orgs/:orgHandle/projects/:projectHandle/tasks/ready
List high-priority tasks ready for implementation. Query params: limit (default 10).
POST /api/orgs/:orgHandle/projects/:projectHandle/tasks
Create a task.
{
"title": "Fix checkout abandonment",
"description": "Users drop off at payment step",
"task_type": "bug",
"priority_score": 75,
"effort_estimate": "m"
}
GET /api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId
Get task with linked signals.
PATCH /api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId
Update task fields. Priority label auto-calculated from score (critical >= 80, high >= 60, medium >= 40, low < 40).
DELETE /api/orgs/:orgHandle/projects/:projectHandle/tasks/:taskId
Delete a task.
Screeners
Dashboard Routes (JWT + Project)
GET /api/orgs/:orgHandle/projects/:projectHandle/screeners — List screeners.
POST /api/orgs/:orgHandle/projects/:projectHandle/screeners — Create a screener with optional inline questions.
{
"title": "Beta User Study",
"welcome_message": "Help us improve!",
"brand_color": "#3b82f6",
"consent_text": "I agree to participate",
"max_participants": 50,
"questions": [
{
"question_text": "How often do you use our product?",
"question_type": "single_choice",
"options": ["Daily", "Weekly", "Monthly", "Rarely"],
"qualification_rules": { "qualify": ["Daily", "Weekly"] }
}
]
}
GET /api/orgs/:orgHandle/projects/:projectHandle/screeners/:screenerId — Get screener with questions and responses.
PATCH /api/orgs/:orgHandle/projects/:projectHandle/screeners/:screenerId — Update screener fields.
DELETE /api/orgs/:orgHandle/projects/:projectHandle/screeners/:screenerId — Delete screener.
POST /api/orgs/:orgHandle/projects/:projectHandle/screeners/:screenerId/questions — Add a question.
PATCH /api/orgs/:orgHandle/projects/:projectHandle/screeners/:screenerId/questions/:questionId — Update a question.
DELETE /api/orgs/:orgHandle/projects/:projectHandle/screeners/:screenerId/questions/:questionId — Delete a question.
POST /api/orgs/:orgHandle/projects/:projectHandle/screeners/:screenerId/questions/reorder — Reorder questions. Body: { "question_ids": ["q1", "q2", "q3"] }.
Public SDK Routes (SDK Key)
GET /api/sdk/screeners — List active screeners for the project. Rate limited: 30 req/min.
GET /api/sdk/screener/:screenerId — Get screener with questions (public fields only). Rate limited: 60 req/min.
POST /api/sdk/screener/:screenerId/respond — Submit screener response. Rate limited: 10 req/min.
{
"answers": { "q1": "Daily", "q2": "Developer" },
"consent_given": true,
"consent_recording": true,
"participant_name": "Jane",
"participant_email": "jane@example.com"
}
Studies
Dashboard Routes (JWT + Project)
GET /api/orgs/:orgHandle/projects/:projectHandle/studies — List studies.
POST /api/orgs/:orgHandle/projects/:projectHandle/studies — Create a study.
{
"title": "Checkout Research",
"study_type": "usability",
"goals": [{ "id": "g1", "description": "Find checkout friction" }],
"script": {
"version": 2,
"goals": [{ "id": "g1", "description": "Find checkout friction" }],
"segments": [
{ "id": "intro", "mode": "speak", "title": "Instructions" },
{ "id": "task", "mode": "observe", "title": "Complete checkout" },
{ "id": "debrief", "mode": "talk", "title": "Discuss" }
]
}
}
GET /api/orgs/:orgHandle/projects/:projectHandle/studies/:studyId — Get study details.
PATCH /api/orgs/:orgHandle/projects/:projectHandle/studies/:studyId — Update study.
DELETE /api/orgs/:orgHandle/projects/:projectHandle/studies/:studyId — Delete study.
Public SDK Route
GET /api/sdk/study/:studyId — Get active study with parsed script and settings.
Conductor (Interview Runtime)
SDK Key auth. These power the real-time interview experience.
POST /api/sdk/conductor/:sessionId/start
Initialize conductor session. Returns WebSocket URL. Rate limited: 10 req/min.
{ "ws_url": "wss://app.usertold.ai/api/sdk/conductor/ses_.../ws" }
GET /api/sdk/conductor/:sessionId/ws
WebSocket upgrade endpoint for real-time interview orchestration.
POST /api/sdk/conductor/:sessionId/transcription-secret
Get ephemeral token for browser-side speech transcription. Rate limited: 5 req/min.
POST /api/sdk/conductor/:sessionId/sts-secret
Get ephemeral token for browser-side voice conversation. Rate limited: 5 req/min.
POST /api/sdk/conductor/:sessionId/end
End conductor session. Idempotent. Queues session for processing.
SDK Utilities
POST /api/sdk/sessions
Create a session without a screener. Rate limited: 20 req/min.
POST /api/sdk/audio/upload
Upload audio chunk (FormData). Max 10 MB per chunk. Rate limited: 30 req/min per session.
POST /api/sdk/screen/upload
Upload screen recording chunk (FormData). Max 25 MB per chunk. Rate limited: 30 req/min per session.
POST /api/sdk/events
Batch event ingestion. Max 256 KB payload.
{
"session_id": "ses_...",
"events": [
{ "type": "click", "timestamp_ms": 1707000000000, "data": { "selector": ".btn" } },
{ "type": "navigate", "timestamp_ms": 1707000001000, "data": { "url": "/checkout" } }
]
}
Billing
GET /api/billing
Auth: JWT. Get billing status (payment status, spending cap, usage).
PATCH /api/billing/cap
Auth: JWT. Set spending cap. Minimum $10 (1000 cents).
{ "spending_cap_cents": 5000 }
POST /api/billing/checkout
Auth: JWT. Create payment checkout session.
Webhooks
POST /api/webhooks/github
GitHub webhook receiver. Verified via HMAC-SHA256. Handles issue state changes for task status tracking.
POST /api/webhooks/polar
Polar.sh webhook receiver. Verified via HMAC-SHA256. Handles payment events.
Rate Limits
| Endpoint Category | Limit |
|---|---|
| Auth (Google) | 10 req/min per IP |
| SDK screener list | 30 req/min per IP |
| SDK screener detail | 60 req/min per IP |
| SDK screener respond | 10 req/min per IP |
| SDK session create | 20 req/min per IP |
| SDK audio/screen upload | 30 req/min per session |
| Conductor start | 10 req/min per IP |
| Conductor secrets | 5 req/min per IP |
Upload limits: audio 10 MB/chunk, screen 25 MB/chunk, events 256 KB/batch, max 500 chunks per session over 24h.
See also
- MCP Integration — use MCP tools instead of raw API calls
- Quickstart — end-to-end setup guide