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

NameTypeRequiredDescription
AuthorizationheaderAPIBearer token from Home → Developer. Shown once at creation. Scope is the personal account only.
ConnectorURLMCPhttps://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_results
POST

Create 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

NameTypeRequiredDescription
key_research_goalstringYesResearch question. Trimmed, 5–2000 characters.
business_contextstringNoProduct context, 5–2000 characters when present. Shorter than 5 is 400. It is not copied from the goal.
additional_context_promptstringNoExtra interviewer guidance, max 1000 characters.
target_interviewsnumberNo1–200. Default 1. Reserves 10 credits per interview.
duration_minutesnumberNo5–65. Default 12.
interview_modestringNovoice (default), text, or voice_and_text.
languagesstring[]NoOne 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_genderstringNofemale (default on create) or male. The server picks the voice. Do not send a voice id.
enable_link_contextbooleanNoDefault false. Query params on the interview link are ignored until this is true.
custom_link_variablesarrayNoUp to 10 declared custom keys: { key, label?, default_value? }. key is snake_case, max 32, not reserved.
metadataobjectNoYour own key-value pairs.
study_mediaobjectNo{ 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?"
  }'
GET

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

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

NameTypeRequiredDescription
study_iduuidYesPath 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"
PATCH

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

NameTypeRequiredDescription
target_interviewsnumberNo1–200. Increasing reserves 10 credits per added interview. Decreasing releases unused reserved credits.
is_link_disabledbooleanNoSet false before a person sends the link, unless the study already hit its target. You cannot send the link.
interview_modestringNovoice, text, or voice_and_text.
ai_agent_intro_messagestringNo1–5000 characters.
key_learning_goalsstringNo1–5000 characters. Required before a guide review.
workflow_end_messagestringNo1–5000 characters.
workflow_questionsarrayNoReplaces the question list. Adaptive probing is isAdaptive true and followUpCount 3. followUpCount 3 without isAdaptive is rejected.
study_mediaobject | nullNoReplaces media on every question. null removes it.
languagesstring[]NoSame rules as create. One locale turns the picker off. Two or more turn it on.
voice_genderstringNofemale or male. Omit it to leave the stored voice gender.
enable_link_contextbooleanNoOmit it to leave the column unchanged.
custom_link_variablesarrayNoReplaces 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

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

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"
POST

Start 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

NameTypeRequiredDescription
persona.namestringWith persona1–80 characters. Both persona fields are required if you send persona.
persona.promptstringWith persona1–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 '{}'
GET

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"
POST

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

NameTypeRequiredDescription
bodyobjectEmptySend {} 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

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

NameTypeRequiredDescription
formatqueryNosummary (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

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"
GET

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

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

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

NameTypeRequiredDescription
providerqueryNoposthog (default), mixpanel, amplitude, segment, ga4, or custom.
eventsqueryNoComma-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"
POST

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

NameTypeRequiredDescription
study_iduuidYesStudy that owns the interview. It needs an interview link.
event_namestringYesOne observed event, 1–120 characters.
propertiesobjectNoExact equality on event properties. Values are string, number, or boolean. Not-equals, ranges, counts, and missing values are rejected.
traitsobjectNoExact equality on user traits. Same limits as properties.
urlobjectNo{ operator: equals | contains | starts_with, value }.
dwell_secondsnumberNo1–600. Page-visit triggers only.
sampling_percentnumberNo1–100.
cooldown_daysnumberNo0–365.
max_invites_per_daynumberNo1–100.
delivery_methodstringNointercept (default) or webhook. webhook requires a public https webhook_url.
invite_link_paramsobjectNostatic, 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" } }
  }'
GET

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

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"
PATCH

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

NameTypeRequiredDescription
statusstringNopaused is allowed. active returns 409 activation_required and activation_url.
properties, traits, url, dwell_seconds, sourcevalue or nullNonull 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

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

NameTypeRequiredDescription
x-usercall-signatureheaderYest=<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-idheaderYesSame 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));
GET

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.wav

Error 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 codes

Example

# 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 }
POST

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

NameTypeRequiredDescription
pack_idstringYespack_50, pack_100, pack_300, or pack_500.
success_urlstringNoHTTPS URL after purchase.
cancel_urlstringNoHTTPS 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.co

Server 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.

ToolreadOnlydestructiveopenWorld
create_studyfalsefalsetrue
update_studyfalsefalsefalse
get_study_statustruefalsefalse
get_study_resultstruefalsefalse
simulate_interviewfalsefalsetrue
review_studyfalsefalsefalse
delete_studyfalsetruefalse
get_trigger_capabilitiestruefalsefalse
get_trigger_sdk_setupfalsefalsefalse
list_trigger_eventstruefalsefalse
get_trigger_event_schematruefalsefalse
list_studiestruefalsefalse
create_research_triggerfalsefalsefalse
list_research_triggerstruefalsefalse
get_research_triggertruefalsefalse
update_research_triggerfalsefalsefalse
delete_research_triggerfalsetruefalse

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

NameTypeRequiredDescription
key_research_goalstringYesThe question, in plain language. 5–2000 characters.
business_contextstringNoWhat the product is, only if you have it. At least 5 characters.
languagesstring[]NoOne locale runs the whole interview in that language. Two or more let the participant choose.
voice_genderstringNofemale 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

NameTypeRequiredDescription
study_iduuidYesFrom create_study.
simulation_iduuidTo readOmit to start. Pass the id from the start when you want the transcript.
personaobjectNoname 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

NameTypeRequiredDescription
study_iduuidYesFrom 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

NameTypeRequiredDescription
study_iduuidYesThe study to change.
workflow_questionsarrayNoThe new questions. Adaptive probing is isAdaptive true and followUpCount 3.
is_link_disabledbooleanNoSet false before a person sends the link, unless the study already hit its target. You cannot send the link.
enable_link_contextbooleanNotrue 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

NameTypeRequiredDescription
study_iduuidYesFrom 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

NameTypeRequiredDescription
study_iduuidYesThe finished study.
formatstringNosummary (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

NameTypeRequiredDescription
study_iduuidYesThe 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

NameTypeRequiredDescription
event_namestringYesAn 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

NameTypeRequiredDescription
providerstringNoposthog, mixpanel, amplitude, segment, ga4, or custom.
eventsstring[]NoEvent 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

NameTypeRequiredDescription
study_iduuidYesThe study those people should take.
event_namestringYesAn observed event, such as study_tested.
traitsobjectNoExact user traits, such as { "plan": "free" }.
propertiesobjectNoExact event properties. Do not put traits here.
invite_link_paramsobjectNostatic, 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

NameTypeRequiredDescription
trigger_iduuidYesFrom 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

NameTypeRequiredDescription
trigger_iduuidYesThe trigger to change.
sampling_percentnumberNo1–100.
statusstringNopaused 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

NameTypeRequiredDescription
trigger_iduuidYesThe trigger to delete.

Example

delete_research_trigger({
  "trigger_id": "<trigger_id from create_research_trigger>"
})