POST /survey
Submit panelist votes for survey questions.
Request
POST /public/survey
Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Compound token from Tenant API |
| Content-Type | Yes | application/json |
Request Body
| Field | Type | Description |
|---|---|---|
| responses | object | Map of questionId to response |
| isFinal | boolean | Whether this is the final submission for the survey (answer to the final question) |
Note: If the survey allows partial responses, you can submit responses to questions as a panelist is completing them. See Partial Submissions.
{
"responses": {
"question-id-1": { "optionIds": ["3"] },
"question-id-2": { "optionIds": ["yes"] },
"question-id-3": { "optionIds": ["lint", "ci"] },
"question-id-4": { "optionIds": [], "explanation": "Great product, love it!" }
},
"isFinal": true
}
Responses Object
The responses object’s shape depends on the question’s questionType and responseType (returned by GET /public/survey).
Single-choice questions
(questionType: 'single', the default)
Submit the selected option ID as a single element of the optionIds array:
{ "optionIds": ["3"] }
Multiple-choice questions
(questionType: 'multi-choice')
Submit the IDs of all the selected options in the optionIds array (the order is not important):
{ "optionIds": ["lint", "ci"] }
Each entry in optionIds must match an optionId from the survey’s answerOptions. Submitting an empty array ([]) records “no option selected”; the question still counts toward totalResponses when isFinal: true.
Codependent questions (Kano)
(questionType: 'kano', responseType: 'codependent')
The two halves of a codependent pair are submitted as a single response with both optionIds in the array, in the order the survey defines them (functional first, dysfunctional second):
{ "optionIds": ["1", "10"] }
Open-ended questions
(questionType: 'open-ended')
Submit the free-text answer in explanation and leave optionIds empty:
{ "optionIds": [], "explanation": "Great product, love it!" }
The explanation field is the panelist’s free-text answer. Open-ended responses increment totalResponses but are not aggregated into per-option counters.
Display-only questions
(questionType: 'text', responseType: 'none')
Do not include display-only questions in responses. They have no answer to record.
Response
{
"count": 2
}
Response Fields
| Field | Type | Description |
|---|---|---|
| count | integer | Number of responses recorded |
Examples
JavaScript
const submitVotes = async (publicKey, responses, isFinal = true) => {
const response = await fetch('https://api.votito.com/public/survey', {
method: 'POST',
headers: {
'Authorization': publicKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ responses, isFinal })
});
if (!response.ok) {
throw new Error(`Failed to submit: ${response.status}`);
}
return response.json();
};
// Usage - mix of question types in a single batch
const result = await submitVotes(publicKey, {
'q1': { optionId: '4' }, // single-choice
'q2': { optionIds: ['lint', 'ci'] }, // multi-choice
'q3': { optionIds: [], explanation: 'Faster onboarding.' } // open-ended
});
console.log(`Submitted ${result.count} responses`);
React Form Handler
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const responses = {};
survey.questions.forEach((q) => {
const value = formData.get(q.questionId);
if (value) {
responses[q.questionId] = { optionId: value };
}
});
try {
await submitVotes(publicKey, responses);
setSubmitted(true);
} catch (error) {
setError('Failed to submit survey');
}
};
Python
import requests
def submit_votes(public_key, responses, is_final=True):
response = requests.post(
"https://api.votito.com/public/survey",
headers={
"Authorization": public_key,
"Content-Type": "application/json"
},
json={
"responses": responses,
"isFinal": is_final
}
)
response.raise_for_status()
return response.json()
# Usage - mix of question types in a single batch
result = submit_votes(public_key, {
"q1": {"optionId": "4"}, # single-choice
"q2": {"optionIds": ["lint", "ci"]}, # multi-choice
"q3": {"optionIds": [], "explanation": "Faster onboarding."} # open-ended
})
print(f"Submitted {result['count']} responses")
Partial submissions
If the survey supports partial responses, you can submit responses multiple times, incrementally, with isFinal equal to false.
Partial responses are useful for longer surveys where you may expect that some people do not complete all the questions, but you want to capture the answers to the first few questions anyway.
You can submit the answers to the same question multiple times with partial submission, the last response overwrites the previous ones.
YOU MUST submit the final batch of answers with isFinal equal to true. Having all questions submitted is not a clear signal for the completion of a survey session, as the panelists can technically go back and modify answers to previous questions when partial submissions are enabled.
// Save progress without finalizing
await submitVotes(publicKey, { 'q1': { optionId: '3' } }, false);
// Later, finalize the submission
await submitVotes(publicKey, { 'q2': { optionId: 'yes' } }, true);
Error Responses
400 Bad Request
{
"error": "invalid_response",
"details": "Missing required question: q1"
}
401 Unauthorized
Invalid or missing authorization token.
403 Forbidden
- Survey is closed
- Panelist has already submitted a final response (when multiple responses disabled)