Owlmetry
MCP

Questionnaires

MCP tools for creating, editing, and triaging in-app questionnaires.

Questionnaires are structured multi-question surveys shown in-app via the Swift SDK's OwlQuestionnaireView or the auto-trigger .owlQuestionnaire(slug:trigger:…) view modifier. The auto-trigger opens at a small "Have a minute for feedback?" detent with three actions (take the survey, maybe later, never ask again) and expands to a step-through flow on consent — one question per page with a progress bar, non-swipe-dismissible so users finish or cancel deliberately. Each questionnaire has an immutable slug, a versioned schema of up to 30 questions (text, single/multi choice, rating, NPS), and a one-and-done global dismiss path so users who reject are never asked again. Responses store a snapshot of the schema they were submitted against, so historical answers always render correctly even after the parent definition has been edited.

Drafts are first-class responses. The SDK saves the current answer set on every Next tap (not just on Submit), so a user who quits halfway leaves a row with submitted_at = null and status = 'draft'. The next eligible launch resumes them at the first unanswered question with prior answers pre-filled (consent is skipped on resume). The final Submit flips submitted_at to non-null, snapshots the schema onto the row, transitions status to new, and fires the team notification exactly once. Per-question analytics and response lists include drafts by default so abandonment shows up naturally as a drop-off curve; pass submitted_only to scope to completed submissions. Abandoned drafts untouched for 90 days are soft-deleted by the daily questionnaire_draft_cleanup job.

The questionnaire tool surface mirrors the CLI — agents can create + edit + triage, but soft-deleting a questionnaire is user-only (mirroring the dashboard behaviour).

Tools

ToolDescription
list-questionnairesList questionnaire definitions — pass project_id for one project, or team_id for every questionnaire across the team's projects (mutually exclusive). Optional data_mode (production, development, all) filters the rolled-up response_count / submitted_count / last_response_at on each row; default is all modes mixed
get-questionnaireFetch a single definition with response_count + last_response_at. Optional data_mode (production, development, all) filters those rollups; default is all modes mixed
create-questionnaireCreate a new questionnaire with slug + schema
update-questionnaireUpdate name, description, schema, is_active, app_id pin
delete-questionnaire⚠️ User-only — agent calls receive 403
list-questionnaire-responsesList responses with filters + pagination. Drafts (status: "draft", submitted_at: null) are included by default — pass submitted_only: true to hide them, or status: "draft" to scope to drafts only
get-questionnaire-responseFetch a single response with comments + schema_snapshot (snapshot is null on drafts)
update-questionnaire-response-statusTriage state transition. Statuses: draft, new, in_review, addressed, dismissed
add-questionnaire-response-commentAnnotate a response
get-questionnaire-analyticsPre-aggregated per-question distribution. Drafts contribute to the rollups by default — a Q1-only draft adds to Q1's count and naturally drops out of Q2+, so abandonment shows up as a drop-off curve. Pass submitted_only: true to scope rollups to fully-submitted responses

Schema shape

create-questionnaire and update-questionnaire accept the same JSON schema:

{
  "version": 1,
  "questions": [
    { "id": "q_rating", "type": "rating", "title": "Rate us", "required": true, "scale": 5 },
    {
      "id": "q_role",
      "type": "single_choice",
      "title": "How would you describe yourself?",
      "required": true,
      "options": [
        { "id": "hobby", "label": "Hobbyist" },
        { "id": "indie", "label": "Indie developer" }
      ]
    },
    { "id": "q_nps", "type": "nps", "title": "How likely to recommend?", "required": false },
    { "id": "q_text", "type": "text", "title": "Anything else?", "required": false, "multiline": true }
  ]
}

Constraints:

  • slug matches ^[a-z0-9-]+$, max 64 chars, immutable after creation
  • Up to 30 questions per questionnaire
  • Question id matches ^[a-z0-9_]+$, max 32 chars, unique per questionnaire
  • single_choice and multi_choice require 2–20 options
  • rating.scale is fixed at 5 in V1
  • nps is implicit 0–10
  • text defaults to a single-line input. Add "multiline": true (as on q_text above) for paragraph-style answers — the SDK renders a tall, growing text editor. Same 4000-character cap either way.

Typical workflow

  1. Create a survey definitioncreate-questionnaire with a slug + schema (agent-friendly: agents can build short market-research surveys autonomously).
  2. Wait for responses — the iOS app's .owlQuestionnaire(slug:trigger:…) modifier fetches and presents the survey on the configured trigger, saving a draft after every Next tap.
  3. Aggregate analyticsget-questionnaire-analytics returns counts/averages/NPS score per question; the agent can summarize trends without paging through individual responses. Use submitted_only: true if you want completion-rate-style headline numbers without draft contributions; leave it off to see where users drop out.
  4. Drill into interesting answerslist-questionnaire-responses then get-questionnaire-response for context. session_id on the response links to the user's event timeline — pass it to investigate-event to see what they were doing. Filter to drafts (status: "draft") to study abandonment, or pass submitted_only: true for completed submissions only.
  5. Annotate or triageadd-questionnaire-response-comment and update-questionnaire-response-status cooperate with human teammates on the dashboard.

Eligibility model (read-only)

The eligibility envelope (already_responded / globally_dismissed / inactive) is enforced by the SDK ingest endpoints and isn't directly exposed via MCP. To understand why a particular user wouldn't see a survey, check:

  • The questionnaire's is_active flag (get-questionnaire)
  • The user's app_users.properties._questionnaires_dismissed_at (visible via the user page in the dashboard; soon via a get-user MCP tool)
  • Whether the user already has a response (list-questionnaire-responses filtered to their user_id)

Ready to get started?

Connect your agent via MCP or CLI and start tracking.