{"openapi":"3.1.0","info":{"title":"Surveymatic API","version":"1.0.0","description":"Create surveys, collect responses with AI follow-up probing, and read solid-response results. AI agents can also connect over MCP at POST /api/mcp (see the MCP tag). The public respondent endpoints (`/api/public/s/:slug/*`) are intentionally undocumented here — they are unauthenticated by design and belong to the respondent side, not the owner/integrator side. Integrations authenticate as `Authorization: Bearer <redacted>` with a scoped API key (`smk_…`, Growth and Pro plans). The web app uses an httpOnly session cookie and does not accept that cookie on MCP."},"servers":[{"url":"https://surveymatic.com","description":"Deployment origin"}],"paths":{"/api/auth/register":{"post":{"summary":"Register","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"description":"Registration details"},"responses":{"200":{"description":"Sets the session cookie and returns the user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionToken"}}}},"400":{"description":"Email already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/login":{"post":{"summary":"Log in","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"description":"Credentials"},"responses":{"200":{"description":"Sets the session cookie and returns the user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionToken"}}}},"401":{"description":"Invalid email or password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/forgot-password":{"post":{"summary":"Request a password reset","description":"Always returns the same message. A reset email is sent only when the address has an account and the send window allows it.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"description":"Email address"},"responses":{"200":{"description":"Neutral response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"message":{"type":"string"}}}}}}}}},"/api/auth/reset-password":{"post":{"summary":"Set a new password from a reset link","description":"Single-use link, valid for 1 hour. Also confirms the email, signs out other sessions, and signs in on this device.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"description":"Token and new password"},"responses":{"200":{"description":"Sets the session cookie, signs out other sessions, and returns the user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionToken"}}}},"400":{"description":"Link expired, already used, or passwords do not match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/change-password":{"post":{"summary":"Change password","description":"Requires the current password. Signs out every other session.","tags":["Auth"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"description":"Current and new password"},"responses":{"200":{"description":"Password changed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"description":"Current password is incorrect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/account/export":{"get":{"summary":"Download all my data","description":"One zip of every survey's responses spreadsheet, survey definitions, invite lists, and account details. Available on every plan. The billing owner and workspace admins only. The file downloads directly and is not emailed. Sign in with the session cookie. API keys are not accepted. Card numbers are not included.","tags":["Auth"],"responses":{"200":{"description":"Zip archive","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Sign in required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Only the account owner and admins can download all the data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/logout":{"post":{"summary":"Log out","tags":["Auth"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Logged out","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/auth/me":{"get":{"summary":"Current user","tags":["Auth"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Signed-in user","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys":{"get":{"summary":"List surveys","tags":["Surveys"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Owned surveys","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}}}}}}},"post":{"summary":"Create survey","description":"Creates a survey in `draft` status.","tags":["Surveys"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSurveyRequest"}}},"description":"Survey details"},"responses":{"201":{"description":"Created survey","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Survey"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}":{"get":{"summary":"Get survey","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Survey with questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyDetail"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Save survey","description":"Replaces title, description, status, layout, and questions. Saving status live with an API key is rejected. Agents publish with POST /api/surveys/{id}/launch, which uses the same live-survey limit.","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSurveyRequest"}}},"description":"Full survey definition"},"responses":{"200":{"description":"Saved survey","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyDetail"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete survey","description":"Human session only — excluded from scoped API keys in v1.","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/duplicate":{"post":{"summary":"Duplicate survey","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Duplicated survey (draft)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Survey"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/close":{"post":{"summary":"Close survey","description":"Stops a live survey; responses are no longer accepted.","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Closed survey","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Survey"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/results":{"get":{"summary":"Get results","description":"Response-level results. Headline counts use solid responses. Each row keeps a per-response score, flags, and reasons.","tags":["Results"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Results with solid-response counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Results"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/launch":{"post":{"summary":"Request or perform a launch","description":"API keys follow #295 and receive pending_approval, approved, or declined. A signed-in person publishes directly. The live-survey limit still applies.","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Launch result","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"requestId":{"type":"integer","nullable":true},"surveyStatus":{"type":"string"},"url":{"type":"string","nullable":true}}}}}},"403":{"description":"Live survey limit or role","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/one-question":{"post":{"summary":"Ask one question","description":"Creates a one-question survey and tries to publish it. Open text is the default. Counts as one live survey.","tags":["Surveys"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Created question","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"url":{"type":"string"},"status":{"type":"string"}},"required":["id","url","status"],"additionalProperties":false}}}},"400":{"description":"Could not create","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/ask":{"post":{"summary":"Ask your results","description":"Answers a question from every solid response, including follow-up replies, and cites response ids. analyzedCount is how many responses were read. Metered once per question, per plan, per month. Labeled AI-generated.","tags":["Results"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Answer with citations","content":{"application/json":{"schema":{"type":"object","properties":{"answer":{"type":"string"},"supportCount":{"type":"number"},"analyzedCount":{"type":"number"},"basis":{"type":"string"},"label":{"type":"string"}},"required":["answer","supportCount","analyzedCount","basis","label"],"additionalProperties":false}}}},"403":{"description":"Plan or monthly cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/certificate":{"post":{"summary":"Create results certificate","description":"Signs a snapshot of the solid-response result. Growth and Pro. Later changes create a new certificate.","tags":["Results"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Certificate","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"}},"required":["id","url"],"additionalProperties":false}}}},"403":{"description":"Growth or Pro required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/results/summary":{"get":{"summary":"Results summary for charts","description":"Counts and chart tallies over every solid response, plus the same tallies over all completed responses for the banner. Does not include response bodies. The results page uses this for charts and totals.","tags":["Results"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Chart summary without response bodies","content":{"application/json":{"schema":{"type":"object","properties":{"counts":{"type":"object","properties":{"total":{"type":"integer"},"solid":{"type":"integer"},"setAside":{"type":"integer"}}},"questionStats":{"type":"array","items":{"type":"object"}}}}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/responses":{"get":{"summary":"One page of responses","description":"At most 5 completed responses for the current filter, search, and sort. Each row's number is its place in that filter and sort. #1 is the first row. Searching does not change the number. Search matches answer text, follow-up replies, and that number. A larger pageSize is ignored. Charts stay on the summary route.","tags":["Results"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1},"description":"1-based page. Clamped to the last page."},{"name":"filter","in":"query","schema":{"type":"string","enum":["all","solid","aside"]}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Answer text, follow-up reply, or response number."},{"name":"sort","in":"query","schema":{"type":"string","enum":["newest","oldest","score_desc","score_asc"]}},{"name":"focus","in":"query","schema":{"type":"integer"},"description":"Response number in the current filter and sort. #1 is the first row. The returned page contains that row when it also matches the search."}],"responses":{"200":{"description":"One page of at most 5 responses","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"pageCount":{"type":"integer"},"responses":{"type":"array","items":{"type":"object"}}}}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/results.csv":{"get":{"summary":"Export results CSV","tags":["Results"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"CSV of every completed response. Scores, flags, reasons, and one column per question. An NPS answer is written as on the results screen, for example 9/10. Times are UTC.","content":{"text/csv":{"schema":{"type":"string"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/responses/{responseId}":{"patch":{"summary":"Include or exclude a response","tags":["Results"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}},{"name":"responseId","in":"path","required":true,"description":"Response id","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetResponseIncludedRequest"}}},"description":"Inclusion flag"},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"404":{"description":"Response not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/team":{"get":{"summary":"Get team","tags":["Team"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Team snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}}}}},"/api/team/invites":{"post":{"summary":"Invite team member","tags":["Team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTeamInviteRequest"}}},"description":"Invite details"},"responses":{"200":{"description":"Invite created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/team/invites/{inviteId}":{"delete":{"summary":"Revoke invite","tags":["Team"],"security":[{"apiKey":[]}],"parameters":[{"name":"inviteId","in":"path","required":true,"description":"Invite id","schema":{"type":"string"}}],"responses":{"200":{"description":"Invite revoked","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/team/members/{userId}":{"delete":{"summary":"Remove team member","tags":["Team"],"security":[{"apiKey":[]}],"parameters":[{"name":"userId","in":"path","required":true,"description":"Member user id","schema":{"type":"string"}}],"responses":{"200":{"description":"Member removed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/team/accept":{"post":{"summary":"Accept invite","tags":["Team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptTeamInviteRequest"}}},"description":"Invite token"},"responses":{"200":{"description":"Invite accepted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"description":"Invalid or expired invite","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/team/leave":{"post":{"summary":"Leave team","tags":["Team"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Left team","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/team/invite-preview":{"get":{"summary":"Preview invite","tags":["Team"],"responses":{"200":{"description":"Invite preview","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}}}}},"404":{"description":"Invite not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys":{"get":{"summary":"List API keys","description":"Human session only. Scoped API keys are rejected here — keys cannot manage other keys.","tags":["API keys"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Workspace API keys","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}},"post":{"summary":"Create API key","description":"Human session only. Scoped API keys are rejected here — keys cannot manage other keys. Requires a Growth or Pro plan; workspaces are capped on active keys. The raw key is returned once in the response — store it now.","tags":["API keys"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}},"description":"Key name and scopes"},"responses":{"201":{"description":"Created key (raw key shown once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan required or key limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys/{id}/revoke":{"post":{"summary":"Revoke API key","description":"Human session only. Scoped API keys are rejected here — keys cannot manage other keys.","tags":["API keys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"API key id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys/{id}/revoke-launch":{"post":{"summary":"Remove launch permission","description":"Human session only. Scoped API keys are rejected here — keys cannot manage other keys. Launch permission can be removed. It cannot be added after the key is created.","tags":["API keys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"API key id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Launch permission removed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"404":{"description":"API key not found or no launch permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/workspace/approval-mode":{"get":{"summary":"Read Allow approvals","description":"Human session. Admins and solo owners. Default is per_survey.","tags":["API keys"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Approval mode","content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","enum":["per_survey","per_api_key"]}}}}}}}},"put":{"summary":"Set Allow approvals","description":"Human session. Per survey, or Per API key (requires key configuration).","tags":["API keys"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalModeRequest"}}},"description":"per_survey or per_api_key"},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string"}}}}}}}}},"/api/surveys/{id}/launch-requests":{"get":{"summary":"Launch request status","description":"Latest agent launch status for a survey: pending_approval, approved, or declined.","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Status","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}}}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/launch-requests/{id}/approve":{"post":{"summary":"Approve an agent launch","description":"Human session. Admin or solo owner. Publishes the survey.","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Launch request id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Approved","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}}}}}}}}},"/api/launch-requests/{id}/decline":{"post":{"summary":"Decline an agent launch","description":"Human session. Admin or solo owner. The survey stays a draft.","tags":["Surveys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Launch request id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Declined","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}}}}}}}}},"/api/surveys/{id}/invites/draft":{"post":{"summary":"Draft email invites","description":"API key only. Saves recipients for a person to send. Agents cannot send invites. Pro only.","tags":["Invites"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"201":{"description":"Draft saved","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"},"sent":{"type":"boolean"}}}}}},"403":{"description":"Pro plan required, or an agent tried to send","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/surveys/{id}/invites/send":{"post":{"summary":"Send email invites","description":"Human session only. API keys receive AGENT_CANNOT_SEND_INVITES. No setting changes that.","tags":["Invites"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Survey id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Queued","content":{"application/json":{"schema":{"type":"object","properties":{"queued":{"type":"integer"}}}}}},"403":{"description":"Agents cannot send invites","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/api-keys/{id}/renew":{"post":{"summary":"Renew API key","description":"Human session only. Scoped API keys are rejected here — keys cannot manage other keys. Extends the key's expiry by the default TTL.","tags":["API keys"],"security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"API key id","schema":{"type":"integer"}}],"responses":{"200":{"description":"Renewed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/billing/status":{"get":{"summary":"Billing status","description":"Human session only. Scoped API keys are rejected on billing endpoints (issue #203).","tags":["Billing"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Billing status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatus"}}}}}}},"/api/billing/checkout-session":{"post":{"summary":"Create checkout session","description":"Human session only. Scoped API keys are rejected on billing endpoints (issue #203).","tags":["Billing"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Stripe Checkout URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/billing/portal-session":{"post":{"summary":"Create billing portal session","description":"Human session only. Scoped API keys are rejected on billing endpoints (issue #203).","tags":["Billing"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Portal URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}}}}},"/api/billing/switch-plan":{"post":{"summary":"Switch plan","description":"Human session only. Scoped API keys are rejected on billing endpoints (issue #203).","tags":["Billing"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Updated billing status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatus"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/billing/sync":{"post":{"summary":"Sync billing from Stripe","description":"Human session only. Scoped API keys are rejected on billing endpoints (issue #203).","tags":["Billing"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Billing status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatus"}}}}}}},"/api/mcp":{"post":{"summary":"MCP endpoint","description":"Model Context Protocol (Streamable HTTP) endpoint for AI agents. Send JSON-RPC 2.0 requests — run the \"initialize\" handshake, then \"tools/list\" / \"tools/call\". Authenticated by scoped API key only (`Authorization: Bearer <redacted>`); browser sessions are not accepted. Available tools: `create_survey`, `create_survey_from_goal`, `launch_survey`, `request_launch` (a person approves unless the workspace is Per API key and this key was given launch permission), `get_launch_status`, `draft_invites` (a person sends), `get_results`, `ask_results`, `create_certificate` (Growth and Pro), `list_surveys`, and `stop_survey`. Saving a survey as live through the generic save is still rejected for API keys. Agents cannot send email invites. Tool-level failures arrive as JSON-RPC errors (e.g. insufficient_scope, not_found).","tags":["MCP"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpRequest"}}},"description":"JSON-RPC 2.0 request"},"responses":{"200":{"description":"JSON-RPC 2.0 response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpResponse"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Your credential as `Authorization: Bearer <redacted>`. Use a scoped API key (`smk_…`, issued on the in-app API page; Growth and Pro plans) for integrations and agents. The web app signs in with an httpOnly cookie instead of this header."}},"schemas":{"Error":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false,"description":"Error payload"},"User":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"firstName":{"type":"string"},"lastName":{"type":"string"},"plan":{"type":"string"},"planStatus":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","email","firstName","lastName","plan"],"additionalProperties":false,"description":"Public user profile returned by auth endpoints"},"StoredQuestion":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"surveyId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"type":{"type":"string","enum":["single","multi","scale","nps","text"]},"prompt":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"required":{"type":"boolean"},"sortOrder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","surveyId","type","prompt","options","required","sortOrder"],"additionalProperties":false,"description":"A stored survey question"},"Survey":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"ownerId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"title":{"type":"string"},"description":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["draft","live","paused","closed"]},"respondLayout":{"type":"string","enum":["one_at_a_time","single_page"]},"settings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","ownerId","title","description","slug","status","respondLayout","settings","createdAt","updatedAt"],"additionalProperties":false,"description":"A survey. New surveys start as `draft`."},"ResultAnswer":{"type":"object","properties":{"questionId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"value":{}},"required":["questionId","value"],"additionalProperties":false,"description":"One answer within a response"},"Results":{"type":"object","properties":{"survey":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"title":{"type":"string"},"status":{"type":"string"}},"required":["id","title","status"],"additionalProperties":false},"totalResponses":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"avgConfidence":{"type":"number"},"higherConfidenceCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"needsReviewCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"responses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"confidenceScore":{"type":"number"},"flags":{"type":"array","items":{"type":"string"}},"reasons":{"type":"array","items":{"type":"string"}},"affirmations":{"type":"array","items":{"type":"string"}},"included":{"type":"boolean"},"durationMs":{"type":"number"},"createdAt":{"type":"string"},"answers":{"type":"array","items":{"type":"object","properties":{"questionId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"value":{}},"required":["questionId","value"],"additionalProperties":false}},"followups":{"type":"array","items":{"type":"object","properties":{"questionId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"question":{"type":"string"},"answer":{"type":"string"},"turns":{"type":"array","items":{"type":"object","properties":{"followUp":{"type":"string"},"reply":{"type":"string"}},"required":["followUp","reply"],"additionalProperties":false}}},"required":["questionId","question","answer","turns"],"additionalProperties":false}}},"required":["id","confidenceScore","flags","reasons","affirmations","included","durationMs","createdAt","answers","followups"],"additionalProperties":false}}},"required":["survey","totalResponses","avgConfidence","higherConfidenceCount","needsReviewCount","responses"],"additionalProperties":false,"description":"Response-level results. Headline counts use solid responses. Each row keeps a per-response score, flags, reasons, and any AI follow-up conversation."},"TeamMember":{"type":"object","properties":{"userId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"},"role":{"type":"string","enum":["admin","editor","viewer","owner"]},"createdAt":{"type":"string"}},"required":["userId","email","name","role","createdAt"],"additionalProperties":false,"description":"A team member"},"Team":{"type":"object","properties":{"role":{"anyOf":[{"type":"string","enum":["admin","editor","viewer","owner"]},{"type":"null"}]},"billingOwnerId":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"seatUsed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"seatLimit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"canInvite":{"type":"boolean"},"members":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"type":"string"},"role":{"type":"string","enum":["admin","editor","viewer","owner"]},"createdAt":{"type":"string"}},"required":["userId","email","name","role","createdAt"],"additionalProperties":false}}},"required":["role","billingOwnerId","seatUsed","seatLimit","canInvite","members"],"additionalProperties":false,"description":"Team snapshot for the signed-in user"},"BillingStatus":{"type":"object","properties":{"plan":{"type":"string"},"planStatus":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["plan"],"additionalProperties":{},"description":"Billing status. Human session only — scoped API keys are rejected on billing endpoints."},"ApiKey":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"},"keyPrefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"expiresAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastUsedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"revokedAt":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","keyPrefix","scopes","createdAt","expiresAt","lastUsedAt","revokedAt"],"additionalProperties":false,"description":"API key metadata. The raw key is returned once at creation and never again."},"CreateApiKeyResponse":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"},"keyPrefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"expiresAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"key":{"type":"string"}},"required":["id","name","keyPrefix","scopes","createdAt","expiresAt","key"],"additionalProperties":false,"description":"Newly created API key, including the raw key (shown once — store it now)"},"RegisterRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":8},"firstName":{"type":"string","minLength":1,"maxLength":80},"lastName":{"type":"string","minLength":1,"maxLength":80}},"required":["email","password","firstName","lastName"],"additionalProperties":false,"description":"Registration details"},"LoginRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":4}},"required":["email","password"],"additionalProperties":false,"description":"Login credentials"},"ForgotPasswordRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["email"],"additionalProperties":false,"description":"Address that may have an account"},"ResetPasswordRequest":{"type":"object","properties":{"token":{"type":"string","minLength":1},"password":{"type":"string","minLength":8},"confirmPassword":{"type":"string","minLength":8}},"required":["token","password","confirmPassword"],"additionalProperties":false,"description":"Reset token and the new password"},"ChangePasswordRequest":{"type":"object","properties":{"currentPassword":{"type":"string","minLength":1},"newPassword":{"type":"string","minLength":8},"confirmPassword":{"type":"string","minLength":8}},"required":["currentPassword","newPassword","confirmPassword"],"additionalProperties":false,"description":"Current password and the new password"},"CreateSurveyRequest":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"respondLayout":{"type":"string","enum":["one_at_a_time","single_page"]}},"required":["title"],"additionalProperties":false,"description":"Survey details"},"SaveSurveyRequest":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"status":{"type":"string","enum":["draft","live","paused","closed"]},"respondLayout":{"type":"string","enum":["one_at_a_time","single_page"]},"questions":{"minItems":1,"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"clientKey":{"type":"string","minLength":1,"maxLength":64},"type":{"type":"string","enum":["single","multi","scale","nps","text"]},"prompt":{"type":"string","minLength":1},"options":{"default":[],"type":"array","items":{"type":"string"}},"required":{"default":true,"type":"boolean"},"sortOrder":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipRule":{"anyOf":[{"type":"object","properties":{"ifOption":{"type":"string","minLength":1,"maxLength":200},"skipToKey":{"type":"string","minLength":1,"maxLength":64}},"required":["ifOption","skipToKey"],"additionalProperties":false},{"type":"null"}]}},"required":["type","prompt","options","required","sortOrder"],"additionalProperties":false}},"controls":{"type":"object","properties":{"completionRedirect":{"anyOf":[{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2000},"appendResponseId":{"type":"boolean"}},"required":["url","appendResponseId"],"additionalProperties":false},{"type":"null"}]},"confidence":{"type":"object","properties":{"higherConfidenceMin":{"type":"number","minimum":0,"maximum":1},"disabledFlags":{"type":"array","items":{"type":"string","enum":["too_fast","straight_line","thin_text","gibberish","duplicate_fingerprint","low_effort_followup","rushed_question","likely_bot","likely_ai_text"]}}},"required":["higherConfidenceMin","disabledFlags"],"additionalProperties":false},"responseQuota":{"anyOf":[{"type":"integer","exclusiveMinimum":0,"maximum":1000000},{"type":"null"}]},"password":{"anyOf":[{"type":"string","minLength":4,"maxLength":200},{"type":"null"}]},"opensAt":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]},"closesAt":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]},"hiddenFields":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":32}}},"additionalProperties":false}},"required":["title","questions"],"additionalProperties":false,"description":"Full survey definition"},"QuestionInput":{"type":"object","properties":{"id":{"type":"number"},"clientKey":{"type":"string","minLength":1,"maxLength":64},"type":{"type":"string","enum":["single","multi","scale","nps","text"]},"prompt":{"type":"string","minLength":1},"options":{"default":[],"type":"array","items":{"type":"string"}},"required":{"default":true,"type":"boolean"},"sortOrder":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipRule":{"anyOf":[{"type":"object","properties":{"ifOption":{"type":"string","minLength":1,"maxLength":200},"skipToKey":{"type":"string","minLength":1,"maxLength":64}},"required":["ifOption","skipToKey"],"additionalProperties":false},{"type":"null"}]}},"required":["type","prompt","options","required","sortOrder"],"additionalProperties":false,"description":"A question in a save request"},"SetResponseIncludedRequest":{"type":"object","properties":{"included":{"type":"boolean"}},"required":["included"],"additionalProperties":false,"description":"Include/exclude flag for a response"},"CreateTeamInviteRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"type":"string","enum":["admin","editor","viewer"]}},"required":["email"],"additionalProperties":false,"description":"Invite details"},"AcceptTeamInviteRequest":{"type":"object","properties":{"token":{"type":"string","minLength":1}},"required":["token"],"additionalProperties":false,"description":"Invite token"},"ApprovalModeRequest":{"type":"object","properties":{"mode":{"type":"string","enum":["per_survey","per_api_key"]}},"required":["mode"],"additionalProperties":false,"description":"Allow approvals: Per survey, or Per API key"},"CreateApiKeyRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64},"scopes":{"minItems":1,"type":"array","items":{"type":"string","enum":["surveys:read","surveys:write","responses:read","responses:write"]}},"canLaunch":{"type":"boolean"}},"required":["name","scopes"],"additionalProperties":false,"description":"API key details (mirrors createApiKeySchema in server/api-keys.ts). canLaunch is chosen at creation and can only be removed later."},"SessionToken":{"type":"object","properties":{"user":{"type":"object","properties":{},"additionalProperties":{}}},"required":["user"],"additionalProperties":false,"description":"Signed-in user. The web app session is the httpOnly cookie surveymatic_session. The JSON body does not include the token."},"SurveyDetail":{"description":"A survey with its questions","allOf":[{"$ref":"#/components/schemas/Survey"},{"type":"object","properties":{"questions":{"type":"array","items":{"$ref":"#/components/schemas/StoredQuestion"}}}}]},"McpRequest":{"description":"JSON-RPC 2.0 request envelope for POST /api/mcp","type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0","description":"Must be \"2.0\""},"id":{"description":"Request id (number or string), echoed back in the response"},"method":{"type":"string","description":"MCP method: \"initialize\", \"notifications/initialized\", \"tools/list\", \"tools/call\", \"ping\""},"params":{"type":"object","description":"Method parameters — e.g. tools/call takes { name, arguments } where name is one of create_survey, get_results, list_surveys, stop_survey"}}},"McpResponse":{"description":"JSON-RPC 2.0 response envelope from POST /api/mcp","type":"object","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"Echo of the request id (null on protocol errors)"},"result":{"description":"Success payload (method-dependent). Tool results arrive as { content: [{ type: \"text\", text: \"<json>\" }] }."},"error":{"type":"object","description":"JSON-RPC error, e.g. insufficient_scope or not_found (see the description of POST /api/mcp)","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}}}}