Building a survey
Create a survey from a short draft, publish it, change its questions and settings, and know which limits apply along the way.
Die Entwickler-Dokumentation gibt es nur auf Englisch, wie die API selbst.
This guide creates a survey through the API, publishes it, and changes it afterwards. It needs a key with the scope
surveys:write, and surveys:read to read the survey back. The shell variables SF_API and SF_KEY are set up as
in the Quickstart.
How a survey is built
A survey is a list of pages, called modules, plus settings:
- a start page with a headline and a button
- one or more questions pages
- an end page that thanks the respondent
Every text is an object keyed by language code, for example { "en": "How was the workshop?" }. The full shape has
around thirty fields per page: ids, order, presentation defaults. You rarely write it by hand. Describe the survey as
a short draft instead, and the server builds the rest.
Create a survey from a draft
curl -X POST "$SF_API/v1/surveys" \
-H "Authorization: Bearer $SF_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Workshop feedback",
"draft": {
"pollName": "Workshop feedback",
"language": "en",
"welcomeTitle": "Thanks for joining the workshop",
"questions": [
{ "kind": "rating", "text": "How was the workshop?" },
{ "kind": "choice", "text": "Which session did you attend?", "options": ["Morning", "Afternoon", "Evening"] },
{ "kind": "nps", "text": "How likely are you to recommend us?" },
{ "kind": "open_text", "text": "Anything we should improve?" }
]
}
}'The answer is 201 with the whole survey, including the generated modules and settings. Keep its id:
export SURVEY_ID="4c303674-1dd4-4836-ada7-0092fe2c7df3"name is the internal name shown in the dashboard; draft.pollName is the survey's own name. Without venueId the
survey goes to the organization's default venue, or to the key's venue for a venue key.
Draft fields
| Field | Required | Meaning |
|---|---|---|
pollName |
yes | Name of the survey |
language |
yes | Language code all texts are written in |
welcomeTitle |
yes | Headline of the start page |
welcomeDescription |
no | Text under the headline |
startButtonText |
no | Label of the start button |
questions |
yes | The questions, in order |
endTitle |
no | Headline of the end page |
endMessage |
no | Text on the end page |
Question kinds
Every question has a kind and a text, and optionally a subtitle.
kind |
Asks for | Extra fields |
|---|---|---|
rating |
A rating on a scale | ratingSymbol: star, heart, thumbs_up, smiley or number; ratingLevels: 3 to 10, default 5 |
choice |
One or more of a list of options | options: at least two labels; multipleChoice: true to allow several; exclusiveOptions: labels that rule out every other answer, like "None of these" |
nps |
Net Promoter Score, 0 to 10 | none |
open_text |
Free text | multiline: false for a single line |
section |
Nothing; a heading between questions | none |
In a full modules body the same thing is exclusive: true on the option itself. A respondent who picks an exclusive
option loses the other ticks, and picking another option clears the exclusive one.
An invalid draft answers 400 VALIDATION_FAILED with the message Invalid survey draft, and details.errors says
which field is wrong. Nothing is created.
Publish it
A new survey is a draft that respondents can't open yet. Publish it with a separate call:
curl -X PATCH "$SF_API/v1/surveys/$SURVEY_ID" \
-H "Authorization: Bearer $SF_KEY" \
-H "Content-Type: application/json" \
-d '{ "active": true }'Publishing needs two things:
- The person who created the API key has verified their email address. Otherwise the call answers
403 EMAIL_NOT_VERIFIED, withdetails.reasonset toEMAIL_NOT_VERIFIEDas well. - The plan has room for another active survey. Otherwise it answers
409 TIER_LIMIT_REACHED.
Once published, share its link or print QR codes.
Change a question
PATCH replaces every field you send as a whole, so change a question by editing the survey you read:
- Read the survey with
GET /v1/surveys/$SURVEY_ID. - Change what you need in its
modulesarray, for example a question's text. - Send the whole array back:
curl -X PATCH "$SF_API/v1/surveys/$SURVEY_ID" \
-H "Authorization: Bearer $SF_KEY" \
-H "Content-Type: application/json" \
-d @modules.jsonwhere modules.json is { "modules": [ ... ] } with all pages. Keep the id of every question you don't mean to
replace: responses refer to questions by id, so a question with a new id starts without answers.
Change the settings
settings holds the survey's name and languages (general), how it is shown (display) and how often a respondent may
answer (participation). Like modules, it is replaced as a whole and validated as a whole: read it, change a
field, and send all of it back.
{
"settings": {
"general": { "active": false, "pollName": "Workshop feedback", "languages": ["en"], "defaultLanguage": "en" },
"display": {
"pageMode": "one_page",
"theme": { "font": "Inter", "colorScheme": "system" },
"showProgressBar": true,
"showQuestionNumbers": true
},
"participation": { "repeatParticipation": "daily", "allowModuleBack": true, "allowQuestionBack": true }
}
}A settings object with parts missing answers 400 VALIDATION_FAILED.
| Setting | Values |
|---|---|
display.pageMode |
one_page, one_page_per_module, one_page_per_question |
display.pagePerSection |
true: each section starts a page with its questions; otherwise one per page |
participation.repeatParticipation |
always, once, daily, weekly, monthly: how often one device may answer |
participation.accessMode |
open (or missing): anyone with the link; code: only with an unused single-use code |
daily, weekly and monthly count 24 hours, 7 days and 30 days from the device's last response.
Add a header, footer and logo
branding sets the bar above and below every page. Each has a mode: default shows the Simple Feedback bar,
custom shows your text, links and logo, hidden shows nothing. surface tints the bar with default, tinted,
accent or bold, a colour role from the survey's theme. The full shape is in the
API reference and, for agents, in the MCP resource simplefeedback://schema/survey-branding.
For a logo, upload the image first. This needs the scope files:write:
curl -X POST "$SF_API/v1/images" \
-H "Authorization: Bearer $SF_KEY" \
-H "Content-Type: application/json" \
-d "{\"kind\": \"survey_logo\", \"fileName\": \"logo.png\", \"imageBase64\": \"$(base64 -w0 logo.png)\"}"{
"id": "3dea5e0b-2c14-42a5-85fc-386b84768671",
"kind": "survey_logo",
"width": 400,
"height": 400
}The image is PNG, JPEG or WebP and at most 2 MB. Then put the id into the header:
curl -X PATCH "$SF_API/v1/surveys/$SURVEY_ID" \
-H "Authorization: Bearer $SF_KEY" \
-H "Content-Type: application/json" \
-d '{
"branding": {
"header": {
"mode": "custom",
"logoAssetId": "3dea5e0b-2c14-42a5-85fc-386b84768671",
"text": { "en": "Visitor survey" },
"surface": "tinted"
},
"footer": {
"mode": "custom",
"text": { "en": "Thank you for your time." },
"links": [{ "id": "web", "label": { "en": "Our website" }, "url": "https://example.com", "icon": "web" }],
"showPoweredBy": true
}
}
}'The same id works as a start page's logoAssetId. Upload with "kind": "survey_background" for a page's
background.backgroundImageAssetId. Images count against your plan's storage, and an image that no survey refers to
within 24 hours is deleted.
Delete it
curl -X DELETE "$SF_API/v1/surveys/$SURVEY_ID" -H "Authorization: Bearer $SF_KEY"The answer is 204. The survey and its link stop working at once, and it can't be restored.
Limits
Your plan limits how many surveys can be active and how many pages and languages a survey may have. A create or
change that goes over a limit answers 409 TIER_LIMIT_REACHED and changes nothing. Custom branding needs a plan
that includes it, otherwise 403 FEATURE_NOT_IN_TIER. An image upload over the plan's storage answers
403 TIER_LIMIT_REACHED. All codes are in Errors.