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

definitionId

UUID string

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

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).

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

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",
  "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

sessionId

One of sessionId/projectId

UUID

Fetch the after-session specification(s) eligible for this session. Requires an authenticated caller or an anonymous caller identified by deviceRef.

projectId

One of sessionId/projectId

UUID

Fetch the before-session specification(s) currently eligible for this project. Requires an authenticated caller or an anonymous caller identified by deviceRef.

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 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

sessionId

Every eligible Published, AfterSession definition for that session’s project — a project can have more than one, so this can have more than one element. A definition configured to ask once per user is excluded once the caller has already answered it.

projectId

Every Published, BeforeSession definition not yet answered by the caller — a project can have more than one, so this can have more than one element too.

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

definitionId

UUID

The definitionId of the FeedbackSpecification being answered.

Query Parameters

Parameter Required Type Description

projectId

Yes

UUID

The project this feedback relates to.

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.

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 answered question (see Answer Object). Omit unanswered questions entirely rather than sending an empty or null value for them.

Answer Object

Field Type Description

questionId

UUID string

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

value

Object

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.

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

Session passed in the request; null for a BeforeSession definition submitted without one.

projectId

UUID string

Project passed in the request.

userId

UUID string

The authenticated user who submitted the feedback.

submitted

Timestamp

When the feedback was recorded.

answers

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.