Feedback API
Projects can collect structured feedback from users through configurable feedback definitions. A definition is made up of one or more entries — a card or screen shown to the user — and each entry asks one or more questions drawn from a small set of input types (thumbs, star rating, linear scale, multiple choice, single choice, free text).
A definition is configured to appear either after a session ends, or before the very first
interaction with the project (for example a one-time welcome survey). Both cases are fetched the
same way — GET /api/feedback/specification below, the only way a client retrieves a
FeedbackSpecification — and posted back through the same submission endpoint.
Every feedback operation requires a resolvable caller identity — either an authenticated caller, or
(fetching a specification only, not submitting one) an anonymous caller identified by deviceRef.
A definition that opts into once-per-user deduplication is excluded once that caller has already
answered it. Once a POST to the submission endpoint below has been recorded for a given
before-session definition, that same definition is no longer returned to that caller — a project
with several before-session definitions moves on to the next unanswered one.
Feedback Specification Object
| Field | Type | Description |
|---|---|---|
|
UUID string |
Identifies the feedback definition this specification was generated from. Pass it as the path parameter of the submission endpoint below. |
|
Array |
The entries to render, in order (see Entry Object). |
Entry Object
| Field | Type | Description |
|---|---|---|
|
UUID string |
Identifies this entry. Not needed to submit answers — those reference |
|
String |
Display/internal name of the entry. |
|
Array |
The question(s) to render for this entry, in order (see Question Object). |
Question Object
Every question is polymorphic on its own type and always carries id (use it as questionId
when submitting an answer) and text (the prompt to display). Answering a question is always
optional — there is no "required" flag; let the user skip any or all of them.
type |
Fields | Suggested UI |
|---|---|---|
|
— |
Thumbs up / down toggle |
|
|
Star picker, 1 to |
|
|
1 to |
|
|
Multi-select over |
|
|
Single-select over |
|
— |
Free-text input |
Complete Specification Example
The example below is deliberately exhaustive: two entries covering all six question types.
{
"definitionId": "8f14e45f-ceea-467e-adde-3fb5023abc1b",
"entries": [
{
"entryId": "1a79a4d6-0f60-4f9f-9e6e-3b1d4c2e7a11",
"name": "Quick reaction",
"questions": [
{
"type": "thumbs",
"id": "3d5e2c1a-9f4b-4a2e-8c3d-1e6f7a8b9c0d",
"text": "Did the agent help you achieve your goal?"
},
{
"type": "star-rating",
"id": "7b2f9e4d-3c1a-4e6f-9d8b-2a5c6e7f8091",
"text": "How would you rate the quality of this conversation?",
"maxStars": 5
}
]
},
{
"entryId": "2b8ab5e7-1071-450a-8f7f-4c2e5d3f8a22",
"name": "Tell us more",
"questions": [
{
"type": "linear-scale",
"id": "9c3a1f5e-6d2b-48c7-b1e9-3f4a5b6c7d82",
"text": "How likely are you to recommend this assistant to a colleague?",
"points": 10,
"minLabel": "Not at all likely",
"maxLabel": "Extremely likely"
},
{
"type": "multiple-choice",
"id": "4e6d2c8a-7b1f-4a3e-9c5d-6e7f8a9b0c13",
"text": "Which topics did you discuss today? (select all that apply)",
"options": ["Billing", "Technical support", "Product features", "Account setup"]
},
{
"type": "single-choice",
"id": "5f7e3d9b-8c2a-4b4f-ad6e-7f8a9b0c1d24",
"text": "How did you first hear about this assistant?",
"options": ["Search engine", "Social media", "Friend or colleague", "Advertisement"]
},
{
"type": "text",
"id": "6a8f4eac-9d3b-4c5a-be7f-8a9b0c1d2e35",
"text": "What is one thing we could improve?"
}
]
}
]
}
The object above appears exactly as shown, with no envelope, as an array element in the response below.
GET /api/feedback/specification
Fetch feedback specification(s) eligible right now. Pass exactly one of sessionId (after-session)
or projectId (before-session); the two cases behave differently, described below.
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
|
One of |
UUID |
Fetch the after-session specification(s) eligible for this session. Requires an authenticated
caller or an anonymous caller identified by |
|
One of |
UUID |
Fetch the before-session specification(s) currently eligible for this project. Requires an
authenticated caller or an anonymous caller identified by |
|
No |
String |
Identifies an anonymous caller when no |
If neither sessionId nor projectId is given, the response is an empty array. Takes no request
body.
Response
200 OK with an array of Feedback
Specification objects.
| Query parameter used | Array contents |
|---|---|
|
Every eligible |
|
Every |
Responds 404 when the caller could not be identified — neither an authenticated caller nor
deviceRef was provided — or, for sessionId, when the session does not exist.
Complete Response Examples
?sessionId=… for a session whose project has two eligible AfterSession definitions — the
empty-array case (no eligible definition, or none of a definition’s entries apply to this
session’s length) is also valid and just as common:
[
{
"definitionId": "8f14e45f-ceea-467e-adde-3fb5023abc1b",
"entries": [
{
"entryId": "1a79a4d6-0f60-4f9f-9e6e-3b1d4c2e7a11",
"name": "Quick reaction",
"questions": [
{
"type": "thumbs",
"id": "3d5e2c1a-9f4b-4a2e-8c3d-1e6f7a8b9c0d",
"text": "Did the agent help you achieve your goal?"
}
]
}
]
},
{
"definitionId": "1e2d3c4b-5a6f-4788-9e0d-1c2b3a4f5e6d",
"entries": [
{
"entryId": "2f3e4d5c-6b7a-4899-af1e-2d3c4b5a6f7e",
"name": "Product feedback",
"questions": [
{
"type": "single-choice",
"id": "3a4b5c6d-7e8f-4900-ba1f-2e3d4c5b6a70",
"text": "How did you first hear about this assistant?",
"options": ["Search engine", "Social media", "Friend or colleague", "Advertisement"]
}
]
}
]
}
]
?projectId=… for a caller — authenticated, or anonymous via deviceRef — who hasn’t yet
answered either of the project’s two BeforeSession definitions:
[
{
"definitionId": "2c3b4a5f-6e7d-4809-9f0e-1d2c3b4a5f6e",
"entries": [
{
"entryId": "3b4a5f6e-7d8c-490a-8e1f-2c3b4a5f6e7d",
"name": "Welcome survey",
"questions": [
{
"type": "star-rating",
"id": "4a5f6e7d-8c9b-4a1b-9f2e-3b4a5f6e7d8c",
"text": "How familiar are you with virtual assistants?",
"maxStars": 5
}
]
}
]
},
{
"definitionId": "5f6e7d8c-9b0a-491b-8f2e-3a4b5c6d7e8f",
"entries": [
{
"entryId": "6e7d8c9b-0a1b-4a2c-9f3e-4a5b6c7d8e9f",
"name": "Expectations",
"questions": [
{
"type": "single-choice",
"id": "7d8c9b0a-1b2c-4a3d-af4e-5b6c7d8e9f0a",
"text": "What are you hoping to use this assistant for?",
"options": ["Support", "Sales", "General questions"]
}
]
}
]
}
]
Once the caller answers the first one, only the second remains on the next call — a caller who has
answered every eligible definition gets []; a request with neither an authenticated caller nor
deviceRef gets 404 instead (see above).
POST /api/feedback/submission/{definitionId}
Submit the user’s answers to a feedback specification. Requires the consumer role — there is no
anonymous feedback submission, so this always attributes the feedback to a real user.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
|
UUID |
The |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
|
Yes |
UUID |
The project this feedback relates to. |
|
Only for an |
UUID |
The session this feedback relates to. Required when |
|
No |
String |
JWT bearer token, when not passed as a header. See Authentication. |
Request Body
FeedbackSubmission:
| Field | Type | Description |
|---|---|---|
|
Array |
One entry per answered question (see Answer Object). Omit unanswered questions entirely rather than sending an empty or null value for them. |
Answer Object
| Field | Type | Description |
|---|---|---|
|
UUID string |
The |
|
Object |
The answer, using the same polymorphic value shape as
evaluation insight values ( |
Answer Value Shapes
The server discards an individual answer whose value type does not match the question, or whose
content is invalid, rather than rejecting the whole submission.
Question type |
value.type |
Constraint |
|---|---|---|
|
|
— |
|
|
Integer in |
|
|
Integer in |
|
|
Subset of the question’s |
|
|
Must be one of the question’s |
|
|
Free text; blank values are dropped. |
Response
200 OK with the persisted feedback record:
| Field | Type | Description |
|---|---|---|
|
UUID string |
Unique identifier of this feedback submission. |
|
UUID string |
The feedback definition this submission answers. |
|
UUID string | null |
Session passed in the request; |
|
UUID string |
Project passed in the request. |
|
UUID string |
The authenticated user who submitted the feedback. |
|
Timestamp |
When the feedback was recorded. |
|
Array |
Only the answers that passed the constraints in Answer Value Shapes; answers with an invalid or mismatched value are silently omitted. |
Responds 404 when definitionId does not resolve to a feedback definition, or 406 when
sessionId is missing and the definition is AfterSession.
Complete Submission Example
POST /api/feedback/submission/8f14e45f-ceea-467e-adde-3fb5023abc1b?projectId=8e29b3d1-6a4f-4c2e-9d70-1f2a3b4c5d6e&sessionId=0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f
for an AfterSession definition — a BeforeSession definition is submitted the same way, just
without sessionId, and its response’s sessionId field comes back null. Request body answering
everything in the specification example above except the single-choice question — skipping a
question just means omitting it:
{
"answers": [
{ "questionId": "3d5e2c1a-9f4b-4a2e-8c3d-1e6f7a8b9c0d", "value": { "type": "boolean", "value": true } },
{ "questionId": "7b2f9e4d-3c1a-4e6f-9d8b-2a5c6e7f8091", "value": { "type": "number", "value": 4 } },
{ "questionId": "9c3a1f5e-6d2b-48c7-b1e9-3f4a5b6c7d82", "value": { "type": "number", "value": 9 } },
{ "questionId": "4e6d2c8a-7b1f-4a3e-9c5d-6e7f8a9b0c13", "value": { "type": "list", "value": ["Billing", "Product features"] } },
{ "questionId": "6a8f4eac-9d3b-4c5a-be7f-8a9b0c1d2e35", "value": { "type": "text", "value": "Faster response times would be great." } }
]
}
The corresponding response — note that star-rating and linear-scale answers come back with
their clamping range attached:
{
"id": "d2c1b0a9-8f7e-4d3c-9b1a-0e6f5d4c3b2a",
"definitionId": "8f14e45f-ceea-467e-adde-3fb5023abc1b",
"sessionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"projectId": "8e29b3d1-6a4f-4c2e-9d70-1f2a3b4c5d6e",
"userId": "5c8a0f12-7d34-4e56-9a1b-2c3d4e5f6a7b",
"submitted": "2026-09-15T10:15:30.000Z",
"answers": [
{ "questionId": "3d5e2c1a-9f4b-4a2e-8c3d-1e6f7a8b9c0d", "value": { "type": "boolean", "value": true } },
{ "questionId": "7b2f9e4d-3c1a-4e6f-9d8b-2a5c6e7f8091", "value": { "type": "number", "value": 4, "minValue": 1, "maxValue": 5 } },
{ "questionId": "9c3a1f5e-6d2b-48c7-b1e9-3f4a5b6c7d82", "value": { "type": "number", "value": 9, "minValue": 1, "maxValue": 10 } },
{ "questionId": "4e6d2c8a-7b1f-4a3e-9c5d-6e7f8a9b0c13", "value": { "type": "list", "value": ["Billing", "Product features"] } },
{ "questionId": "6a8f4eac-9d3b-4c5a-be7f-8a9b0c1d2e35", "value": { "type": "text", "value": "Faster response times would be great." } }
]
}
Submitting no answers at all ({"answers": []}) is valid — it records an empty feedback event.