> ## Documentation Index
> Fetch the complete documentation index at: https://docs.listenlabs.com/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

| 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 |
| `creator` | object \| null | Study creator. Contains `name` and `email`, each of which may be null. |
| `folderPath` | string\[] \| null | Study folder path as an array of strings. |

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.