---
title: "Survey definition"
description: "The JSON format of a Gleap survey: blocks, answer keys, logic jumps and conditions, endings and hidden fields, with complete examples."
url: https://docs.gleap.ai/en/articles/700-survey-definition
collection: "Surveys"
updated: 2026-11-01
language: en
---

# Survey definition

The JSON format of a Gleap survey: blocks, answer keys, logic jumps and conditions, endings and hidden fields, with complete examples.

A survey definition is a JSON document. The dashboard editor, the [REST API](https://gleap-developer-docs.gleap.help/en/articles/702-surveys-rest-api), the [MCP server](https://gleap-developer-docs.gleap.help/en/articles/705-surveys-with-ai-agents-mcp) and Kai all read and write this format, and Gleap validates it the same way everywhere.

```json
{
  "schemaVersion": 2,
  "format": "card",
  "defaultLanguage": "en",
  "welcome": { … },
  "blocks": [ … ],
  "endings": [ … ],
  "fields": [ … ],
  "showProgress": true,
  "allowSkipBack": true
}
```

## Top level

`schemaVersion` _2_

Always `2`.

`format` _'card' | 'full'_ (required)

`card` (compact card, bottom sheet on phones) or `full` (Typeform-style full screen). The legacy values `survey` and `survey_full` are deprecated aliases of `card` and `full`.

`defaultLanguage` _string_ (required)

Locale of the default texts, for example `en`.

`welcome` _Welcome_

Optional welcome screen before the first question — see [Welcome screen](#welcome-screen).

`blocks` _Block\[\]_ (required)

The questions in display order. 1–60 blocks.

`endings` _Ending\[\]_ (required)

Thank-you screens. 1–10 endings.

`fields` _HiddenField\[\]_

Hidden fields: values passed in by the SDK, URL parameters or personal links.

`showProgress` _boolean_

Show the progress bar.

`allowSkipBack` _boolean_

Full screen: allow going back to earlier questions.

`theme` _Theme_

The survey's design — see [Theme](#theme). Every field is optional; unset fields use your widget settings.

`sender` _object_

`{ userId? }` — the teammate shown in the card's sender row. Defaults to the project's default sender.

### Localized texts

Every text (`title`, `description`, choice `label`, …) is a map of locale to text:

```json
{ "en": "How likely are you to recommend us?", "de": "Wie wahrscheinlich ist es, dass Sie uns weiterempfehlen?" }
```

Titles may contain tokens that are replaced for each respondent: `{{contact.first_name}}`, `{{field.<hidden field key>}}` and `{{answer.<earlier answer key>}}`.

## Theme

`theme` sets the survey's design. Every field is optional: a field you leave out uses your project's widget settings (widget colour, widget logo, widget light/dark setting), so a survey without `theme` looks like your widget.

`brandColor` _hex_

Buttons, selected choices and the progress bar. Default: the widget colour.

`buttonTextColor` _hex_

Text on brand-filled buttons and selected choices. Default: black or white, whichever contrasts better with `brandColor`.

`appearance` _'auto' | 'light' | 'dark'_

Light or dark surfaces. `auto` (default) follows the widget's light/dark setting.

`background` _'tint' | 'plain' | 'color' | 'image'_

The backdrop of the full-screen format and the survey page. `tint` (default) is a light tint of the brand colour, `plain` has no tint, `color` uses `backgroundColor`, `image` uses `backgroundImage`. The card and the bottom sheet always stay a solid light or dark surface.

`backgroundColor` _hex_

Backdrop colour for `background: "color"`.

`backgroundImage` _string_

Backdrop image for `background: "image"`. Must be an `https` URL without quotes, parentheses or whitespace.

`backgroundOverlay` _number_

0–0.8. Black darkening over a colour or image backdrop so text stays readable. Default `0.3` for images, `0` for colours.

`textColor` _hex_

Text on the full-screen and survey page backdrop. Default: picked by contrast — light text on dark colours and images.

`corners` _'sharp' | 'rounded' | 'pill'_

Corner radius of choices, buttons and inputs. Default `rounded`.

`logoUrl` _string_

`https` URL of the logo. Default: the widget logo.

`showLogo` _boolean_

Show the logo — top left in the full-screen format and in the survey page header. Default `true`.

Where each field applies:

<table style="min-width: 100px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p></p></th><th colspan="1" rowspan="1"><p>Card / bottom sheet</p></th><th colspan="1" rowspan="1"><p>Full screen</p></th><th colspan="1" rowspan="1"><p>Survey page</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>brandColor</code>, <code>buttonTextColor</code>, <code>corners</code>, <code>appearance</code></p></td><td colspan="1" rowspan="1"><p>✓</p></td><td colspan="1" rowspan="1"><p>✓</p></td><td colspan="1" rowspan="1"><p>✓</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>background</code>, <code>backgroundColor</code>, <code>backgroundImage</code>, <code>backgroundOverlay</code>, <code>textColor</code></p></td><td colspan="1" rowspan="1"><p>—</p></td><td colspan="1" rowspan="1"><p>✓</p></td><td colspan="1" rowspan="1"><p>✓</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>logoUrl</code>, <code>showLogo</code></p></td><td colspan="1" rowspan="1"><p>—</p></td><td colspan="1" rowspan="1"><p>✓</p></td><td colspan="1" rowspan="1"><p>✓</p></td></tr></tbody></table>

A brand colour, a photo backdrop darkened for legibility and pill-shaped choices:

```json
"theme": {
  "brandColor": "#0f766e",
  "background": "image",
  "backgroundImage": "https://cdn.example.com/surveys/forest.jpg",
  "backgroundOverlay": 0.4,
  "corners": "pill"
}
```

Colours are hex (`#0f766e`, `#fff`, or with alpha). An invalid `backgroundImage` or `backgroundColor` falls back to `tint`.

### Resolved theme

The SDK and the survey page load a survey through `GET /surveys/{id}` and `GET /surveys/page/{slug}`. Both return `theme` with the widget defaults already filled in:

```json
"theme": {
  "brandColor": "#0f766e",
  "appearance": "auto",
  "autoAppearance": "system",
  "background": "image",
  "backgroundImage": "https://cdn.example.com/surveys/forest.jpg",
  "backgroundOverlay": 0.4,
  "corners": "pill",
  "showLogo": true,
  "logoUrl": "https://cdn.example.com/logo.png",
  "widgetBackgroundColor": "#ffffff",
  "buttonColor": "#0f766e",
  "defaults": { "brandColor": "#485bff", "appearance": "system", "logoUrl": "https://cdn.example.com/logo.png", "backgroundColor": "#ffffff" }
}
```

<table style="min-width: 50px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Field</p></th><th colspan="1" rowspan="1"><p></p></th></tr><tr><td colspan="1" rowspan="1"><p><code>brandColor</code>, <code>background</code>, <code>backgroundOverlay</code>, <code>corners</code>, <code>showLogo</code>, <code>appearance</code></p></td><td colspan="1" rowspan="1"><p>Always present, defaults applied.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>autoAppearance</code></p></td><td colspan="1" rowspan="1"><p>What <code>auto</code> means for this project: <code>light</code>, <code>dark</code> or <code>system</code> (follows the visitor's device).</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>buttonTextColor</code>, <code>textColor</code></p></td><td colspan="1" rowspan="1"><p>Only when the survey sets them; otherwise pick by contrast.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>backgroundColor</code>, <code>backgroundImage</code></p></td><td colspan="1" rowspan="1"><p>Only for a <code>color</code> / <code>image</code> background.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>logoUrl</code></p></td><td colspan="1" rowspan="1"><p>The survey logo, else the widget logo; absent when <code>showLogo</code> is <code>false</code> or there is no logo.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>widgetBackgroundColor</code>, <code>buttonColor</code></p></td><td colspan="1" rowspan="1"><p>The widget's own colours, when set.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>defaults</code></p></td><td colspan="1" rowspan="1"><p>The widget values unset fields fall back to: <code>brandColor</code>, <code>appearance</code>, <code>logoUrl?</code>, <code>backgroundColor?</code>.</p></td></tr></tbody></table>

## Blocks

`id` _string_ (required)

Stable id, unique across blocks **and** endings (letters, digits, `_`, `-`; max 40). Jumps point to it.

`key` _string_ (required)

Stable answer key, unique, `^[a-z][a-z0-9_]{1,40}$` — for example `nps_score`. Webhooks, exports, the API and filters use it. Don't rename it on a live survey.

`type` _string_ (required)

One of the types below.

`title` _Localized_ (required)

The question.

`description` _Localized_

Help text under the question.

`required` _boolean_ (required)

Whether the question must be answered. `statement` blocks are never required; a required `consent` must be ticked.

`defaultValue` _string | number | boolean | string\[\]_

Preselected answer the question starts with: a choice id (`single`), choice ids (`multi`), a scale value, `true` / `false` (`yesno`) or text. The respondent still confirms it; nothing is saved before. Must be a valid answer of the block (`invalid_default` otherwise).

`jumps` _Jump\[\]_ (required)

Logic rules, checked top to bottom; the first match wins. Max 20. `[]` for none.

`next` _string | null_ (required)

"All other cases go to": a later block id or an ending id. `null` = the next block in order (after the last block, the first ending).

Type-specific options:

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Type</p></th><th colspan="1" rowspan="1"><p>Answer value</p></th><th colspan="1" rowspan="1"><p>Options</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>nps</code></p></td><td colspan="1" rowspan="1"><p>number 0–10</p></td><td colspan="1" rowspan="1"><p><code>scaleStyle</code> (<code>numbers</code> / <code>emoji</code> / <code>compact</code>), <code>lowLabel</code>, <code>highLabel</code>. <code>compact</code> shows six buttons 0–5 that answer 0, 2, 4, 6, 8, 10, so NPS groups and logic stay on 0–10 (the pre-2.0 "modern" NPS)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>csat</code></p></td><td colspan="1" rowspan="1"><p>number 1–5</p></td><td colspan="1" rowspan="1"><p><code>scaleStyle</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>rating</code></p></td><td colspan="1" rowspan="1"><p>number <code>scaleMin</code>–<code>scaleMax</code></p></td><td colspan="1" rowspan="1"><p><code>scaleMin</code>, <code>scaleMax</code>, <code>scaleStyle</code> (<code>stars</code>)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>scale</code></p></td><td colspan="1" rowspan="1"><p>number <code>scaleMin</code>–<code>scaleMax</code></p></td><td colspan="1" rowspan="1"><p><code>scaleMin</code>, <code>scaleMax</code>, <code>scaleStyle</code>, <code>lowLabel</code>, <code>highLabel</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>single</code></p></td><td colspan="1" rowspan="1"><p>choice id, or <code>{ "other": "…" }</code></p></td><td colspan="1" rowspan="1"><p><code>choices</code> (max 25), <code>allowOther</code>, <code>randomize</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>multi</code></p></td><td colspan="1" rowspan="1"><p>array of choice ids (plus <code>{ "other": "…" }</code>)</p></td><td colspan="1" rowspan="1"><p><code>choices</code>, <code>allowOther</code>, <code>randomize</code>, <code>minSelect</code>, <code>maxSelect</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>yesno</code></p></td><td colspan="1" rowspan="1"><p><code>true</code> / <code>false</code></p></td><td colspan="1" rowspan="1"><p>—</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>short</code>, <code>long</code></p></td><td colspan="1" rowspan="1"><p>string</p></td><td colspan="1" rowspan="1"><p><code>placeholder</code>, <code>maxLength</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>email</code></p></td><td colspan="1" rowspan="1"><p>string</p></td><td colspan="1" rowspan="1"><p><code>placeholder</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>number</code></p></td><td colspan="1" rowspan="1"><p>number</p></td><td colspan="1" rowspan="1"><p><code>placeholder</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>date</code></p></td><td colspan="1" rowspan="1"><p><code>YYYY-MM-DD</code></p></td><td colspan="1" rowspan="1"><p>—</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>upload</code></p></td><td colspan="1" rowspan="1"><p>array of file URLs</p></td><td colspan="1" rowspan="1"><p><code>accept</code> (file types like an <code>&lt;input accept&gt;</code>: <code>.png,.jpg,application/pdf</code>, <code>image/*</code>)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>statement</code></p></td><td colspan="1" rowspan="1"><p>— (no answer)</p></td><td colspan="1" rowspan="1"><p>—</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>consent</code></p></td><td colspan="1" rowspan="1"><p><code>true</code> (ticked)</p></td><td colspan="1" rowspan="1"><p><code>linkUrl</code> (https, the policy), <code>linkLabel</code> (its text, default "privacy policy"). <code>title</code> is the checkbox text; leave it empty to show the widget's "I have read and accept the privacy policy." in the respondent's language. Logic only on <code>answered</code> / <code>skipped</code></p></td></tr></tbody></table>

Choices are `{ "id": "too_expensive", "label": { "en": "Too expensive" } }`. Choice ids are unique within the block; answers and conditions use the id, never the label. Any block can also carry `media: { type: 'image' | 'video', url }`.

## Logic: jumps and conditions

A jump is `{ "if": <condition>, "go": "<block id or ending id>" }`. `go` must point to a **later** block or to an ending — jumps never go backwards, so there are no loops.

Where a person goes after answering:

1.  the first jump whose condition matches,
    
2.  otherwise `next`,
    
3.  otherwise the next block in order,
    
4.  after the last block, the first ending.
    

### Conditions on the answer

<table style="min-width: 50px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Condition</p></th><th colspan="1" rowspan="1"><p>Use with</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "answer", "op": "eq", "value": "too_expensive" }</code></p></td><td colspan="1" rowspan="1"><p><code>single</code> (choice id), <code>yesno</code> (<code>true</code>/<code>false</code>), numbers</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "answer", "op": "neq", "value": … }</code></p></td><td colspan="1" rowspan="1"><p>same as <code>eq</code></p></td></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "answer", "op": "between", "value": [0, 6] }</code></p></td><td colspan="1" rowspan="1"><p><code>nps</code>, <code>csat</code>, <code>rating</code>, <code>scale</code>, <code>number</code> — inclusive</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "answer", "op": "includes", "value": "slack" }</code></p></td><td colspan="1" rowspan="1"><p><code>multi</code> (choice id)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "answer", "op": "answered" }</code> / <code>"skipped"</code></p></td><td colspan="1" rowspan="1"><p>any optional question</p></td></tr></tbody></table>

### Conditions on the person

<table style="min-width: 50px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Condition</p></th><th colspan="1" rowspan="1"><p>Meaning</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "contact", "key": "plan", "op": "eq", "value": "pro" }</code></p></td><td colspan="1" rowspan="1"><p>Contact property. <code>op</code>: <code>eq</code>, <code>neq</code>, <code>contains</code>, <code>exists</code>.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "field", "key": "source", "op": "eq", "value": "newsletter" }</code></p></td><td colspan="1" rowspan="1"><p>Hidden field (must be listed in <code>fields</code>).</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>{ "src": "url", "op": "contains", "value": "/billing" }</code></p></td><td colspan="1" rowspan="1"><p>Page URL the survey is shown on. <code>op</code>: <code>contains</code>, <code>eq</code>.</p></td></tr></tbody></table>

The editor lists person conditions before answer conditions; put them first when you write definitions yourself.

## Welcome screen

`welcome` adds an intro screen before the first question, like Typeform's. It is not a block: it has no answer key, no logic and does not count as a question. In full screen and on the survey page it shows centred, with the optional image, a large title, the description, the Start button ("press Enter ↵") and the time estimate. The card and the phone bottom sheet show a compact version (title, description, Start). People who come back to finish a response they started skip it.

`enabled` _boolean_ (required)

`true` shows the welcome screen. `false` keeps the texts without showing them.

`title` _Localized_ (required)

The heading. Required when `enabled` is `true`; may contain tokens like `{{contact.first_name}}`.

`description` _Localized_

One or two sentences under the title.

`buttonLabel` _Localized_

Start button text. Default "Start".

`media` _object_

`{ type: "image", url }` — an image above the title (full screen and survey page). `https` only.

`showTimeToComplete` _boolean_

Show "Takes about N minutes" (about 15 seconds per question). Default `true`.

```json
"welcome": {
  "enabled": true,
  "title": { "en": "Help shape Acme" },
  "description": { "en": "Three quick questions. Your answers go straight to the product team." },
  "buttonLabel": { "en": "Let's go" },
  "media": { "type": "image", "url": "https://cdn.acme.com/survey-hero.png" }
}
```

Use the welcome screen rather than a leading `statement` block: question numbers, the step funnel and "starts" (first answer) stay the same, and the analytics report how many people pressed Start.

## Endings

`id` _string_ (required)

Unique across blocks and endings.

`title` _Localized_ (required)

Thank-you text.

`description` _Localized_

`button` _object_

`{ label: Localized, url }` — a call to action.

`redirectUrl` _string_

Survey page: redirect after this ending.

`autoCloseSeconds` _number_

In the app: close after N seconds.

`tagContact` _string_

Tag added to the contact who reaches this ending.

## Hidden fields

```json
"fields": [{ "key": "plan", "label": "Plan" }, { "key": "source" }]
```

Values come from `showSurvey(id, { fields })`, URL parameters on the survey page (`?plan=pro`) and personal links. They are stored on the response under `fields`.

## Validation

Gleap rejects a definition at publish time when:

-   ids or keys are missing, malformed or duplicated (ids are unique across blocks and endings; choice ids within a block),
    
-   a `go` or `next` points to an unknown id or to an earlier block (loop),
    
-   a block can't be reached from the first block, or a path doesn't end in an ending,
    
-   a condition doesn't fit its block type (for example `includes` on a `single` block, `between: [0, 11]` on NPS, or a value condition on a `consent`),
    
-   `scaleStyle: 'compact'` is set on anything but NPS, or a `defaultValue` is not a valid answer of its block,
    
-   a limit is exceeded: 60 blocks, 10 endings, 25 choices per block, 20 jumps per block.
    

Saving a draft accepts invalid logic and returns the problems as warnings; publishing returns `422` with the same list. Use `POST /validate` to check a definition without saving. Errors look like:

```json
{ "ok": false, "errors": [{ "path": "blocks.2.jumps.0.go", "code": "backward_jump", "message": "\"q_reason\" is not after this block" }] }
```

## Example: NPS with follow-ups

Detractors (0–6), passives (7–8) and promoters (9–10) each get their own follow-up question.

```json
{
  "schemaVersion": 2,
  "format": "card",
  "defaultLanguage": "en",
  "blocks": [
    {
      "id": "q_nps", "key": "nps_score", "type": "nps", "required": true,
      "title": { "en": "How likely are you to recommend us to a friend?" },
      "lowLabel": { "en": "Not likely" }, "highLabel": { "en": "Very likely" },
      "jumps": [
        { "if": { "src": "answer", "op": "between", "value": [0, 6] }, "go": "q_detractor" },
        { "if": { "src": "answer", "op": "between", "value": [7, 8] }, "go": "q_passive" },
        { "if": { "src": "answer", "op": "between", "value": [9, 10] }, "go": "q_promoter" }
      ],
      "next": null
    },
    { "id": "q_detractor", "key": "detractor_reason", "type": "long", "required": false,
      "title": { "en": "Sorry to hear that. What disappointed you?" }, "jumps": [], "next": "end_thanks" },
    { "id": "q_passive", "key": "passive_improve", "type": "long", "required": false,
      "title": { "en": "What would make it a 10?" }, "jumps": [], "next": "end_thanks" },
    { "id": "q_promoter", "key": "promoter_love", "type": "long", "required": false,
      "title": { "en": "Great! What do you love most?" }, "jumps": [], "next": "end_thanks" }
  ],
  "endings": [{ "id": "end_thanks", "title": { "en": "Thanks for your feedback!" } }],
  "fields": [],
  "showProgress": true,
  "allowSkipBack": true
}
```

## Example: cancellation survey

People who pick "Too expensive" are asked what price would have worked; everyone else goes straight to "What could we have done better?". People who agree to be contacted are tagged.

```json
{
  "schemaVersion": 2,
  "format": "full",
  "defaultLanguage": "en",
  "blocks": [
    {
      "id": "q_reason", "key": "cancel_reason", "type": "single", "required": true,
      "title": { "en": "What's the main reason you're leaving?" },
      "choices": [
        { "id": "too_expensive", "label": { "en": "Too expensive" } },
        { "id": "missing_features", "label": { "en": "Missing features" } },
        { "id": "switching", "label": { "en": "Switching to another tool" } },
        { "id": "not_using", "label": { "en": "Not using it enough" } }
      ],
      "allowOther": true,
      "jumps": [{ "if": { "src": "answer", "op": "eq", "value": "too_expensive" }, "go": "q_price" }],
      "next": "q_better"
    },
    { "id": "q_price", "key": "fair_price", "type": "number", "required": false,
      "title": { "en": "What monthly price would have worked for you?" }, "jumps": [], "next": "q_contact" },
    { "id": "q_better", "key": "do_better", "type": "long", "required": false,
      "title": { "en": "What could we have done better?" }, "jumps": [], "next": null },
    {
      "id": "q_contact", "key": "may_contact", "type": "yesno", "required": true,
      "title": { "en": "May we contact you about this, {{contact.first_name}}?" },
      "jumps": [{ "if": { "src": "answer", "op": "eq", "value": true }, "go": "end_contact" }],
      "next": "end_bye"
    }
  ],
  "endings": [
    { "id": "end_contact", "title": { "en": "Thanks — we'll be in touch." }, "tagContact": "churn-followup" },
    { "id": "end_bye", "title": { "en": "Thanks for being a customer." } }
  ],
  "fields": [],
  "showProgress": true,
  "allowSkipBack": true
}
```

## Responses

Answers are stored keyed by the block's `key`:

```json
{
  "id": "rsp_4kq9X2mT7vB1nR8sLp3Z",
  "surveyId": "q7x2kd",
  "version": 3,
  "status": "completed",
  "answers": { "cancel_reason": "too_expensive", "fair_price": 49, "may_contact": true },
  "path": ["q_reason", "q_price", "q_contact", "end_contact"],
  "endingId": "end_contact",
  "channel": "web",
  "fields": { "plan": "pro" },
  "language": "en",
  "startedAt": "2026-10-06T09:12:03.000Z",
  "completedAt": "2026-10-06T09:12:41.000Z"
}
```

`channel` is one of `web`, `ios`, `android`, `page`, `email`, `embed`, `conversation`, `api`.

## Related

- [Surveys](https://docs.gleap.ai/en/articles/699-surveys.md)
- [Survey analytics](https://docs.gleap.ai/en/articles/701-survey-analytics.md)
- [Surveys REST API](https://docs.gleap.ai/en/articles/702-surveys-rest-api.md)
- [Survey webhooks](https://docs.gleap.ai/en/articles/703-survey-webhooks.md)
- [Survey automations](https://docs.gleap.ai/en/articles/704-survey-automations.md)
