Questions API
Endpoints that manage the questions inside a section. Choices, matrix rows and columns, free-text fields, sub-questions, branch rules, and visibility rules are all configured through the question update.
See API Definition for authentication, response format, and error handling.
Method
HTTP Methods
| Method | URI | Overview |
|---|---|---|
| POST | /api/sections/{sectionId}/questions |
Add a question |
| PATCH | /api/questions/{questionId} |
Update a question |
| DELETE | /api/questions/{questionId} |
Delete a question |
| POST | /api/questions/{questionId}/duplicate |
Duplicate a question |
| POST | /api/questions/import |
Import a question |
Resource Definition
Question Types
| Type | Value | Description |
|---|---|---|
| SA | single |
Single answer |
| MA | multi |
Multiple answers |
| FA | free_text |
Free answer (text input) |
| TM | matrix |
Text matrix (rows ร columns) |
| PD | pulldown |
Dropdown selection |
| Intro step | intro |
Explanatory text with no answer field |
Question Schema
| Field | Type | Notes |
|---|---|---|
| id | string | UUID of the snapshot row. Changes when the version changes |
| code | string | Question code. The reference key for branch conditions and destinations. Unique within a survey |
| questionType | enum | Question type |
| promptText | string | Prompt text |
| noteText | string | Note |
| isRequired | boolean | Whether an answer is mandatory |
| isLinkedToPrevious | boolean | Whether to show on the same page as the previous question |
| sortOrder | number | Position within the section |
| options | QuestionOption[] | Choices (input fields for FA) |
| matrixRows | QuestionOption[] | Matrix rows |
| matrixColumns | QuestionOption[] | Matrix columns |
| isSingleMatrixSelect | boolean | null | Whether each matrix row allows only one selection |
| minSelections | number | null | Minimum selections for MA. null = no limit |
| maxSelections | number | null | Maximum selections for MA. null = no limit |
| freeTextFields | FreeTextField[] | FA input fields (label, placeholder, per-field required) |
| subQuestions | SubQuestion[] | Sub-questions (label + choices) |
| branchRules | QuestionBranchRule[] | Branch rules |
| visibilityRules | QuestionVisibilityRule[] | Visibility rules |
QuestionOption
| Field | Type | Notes |
|---|---|---|
| id | string | UUID |
| label | string | Choice label. Referenced as the comparison value of branch conditions |
| isExclusive | boolean | Exclusive choice |
| allowOtherInput | boolean | Shows an "other" free input field |
| isNotApplicable | boolean | The "none of the above" special choice |
| axis | enum | null | row / column (matrix only) |
| placeholder | string | null | Placeholder of the input field (FA only) |
| fieldIsRequired | boolean | null | Per-field required flag (FA only) |
QuestionBranchRule
| Field | Type | Notes |
|---|---|---|
| logicalOperator | enum | AND / OR |
| conditions | BranchCondition[] | Array of conditions |
| destinationQuestionCode | string | Destination question code. SO / SC / SX are also accepted |
| message | string | null | Message shown when the condition holds. Acts as an exclusive setting when the destination is the question itself |
BranchCondition
| Field | Type | Notes |
|---|---|---|
| sourceQuestionCode | string | Source question code |
| operator | enum | equals / includes / not_includes / only / not_only / has_other / answered |
| value | string | Comparison value: choice label, matrix row label, or FA matching text |
| subValue | string | null | Matrix column label or FA target field label. null = any field |
| subQuestionIndex | number | null | Sub-question position. null = treat all sub-questions flatly |
QuestionVisibilityRule
| Field | Type | Notes |
|---|---|---|
| logicalOperator | enum | AND / OR |
| conditions | BranchCondition[] | Same structure as branch conditions |
| targets | VisibilityTarget[] | Targets to hide (label / axis / subQuestionIndex) |
Authentication
User authentication via the x-user-email / x-user-name headers is required. Every operation needs edit permission on the target survey, and imports also need view permission on the source survey. Missing permission returns 404 Not Found.
Add a Question
Overview
Appends a question to the end of a section.
URI
| Parameter | Type | Required | Notes |
|---|---|---|---|
| sectionId | string | โฏ | Section UUID |
Request Body
{
"code": "Q1",
"questionType": "multi",
"promptText": "ไปฅไธใฎใใกใ็ฅใฃใฆใใใใฉใณใใใในใฆใ้ธใณใใ ใใใ",
"isRequired": true,
"isLinkedToPrevious": false,
"options": [
{ "label": "ใใฉใณใ A", "isExclusive": false, "allowOtherInput": false, "isNotApplicable": false },
{ "label": "ใใใใ็ฅใใชใ", "isExclusive": true, "allowOtherInput": false, "isNotApplicable": false }
],
"minSelections": 1,
"maxSelections": 3
}
Validation Rules
| Field | Rule |
|---|---|
| code | Required. String (question code) |
| questionType | Required. single / multi / free_text / matrix / intro / pulldown |
| promptText | Required. String |
| isRequired | Optional. Defaults to false |
| isLinkedToPrevious | Optional. Defaults to false |
| options | Optional. label / isExclusive / allowOtherInput / isNotApplicable |
| matrixRows / matrixColumns | Optional. Matrix rows and columns |
| isSingleMatrixSelect | Optional. Boolean |
| minSelections / maxSelections | Optional. Integer of 1 or greater, or null |
| freeTextFields | Optional. label / placeholder / isRequired |
| subQuestions | Optional. label / options |
Response (201 Created)
Returns the added question schema.
Error Handling
| Description | Status code | Status name |
|---|---|---|
| Malformed request body | 400 | Bad Request |
Section does not exist, or edit permission is missing |
404 | Not Found |
Processing Flow
- Resolve the owning survey from the section and check
editpermission - Determine the last sort order within the section
- Insert the question
- Insert the choices, matrix rows and columns, FA fields, and sub-question choices into
survey_version_question_options
Update a Question
Overview
Updates a question. Besides attributes such as the prompt text, choices, branch rules, and visibility rules are also configured through this API.
Array fields (options / matrixRows / matrixColumns / freeTextFields / subQuestions / branchRules / visibilityRules) are replaced entirely by what is sent. Fields that are not sent are left unchanged.
URI
| Parameter | Type | Required | Notes |
|---|---|---|---|
| questionId | string | โฏ | Question UUID |
Request Body
{
"promptText": "ใใชใใฎๆงๅฅใใ็ญใใใ ใใใ",
"isRequired": true,
"options": [
{ "label": "็ทๆง", "isExclusive": false, "allowOtherInput": false, "isNotApplicable": false },
{ "label": "ๅฅณๆง", "isExclusive": false, "allowOtherInput": false, "isNotApplicable": false }
],
"branchRules": [
{
"logicalOperator": "AND",
"conditions": [
{ "sourceQuestionCode": "Q1", "operator": "equals", "value": "็ทๆง" }
],
"destinationQuestionCode": "Q5"
}
],
"visibilityRules": []
}
Validation Rules
| Field | Rule |
|---|---|
| code | Optional. Question code |
| questionType | Optional. Question type |
| promptText | Optional. String |
| noteText | Optional. String |
| isRequired / isLinkedToPrevious | Optional. Boolean |
| sortOrder | Optional. Integer |
| options / matrixRows / matrixColumns | Optional. Replaced entirely |
| minSelections / maxSelections | Optional. Integer of 1 or greater, or null. null clears the limit; discarded when the type changes away from multi |
| freeTextFields / subQuestions | Optional. Replaced entirely |
| branchRules / visibilityRules | Optional. Replaced entirely. An intro question as a source is an error |
Response (200 OK)
Returns the updated question schema.
Error Handling
| Description | Status code | Status name |
|---|---|---|
| Malformed request body | 400 | Bad Request |
An intro step (intro) was given as a branch condition source |
400 | Bad Request |
Question does not exist, or edit permission is missing |
404 | Not Found |
Processing Flow
Sequence Diagram
sequenceDiagram
participant Client
participant API
participant DB
Client->>API: PATCH /api/questions/{questionId}
API->>API: Check edit permission
API->>API: Validate branch and visibility conditions
alt Source is an intro question
API-->>Client: 400 Bad Request
else
API->>DB: Update the question attributes
API->>DB: Replace choices, rows/columns, FA fields, sub-questions
API->>DB: Replace branch and visibility rules
API-->>Client: 200 OK
end
Choice IDs are not preserved
Array fields are deleted and recreated every time, so choice ids change on every update. That is why branch conditions reference choices by their label string.
Delete a Question
Overview
Deletes a question. Its choices, branch rules, and visibility rules are deleted along with it.
URI
Response (200 OK)
Error Handling
| Description | Status code | Status name |
|---|---|---|
Question does not exist, or edit permission is missing |
404 | Not Found |
Branches referencing it are not repaired here
Rules whose destination was the deleted question are detected as invalid destinations on the frontend and repaired there.
Question codes are renumbered afterwards
Right after a deletion the frontend calls Renumber the Question Codes. The survey's codes are put back into a clean sequence so no gaps remain, and branch and visibility references are re-pointed.
Duplicate a Question
Overview
Duplicates a question and appends it to the same section.
The question code is re-assigned so that it stays unique within the survey, and branch conditions and destinations that referenced the original question are re-pointed at the new code. References to other questions are left as they are.
URI
Response (201 Created)
Returns the duplicated question schema.
Error Handling
| Description | Status code | Status name |
|---|---|---|
Question does not exist, or edit permission is missing |
404 | Not Found |
Processing Flow
- Resolve the owning survey from the question and check
editpermission - Load the question with its choices, branch rules, and visibility rules
- Collect the existing question codes and pick a non-conflicting new code
- Re-point branch condition sources and destinations that referred to the original code
- Insert the question at the end of the section
Import a Question
Overview
Imports a question from the question library, or from another survey, to the end of the given section.
There are two ways to specify the source, and one of them is required.
| Method | Required fields |
|---|---|
| Via the question library | libraryItemId |
| Direct survey reference | The trio sourceSurveyId + sourceSectionId + sourceQuestionId |
URI
Request Body
Validation Rules
| Field | Rule |
|---|---|
| targetSectionId | Required. UUID of the destination section |
| libraryItemId | Question library UUID. Required when the trio is not used |
| sourceSurveyId | UUID of the source survey. Required when using the trio |
| sourceSectionId | UUID of the source section. Same |
| sourceQuestionId | UUID of the source question. Same |
Either libraryItemId, or all of sourceSurveyId + sourceSectionId + sourceQuestionId, is required.
Response (201 Created)
Returns the imported question schema.
Error Handling
| Description | Status code | Status name |
|---|---|---|
| Malformed request body, or an incomplete source specification | 400 | Bad Request |
| Source or destination does not exist, or permission is missing | 404 | Not Found |
Processing Flow
Sequence Diagram
sequenceDiagram
participant Client
participant API
participant DB
participant Library as Question library
Client->>API: POST /api/questions/import
API->>API: Check edit permission on the destination
alt libraryItemId given
API->>Library: Fetch the library entry
alt Internal origin
API->>DB: Build from the source survey's snapshot
else External origin
API->>API: Build from the normalized payload
end
else Trio given
API->>DB: Build from the source survey's snapshot
end
API->>API: Re-assign the question code
API->>DB: Insert at the end of the section
API-->>Client: 201 Created
Behavior per source:
- Question library (internal origin): if the source survey still exists,
viewpermission on it is required. Reuse is allowed even if the source survey was deleted, as long as the snapshot row remains - Question library (external origin): built from the normalized payload. Matrix sub items have nowhere to be stored, so they are written into the note text
- Direct survey reference:
viewpermission on the source survey is checked and the question is built from its snapshot
Branching is not carried over
Reuse through the question library does not carry over branch rules or visibility logic.