Agent API and MCP
Usercall API
An agent can talk to customers and learn why. These operations are the Agent API. MCP tools call the same paths.
Talk to customers and learn why
An agent can ask a research question, hear it from a participant, and read back themes, quotes, and risks. The Agent API and the hosted MCP server are the same operations.
When to call it
Start here, then authenticate, then follow the workflow once before calling individual operations.
Authentication
Machine clients send a personal-account API key. Claude, ChatGPT, and Cursor use the hosted connector and OAuth instead of a key.
When to call it
Send the key on every Agent API call. Use the connector when a person is connecting an assistant.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| Authorization | header | API | Bearer token from Home → Developer. Shown once at creation. Scope is the personal account only. |
| Connector | URL | MCP | https://mcp.usercall.co. OAuth signs the person in. No API key for the hosted connector. |
Response
401 { "error": "unauthorized", "message": "Unauthorized" }
Token revoked, expired, or not valid for the personal account uses the same error with a specific message.
500 { "error": "auth_lookup_failed", "message": "Failed to validate API token" }
Rate limit is 60 requests per minute per token:
429 { "error": "rate_limited", "message": "Rate limit exceeded", "limit": 60, "remaining": 0, "reset_at": "..." }Example
curl -s "$BASE_URL/api/v1/agent/studies" \
-H "Authorization: Bearer $USERCALL_API_KEY"One workflow
Go from a product problem to evidence in this order. A person shares the interview link. A person opens the activation URL. The agent does neither.
- GET /api/v1/agent/studies (list_studies) before you create. Reuse a study that already asks this question.
- POST /api/v1/agent/studies (create_study). One active agent study. A 402 returns checkout_url for a person to open.
- POST then GET .../simulations (simulate_interview). Omit the simulation id to start. Read it back with that id. Cap is 5 simulations per account per UTC day.
- POST .../reviews (review_study) is optional. It checks the guide only. It does not apply edits.
- A person shares interview_link. Or, after GET .../triggers/events (list_trigger_events), POST /api/v1/agent/triggers (create_research_trigger). The trigger is paused. A person opens activation_url. A 409 returns activation_url and changes nothing.
- GET the study (get_study_status) until status is complete. running and analyzing are not findings.
- GET .../results (get_study_results). Use format=full only when you need a transcript.
When to call it
Follow this order. There is no share operation and no activate operation.
Example
list_studies
create_study
simulate_interview # start, then read with simulation_id
review_study # optional
# a person shares interview_link
# or list_trigger_events, then create_research_trigger (paused)
get_study_status # until complete
get_study_resultsCreate a study
/api/v1/agent/studies
Creates a study from the research question and returns the participant link. You do not send that link.
When to call it
Call list studies first and reuse a study that already asks this question. One active agent study per account. On insufficient credits the body is 402 with checkout_url for a person to open. The next call is start a simulation, before any real participant.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| key_research_goal | string | Yes | Research question. Trimmed, 5–2000 characters. |
| business_context | string | No | Product context, 5–2000 characters when present. Shorter than 5 is 400. It is not copied from the goal. |
| additional_context_prompt | string | No | Extra interviewer guidance, max 1000 characters. |
| target_interviews | number | No | 1–200. Default 1. Reserves 10 credits per interview. |
| duration_minutes | number | No | 5–65. Default 12. |
| interview_mode | string | No | voice (default), text, or voice_and_text. |
| languages | string[] | No | One locale runs the interview in that language and leaves the picker off. Two or more let the participant choose. Empty, unknown, or duplicate values are 400. Omit it for English voice and questions spoken as written. Locales: en-US, en-GB, en-AU, en-IN, hi-IN, ta-IN, mr-IN, kn-IN, ur-IN, pt-BR, es-ES, es-419, fr-FR, de-DE, zh-CN, ja-JP, it-IT, th-TH, ms-MY, vi-VN, id-ID, ar-SA, ko-KR, sv-SE, ru-RU. |
| voice_gender | string | No | female (default on create) or male. The server picks the voice. Do not send a voice id. |
| enable_link_context | boolean | No | Default false. Query params on the interview link are ignored until this is true. |
| custom_link_variables | array | No | Up to 10 declared custom keys: { key, label?, default_value? }. key is snake_case, max 32, not reserved. |
| metadata | object | No | Your own key-value pairs. |
| study_media | object | No | { type: image | prototype, url, description? }. Shown with questions for web participants. |
Response
{
"study_id": "uuid",
"interview_link": "https://usercall.co/interview/shortid",
"target_interviews": 1,
"interview_mode": "voice",
"credits_reserved": 10,
"status": "running",
"study_media": null,
"link_context_enabled": false,
"custom_link_variable_keys": [],
"languages": [],
"voice_gender": "female",
"next_step": { "action": "simulate_interview", "reason": "..." },
"generated_guide": {
"ai_agent_intro_message": "...",
"key_learning_goals": "...",
"workflow_end_message": "...",
"workflow_questions": [
{ "id": "question_1", "text": "...", "order": 0, "follow_up_depth": "adaptive" }
]
}
}Example
curl -s -X POST "$BASE_URL/api/v1/agent/studies" \
-H "Authorization: Bearer $USERCALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key_research_goal": "Why do people drop out of onboarding after the first project?"
}'List studies
/api/v1/agent/studies
Lists studies on the personal account, each with the same interview link create returned.
When to call it
Call this before create a study. Reuse a study that already asks the question. Also call it before you attach a research trigger to an existing study. Stop once you have picked that study or confirmed none exists.
Response
{
"studies": [
{
"study_id": "uuid",
"title": "Why do people drop out of onboarding?",
"created_at": "2026-09-30T00:00:00.000Z",
"is_agent_api_study": true,
"archived": false,
"link_disabled": false,
"interview_link": "https://usercall.co/interview/shortid",
"link_context_enabled": false,
"trigger_eligible": true,
"interview_mode": "voice"
}
]
}Example
curl -s "$BASE_URL/api/v1/agent/studies" \
-H "Authorization: Bearer $USERCALL_API_KEY"Get study status
/api/v1/agent/studies/{study_id}
Returns progress, the interview link, and the next step.
When to call it
Poll this after interviews exist until status is complete. running and analyzing mean wait and call again. Do not treat those payloads as findings. Then get study results. next_step share means a person sends interview_link, or you create a research trigger only after list trigger events has seen the event. There is no share operation.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | Path id from create. |
Response
{
"status": "running",
"completed_interviews": 0,
"target_interviews": 1,
"last_updated": "2026-09-30T00:00:00.000Z",
"interview_link": "https://usercall.co/interview/shortid",
"link_disabled": false,
"link_context_enabled": false,
"next_step": { "action": "simulate_interview", "reason": "..." }
}Example
curl -s "$BASE_URL/api/v1/agent/studies/$STUDY_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY"Update a study
/api/v1/agent/studies/{study_id}
Changes targets, the guide, interview mode, languages, or link context. The research question cannot be changed.
When to call it
Call this to apply a review suggestion, re-enable a link, or turn link context on. Send at least one field.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| target_interviews | number | No | 1–200. Increasing reserves 10 credits per added interview. Decreasing releases unused reserved credits. |
| is_link_disabled | boolean | No | Set false before a person sends the link, unless the study already hit its target. You cannot send the link. |
| interview_mode | string | No | voice, text, or voice_and_text. |
| ai_agent_intro_message | string | No | 1–5000 characters. |
| key_learning_goals | string | No | 1–5000 characters. Required before a guide review. |
| workflow_end_message | string | No | 1–5000 characters. |
| workflow_questions | array | No | Replaces the question list. Adaptive probing is isAdaptive true and followUpCount 3. followUpCount 3 without isAdaptive is rejected. |
| study_media | object | null | No | Replaces media on every question. null removes it. |
| languages | string[] | No | Same rules as create. One locale turns the picker off. Two or more turn it on. |
| voice_gender | string | No | female or male. Omit it to leave the stored voice gender. |
| enable_link_context | boolean | No | Omit it to leave the column unchanged. |
| custom_link_variables | array | No | Replaces the declared list. [] clears declarations. |
Response
{
"study_id": "uuid",
"target_interviews": 1,
"is_link_disabled": false,
"interview_mode": "voice",
"updated_at": "2026-09-30T00:00:00.000Z",
"languages": ["ko-KR"],
"voice_gender": "female",
"link_context_enabled": true,
"custom_link_variable_keys": ["company"]
}Example
curl -s -X PATCH "$BASE_URL/api/v1/agent/studies/$STUDY_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workflow_questions": [
{ "text": "What were you trying to finish?", "isAdaptive": true, "followUpCount": 3 }
]
}'Delete a study
/api/v1/agent/studies/{study_id}
Deletes the study, its calls, and releases unused reserved credits.
When to call it
Call this when the study should not run again. This cannot be undone.
Response
{ "study_id": "uuid", "deleted": true, "credits_released": 10 }Example
curl -s -X DELETE "$BASE_URL/api/v1/agent/studies/$STUDY_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY"Delete call content
/api/v1/agent/studies/{study_id}/calls/{call_id}
Removes one call’s transcript, recording URLs, and summary.
When to call it
Call this to redact a single interview. Credits are not refunded.
Response
{ "study_id": "uuid", "call_id": "call_id", "deleted": true }Example
curl -s -X DELETE "$BASE_URL/api/v1/agent/studies/$STUDY_ID/calls/$CALL_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY"Link parameters
Query parameters on interview_link are stored only when enable_link_context is true.
When to call it
Turn the flag on at create or update before you put context on the participant URL. A trigger invite map turns the flag on for you.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| customer_profile | query | No | Fixed key. Max 200 characters after sanitizing. Longer values are dropped. |
| product_or_feature | query | No | Fixed key. Same length rule. |
| journey_moment | query | No | Fixed key. Same length rule. |
| recent_activity | query | No | Fixed key. Same length rule. |
| other_context | query | No | Fixed key. Same length rule. |
| external_id | query | No | The caller’s person id, not a Usercall id. |
| custom key | query | No | Must be declared. A declared default is stored when the query value is absent. Undeclared keys are dropped. {{key}} works in consent and email copy. |
Response
Values are untrusted participant context. By default the moderator uses them as background and does not say them back. They return on the interview.completed webhook as link_context, external_id, and custom_variables. They are not on get study results.Example
https://usercall.co/interview/shortid?customer_profile=pm&external_id=cus_1&company=AcmeStart a simulation
/api/v1/agent/studies/{study_id}/simulations
Starts a dry run and returns immediately with status running. This is the start half of simulate_interview.
When to call it
Call this before any real participant, after create or a guide edit. Do not wait on this call for a transcript. Read it with the simulation id. Cap is 5 simulations per account per UTC day. A 429 means stop for the day. A simulation is not an interview and does not change completed_interviews.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| persona.name | string | With persona | 1–80 characters. Both persona fields are required if you send persona. |
| persona.prompt | string | With persona | 1–4000 characters. Omit persona to use the default participant. |
Response
{
"study_id": "uuid",
"simulation_id": "uuid",
"status": "running",
"next_step": { "action": "simulate_interview", "reason": "..." }
}Example
curl -s -X POST "$BASE_URL/api/v1/agent/studies/$STUDY_ID/simulations" \
-H "Authorization: Bearer $USERCALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Read a simulation
/api/v1/agent/studies/{study_id}/simulations/{simulation_id}
Returns the simulation status, and the transcript once the run is finished. This is the read half of simulate_interview.
When to call it
Call this after start, using the simulation id. While status is running, call again. On pass, a person may share or you may review the guide. On fail, update the study, then start another simulation. A simulation transcript is not a study finding.
Response
While running, transcript is omitted:
{ "study_id": "uuid", "simulation_id": "uuid", "status": "running", "next_step": { "action": "simulate_interview", "reason": "..." } }
When finished:
{
"study_id": "uuid",
"simulation_id": "uuid",
"status": "pass",
"result_explanation": "...",
"transcript": [],
"simulated_speech_duration_seconds": 42,
"simulated_wall_clock_seconds": 90,
"next_step": { "action": "review_study", "reason": "..." }
}Example
curl -s "$BASE_URL/api/v1/agent/studies/$STUDY_ID/simulations/$SIMULATION_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY"Review the guide
/api/v1/agent/studies/{study_id}/reviews
Runs a guide review and returns findings and up to three suggested edits. It costs 1 credit. Optional before a person shares the link.
When to call it
Call this before a person shares the link, with an empty body. It reads the guide only. It does not read transcripts and it does not apply edits. Short credits return 402 checkout_url for a person to open. Write suggested changes with update study. Stop after one review unless the guide changed.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| body | object | Empty | Send {} or omit the body. call_ids and any other field are 400 invalid_request. |
Response
{
"study_id": "uuid",
"review_id": "uuid",
"kind": "guide",
"credits_charged": 1,
"summary": "...",
"guide_findings": [],
"goal_assessments": [],
"recommendations": [
{ "kind": "guide", "title": "...", "rationale": "...", "label": "...", "suggested_change": {} }
],
"interview_moments": [],
"study_findings": [],
"next_step": { "action": "update_study", "reason": "..." }
}
Write suggested_change onto workflow_questions with update study. If recommendations is empty, next_step.action is share.Example
curl -s -X POST "$BASE_URL/api/v1/agent/studies/$STUDY_ID/reviews" \
-H "Authorization: Bearer $USERCALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Get study results
/api/v1/agent/studies/{study_id}/results
Returns themes, quotes, and risks that explain why people behaved this way.
When to call it
Call this after get study status is complete. Empty themes while the study is running or analyzing are not a finding. Stay on format=summary. Use format=full only for a quote.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| format | query | No | summary (default) or full. full adds transcripts and audio_urls. |
Response
{
"status": "complete",
"completed_interviews": 1,
"target_interviews": 1,
"themes": [
{ "name": "Setup friction", "summary": "...", "confidence": 0.4, "quotes": ["..."] }
],
"key_insights": ["..."],
"risks_or_unknowns": [],
"next_step": { "action": "done", "reason": "..." }
}
format=full also returns transcripts: [{ "call_id": "...", "transcript": "..." }] and audio_urls. In-progress responses still include empty transcripts and audio_urls. Link context is not on this payload.Example
curl -s "$BASE_URL/api/v1/agent/studies/$STUDY_ID/results?format=summary" \
-H "Authorization: Bearer $USERCALL_API_KEY"Get trigger capabilities
/api/v1/agent/triggers/capabilities
Says which trigger conditions exist and which do not. Matching is an exact property or trait value only.
When to call it
Call this before you design a trigger. Supported: one event, exact-match property or trait filters, a URL rule, page dwell, sampling, cooldown, and a daily cap. Not supported, and rejected rather than simplified: counts, sequences, absence ("did not do X"), time windows, and not-equals. If the condition you need is unsupported, stop and hand interview_link to a person.
Example
curl -s "$BASE_URL/api/v1/agent/triggers/capabilities" \
-H "Authorization: Bearer $USERCALL_API_KEY"List trigger events
/api/v1/agent/triggers/events
Lists events the Usercall SDK has received in the last 30 days.
When to call it
Call this before create a research trigger. Only an observed event can be used. If the list is empty, get SDK setup. If your event is missing, stop and hand interview_link to a person.
Response
{ "events": [], "setup_required": true, "hint": "No events have been received..." }
When events exist, setup_required is false and each event has event_name, sources, last_seen_at, sample_count, and intercept_capable.Example
curl -s "$BASE_URL/api/v1/agent/triggers/events" \
-H "Authorization: Bearer $USERCALL_API_KEY"Get an event schema
/api/v1/agent/triggers/events/{event_name}/schema
Splits an observed event into properties and traits, with types and sample values.
When to call it
Call this to pick exact filters. An unknown event is 404 event_not_observed.
Example
curl -s "$BASE_URL/api/v1/agent/triggers/events/study_tested/schema" \
-H "Authorization: Bearer $USERCALL_API_KEY"Get SDK setup
/api/v1/agent/triggers/setup
Returns the install snippet for the analytics provider and whether events are already arriving.
When to call it
Call this when list events is empty or a needed event has not been observed.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| provider | query | No | posthog (default), mixpanel, amplitude, segment, ga4, or custom. |
| events | query | No | Comma-separated event names to allow, max 50. |
Response
install_snippet, identify_snippet, allowlist_update_snippet, instructions, and status when an active ingestion project exists. 404 ingestion_project_not_found when it does not. This call does not create a project. The secret key is never returned.Example
curl -s "$BASE_URL/api/v1/agent/triggers/setup?provider=posthog&events=study_tested" \
-H "Authorization: Bearer $USERCALL_API_KEY"Create a research trigger
/api/v1/agent/triggers
Creates a paused trigger for people who did something in the product, and returns an activation URL. You cannot turn it on.
When to call it
Call list research triggers first and reuse a paused trigger when one already matches this study and event. Call this only after a study exists and list trigger events has seen the event. Hand activation_url to a person. Sending status active is not this call. Unsupported filters (counts, sequences, absence, time windows, not-equals) are rejected, not dropped.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | Study that owns the interview. It needs an interview link. |
| event_name | string | Yes | One observed event, 1–120 characters. |
| properties | object | No | Exact equality on event properties. Values are string, number, or boolean. Not-equals, ranges, counts, and missing values are rejected. |
| traits | object | No | Exact equality on user traits. Same limits as properties. |
| url | object | No | { operator: equals | contains | starts_with, value }. |
| dwell_seconds | number | No | 1–600. Page-visit triggers only. |
| sampling_percent | number | No | 1–100. |
| cooldown_days | number | No | 0–365. |
| max_invites_per_day | number | No | 1–100. |
| delivery_method | string | No | intercept (default) or webhook. webhook requires a public https webhook_url. |
| invite_link_params | object | No | static, from_traits, and from_properties maps. A non-empty map turns enable_link_context on for the study. {} does not. |
Response
201 with the trigger, status "paused", activation_url, summary, warnings, and next_step telling you to show the link to a human.
Unknown condition keys are 422 invalid_trigger and nothing is written.Example
curl -s -X POST "$BASE_URL/api/v1/agent/triggers" \
-H "Authorization: Bearer $USERCALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"study_id": "'"$STUDY_ID"'",
"event_name": "study_tested",
"traits": { "plan": "free" },
"sampling_percent": 25,
"invite_link_params": { "static": { "customer_profile": "pm" } }
}'List research triggers
/api/v1/agent/triggers
Lists triggers on the account with status and activation_url.
When to call it
Call this before create a research trigger. Reuse a paused trigger and hand activation_url to a person. Create one only when none matches. activation_url is null once a person has activated the trigger.
Example
curl -s "$BASE_URL/api/v1/agent/triggers" \
-H "Authorization: Bearer $USERCALL_API_KEY"Get a research trigger
/api/v1/agent/triggers/{trigger_id}
Returns one trigger plus invite and interview counts.
When to call it
Call this to read status, activation_url, and stats for a trigger you already created.
Response
The trigger object plus stats: { "invited": 0, "interviews_started": 0, "interviews_completed": 0 }.
webhook_secret is never returned. webhook_secret_set says whether one is stored.Example
curl -s "$BASE_URL/api/v1/agent/triggers/$TRIGGER_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY"Update a research trigger
/api/v1/agent/triggers/{trigger_id}
Changes targeting, sampling, copy, or delivery. Changing an active trigger pauses it again. You cannot set it active.
When to call it
Call this to edit targeting, sampling, cooldown, daily cap, intercept copy, or delivery, or to pause a trigger. status active returns 409 activation_required with activation_url and writes nothing. Hand that URL to a person. Exact property or trait match only. Counts, sequences, absence, time windows, and not-equals are rejected.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | No | paused is allowed. active returns 409 activation_required and activation_url. |
| properties, traits, url, dwell_seconds, source | value or null | No | null clears that filter. |
Example
curl -s -X PATCH "$BASE_URL/api/v1/agent/triggers/$TRIGGER_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "sampling_percent": 10 }'Delete a research trigger
/api/v1/agent/triggers/{trigger_id}
Deletes the trigger.
When to call it
Call this when the trigger should stop matching. The study stays.
Response
{ "trigger_id": "uuid", "deleted": true }Example
curl -s -X DELETE "$BASE_URL/api/v1/agent/triggers/$TRIGGER_ID" \
-H "Authorization: Bearer $USERCALL_API_KEY"Verify interview.completed
A human saves an HTTPS URL under Home → Developer. Usercall POSTs interview.completed when a qualified interview has a summary and a transcript.
When to call it
Use this on your server to verify the body. The agent cannot set the URL. This is not the research-trigger webhook, which receives a matched user and an invite link.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| x-usercall-signature | header | Yes | t=<unix>,v1=<hex>. HMAC-SHA256 of `${t}.${rawBody}` with the signing secret. Compare v1 in constant time. No max clock skew is defined. |
| x-usercall-event-id | header | Yes | Same value as the idempotency-key header. Treat them as one delivery id. |
Response
{
"event": "interview.completed",
"event_id": "...",
"call_id": "...",
"interview_study_id": "...",
"interview_study_name": "...",
"interview_type": "voice",
"completed_at": "...",
"duration_seconds": 120,
"summary": "...",
"transcript_url": "https://app.usercall.co/api/webhooks/interview-completed/resource?token=..."
}
Optional: recording_url, screen_recording_url, link_context, custom_variables, external_id, email.
interview_type is text, video, or voice. Qualified agent voice interviews are included. Preview calls and failed or too-short calls are not. Retries run up to 3 attempts.Example
const crypto = require('crypto');
const [tPart, v1Part] = signatureHeader.split(',');
const timestamp = tPart.slice(2);
const v1 = v1Part.slice(3);
const expected = crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex');
const valid = expected.length === v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));Download the recording
/api/webhooks/interview-completed/resource?token=...
Downloads the signed recording or transcript from the webhook. No account auth.
When to call it
Call the recording_url within 24 hours. A valid token redirects 302 to storage. Text interviews omit recording_url. Screen recording is the separate screen_recording_url.
Response
recording_url and transcript_url are signed Usercall URLs.
Recording: 302 to storage, or 404 when there is no audio.
Transcript: 200 text/plain.
Missing token is 400. Invalid or expired token is 401.Example
curl -sL "$RECORDING_URL" -o interview.wavError envelope
Failed calls return JSON with error and message. Extra fields depend on the error.
When to call it
Branch on error, not on message text, except where a code below says otherwise.
Response
{ "error": "invalid_request", "message": "...", "details": {} }
400 invalid_json: body was not JSON
400 invalid_request: schema failed, a voice id was sent, or review included call_ids
400 review_not_ready: the study has no key learning goals
401 unauthorized
402 insufficient_credits: required, available, recommended_pack, checkout_url. A person opens checkout_url.
404 study_not_found, simulation_not_found, call_not_found, trigger_not_found, event_not_observed
409 activation_required: activation_url, status. Nothing was changed. A person opens activation_url. There is no activate tool.
422 invalid_trigger: unknown_keys, or an unsupported filter (counts, sequences, absence, time windows, not-equals)
429 study_limit_reached: limit 1 active agent study
429 simulation_limit_reached: limit 5 simulations per account per UTC day. Retell is not started
429 rate_limited
500 study_create_failed, guide_generation_failed, simulation_failed, review_failed, and the other *_failed codesExample
# A voice id is rejected. Send languages and voice_gender instead.
{ "error": "invalid_request", "message": "A voice id cannot be set. Send languages and voice_gender instead." }
# The sixth simulation in a UTC day.
{ "error": "simulation_limit_reached", "message": "You have reached the daily limit of 5 simulations. Please try again tomorrow.", "limit": 5 }Buy credits
/api/v1/billing/checkout
Creates a checkout URL when create or review returns 402.
When to call it
Open the checkout_url from the 402 body, or call this with the recommended pack. Creating a study does this for you.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| pack_id | string | Yes | pack_50, pack_100, pack_300, or pack_500. |
| success_url | string | No | HTTPS URL after purchase. |
| cancel_url | string | No | HTTPS URL if the person cancels. |
Response
{ "checkout_url": "https://usercall.co/agent-checkout?checkout_token=..." }Example
curl -s -X POST "$BASE_URL/api/v1/billing/checkout" \
-H "Authorization: Bearer $USERCALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "pack_id": "pack_50" }'Hosted connector
Connect an assistant when a person wants it to talk to customers and learn why. These 17 tools call the operations above. A result is the API JSON plus http_status. Treat http_status of 400 or higher as a failure. https://app.usercall.co/docs/mcp opens this same page.
- You cannot activate a trigger or send the interview link. A person shares interview_link or opens activation_url.
- Never treat in-progress results as findings. Wait until get_study_status is complete.
- Call list_studies before create_study. Call list_trigger_events before any trigger write.
- One active agent study. Five simulations per account per UTC day.
- 402 returns checkout_url. 409 returns activation_url. Hand either URL to a person.
When to call it
Add https://mcp.usercall.co in Claude, ChatGPT, Codex, or Cursor and sign in with OAuth. No API key. There is no share tool and no tool that activates a trigger.
Example
https://mcp.usercall.coServer instructions
Initialize returns a server instruction string. ChatGPT, Codex, and other OpenAI clients keep the first 512 characters of that string, so the workflow and the hard rules are written to fit that window. The block below is that prefix. Each tool description on tools/list carries the same limits again.
When to call it
Follow this text when a client shows only the opening of the instructions. After https://mcp.usercall.co initialize includes this wording, that response and tools/list are the source of truth. Until that deploy, this page is the contract.
Example
Order: list_studies, create_study, simulate_interview, optional review_study, a person shares interview_link OR create_research_trigger (paused; person opens activation_url), get_study_status until complete, get_study_results. You cannot activate a trigger or send the interview link. No share tool. No activate tool. In-progress results are not findings. list_studies before create. list_trigger_events before triggers. One active agent study. 5 simulations per UTC day. 402 checkout_url. 409 activation_url.Tool hints
Each tool advertises readOnlyHint, destructiveHint, and openWorldHint, plus a title (Create Study, Simulate Interview, Review Study, and the same pattern for the other tools). Deletes are destructive. Reads are readOnly. Creates and updates are neither. create_study and simulate_interview are openWorld because they publish a link or start a run a person did not already ask to inspect. Every other tool is closed. get_trigger_sdk_setup is not readOnly: it is the install step, not a lookup. review_study is not readOnly: it spends 1 credit.
| Tool | readOnly | destructive | openWorld |
|---|---|---|---|
| create_study | false | false | true |
| update_study | false | false | false |
| get_study_status | true | false | false |
| get_study_results | true | false | false |
| simulate_interview | false | false | true |
| review_study | false | false | false |
| delete_study | false | true | false |
| get_trigger_capabilities | true | false | false |
| get_trigger_sdk_setup | false | false | false |
| list_trigger_events | true | false | false |
| get_trigger_event_schema | true | false | false |
| list_studies | true | false | false |
| create_research_trigger | false | false | false |
| list_research_triggers | true | false | false |
| get_research_trigger | true | false | false |
| update_research_trigger | false | false | false |
| delete_research_trigger | false | true | false |
When to call it
Use the same matrix the server returns. Do not treat a create or an update as read-only, and do not treat it as destructive.
stdio and npm
A local stdio client must expose this same 17-tool surface: the same names, the same when-to-use descriptions, and the same hints. The hosted connector at https://mcp.usercall.co is that surface.
When to call it
Do not build against @usercall/mcp@0.1.1. That package is an older schema and is not this contract. Use a release around 0.3.0 that matches tools/list on the hosted server.
create_study
create_study. Start an interview from the question you need answered. You get a study and a participant link. You do not send that link.
When to call it
Interview affected users when analytics already shows a signal (funnel drop-off, churn, stalled activation, or an AI-feature failure) and still cannot say why. A PostHog team-invite drop, Amplitude export retention, a Pendo billing-guide dismissal, or AI summary regenerations are this job. key_research_goal is that question. business_context is optional: the product, the metric, the window, the counts, and who to interview. key_research_goal alone still creates a study. Returns study_id and interview_link. One active agent study per account. Short credits return checkout_url for a person to open. Skip this when the chart already names a tracking gap or a missing control. Call list_studies first and reuse a study that already asks this question. Next is simulate_interview. There is no share tool.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| key_research_goal | string | Yes | The question, in plain language. 5–2000 characters. |
| business_context | string | No | What the product is, only if you have it. At least 5 characters. |
| languages | string[] | No | One locale runs the whole interview in that language. Two or more let the participant choose. |
| voice_gender | string | No | female or male. Do not send a voice id. |
Response
interview_link, study_id, and next_step.action simulate_interview. If credits are short, open checkout_url.Example
create_study({
"key_research_goal": "Why do people abandon onboarding after they create their first project?"
})simulate_interview
simulate_interview. One tool, two calls: start, then read. Hear the interview before a real participant does.
When to call it
Call before any real participant, after create or a guide edit. Omit simulation_id to start. The start returns immediately with running and simulation_id; stop there and call again with that id. A later read returns pass, fail, or error and the transcript when the run is finished. Cap is 5 simulations per account per UTC day; a 429 means stop for the day. A simulation is not an interview and does not change completed_interviews. On pass, a person shares or you review. On fail, call update_study, then simulate again.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | From create_study. |
| simulation_id | uuid | To read | Omit to start. Pass the id from the start when you want the transcript. |
| persona | object | No | name and prompt together, only when starting. Omit both to use the default participant. |
Response
Start: status running and simulation_id. Later read: pass, fail, or error, plus the transcript when the run is finished.Example
simulate_interview({
"study_id": "<study_id from create_study>"
})
simulate_interview({
"study_id": "<study_id from create_study>",
"simulation_id": "<simulation_id from the start>"
})review_study
review_study. Optional check of the interview guide before a person shares the link.
When to call it
Call before a person shares when you want a check of the interview guide. It reads the guide only. It does not read transcripts and it does not apply edits. It costs 1 credit and works when the in-app study review control is hidden. Short credits return checkout_url for a person to open. Write suggested changes with update_study. Stop after one review unless the guide changed.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | From create_study. Do not send call_ids. |
Response
summary, guide_findings, and up to three recommendations. If recommendations is empty, next_step is share. Otherwise next_step is update_study.Example
review_study({
"study_id": "<study_id from create_study>"
})update_study
update_study. Change the guide, the language, the number of interviews, or whether the link accepts context.
When to call it
Call after review_study or a failed simulation names a guide change, or when the link is disabled and a person is about to share. Writes target interviews, interview mode, languages, voice gender, link context, guide text, or media. One locale turns the language picker off; two or more turn it on. Query params on interview_link are ignored until enable_link_context is true. You cannot change key_research_goal; delete the study and call create_study. Stop when the update returns. Then simulate or review again before a person shares.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | The study to change. |
| workflow_questions | array | No | The new questions. Adaptive probing is isAdaptive true and followUpCount 3. |
| is_link_disabled | boolean | No | Set false before a person sends the link, unless the study already hit its target. You cannot send the link. |
| enable_link_context | boolean | No | true before query params on the link are stored. |
Example
update_study({
"study_id": "<study_id from create_study>",
"workflow_questions": [
{ "text": "What were you trying to finish when you left?", "isAdaptive": true, "followUpCount": 3 }
]
})get_study_status
get_study_status. Poll until the study is complete. In-progress payloads are not findings.
When to call it
Poll until complete after interviews are in progress. running and analyzing mean wait and call this again. Do not treat those payloads as a finding. Then call get_study_results. The response includes interview_link and next_step. simulate_interview means dry-run or review the guide before anyone is invited. share means a person sends interview_link, or call create_research_trigger only after list_trigger_events has seen the event. There is no share tool.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | From create_study or list_studies. |
Response
next_step.action:
simulate_interview: nobody real has been interviewed, and you have not finished a simulation or a guide review
share: a person sends interview_link, or call create_research_trigger. You cannot send the link. There is no share tool.
poll_study_status: interviews are in progress. That payload is not a finding.
get_study_results: the study is complete
If link_disabled is true, call update_study with is_link_disabled false before a person sends the link, unless the study already hit its target.Example
get_study_status({
"study_id": "<study_id from create_study>"
})get_study_results
get_study_results. Read why people behaved this way: themes, quotes, and risks.
When to call it
Call after get_study_status is complete. Default format=summary returns themes, insights, and risks. format=summary is the concise response and format=full is the detailed one; stay on summary. Use format=full only for a quote. Summary is the evidence to place beside the original metric. Stop when that summary answers why. Empty themes while the study is still running are not a finding.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | The finished study. |
| format | string | No | summary (default) or full. |
Example
get_study_results({
"study_id": "<study_id from create_study>",
"format": "summary"
})list_studies
list_studies. Read the studies on this account before you create another.
When to call it
Call before create_study. Reuse an existing study when one already asks this question. Call create_study when it does not. Each row includes the same interview_link create returns. Stop once you have picked that study or confirmed none exists.
Example
list_studies({})delete_study
delete_study. Stop a study for good and release unused credits. Destructive.
When to call it
Call when this study asks the wrong question or you must free the one active agent study. Permanently deletes the study and its interview calls and releases unused reserved credits. Stop. This cannot be undone. To stop new interviews without deleting evidence, call update_study with is_link_disabled true.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | The study to delete. |
Example
delete_study({
"study_id": "<study_id from create_study>"
})get_trigger_capabilities
get_trigger_capabilities. Read what a trigger can match before you design one.
When to call it
Call before you design a trigger. Returns what Research Triggers support (one event plus exact-match property or trait filters, URL, page dwell, sampling, cooldown, daily cap) and what they do not (counts, sequences, absence, time windows, not-equals). Stop and hand interview_link to a person when the condition you need is unsupported.
Example
get_trigger_capabilities({})list_trigger_events
list_trigger_events. See which product events Usercall has actually received.
When to call it
Call before create_research_trigger. Returns event names Usercall has received for this account in the last 30 days. Only an observed event can be used. If the list is empty, call get_trigger_sdk_setup. If your event is missing, stop and hand interview_link to a person.
Example
list_trigger_events({})get_trigger_event_schema
get_trigger_event_schema. See the real fields on one event so a filter uses a value you have seen.
When to call it
Call after list_trigger_events shows the event and you need a filter. Returns observed properties and traits with types and sample values. Put filters under the right key. Matching is case- and type-sensitive. Stop if the value you need was never observed; do not invent a filter. Not-equals is unsupported.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| event_name | string | Yes | An event from list_trigger_events, such as study_tested. |
Example
get_trigger_event_schema({
"event_name": "study_tested"
})get_trigger_sdk_setup
get_trigger_sdk_setup. Get the snippet that sends product events into Usercall. Not a read-only lookup.
When to call it
Call when list_trigger_events is empty and the product event must reach Usercall before create_research_trigger. Returns the SDK install snippet, an identify snippet, and install status. Never includes secret keys. If you can edit the codebase, apply the snippet; otherwise show it to a person. Then call list_trigger_events again. Stop and hand interview_link to a person if nobody can install the SDK.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| provider | string | No | posthog, mixpanel, amplitude, segment, ga4, or custom. |
| events | string[] | No | Event names to allow, such as study_tested. |
Example
get_trigger_sdk_setup({
"provider": "posthog",
"events": ["study_tested"]
})create_research_trigger
create_research_trigger. Invite people when an observed event happens. Created paused. You cannot turn it on.
When to call it
Call only after a study exists and list_trigger_events has seen the event. Call list_research_triggers first and reuse a match. It invites those people when that event occurs so you can ask why. Created paused. A person opens activation_url. You cannot turn it on. There is no activate tool. Hand interview_link to a person when the event is not in Usercall yet. Stop after you hand activation_url to a person. Unsupported conditions (counts, sequences, absence, time windows, not-equals) are rejected. Exact property or trait match only. Setting it active returns 409 and activation_url.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| study_id | uuid | Yes | The study those people should take. |
| event_name | string | Yes | An observed event, such as study_tested. |
| traits | object | No | Exact user traits, such as { "plan": "free" }. |
| properties | object | No | Exact event properties. Do not put traits here. |
| invite_link_params | object | No | static, from_traits, or from_properties. Empty {} does not change the study. |
Response
status paused, summary, and activation_url. Hand that URL to a person.Example
create_research_trigger({
"study_id": "<study_id from create_study>",
"event_name": "study_tested",
"traits": { "plan": "free" },
"sampling_percent": 25,
"invite_link_params": { "static": { "customer_profile": "pm" } }
})list_research_triggers
list_research_triggers. See paused and active triggers before you create another.
When to call it
Call before create_research_trigger, when you may already have a trigger for this study and event. Returns status and activation_url. Reuse a paused trigger and hand activation_url to a person. Call create_research_trigger only when none matches. Stop once you have that link or have confirmed you need a new trigger.
Example
list_research_triggers({})get_research_trigger
get_research_trigger. Read one trigger. You still cannot activate it.
When to call it
Call when you have trigger_id and need status or activation_url while the trigger is paused. Hand activation_url to a person. Agents cannot activate. There is no activate tool. Stop polling this for interview evidence; call get_study_status.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| trigger_id | uuid | Yes | From create_research_trigger or list_research_triggers. |
Example
get_research_trigger({
"trigger_id": "<trigger_id from create_research_trigger>"
})update_research_trigger
update_research_trigger. Change targeting or pause a trigger. You cannot set it active.
When to call it
Call to change targeting, sampling, cooldown, daily cap, intercept copy, or delivery, or to pause a trigger (status paused). Agents cannot set it active. status active returns activation_url for the person and writes nothing (409). Changing an active trigger pauses it for re-approval. A person opens activation_url. Stop after you hand that link to a person. Exact property or trait match only. Counts, sequences, absence, time windows, and not-equals are rejected.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| trigger_id | uuid | Yes | The trigger to change. |
| sampling_percent | number | No | 1–100. |
| status | string | No | paused is allowed. active is refused. |
Example
update_research_trigger({
"trigger_id": "<trigger_id from create_research_trigger>",
"sampling_percent": 10
})delete_research_trigger
delete_research_trigger. Stop inviting people for this trigger. Destructive. Completed interviews stay.
When to call it
Call when the event or the study is wrong. Permanently deletes the trigger. Interviews already completed are kept. Stop. To pause without deleting, call update_research_trigger with status paused.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| trigger_id | uuid | Yes | The trigger to delete. |
Example
delete_research_trigger({
"trigger_id": "<trigger_id from create_research_trigger>"
})