Integration
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 answered through the same submission endpoint. A form the user
dismisses or puts off needs no body at all: it has endpoints of its own for skipping and postponing.
Every feedback operation requires a resolvable caller identity — either an authenticated caller, or
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 or the skip endpoint below has answered or skipped 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. A POST to the postpone endpoint, or a
submission without answers, postpones the definition instead, so it is returned again.
A project can have several definitions, but a single call returns at most one specification: the one that is due for the caller and has the highest priority — see Which Specification Is Returned. The user reacts to a delivered form through the submission endpoint, to answer it, or the skip and postpone endpoints — see Reacting to a Delivered Form.
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. |
|
String |
The description of the feedback definition, to show with the form. |
|
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). |
An entry may be configured as a carousel: it then draws on a pool of questions but always carries exactly one. The pool is rotated per user — the question after the one the user last answered, wrapping around to the first — so a question is shown until it is answered, and the rotation starts from the first question again whenever a new revision of the definition is published.
Question Object
Every question is polymorphic on its own type and always carries id (use it as questionId
when submitting an answer), text (the prompt to display) and description (Markdown
with further explanation to show with the prompt, which the client renders, or null when the
question has none). 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",
"description": "Tell us how the conversation went.",
"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?",
"description": null
},
{
"type": "star-rating",
"id": "7b2f9e4d-3c1a-4e6f-9d8b-2a5c6e7f8091",
"text": "How would you rate the quality of this conversation?",
"description": "One star is the **worst**, five stars the **best**.",
"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?",
"description": null,
"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)",
"description": null,
"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?",
"description": null,
"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?",
"description": null
}
]
}
]
}
The object above appears exactly as shown, with no envelope, as an array element in the response below.
GET /api/feedback/specification
Fetch the feedback specification that is due right now, if there is one. Pass exactly one of
sessionId (after-session) or projectRef (before-session); the two cases behave differently,
described below.
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
|
One of |
UUID |
Fetch the after-session specification due for this session. Requires an authenticated
caller or an anonymous caller identified by |
|
One of |
String |
Fetch the before-session specification currently due for the project with this ref.
Requires an authenticated caller or an anonymous caller identified by |
|
No |
String |
Identifies an anonymous caller when no |
If neither sessionId nor projectRef 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 |
|---|---|
|
The |
|
The |
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.
Which Specification Is Returned
Each time a specification is returned, it is recorded as a feedback of the caller: stamped with the time it was requested and the revision of the definition, and not yet submitted. The submission endpoint fills the feedback in, and its state once submitted — postponed, answered or skipped — is what the rules below look at; see Reacting to a Delivered Form.
The revisions of a definition are separate documents with their own definitionId that share a
ref, and only the newest Published revision of each ref is considered. It is considered for
the caller only if it is not in test mode, is of the requested kind (AfterSession or
BeforeSession) and — for sessionId — applies to the length of that session; when the newest
revision is in test mode or of the other kind, no older revision takes its place. Of those, a
definition is dropped when
-
the caller has skipped it, in any revision of it;
-
it asks once per user (always the case for
BeforeSession) and the caller has already answered it, in any revision of it; -
it is not due. A definition is due when the caller has postponed it — its latest feedback for the current revision was submitted without answers — or when its schedule says so:
| Schedule | Due when |
|---|---|
Periodic |
At least the configured interval — daily, weekly or monthly — has passed since the definition was last requested, or it was never requested. A definition set to repeat after every conversation is always due. |
Usage based |
Since the definition was last requested, the caller has held the configured number of engaged conversations, or has spent the configured number of minutes in conversations. |
Intelligent |
Since the definition was last requested, the caller has held enough engaged conversations, spent enough time in them and has been using the project for long enough. |
Evaluation based |
The caller’s evaluation results satisfy the definition’s condition: an evaluation insight or the featured result of an evaluation has reached a value, or stayed at or under one. It is never returned again once the caller has answered it, in any revision of it. |
Of the definitions that remain, only one specification is returned: a postponed definition comes
before one that is merely due, and within either group the definition with the lowest priority
number wins. A definition with no entry applicable to the caller’s situation is passed over in
favor of the next one.
Publishing a new revision of a definition replaces the one served until then. Its schedule — and
the rotation of its carousel entries — start over for every caller, because they follow the
feedbacks of the revision being served. A skip or an answer, in contrast, carries over from every
earlier revision, since it is looked up by the ref the revisions share.
Reacting to a Delivered Form
The user reacts through one of three endpoints, and the state of the feedback follows from the reaction:
| Reaction | Request | Effect on later calls |
|---|---|---|
Answer |
|
The feedback is answered. A definition that asks once per user, a |
Skip |
|
The feedback is skipped: the definition is never returned to this caller again, in any revision. |
Postpone |
|
The feedback is postponed: the definition is returned again on the next call, whatever its schedule and ahead of definitions that are merely due. Returning it is a new request, so it is back to normal unless the caller postpones it once more. |
No reaction |
No submission. |
The form was ignored, and the definition follows its schedule as usual, counted from the time it was requested; an evaluation-based one is returned again on the next call. |
Complete Response Examples
?sessionId=… for a session whose project has two AfterSession definitions that are both due —
only the one with the higher priority is returned. The empty-array case (no definition is due, 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",
"description": "Tell us how the conversation went.",
"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?",
"description": null
}
]
}
]
}
]
?projectRef=… for a caller — authenticated, or anonymous via deviceRef — who hasn’t yet
answered either of the project’s two BeforeSession definitions — one is returned at a time, the
one with the higher priority:
[
{
"definitionId": "2c3b4a5f-6e7d-4809-9f0e-1d2c3b4a5f6e",
"description": "A few questions to get you started.",
"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?",
"description": "One star means not at all, five stars means very.",
"maxStars": 5
}
]
}
]
}
]
Once the caller answers it, the next call returns the project’s other BeforeSession definition —
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 an authenticated caller or an
anonymous caller identified by deviceRef; the feedback is attributed to that caller’s user. The
submission fills in the feedback that was requested last for the definition, if it was not
submitted yet, and is recorded as a feedback of its own otherwise — see
Reacting to a Delivered Form.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
|
UUID |
The |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
|
Yes |
String |
The ref of the project this feedback relates to. A ref that matches no project records nothing and
responds |
|
Only for an |
UUID |
The session this feedback relates to. Required when |
|
No |
String |
Identifies an anonymous caller when no |
|
No |
String |
JWT bearer token, when not passed as a header. See Authentication. |
Request Body
FeedbackSubmission:
| Field | Type | Description |
|---|---|---|
|
Array |
One entry per question the user reacted to (see Answer Object): answered with a
value, or skipped with |
Answer Object
| Field | Type | Description |
|---|---|---|
|
UUID string |
The |
|
Object | null |
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 |
The session the feedback relates to — the one the specification was requested for, or the one
passed in the request; |
|
UUID string |
Identifier of the project resolved from the |
|
UUID string |
The user who submitted the feedback — the authenticated caller, or the user of the device
identified by |
|
Integer |
The revision of the definition that the feedback relates to. |
|
Timestamp |
When the specification was returned to the caller; for a submission without a preceding request, when it was recorded. |
|
Timestamp |
When the answers were recorded. |
|
Array |
Only the answers that passed the constraints in Answer Value Shapes;
answers with an invalid or mismatched value are silently omitted. A skipped question stays, with
|
|
String |
|
Responds 404 when definitionId does not resolve to a feedback definition or the caller could not
be identified, or 406 when sessionId is missing and the definition is AfterSession.
Complete Submission Example
POST /api/feedback/submission/8f14e45f-ceea-467e-adde-3fb5023abc1b?projectRef=acme-support&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, which is left
out:
{
"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",
"revision": 3,
"requested": "2026-09-15T10:12:04.000Z",
"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." } }
],
"state": "Answered"
}
Skipping
The user dismisses the form: every question is sent with a null value, and the response keeps
them.
{
"answers": [
{ "questionId": "3d5e2c1a-9f4b-4a2e-8c3d-1e6f7a8b9c0d", "value": null },
{ "questionId": "7b2f9e4d-3c1a-4e6f-9d8b-2a5c6e7f8091", "value": null }
]
}
{
"id": "e3d2c1b0-9a8f-4e7d-8c2b-1f7a6e5d4c3b",
"definitionId": "8f14e45f-ceea-467e-adde-3fb5023abc1b",
"sessionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"projectId": "8e29b3d1-6a4f-4c2e-9d70-1f2a3b4c5d6e",
"userId": "5c8a0f12-7d34-4e56-9a1b-2c3d4e5f6a7b",
"revision": 3,
"requested": "2026-09-15T10:12:04.000Z",
"submitted": "2026-09-15T10:15:30.000Z",
"answers": [
{ "questionId": "3d5e2c1a-9f4b-4a2e-8c3d-1e6f7a8b9c0d", "value": null },
{ "questionId": "7b2f9e4d-3c1a-4e6f-9d8b-2a5c6e7f8091", "value": null }
],
"state": "Skipped"
}
Postponing
The user puts the form off: a submission without answers ({"answers": []}) postpones the feedback,
and the definition is returned again on the next call.
{
"id": "f4e3d2c1-0b9a-4f8e-9d3c-2a8b7f6e5d4c",
"definitionId": "8f14e45f-ceea-467e-adde-3fb5023abc1b",
"sessionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"projectId": "8e29b3d1-6a4f-4c2e-9d70-1f2a3b4c5d6e",
"userId": "5c8a0f12-7d34-4e56-9a1b-2c3d4e5f6a7b",
"revision": 3,
"requested": "2026-09-15T10:12:04.000Z",
"submitted": "2026-09-15T10:15:30.000Z",
"answers": [],
"state": "Postponed"
}
POST /api/feedback/postpone/{definitionId}
Put a delivered form off: the user is not answering it now, and the definition is returned again on
the next call to the specification endpoint, whatever its schedule says and ahead of definitions
that are merely due. Requires an authenticated caller or an anonymous caller identified by
deviceRef; the feedback is attributed to that caller’s user. The request has no body.
It acts as a submission without answers: it fills in the feedback that was requested last for the definition, if it was not submitted yet, and is recorded as a feedback of its own otherwise — see Reacting to a Delivered Form. Unlike a submission without answers it is always a postponement, even for a definition that treats empty answers as a skip.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
|
UUID |
The |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
|
Yes |
String |
The ref of the project this feedback relates to. A ref that matches no project records nothing and
responds |
|
Only for an |
UUID |
The session this feedback relates to. Required when |
|
No |
String |
Identifies an anonymous caller when no |
|
No |
String |
JWT bearer token, when not passed as a header. See Authentication. |
Response
200 OK with the persisted feedback record, described for the
submission endpoint. Its answers is empty and its state is Postponed:
{
"id": "f4e3d2c1-0b9a-4f8e-9d3c-2a8b7f6e5d4c",
"definitionId": "8f14e45f-ceea-467e-adde-3fb5023abc1b",
"sessionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"projectId": "8e29b3d1-6a4f-4c2e-9d70-1f2a3b4c5d6e",
"userId": "5c8a0f12-7d34-4e56-9a1b-2c3d4e5f6a7b",
"revision": 3,
"requested": "2026-09-15T10:12:04.000Z",
"submitted": "2026-09-15T10:15:30.000Z",
"answers": [],
"state": "Postponed"
}
Responds 404 when definitionId does not resolve to a feedback definition or the caller could not
be identified, or 406 when sessionId is missing and the definition is AfterSession.
POST /api/feedback/skip/{definitionId}
Dismiss a delivered form for good: the definition is never returned to this caller again, in any of
its revisions. Requires an authenticated caller or an anonymous caller identified by deviceRef;
the feedback is attributed to that caller’s user. The request has no body.
It acts as a submission in which every question is skipped: it fills in the feedback that was
requested last for the definition, if it was not submitted yet, and is recorded as a feedback of its
own otherwise. The record’s answers hold every question of the definition once — those of all
its entries, for every session length — each with a value of null.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
|
UUID |
The |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
|
Yes |
String |
The ref of the project this feedback relates to. A ref that matches no project records nothing and
responds |
|
Only for an |
UUID |
The session this feedback relates to. Required when |
|
No |
String |
Identifies an anonymous caller when no |
|
No |
String |
JWT bearer token, when not passed as a header. See Authentication. |
Response
200 OK with the persisted feedback record, described for the
submission endpoint. Every answer has a value of null and its state
is Skipped:
{
"id": "e3d2c1b0-9a8f-4e7d-8c2b-1f7a6e5d4c3b",
"definitionId": "8f14e45f-ceea-467e-adde-3fb5023abc1b",
"sessionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"projectId": "8e29b3d1-6a4f-4c2e-9d70-1f2a3b4c5d6e",
"userId": "5c8a0f12-7d34-4e56-9a1b-2c3d4e5f6a7b",
"revision": 3,
"requested": "2026-09-15T10:12:04.000Z",
"submitted": "2026-09-15T10:15:30.000Z",
"answers": [
{ "questionId": "3d5e2c1a-9f4b-4a2e-8c3d-1e6f7a8b9c0d", "value": null },
{ "questionId": "7b2f9e4d-3c1a-4e6f-9d8b-2a5c6e7f8091", "value": null }
],
"state": "Skipped"
}
Responds 404 when definitionId does not resolve to a feedback definition or the caller could not
be identified, or 406 when sessionId is missing and the definition is AfterSession.