Creates a survey master template. Required scope: w_surveys.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
This endpoint uses thew_surveysscope, which must be enabled through the Integrations section found within the Settings menu.
Requires a user token whose member has permission to manage survey templates, or an account token together with the member_id of such a member (the request is then evaluated with that member's permissions). An account token without member_id gets 403 Account tokens need a member_id for this operation. Pass member_id as a query parameter (?member_id=...), not in the request body: the body rejects unknown fields with 400. The account must have the Surveys feature enabled. The template is saved in the survey category; use the list endpoint to retrieve it afterwards.
The request body is validated before authorization, so an invalid body returns 400 even when the token lacks the required scope or permission.
Unknown keys are rejected with400.
Request body
{
"title": "Quarterly Engagement Pulse",
"questionnaire": {
"sections": [
{
"body": "Your experience",
"questions": [
{
"body": "What part of your role gives you the most energy right now?",
"type": "paragraph"
},
{
"body": "Do you feel supported by your manager?",
"type": "single_choice",
"metadata": {
"mandatory": true,
"answer_options": {
"choices": {
"yes": "Yes",
"no": "No"
}
},
"has_conditional_navigation": true,
"navigation_config": {
"rules": {
"yes": { "type": "submit" },
"no": { "type": "go_to_question", "id": "7d1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f" }
}
}
}
},
{
"id": "7d1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"body": "What could your manager do differently?",
"type": "paragraph"
}
]
}
]
}
}| key | type | description |
|---|---|---|
| title | string | Title of the template (required, trimmed) |
| questionnaire.sections | array | Ordered list of sections (required) |
| questionnaire.sections[].id | string | Optional client-assigned id (UUID) |
| questionnaire.sections[].body | string | Section title / header text (required, may contain HTML) |
| questionnaire.sections[].position | integer | Order of the section (≥ 0). Defaults to the section's index when omitted |
| questionnaire.sections[].questions | array | Ordered list of questions (required, may be empty) |
| questionnaire.sections[].questions[].id | string | Optional client-assigned id (UUID). Required if another question's navigation rule needs to target it |
| questionnaire.sections[].questions[].body | string | Question text (required, may contain HTML) |
| questionnaire.sections[].questions[].position | integer | Order of the question within the section (≥ 0). Defaults to the question's index when omitted |
| questionnaire.sections[].questions[].type | string | Question type. Accepts snake_case (e.g. single_choice) or PascalCase (e.g. SingleChoice). One of: paragraph, paragraph_star_rated, paragraph_numeric_rated, paragraph_custom_rated, single_choice, multiple_choice, star_rated, numeric_rated, custom_rated |
| questionnaire.sections[].questions[].metadata | object | null | Optional question metadata |
| questionnaire.sections[].questions[].metadata.mandatory | boolean | Whether an answer is required |
| questionnaire.sections[].questions[].metadata.semantic_type | string | Semantic role of the question. Accepts snake_case or PascalCase. One of: section_rating, overall_rating |
| questionnaire.sections[].questions[].metadata.rating_options | object | Map of rating value to label (string values) |
| questionnaire.sections[].questions[].metadata.answer_options.choices | object | Map of choice key to label (string values) |
| questionnaire.sections[].questions[].metadata.has_conditional_navigation | boolean | Enables conditional navigation (skip logic). When true, navigation_config is required |
| questionnaire.sections[].questions[].metadata.navigation_config.rules | object | Map of choice key to navigation rule (required when navigation_config is present) |
| questionnaire.sections[].questions[].metadata.navigation_config.rules.*.type | string | Rule type. Accepts snake_case or camelCase. One of: next_question, go_to_question, go_to_section, submit |
| questionnaire.sections[].questions[].metadata.navigation_config.rules.*.id | string | Target question / section id (UUID). Required for go_to_question and go_to_section; not allowed for other types |
Response
The created template is returned with status 201, in snake_case, with the ids and version assigned by the template registry.
{
"id": "b2f0c1de-5a7e-4c1b-9d2a-0f6c3e8a91d4",
"title": "Quarterly Engagement Pulse",
"updated_at": "2025-01-10T09:30:00Z",
"version": 1,
"questionnaire": {
"sections": [
{
"id": "2501ee00-0002-51cc-8e1c-6b29548a3d01",
"body": "Your experience",
"questions": [
{
"id": "2501ee00-0003-51cc-8e1c-6b29548a3d01",
"body": "What part of your role gives you the most energy right now?",
"type": "paragraph"
},
{
"id": "2501ee00-0004-51cc-8e1c-6b29548a3d01",
"body": "Do you feel supported by your manager?",
"type": "single_choice",
"metadata": {
"mandatory": true,
"answer_options": {
"choices": {
"yes": "Yes",
"no": "No"
}
},
"has_conditional_navigation": true,
"navigation_config": {
"rules": {
"yes": {
"type": "submit"
},
"no": {
"type": "go_to_question",
"id": "7d1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f"
}
}
}
}
},
{
"id": "7d1e2f30-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"body": "What could your manager do differently?",
"type": "paragraph"
}
]
}
]
}
}See GET /surveys/templates/:id for the full field reference. default, sample and disabled are included only when returned by the template registry.
Error responses
| Status | Condition |
|---|---|
| 400 | Validation failed (missing title or questionnaire, missing section / question body, invalid type, semantic_type or navigation rule type, non-UUID ids, has_conditional_navigation: true without navigation_config, unknown fields) |
| 401 | Missing or invalid token |
| 403 | Missing w_surveys scope, account token without member_id, the Surveys feature is not enabled for the account, or the user lacks permission to manage survey templates |
| 409 | {"message": "Template title already exists", "errorCode": "template_title_already_exists"}: a template with the same title already exists |
Error bodies: 401 / 403 responses are plain text holding the reason (e.g. Required scopes missing: ["w_surveys"]). 400 validation errors are JSON keyed by the offending field, e.g. {"title": {"code": "any.required", "message": "\"title\" is required"}}.
