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

# Data Map

> How the public API entities relate to each other and which fields to use as join keys.

## Join Keys

<Note>
  Fields below reflect the versioned endpoints (`/api/public/v1/...`), which
  use camelCase. The legacy unversioned endpoints return the same fields in
  snake\_case (e.g. `answer_id`) and are deprecated starting
  **August 1, 2026**.
</Note>

| From     | To            | Join field                  | Notes                                                                                                                                   |
| -------- | ------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Study    | Response      | `linkId`                    | Path param in `GET /api/public/v1/responses/{linkId}`. Accepts the study's `linkId` or its UUID `id`. Single response returns `linkId`. |
| Study    | Question      | `id`                        | Path param in `GET /api/public/v1/studies/{studyId}/questions`. Accepts the study's UUID `id` or its `linkId`.                          |
| Question | Answer        | `discussionGuideQuestionId` | `Question.id` = `Answer.discussionGuideQuestionId`                                                                                      |
| Question | TranscriptRow | `discussionGuideQuestionId` | `Question.id` = `TranscriptRow.discussionGuideQuestionId`                                                                               |
| Answer   | TranscriptRow | `answerId`                  | One answer can have multiple transcript rows (follow-ups where `isFollowUp: true`).                                                     |
| Question | Concept       | `concepts`                  | Array on the Question object. `conceptId` on Answer/TranscriptRow links to `Concept.id`. Empty when the question has no concepts.       |

## Entity Fields

### Study

Returned by [List Studies](/api-v2/list-studies).

| Field           | Type   | Description                                                                                       |
| --------------- | ------ | ------------------------------------------------------------------------------------------------- |
| `id`            | uuid   | Permanent study identifier — the path param for launch, also accepted for questions and responses |
| `linkId`        | string | Editable URL slug — accepted by the questions and response endpoints                              |
| `title`         | string | Study title                                                                                       |
| `responseCount` | number | Completed response count                                                                          |
| `createdAt`     | string | UTC timestamp                                                                                     |

### Response

Returned by [Get Responses](/api-v2/list-responses). Each response belongs to a study via `linkId`.

| Field                     | Type             | Description                                                                                  |
| ------------------------- | ---------------- | -------------------------------------------------------------------------------------------- |
| `id`                      | uuid             | Response identifier                                                                          |
| `readableId`              | number           | Order within the study                                                                       |
| `progress`                | string           | `"complete"`, `"screened_out"`, or `"in_progress"`                                           |
| `responseDurationSeconds` | number           | Total duration                                                                               |
| `qualityScore`            | number \| string | Quality rating                                                                               |
| `answers`                 | object           | Keyed by question text, plus a `"Summary"` key (e.g. `"Summary"`, `"Q1: What is your age?"`) |
| `answersArray`            | Answer\[]        | Structured answers, joinable to questions and transcript                                     |
| `urlParams`               | object           | URL params passed into the study                                                             |
| `tags`                    | string\[]        | Keywords                                                                                     |
| `tagline`                 | string \| null   | One-line synthesis                                                                           |
| `bulletSummary`           | string\[]        | Bullet-point summary                                                                         |
| `shortTranscript`         | string \| null   | Whole conversation with assistant messages shortened to a few words                          |
| `shortAssistantMessages`  | string\[]        | Condensed assistant turns (optional)                                                         |
| `otherRemarks`            | string           | Additional remarks (optional)                                                                |
| `personas`                | string           | Persona classification (optional)                                                            |
| `createdAt`               | string           | UTC timestamp                                                                                |
| `updatedAt`               | string           | UTC timestamp                                                                                |

### Answer

Nested inside `Response.answersArray`.

| Field                       | Type           | Description                                                                                |
| --------------------------- | -------------- | ------------------------------------------------------------------------------------------ |
| `answerId`                  | string         | **Join key** — matches `TranscriptRow.answerId`                                            |
| `discussionGuideQuestionId` | string \| null | **Join key** — matches `Question.id`. Null when the answer isn't tied to a guide question. |
| `conceptId`                 | string \| null | Links to `Concept.id` when the question involves concept testing                           |
| `question`                  | string         | Question text                                                                              |
| `answer`                    | string         | Answer text                                                                                |

### TranscriptRow

Returned by [Get Single Response](/api-v2/get-response) inside the `transcript` array.

| Field                       | Type           | Description                                                                                                   |
| --------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------- |
| `moderator`                 | string         | Assistant/moderator text                                                                                      |
| `user`                      | string         | Participant text                                                                                              |
| `discussionGuideQuestionId` | string \| null | **Join key** — matches `Question.id`. Null for non-question rows (e.g. intro).                                |
| `answerId`                  | string \| null | **Join key** — matches `Answer.answerId`. Null for non-question rows (e.g. intro).                            |
| `conceptId`                 | string \| null | Links to `Concept.id`                                                                                         |
| `responseIndex`             | number         | Zero-based row index                                                                                          |
| `isFollowUp`                | boolean        | `true` when this row is a follow-up to the same question as the previous row                                  |
| `audio`                     | string \| null | Signed URL (\~1 hour validity)                                                                                |
| `video`                     | object \| null | Camera recording — contains `streamUrl` (HLS) and `mp4Url`                                                    |
| `screenVideo`               | object \| null | Screen recording (when the study captured the participant's screen) — contains `streamUrl` (HLS) and `mp4Url` |

### Question

Returned by [Get Study Questions](/api-v2/get-study-questions).

| Field                      | Type           | Description                                                                                  |
| -------------------------- | -------------- | -------------------------------------------------------------------------------------------- |
| `id`                       | string         | **Join key** — referenced as `discussionGuideQuestionId` on answers and transcript rows      |
| `text`                     | string         | Question text shown to participants                                                          |
| `type`                     | string         | `open_ended`, `file_upload`, `multiple_choice`, `ranking`, `statement`, `matrix`, `max_diff` |
| `questionNumber`           | number         | Display order                                                                                |
| `isScreener`               | boolean        | Whether this is a screener question                                                          |
| `options`                  | string\[]      | Choices (for `multiple_choice`, `ranking`, and `max_diff` types)                             |
| `isMultiSelect`            | boolean        | Whether multiple selections are allowed (`multiple_choice` and `matrix` only)                |
| `rows`, `columns`          | string\[]      | Matrix rows and columns (`matrix` only)                                                      |
| `metric`, `itemsPerScreen` | string, number | MaxDiff metric and items shown per screen (`max_diff` only)                                  |
| `concepts`                 | Concept\[]     | Concept definitions attached to this question                                                |

### Concept

Nested inside `Question.concepts`.

| Field         | Type           | Description                                           |
| ------------- | -------------- | ----------------------------------------------------- |
| `id`          | string         | Referenced by `conceptId` on Answer and TranscriptRow |
| `title`       | string         | Concept title                                         |
| `description` | string         | Concept description                                   |
| `media`       | Media\[]       | Attached images/videos (`type`, `name`, `url`)        |
| `embedUrl`    | string \| null | External embed (e.g. Figma link)                      |

## Traversal Examples

### Get all answers for a study, grouped by question

1. `GET /api/public/v1/studies` — find the study's `id` and `linkId`
2. `GET /api/public/v1/studies/{studyId}/questions` — get all questions (using the `id`)
3. `GET /api/public/v1/responses/{linkId}` — get all responses
4. Join each `answersArray[]` item to its question using `discussionGuideQuestionId` = `Question.id`

### Match transcript audio/video to specific questions

1. `GET /api/public/v1/responses/{linkId}/{responseId}` — get the full transcript
2. `GET /api/public/v1/studies/{studyId}/questions` — get question definitions
3. Each transcript row's `discussionGuideQuestionId` tells you which question it belongs to
4. Use `audio`, `video`, and `screenVideo` fields on the transcript row to access the media for that exchange

### Group transcript rows by answer (including follow-ups)

1. `GET /api/public/v1/responses/{linkId}/{responseId}` — get the full transcript
2. Group transcript rows by `answerId` — rows sharing the same `answerId` belong to the same answer, with follow-ups marked by `isFollowUp: true`
3. Cross-reference with `answersArray` from the list endpoint using the same `answerId` to get the extracted answer text
