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

definitionId

UUID string

Identifies the feedback definition this specification was generated from. Pass it as the path parameter of the submission endpoint below.

description

String

The description of the feedback definition, to show with the form.

entries

Array

The entries to render, in order (see Entry Object).

Entry Object

Field Type Description

entryId

UUID string

Identifies this entry. Not needed to submit answers — those reference questionId directly — but useful if the client groups questions visually per entry.

name

String

Display/internal name of the entry.

questions

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

—

Thumbs up / down toggle

star-rating

maxStars (integer)

Star picker, 1 to maxStars

linear-scale

points (integer), minLabel, maxLabel (strings)

1 to points scale, labeled at both ends

multiple-choice

options (array of strings)

Multi-select over options

single-choice

options (array of strings)

Single-select over options

text

—

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

sessionId

One of sessionId/projectRef

UUID

Fetch the after-session specification due for this session. Requires an authenticated caller or an anonymous caller identified by deviceRef.

projectRef

One of sessionId/projectRef

String

Fetch the before-session specification currently due for the project with this ref. Requires an authenticated caller or an anonymous caller identified by deviceRef. A ref that matches no project yields an empty array.

deviceRef

No

String

Identifies an anonymous caller when no Authorization is presented, the same device identifier used by the user endpoints. Either this or an authenticated caller is required — see the error case below.

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

sessionId

The Published, AfterSession definition of that session’s project that is due for the caller — no element or exactly one, even when the project has several definitions. A definition configured to ask once per user is excluded once the caller has already answered it.

projectRef

The Published, BeforeSession definition of the project that is due for the caller and not yet answered by them — no element or exactly one, even when the project has several definitions.

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

POST /api/feedback/submission/{definitionId} with at least one answer with a value.

The feedback is answered. A definition that asks once per user, a BeforeSession one and an evaluation-based one are not returned again; any other follows its schedule, counted from the time it was requested.

Skip

POST /api/feedback/skip/{definitionId}, with no body. A submission whose answers all have a value of null is a skip as well.

The feedback is skipped: the definition is never returned to this caller again, in any revision.

Postpone

POST /api/feedback/postpone/{definitionId}, with no body. A submission with no answers at all ({"answers": []}) is a postponement as well.

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

definitionId

UUID

The definitionId of the FeedbackSpecification being answered.

Query Parameters

Parameter Required Type Description

projectRef

Yes

String

The ref of the project this feedback relates to. A ref that matches no project records nothing and responds 200 with an empty body.

sessionId

Only for an AfterSession definition

UUID

The session this feedback relates to. Required when definitionId resolves to an AfterSession definition — omitting it then responds 406 (see below) — and has no effect otherwise; a BeforeSession definition has no session to relate the feedback to.

deviceRef

No

String

Identifies an anonymous caller when no Authorization is presented, the same device identifier as for the specification endpoint above.

authorization

No

String

JWT bearer token, when not passed as a header. See Authentication.

Request Body

FeedbackSubmission:

Field Type Description

answers

Array

One entry per question the user reacted to (see Answer Object): answered with a value, or skipped with null. Questions the user did not react to can be omitted. Sending no answers at all postpones the feedback.

Answer Object

Field Type Description

questionId

UUID string

The id of the question being answered, from the specification.

value

Object | null

The answer, using the same polymorphic value shape as evaluation insight values (boolean, number, text or list) — see Answer Value Shapes for which one each question type expects. null means the user skipped the question; a submission whose values are all null skips the whole feedback.

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

thumbs

boolean

—

star-rating

number

Integer in [1, maxStars]; out-of-range values are clamped.

linear-scale

number

Integer in [1, points]; out-of-range values are clamped.

multiple-choice

list

Subset of the question’s options; values outside it are dropped.

single-choice

text

Must be one of the question’s options, otherwise the answer is dropped.

text

text

Free text; blank values are dropped.

Response

200 OK with the persisted feedback record:

Field Type Description

id

UUID string

Unique identifier of this feedback submission.

definitionId

UUID string

The feedback definition this submission answers.

sessionId

UUID string | null

The session the feedback relates to — the one the specification was requested for, or the one passed in the request; null for a BeforeSession definition submitted without one.

projectId

UUID string

Identifier of the project resolved from the projectRef passed in the request.

userId

UUID string

The user who submitted the feedback — the authenticated caller, or the user of the device identified by deviceRef.

revision

Integer

The revision of the definition that the feedback relates to.

requested

Timestamp

When the specification was returned to the caller; for a submission without a preceding request, when it was recorded.

submitted

Timestamp

When the answers were recorded.

answers

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 value set to null.

state

String

Answered (at least one value), Skipped (answers that are all null) or Postponed (no answers) — see Reacting to a Delivered Form.

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

definitionId

UUID

The definitionId of the FeedbackSpecification being put off.

Query Parameters

Parameter Required Type Description

projectRef

Yes

String

The ref of the project this feedback relates to. A ref that matches no project records nothing and responds 200 with an empty body.

sessionId

Only for an AfterSession definition

UUID

The session this feedback relates to. Required when definitionId resolves to an AfterSession definition — omitting it then responds 406 (see below) — and has no effect otherwise.

deviceRef

No

String

Identifies an anonymous caller when no Authorization is presented, the same device identifier as for the specification endpoint above.

authorization

No

String

JWT bearer token, when not passed as a header. See Authentication.

Request Body

None.

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

definitionId

UUID

The definitionId of the FeedbackSpecification being dismissed.

Query Parameters

Parameter Required Type Description

projectRef

Yes

String

The ref of the project this feedback relates to. A ref that matches no project records nothing and responds 200 with an empty body.

sessionId

Only for an AfterSession definition

UUID

The session this feedback relates to. Required when definitionId resolves to an AfterSession definition — omitting it then responds 406 (see below) — and has no effect otherwise.

deviceRef

No

String

Identifies an anonymous caller when no Authorization is presented, the same device identifier as for the specification endpoint above.

authorization

No

String

JWT bearer token, when not passed as a header. See Authentication.

Request Body

None.

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.