Survey Presentation Format¶
The Survey Presentation Format is the JSON contract for conducting a live survey session step by step. Each question arrives as a self-contained document carrying everything a client needs to render it — caption, input control specification, and the valid options — while the QML engine keeps flow logic, branching, and answer validation on the server.
A Questionnaire is the verified template; a Survey is one respondent's session through it. The Presentation Format always describes a Survey: the next question for this respondent, computed from their previous answers.
Framework-Agnostic by Design¶
The format deliberately contains no HTML, CSS, or component references:
- Your UI components: Render each control with your own framework — React, Vue, Angular, native mobile, or a terminal
- Your styling: Questions naturally adopt your application's look and feel
- Server-side logic: Branching, skip logic, and postcondition validation stay in the verified QML engine — a client cannot render its way into an invalid path
- One round-trip per question: Submitting an answer returns the next step folded into the response
Askalot's own survey runner (SirWay) is a client of this same item model — the format is not a side export but the way surveys are actually conducted.
The Flow Loop¶
Driving a survey is a three-call state machine on the REST API:
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/v1/surveys/{id}/current-step |
Current question + options + status. Initializes the session on first call. |
POST |
/api/v1/surveys/{id}/responses |
Submit an answer; the next step is folded into the response. |
POST |
/api/v1/surveys/{id}/finish |
Explicitly complete the survey (idempotent). |
The same three operations are available as MCP tools (get_survey_current_item, submit_survey_response, finish_survey) — see the MCP Survey Tools reference.
Authentication is the standard REST credential — the X-Api-Token header (token setup).
GET current-step ──► render item ──► POST responses ──► render `next`
│
└── status == "completed" ──► done
Informational Comment items are processed automatically on the server — clients only ever receive answerable questions.
The Step Document¶
GET /api/v1/surveys/{id}/current-step returns:
{
"survey_id": "c154e81f-4c19-4998-9ffc-df6fee50561f",
"status": "in_progress",
"control_type": "switch",
"required": true,
"message": null,
"item": {
"id": "q_question_switch",
"kind": "Question",
"text": null,
"survey_id": "c154e81f-4c19-4998-9ffc-df6fee50561f",
"extras": {
"title": "Do you agree to participate in this survey?",
"blockId": "block2",
"input": { "control": "Switch", "true": "Yes", "false": "No" },
"outcome": null,
"visited": false
}
},
"options": [
{ "value": true, "text": "Yes" },
{ "value": false, "text": "No" }
]
}
| Field | Meaning |
|---|---|
status |
"in_progress" or "completed". When completed, item is null and message explains why. |
item.id |
The item's QML identifier — echo it back when submitting the answer. |
item.kind |
"Question", "QuestionGroup", or "MatrixQuestion" — determines the shape of the outcome you submit. |
item.extras.title |
The question caption to display. |
item.text |
Optional longer descriptive text below the caption. |
item.extras.input |
The raw QML input control specification (control name plus its configuration — labels, ranges, etc.). |
control_type |
Lowercased control discriminator for the renderer: radio, dropdown, checkbox, switch, slider, range, editbox, textarea. |
options |
Render-ready options, pre-extracted from the control (see below). |
required |
Whether an answer is required to advance. |
Answers Are Numeric Codes¶
Askalot represents answers as numbers everywhere except open text (the textarea control). A choice control's labels map integer answer codes to display strings — e.g. {1: "Hourly", 2: "Daily", 3: "Weekly"} — and the step document delivers them as options pairs ({"value": <code>, "text": <label>}). Your UI shows the text; what you submit is the code. Numeric inputs (editbox, slider, range) submit the number itself, and switch is the code 1/0 (delivered as true/false in the options fold — booleans are accepted as their numeric equivalents).
JSON serialization can turn codes into strings on the wire (the example below shows "value": "0" for label key 0); the engine coerces numeric strings back to numbers on submission, but prefer sending real numbers.
Options by Control Type¶
The options array is pre-folded into a renderable shape per control:
control_type |
options shape |
|---|---|
radio, dropdown, checkbox |
[{ "value": <integer answer code>, "text": <label> }, ...] |
slider |
[{ "type": "range", "min": 0, "max": 100, "step": 1 }] |
switch |
[{ "value": true, "text": "Yes" }, { "value": false, "text": "No" }] |
editbox, range, textarea |
[] — read the bounds (min/max, numeric controls) or text config from item.extras.input |
Submitting Answers¶
POST /api/v1/surveys/{id}/responses with the item id and the outcome:
The outcome shape follows the item kind. Values are the numeric answer codes described above — free text only for the textarea control:
| Item kind / control | Outcome shape | Example |
|---|---|---|
Question (radio, dropdown, slider, range, editbox) |
A single number — the answer code (choice controls) or the value itself (numeric controls) | 2, 42 |
Question with switch control |
1/0 (true/false accepted) |
true |
Question with checkbox control |
Bitmask integer — sum of the selected answer codes (codes are powers of 2) | 5 (codes 1 + 4) |
Question with textarea control |
Free text string | "We mostly use it on weekends" |
QuestionGroup |
Array of numbers, one per entry in questions — every sub-question shares the group's single control, so all values come from the same code space |
[1, 3, 2] |
MatrixQuestion |
Nested array of numbers, rows × columns — one shared control for every cell |
[[1, 2], [3, 1]] |
The response folds in the next step, so one round-trip per question is enough:
{
"survey_id": "c154e81f-4c19-4998-9ffc-df6fee50561f",
"status": "in_progress",
"output": null,
"next": {
"control_type": "radio",
"item": {
"id": "q_question_radio",
"kind": "Question",
"extras": {
"title": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris?",
"input": { "control": "Radio", "labels": { "0": "Option 0", "1": "Option 1" } }
}
},
"options": [
{ "value": "0", "text": "Option 0" },
{ "value": "1", "text": "Option 1" }
],
"required": true,
"status": "in_progress"
}
}
When the submission completes the survey, status is "completed" and next is null.
Server-Side Validation¶
Postconditions written in the QML (e.g. children must be fewer than household members) are evaluated on submission. A rejected answer returns an error envelope whose detail carries the question's hint message, and the flow does not advance — re-render the current item with the hint. The client never needs to replicate validation rules; they live with the questionnaire's formal definition.
Initialize before submitting
The session state is created lazily by the first GET /current-step call. Submitting a response to an uninitialized survey returns an error asking you to read the current step first.
Typical Embedding Pattern¶
- Create Surveys for your respondents via the REST API (
POST /api/v1/surveys) or via a Campaign - Your backend proxies the three flow calls with its API token (keep the token server-side — never ship it to a browser)
- Your frontend renders each step document with its own components and posts outcomes back
- On
status: "completed", close the session; responses are immediately available to the data pipeline
If you don't need custom rendering, Askalot's hosted survey runner (SirWay) already serves every survey at a magic link — the Presentation Format is for when the survey must live inside your product experience.
Next Steps¶
-
REST API
Authentication, entity CRUD, and the full endpoint reference
-
MCP Survey Tools
The same flow loop for AI agents
-
QML Syntax
The verified questionnaire language behind every survey