Skip to content

Survey Tools — MCP Tool Reference

Survey tools handle the survey execution lifecycle: creating survey sessions, navigating questionnaire flows, submitting responses, and generating synthetic test data. Comment items are automatically skipped — callers never receive them.

list_surveys

List surveys, optionally filtered by questionnaire, campaign, respondent, interviewer, or status.

Parameter Type Required Default Description
questionnaire_id string No Filter by questionnaire ID
campaign_id string No Filter by campaign ID
respondent_id string No Filter by respondent ID
interviewer_user_id string No Filter by assigned interviewer (User)
status string No Filter: "pending", "in_progress", or "completed"
expand boolean No false Populate nested questionnaire/interviewer/respondent/campaign objects
cursor string No Pagination cursor from a previous page
limit integer No 50 Page size (max 100)

Returns: { data: [...], next_cursor, has_more } — cursor-paginated. Each survey carries status plus a derived, read-only completed boolean (true exactly when status is "completed"); status is the single source of truth, and there is no completed filter or input anywhere on the survey surface.


update_survey

Update a survey's mutable fields. The bound entities (questionnaire, respondent, interviewer, campaign) are immutable here — delete and recreate the survey to rebind them.

Parameter Type Required Default Description
survey_id string Yes Survey UUID
status string No New status; setting "completed" is how completion is expressed (there is no separate completed input)
qml_state object No Advanced: replace the survey's flow state
current_block_id string No Advanced: reposition the flow pointer
current_item_id string No Advanced: reposition the flow pointer

Returns: the updated survey object (same shape as list_surveys entries, completed derived from status).


create_survey

Create a new survey for a respondent.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire to use
respondent_id string Yes Respondent UUID
interviewer_user_id string No For phone/in-person interviews. Omit for self-completion
campaign_id string No Associate with a campaign

Returns: { id, questionnaire_id, interviewer_user_id, respondent_id, campaign_id, mode }


bulk_create_surveys

Create surveys for all respondents in a campaign's assigned pool. Skips respondents who already have surveys.

Parameter Type Required Default Description
campaign_id string Yes Campaign UUID

Returns:

{
  "status": "success",
  "campaign_id": "...",
  "created_count": 45,
  "skipped_count": 5,
  "surveys": [{"id": "...", "respondent_id": "..."}]
}

Requires: Campaign must have both a questionnaire and a respondent pool assigned.


get_survey_current_item

Get the current question in a survey. Initializes the survey state on first call.

Parameter Type Required Default Description
survey_id string Yes Survey UUID

Returns (question): the current item, its response options folded in, and the control type — a single round-trip carries everything needed to answer.

{
  "survey_id": "...",
  "status": "in_progress",
  "item": {
    "id": "q_gender",
    "kind": "Question",
    "text": "What is your gender?"
  },
  "control_type": "radio",
  "options": [
    {"value": "male", "text": "Male"},
    {"value": "female", "text": "Female"},
    {"value": "other", "text": "Other"}
  ],
  "required": true
}

For a range control, options carries a single range descriptor:

{"control_type": "range", "options": [{"type": "range", "min": 18, "max": 99, "step": 1}]}

Text controls (editbox, textarea) return an empty options list. Control types: radio, dropdown, checkbox, slider, switch, editbox, textarea.

Returns (complete): { survey_id, status: "completed", message: "..." }

Auto-Comment Skipping

Comment items (informational text) are transparently skipped — up to 50 consecutive Comments are auto-processed. Callers only receive actionable items (Question, QuestionGroup, MatrixQuestion).

Option extraction is folded in — there is no separate options tool

The legacy get_question_options tool was removed. Its payload (options + control_type) now folds directly into get_survey_current_item's response, so a single call per question is enough.


submit_survey_response

Submit an answer for the current question and advance to the next item.

Parameter Type Required Default Description
survey_id string Yes Survey UUID
item_id string Yes Item ID being answered
outcome any Yes Response value (type depends on control)

Returns:

{
  "survey_id": "...",
  "status": "in_progress",
  "output": { },
  "next": { }
}

status is "in_progress" or "completed". next folds in the following step (same shape as get_survey_current_item) so you don't need a separate fetch — it is null once the survey completes. Trailing Comment items are auto-processed after each submission.

Call get_survey_current_item first

The survey must be initialized before submitting responses. If not initialized, returns: {"error": "Survey not initialized. Call get_survey_current_item first."}

Answers submitted here are recorded as machine-produced

A survey that has had an invitation sent cannot be answered through this tool at all — that is refused outright, so no live fielded interview can be written to programmatically. What remains is testing and simulation, so every answer submitted here is recorded as machine-produced and its response time is excluded from speeder detection.


finish_survey

Explicitly mark a survey as completed. Use for edge cases where auto-completion doesn't trigger.

Parameter Type Required Default Description
survey_id string Yes Survey UUID

Returns: { status: "success" | "already_complete", survey_id, message }.


mass_fill_surveys

Enqueue an asynchronous background task to fill all pending surveys in a campaign with synthetic test responses. Do not use on campaigns with real respondent data.

The tool returns a task_id immediately; the generation loop runs as a background job on Portor's own worker. Poll get_task_status until the task reaches a terminal state: completed (every survey filled), partial (some surveys filled, others failed — e.g. bad respondent data or a transient QML error), or failed (zero surveys filled — either a pre-run validation error such as a missing campaign, or every survey in the run failed; last_error explains why). The three states are mutually exclusive: any mix of successes and failures lands on partial, never failed.

Parameter Type Required Default Description
campaign_id string Yes Campaign UUID
distribution string No "realistic" Strategy: "realistic", "random", "stratified", or "llm"
max_surveys integer No Limit surveys to fill. null = all pending
profiles string[] No ["random"] Persona profiles, assigned round-robin
concurrency integer No 1 Per-task thread-pool size for the inner loop

Returns (synchronously, after enqueue validation):

{
  "task_id": "f5b6c7d8-...",
  "status": "pending",
  "campaign_id": "...",
  "distribution": "realistic",
  "profiles": ["young_single", "retired"]
}

Poll get_task_status(task_id) until state is completed, partial, or failed. status_text surfaces live progress (e.g. "47 of 200 surveys processed") and, on a terminal state, a fill summary (e.g. "47 of 200 surveys filled (3 failed)"); last_error carries a sanitized reason on failure. To retry after a partial result, re-invoke mass_fill_surveys on the same campaign — the fill only processes surveys that are not yet completed, so already-filled surveys are skipped.

Distribution Modes

Mode Engine Speed Description
realistic Weighted random Fast Profile-aware demographic weighting (default)
random Uniform random Fast Pure random, no demographic influence
stratified Demographic strata Fast Quota-balanced responses
llm Claude Haiku API ~1s/question AI-generated, persona-consistent responses. Requires ANTHROPIC_API_KEY

Persona Profiles

Assigned round-robin to respondents. Available profiles:

Profile Age Income Behavior
young_single 25–35 $65k Progressive, tech-savvy, favors middle options
married_family 30–50 $80k Practical, value-oriented
retired 65+ $55k Conservative, favors early/traditional options
high_income 45 $150k+ Quality-focused, favors premium options
random 40 $70k No particular bias

Item Kind Handling

Answers are numeric codes (a choice control's labels map integer codes to display strings; the code is what is submitted) — free text only for the textarea control. See Survey Presentation Format for the full contract.

Item Kind Outcome Format Example
Comment null (auto-skipped) Informational text
Question Single number: answer code / numeric value / switch 1-0 (Checkbox: bitmask int; Textarea: string) 2, 42, true
QuestionGroup List of numbers, one per sub-question — all from the group's single shared control [1, 3, 2]
MatrixQuestion Nested list of numbers, rows × columns — one shared control [[1, 2], [3, 1]]

bulk_delete_surveys

Delete multiple surveys by filter criteria. Uses a two-step workflow: call with dry_run=true first to preview what will be deleted, then call again with dry_run=false to execute.

Parameter Type Required Default Description
campaign_id string Yes Delete surveys in this campaign
status string No Only target surveys with this status: "pending", "in_progress", or "completed"
dry_run boolean No true Preview mode — returns count and sample without deleting

Dry run returns:

{
  "dry_run": true,
  "count": 25,
  "sample": [{"id": "...", "respondent_id": "...", "status": "pending", "campaign_id": "..."}],
  "message": "Would delete 25 surveys. Call again with dry_run=False to execute."
}

Execute returns: { dry_run: false, deleted: 25 }

Destructive Operation

This tool permanently deletes survey records. Always preview with dry_run=true first.


Error Handling

Common Errors

Scenario Response
Survey not found {"error": "Survey {id} not found"}
Campaign not found {"error": "Campaign {id} not found"}
Questionnaire not found {"error": "Questionnaire {id} not found"}
Survey not initialized {"error": "Survey not initialized. Call get_survey_current_item first."}
No pool assigned {"error": "Campaign has no respondent pool assigned"}
No incomplete surveys {"status": "no_surveys", "message": "No incomplete surveys found in campaign"}

Survey State Machine

[Not Started] → get_survey_current_item → [In Progress]
[In Progress] → submit_survey_response → [In Progress | Completed]
[In Progress] → finish_survey → [Completed]
  • Initialization: First call to get_survey_current_item loads the QML and creates the survey state
  • Auto-completion: Surveys complete automatically when the last item is answered
  • Comment skipping: Comment items are transparently processed (up to 50 consecutive) — callers never receive them
  • Persistence: Every response is immediately persisted to the database