> ## Documentation Index
> Fetch the complete documentation index at: https://docs.listenlabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Study Guide Reference

> How to structure the studyGuide payload: blocks, question types, screening, concepts, conditional logic, and carry-forward

The `studyGuide` field of [Create Study](/api-v2/create-study) defines everything participants see: the questions, their order, screening, concept testing, and routing logic. It's an array of **blocks**, and each block contains one or more **items** (questions).

```json Minimal study theme={null}
{
  "title": "Coffee habits interview",
  "studyGuide": [
    {
      "type": "flat",
      "title": "Main questions",
      "items": [
        { "type": "open_ended", "text": "Walk me through your morning coffee routine." }
      ]
    }
  ]
}
```

## Top-level request fields

<ResponseField name="title" type="string" required>
  Internal study title (shown in the dashboard).
</ResponseField>

<ResponseField name="externalTitle" type="string">
  Participant-facing title. Falls back to `title` when omitted.
</ResponseField>

<ResponseField name="background" type="string">
  Background context for the AI interviewer — what the study is about and who you're talking to.
</ResponseField>

<ResponseField name="studyGoal" type="string">
  What you want to learn. Helps the AI interviewer probe in the right direction.
</ResponseField>

<ResponseField name="config" type="Config">
  Interview mode, languages, and platform targeting. See [Config](#config).
</ResponseField>

<ResponseField name="welcomeMessage" type="object">
  `{ "title": "...", "message": "..." }` shown to participants before the interview starts.
</ResponseField>

<ResponseField name="closingMessage" type="string">
  Message shown when the interview ends.
</ResponseField>

<ResponseField name="studyGuide" type="Block[]" required>
  The blocks described below. At least one.
</ResponseField>

## Config

<ResponseField name="interviewMode" type="string">
  One of `text`, `audio`, `audio_text`, `audio_screen`, `video`, `video_screen`.
</ResponseField>

<ResponseField name="questionLanguage" type="string">
  Language code the questions are written in (e.g. `en`, `de`, `fr`, `es`, `zh-TW`, `en-medical`). See the [complete language list](/setup-to-launch/languages-complete-list).
</ResponseField>

<ResponseField name="availableLanguages" type="string[] | null">
  Translation target codes participants can switch to. `null` or omitted = English only.
</ResponseField>

<ResponseField name="targetPlatforms" type="string[]">
  Restrict which devices can take the study: any of `ios`, `android`, `desktop`.
</ResponseField>

```json Example config theme={null}
{
  "config": {
    "interviewMode": "audio_text",
    "questionLanguage": "en",
    "availableLanguages": ["en", "de", "fr"],
    "targetPlatforms": ["desktop"]
  }
}
```

## Blocks

Every block has a `type`, a `title`, and an `items` array (at least one item).

| Type        | Purpose                                                                                                                            |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `flat`      | A plain sequence of questions.                                                                                                     |
| `screening` | Qualifies participants before the interview. At most one, and it must be the **first** block.                                      |
| `concept`   | Shows each participant one or more concepts (stimuli) and asks the block's questions about them. Requires `conceptSamplingConfig`. |

### Screening blocks

Screening blocks may contain **only `multiple_choice` items**, and every option must carry a `status`:

* `approve` — selecting it qualifies the participant
* `reject` — selecting it screens the participant out
* `neutral` — doesn't affect qualification

```json Screening block theme={null}
{
  "type": "screening",
  "title": "Screener",
  "items": [
    {
      "externalId": "coffee-frequency",
      "type": "multiple_choice",
      "text": "How often do you drink coffee?",
      "options": [
        { "externalId": "opt-daily", "text": "Every day", "status": "approve" },
        { "text": "A few times a week", "status": "approve" },
        { "text": "Rarely or never", "status": "reject" }
      ]
    }
  ]
}
```

Outside screening blocks, `status` must be `null` or omitted.

### Concept blocks

Concept blocks show stimuli — product ideas, ads, prototypes — and ask the block's items about each one. They require a `conceptSamplingConfig` with at least one concept; other block types must omit it.

<ResponseField name="concepts" type="Concept[]" required>
  Each concept has a `nickname` (internal label) and `content`: a participant-facing `title`, optional `description`, optional `media` (images/videos), and an optional `embed` (e.g. a Figma prototype URL).
</ResponseField>

<ResponseField name="subsampleCount" type="integer | null">
  How many concepts each participant sees, randomly sampled. `null` = every participant sees all concepts.
</ResponseField>

Each concept can also carry a [`conditional`](#conditional-logic), so it's only shown to participants matching the criteria — e.g. gate each concept on a screener answer. `selectedTemplateOptions` criteria on a concept must reference an item in a block **before** the concept block.

```json Concept block theme={null}
{
  "type": "concept",
  "title": "Packaging concepts",
  "conceptSamplingConfig": {
    "subsampleCount": 2,
    "concepts": [
      {
        "nickname": "minimal",
        "content": {
          "title": "Minimal design",
          "description": "Clean white packaging with a single accent color.",
          "media": [
            { "name": "minimal.png", "url": "https://example.com/minimal.png", "type": "image" }
          ]
        }
      },
      {
        "nickname": "bold",
        "content": {
          "title": "Bold design",
          "embed": { "url": "https://www.figma.com/proto/..." }
        },
        "conditional": {
          "operator": "and",
          "criteria": [
            {
              "type": "selectedTemplateOptions",
              "questionId": "coffee-frequency",
              "matchingCriteria": "mustSelect",
              "choices": ["opt-daily"]
            }
          ]
        }
      }
    ]
  },
  "items": [
    { "type": "open_ended", "text": "What's your first impression of this design?" }
  ]
}
```

## Question types

All items share a few common fields:

<ResponseField name="text" type="string" required>
  The question text.
</ResponseField>

<ResponseField name="type" type="string" required>
  One of `open_ended`, `multiple_choice`, `ranking`, `matrix`, `max_diff`, `statement`.
</ResponseField>

<ResponseField name="externalId" type="string">
  Stable identifier for the item, unique within the payload. Auto-generated when omitted — set it only when a [conditional](#conditional-logic) or [`carryForwardFrom`](#carry-forward) needs to reference this item.
</ResponseField>

<ResponseField name="conditional" type="Conditional | null">
  Show this question only when the criteria match. See [Conditional logic](#conditional-logic).
</ResponseField>

<ResponseField name="media" type="Media[] | null">
  Images or videos shown with the question: `{ "name", "url", "type": "image" | "video", "widthPercentage"?, "forceWatching"? }`. `forceWatching` requires the participant to finish the video before answering.
</ResponseField>

<ResponseField name="embed" type="Embed | null">
  An embedded web page shown with the question: `{ "url", "proxyUrl"? }`.
</ResponseField>

### Open-ended

The AI interviewer asks the question conversationally and can probe with follow-ups.

<ResponseField name="followUp" type="string | null">
  Follow-up depth: `none`, `light`, `medium`, or `heavy`.
</ResponseField>

<ResponseField name="addInstructions" type="string | null">
  Extra instructions for the AI interviewer on how to probe this question.
</ResponseField>

<ResponseField name="preferredInput" type="string | null">
  `text`, `voice`, `screenRecording`, or `none`.
</ResponseField>

<ResponseField name="screenObservationEnabled" type="boolean | null">
  Lets the AI observe the participant's screen while they answer. Only applies when `preferredInput` is `screenRecording`.
</ResponseField>

```json Open-ended theme={null}
{
  "type": "open_ended",
  "text": "Tell me about the last time you switched coffee brands.",
  "followUp": "medium",
  "addInstructions": "Probe for what triggered the switch and what almost stopped them.",
  "preferredInput": "voice"
}
```

### Multiple choice

<ResponseField name="options" type="Option[]">
  Required unless `carryForwardFrom` is set. Each option: `{ "text", "externalId"?, "status"?, "exclusiveOption"? }`.
</ResponseField>

<ResponseField name="multiSelect" type="boolean | null">
  Allow selecting more than one option.
</ResponseField>

<ResponseField name="minSelect / maxSelect" type="integer | null">
  Bounds on how many options must be selected. Both must be set together (or both omitted), only valid when `multiSelect` is `true`, and `minSelect` ≤ `maxSelect`.
</ResponseField>

<ResponseField name="allowCustomOption" type="boolean | null">
  Adds an "Other" option with free-text input.
</ResponseField>

<ResponseField name="randomizeOptionOrder" type="boolean | null">
  Shuffle option order per participant.
</ResponseField>

<ResponseField name="pinnedFinalOption" type="boolean | null">
  Keep the last option in place when randomizing (e.g. "None of the above").
</ResponseField>

<ResponseField name="carryForwardFrom" type="string | null">
  `externalId` of an earlier multi-select multiple choice item; this question shows only the options the participant selected there. See [Carry-forward](#carry-forward).
</ResponseField>

An option with `exclusiveOption: true` clears and locks the other selections when picked (e.g. "None of the above"). It's only valid on the **final** option of a multi-select question.

```json Multiple choice theme={null}
{
  "externalId": "brands-used",
  "type": "multiple_choice",
  "text": "Which coffee brands have you bought in the last 3 months?",
  "multiSelect": true,
  "minSelect": 1,
  "maxSelect": 4,
  "randomizeOptionOrder": true,
  "pinnedFinalOption": true,
  "options": [
    { "externalId": "opt-blue-bottle", "text": "Blue Bottle" },
    { "externalId": "opt-stumptown", "text": "Stumptown" },
    { "externalId": "opt-lavazza", "text": "Lavazza" },
    { "text": "None of the above", "exclusiveOption": true }
  ]
}
```

### Ranking

Participants order the options. Supports `randomizeOptionOrder` and `carryForwardFrom` (rank only the options selected in an earlier multi-select question).

```json Ranking theme={null}
{
  "type": "ranking",
  "text": "Rank these brands from most to least trusted.",
  "carryForwardFrom": "brands-used"
}
```

### Matrix

A grid: each **row** is rated single-select across the **options** (columns).

```json Matrix theme={null}
{
  "externalId": "satisfaction-matrix",
  "type": "matrix",
  "text": "How satisfied are you with each of the following?",
  "options": [
    { "text": "Not satisfied" },
    { "text": "Somewhat satisfied" },
    { "text": "Very satisfied" }
  ],
  "rows": [
    { "text": "Taste" },
    { "text": "Price" },
    { "text": "Availability" }
  ]
}
```

### MaxDiff

Best/worst scaling across at least two options. See [MaxDiff analysis](/insights-and-reports/max-diff-questions) for how results are reported.

<ResponseField name="options" type="Option[]" required>
  The items being compared (minimum 2).
</ResponseField>

<ResponseField name="metric" type="string | null">
  What participants judge the items on (e.g. "importance", "appeal").
</ResponseField>

<ResponseField name="itemsPerScreen" type="3 | 4 | 5 | null">
  How many items are shown per comparison screen.
</ResponseField>

```json MaxDiff theme={null}
{
  "type": "max_diff",
  "text": "Which of these matters most and least when choosing a coffee?",
  "metric": "importance",
  "itemsPerScreen": 4,
  "options": [
    { "text": "Price" },
    { "text": "Origin" },
    { "text": "Roast level" },
    { "text": "Brand" },
    { "text": "Packaging" }
  ]
}
```

### Statement

Not a question — shows text (and optional media/embed) with a continue button. Use it for instructions or section intros.

<ResponseField name="continueButtonText" type="string">
  Custom label for the continue button.
</ResponseField>

```json Statement theme={null}
{
  "type": "statement",
  "text": "Next, we'll show you a few packaging designs. There are no right or wrong answers.",
  "continueButtonText": "Show me"
}
```

## Conditional logic

Any item — and any [concept](#concept-blocks) in a concept block — can carry a `conditional`: it's only shown when the criteria match. Criteria are combined with an `operator` (`and` / `or`).

```json Show only if they selected Blue Bottle theme={null}
{
  "type": "open_ended",
  "text": "What keeps you coming back to Blue Bottle?",
  "conditional": {
    "operator": "and",
    "criteria": [
      {
        "type": "selectedTemplateOptions",
        "questionId": "brands-used",
        "matchingCriteria": "mustSelect",
        "choices": ["opt-blue-bottle"]
      }
    ]
  }
}
```

Two criterion types:

**`selectedTemplateOptions`** — based on an answer to an earlier `multiple_choice` or `matrix` item.

<ResponseField name="questionId" type="string" required>
  `externalId` of an earlier `multiple_choice` or `matrix` item. For a conditional on a concept, the item must live in a block **before** the concept block.
</ResponseField>

<ResponseField name="matchingCriteria" type="string" required>
  `mustSelect` or `mustNotSelect`.
</ResponseField>

<ResponseField name="choices" type="string[]" required>
  Option `externalId`s (falls back to option text). For a matrix source, these are the columns.
</ResponseField>

<ResponseField name="matrixRow" type="string | null">
  Required only for a matrix source: the exact row text the criterion applies to.
</ResponseField>

**`searchParam`** — based on a URL parameter passed into the study link.

<ResponseField name="parameter" type="string" required>
  The query parameter name, e.g. `segment` in `https://listenlabs.ai/s/abc123?segment=pro`.
</ResponseField>

<ResponseField name="value" type="string" required>
  The value it must equal.
</ResponseField>

See also [Conditional logic](/setup-to-launch/conditional-logic) for how this behaves in the interview.

## Carry-forward

`multiple_choice` and `ranking` items can set `carryForwardFrom` to the `externalId` of an **earlier multi-select multiple choice** item. The question then only shows (or ranks) the options the participant actually selected there — so you can ask "which have you used?" followed by "which of *those* do you prefer?". When `carryForwardFrom` is set, omit `options`; they're inherited from the source question.

## External IDs

`externalId`s are stable handles you assign to blocks, items, options, and concepts. They're optional everywhere — the server auto-generates them when omitted — but you must set one on any item that a `conditional` (`questionId`) or `carryForwardFrom` references. All `externalId`s must be unique across the entire payload.

## Validation rules

Beyond field-level validation, the server enforces these cross-field rules, returning `400` with `code: invalid_study_guide` and a descriptive `error` message when violated:

* At most **one screening block**, and it must be the **first** block.
* Screening blocks may contain only `multiple_choice` items, and every option in them must carry a `status`; outside screening blocks, `status` must be null/omitted.
* Concept blocks require `conceptSamplingConfig` with at least one concept; other block types must omit it.
* `externalId`s must be unique across blocks, items, options, and concepts.
* `conditional.criteria[].questionId` and `carryForwardFrom` must reference the `externalId` of an **earlier** item (of the right type).
* A concept's `conditional` must reference an item in a block **before** the concept block.
* `minSelect`/`maxSelect` must both be set or both omitted, only when `multiSelect` is true, with `minSelect` ≤ `maxSelect`.
* `exclusiveOption` is only valid on the final option of a multi-select `multiple_choice` question.
