Canviq MCP and AI Agent Integration Guide¶
Complete reference for connecting AI agents and automated pipelines to Canviq via the Model Context Protocol (MCP) server.
Overview¶
The Canviq MCP server exposes every capability available in the dashboard as an MCP tool. Agents can create surveys, query PMF scores, stream responses in real time, and trigger automation workflows. All operations use the same authentication layer as the REST API.
Server URL: https://canviq.app/api/mcp
Transport: Streamable HTTP (SSE for streaming tools)
Protocol: MCP 2025-03
Authentication¶
Creating an API key¶
- Sign in to your Canviq dashboard.
- Go to Settings > API Keys.
- Click Create key.
- Select the key type:
- Live (
pk_live_...): Accesses production data - Test (
pk_test_...): Accesses sandbox data only - Select a policy (see Permission scopes).
- Copy the key. It is shown exactly once and cannot be retrieved again.
Store the key in a secrets manager (AWS Secrets Manager, Vault, 1Password, etc.). Never commit it to source control.
Using the key¶
Pass the key in the Authorization header on every request:
For MCP clients that use the api-key configuration field, set:
Permission scopes¶
| Scope | What it allows |
|---|---|
surveys:read | List and view surveys |
surveys:write | Create, update, and delete surveys |
surveys:publish | Publish and unpublish surveys |
responses:read | Query and stream survey responses |
responses:export | Export response data |
analytics:read | Access PMF scores, trends, and sentiment |
automation:read | View workflow definitions |
automation:write | Create and modify workflows |
automation:execute | Trigger workflows manually |
admin:agents | Manage other agent identities |
admin:audit | Read the audit log |
Read-only access to PMF data (surveys:read, responses:read, analytics:read) is available on the Free tier. Write access (surveys:write, responses:export, automation:write) requires the Growth plan.
Key format¶
Keys follow the pattern pk_{env}_{32-char-base64url-secret}. The first 14 characters are used for O(1) database lookup. The full key is verified against an Argon2id hash stored at rest. The plaintext is never persisted.
Rate limits¶
| Tier | Requests/minute | Concurrent streams | Export size |
|---|---|---|---|
| Free / Startup | 30 | 1 | 1,000 rows |
| Growth | 300 | 5 | 10,000 rows |
| Scale | 1,000 | 20 | 100,000 rows |
When you exceed the rate limit, the server returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait. Use exponential backoff for retries:
1st retry: wait Retry-After seconds
2nd retry: wait 2× Retry-After seconds
3rd retry: wait 4× Retry-After seconds
Input modes¶
Structured mode¶
Pass a JSON object matching the tool's input schema. Validated before execution.
{
"title": "Feature Satisfaction Survey",
"questions": [
{
"type": "rating",
"text": "How satisfied are you with the new dashboard?",
"scale": { "min": 1, "max": 5 }
},
{
"type": "free_text",
"text": "What could we improve?",
"optional": true
}
],
"targeting": {
"attributes": { "plan": "pro" },
"behavior": { "last_active_days": 7 }
}
}
Natural language mode¶
Describe what you want in plain text. The server parses your description into a structured schema using Claude Sonnet.
"Create an NPS survey for users who completed onboarding this week.
Ask them to rate likelihood to recommend, then ask what one thing
we could do to improve their experience. Target iOS users only."
When input is ambiguous, the server returns a clarification_needed response rather than guessing:
{
"status": "clarification_needed",
"parsed": { "...": "what was understood" },
"ambiguities": [
{
"field": "targeting.signup_date",
"question": "Should 'this week' include today or start from Monday?",
"options": ["last_7_days", "current_week_monday_start"]
}
]
}
Respond with a clarifying answer or supply the full structured schema to proceed.
Tool reference¶
Survey management¶
survey_create¶
Creates a new survey.
Required scope: surveys:write
Input:
| Parameter | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Survey display name |
questions | array | Yes | Question definitions (see below) |
targeting | object | No | Audience filter rules |
trigger | object | No | Event-based trigger configuration |
theme | object | No | Visual customization overrides |
Question types:
| Type | Description |
|---|---|
sean_ellis | The core PMF question with three named options |
nps | 0–10 numeric scale |
rating | 1–5 or 1–10 emoji or numeric scale |
multiple_choice | Single or multi-select list of options |
free_text | Open-ended text response |
Returns: SurveyPayload (the created survey including its generated ID).
survey_get¶
Fetches a single survey by ID.
Required scope: surveys:read
Input: { "survey_id": "string" }
Returns: SurveyPayload (the requested survey).
survey_update¶
Updates a survey's title, questions, or targeting. Changes take effect immediately for surveys in draft status. For live surveys, changes require republishing.
Required scope: surveys:write
Input: { "survey_id": "string", ...fields to update }
survey_publish¶
Makes a survey live. Users with matching trigger conditions can now see it.
Required scope: surveys:publish
Input: { "survey_id": "string" }
survey_unpublish¶
Pauses a live survey. Users will not see it until republished. Existing responses are preserved.
Required scope: surveys:publish
Input: { "survey_id": "string" }
survey_delete¶
Permanently deletes a survey and all associated responses. Irreversible.
Required scope: surveys:write
Input: { "survey_id": "string" }
surveys_list¶
Returns all surveys for the authenticated organization.
Required scope: surveys:read
Input: { "status": "draft | live | paused | archived" } (optional filter)
Returns: SurveyPayload[] (all surveys for the organization, filtered by status if provided).
Response access¶
responses_query¶
Queries survey responses with optional filters.
Required scope: responses:read
Input:
| Parameter | Type | Required | Description |
|---|---|---|---|
survey_id | string | Yes | Survey to query |
from | ISO 8601 date | No | Start of date range |
to | ISO 8601 date | No | End of date range |
answer_filter | object | No | Filter by a specific question's response value |
limit | integer | No | Max responses per page (default 100, max 1000) |
cursor | string | No | Pagination cursor from previous response |
Returns: { responses: ResponseRecord[], next_cursor: string | null } (the matching responses for the page, plus a cursor for the next page or null if this is the last page).
responses_stream_subscribe¶
Opens a real-time stream of new responses as they arrive.
Required scope: responses:read
Input: { "survey_id": "string", "filters": object } (filters optional)
Behavior: Returns a stream ID. The MCP server sends response.created events over the SSE connection. Call responses_stream_unsubscribe with the stream ID to stop.
responses_export¶
Exports all responses for a survey as a flat record set.
Required scope: responses:export
Input: { "survey_id": "string", "format": "json | csv" }
Returns: string (a temporary download URL for the exported file, valid for 1 hour).
Analytics¶
analytics_summary¶
Returns the current PMF score and response counts.
Required scope: analytics:read
Input: { "survey_id": "string" }
Returns: PmfSummary (the current PMF score and response counts, see schema below).
{
"pmf_score": 43.2,
"response_count": 186,
"very_disappointed_count": 80,
"somewhat_disappointed_count": 68,
"not_disappointed_count": 38,
"last_updated": "2026-06-01T00:00:00Z"
}
analytics_trend¶
Returns PMF score over time, grouped by week.
Required scope: analytics:read
Input:
| Parameter | Type | Description |
|---|---|---|
survey_id | string | Survey ID |
from | ISO 8601 date | Start of range (default: 90 days ago) |
to | ISO 8601 date | End of range (default: today) |
Returns: { weeks: [{ week_start: string, pmf_score: number, response_count: number }] } (PMF score grouped by week for the requested date range).
analytics_cohort¶
Returns the segment breakdown for Very vs. Somewhat Disappointed cohorts, including open-text themes.
Required scope: analytics:read
Input: { "survey_id": "string" }
analytics_sentiment¶
Returns sentiment analysis from Claude Haiku applied to open-text responses.
Required scope: analytics:read
Input: { "survey_id": "string", "question_id": "string" }
Distribution¶
targeting_set¶
Configures audience filter rules for a survey.
Required scope: surveys:write
Input:
{
"survey_id": "string",
"rules": {
"attributes": { "plan": "pro", "country": "US" },
"behavior": { "last_active_days": 14 },
"locale": ["en", "fr"]
}
}
trigger_configure¶
Sets the event-based trigger rule for a survey.
Required scope: surveys:write
Input:
fatigue_configure¶
Sets per-user survey frequency limits.
Required scope: surveys:write
Input:
Automation¶
workflow_create¶
Creates an automation workflow triggered by a survey response event.
Required scope: automation:write
Input:
{
"name": "Low NPS Follow-Up",
"trigger": {
"event": "response.created",
"survey_id": "nps_survey_123",
"conditions": [{ "question": "nps_score", "operator": "lt", "value": 7 }]
},
"actions": [
{
"type": "create_ticket",
"config": { "system": "zendesk", "priority": "high" }
}
]
}
Built-in action types:
| Action | Description |
|---|---|
send_email | Send a templated email to the respondent or a team address |
create_ticket | Create a support ticket in an integrated system |
update_crm | Update a user record in your CRM |
tag_user | Apply a tag to the user for future targeting |
trigger_survey | Launch a follow-up survey after a delay |
call_webhook | POST to a custom URL with the response payload |
update_attribute | Modify a user attribute for future targeting |
Error codes¶
All errors return JSON with a code and message field.
| HTTP status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Input did not pass schema validation. details field lists each invalid field. |
| 401 | UNAUTHORIZED | Missing or invalid API key. |
| 403 | FORBIDDEN | Key does not have the required scope for this operation. |
| 404 | NOT_FOUND | Resource does not exist or your key cannot access it. |
| 409 | CONFLICT | Operation conflicts with current state (e.g. publishing an already-live survey). |
| 422 | CLARIFICATION_NEEDED | Natural language input was ambiguous. See ambiguities field. |
| 429 | RATE_LIMITED | Rate limit exceeded. See Retry-After header. |
| 500 | INTERNAL_ERROR | Server error. Retry after a short delay. |
Natural language examples¶
The following queries work in natural language mode:
"What is my current PMF score?"
→ calls analytics_summary for the most recent active survey
"Show me the trend for the last 3 months"
→ calls analytics_trend with from = 3 months ago
"Create a quick NPS survey for users on the Pro plan"
→ calls survey_create with type=nps and targeting.attributes.plan=pro
"How many responses have we gotten this week?"
→ calls responses_query with from = start of current week, count only
"Pause the onboarding survey"
→ calls survey_unpublish for a survey matching "onboarding" in title
"Export all responses from the Q2 PMF survey as CSV"
→ calls responses_export with format=csv
Example: Claude Desktop configuration¶
{
"mcpServers": {
"canviq": {
"url": "https://canviq.app/api/mcp",
"apiKey": "pk_live_your_key_here"
}
}
}
Once connected, you can ask Claude:
"Query my Canviq PMF score and summarize the top themes from the Very Disappointed cohort."
Related¶
- Founder Guide: Dashboard features and PMF methodology
- Respondent Guide: What survey respondents see
- iOS SDK API Reference: iOS SDK public methods