Normalized Interview Format
What Is This? When you export an interview from ThirdEar using the Export Interview dialog and select JSON , you get a file in the NormalizedInterview ...
What Is This?
When you export an interview from ThirdEar using the Export Interview dialog and select JSON, you get a file in the NormalizedInterview v2.0 format. This is ThirsEar's native data format — it's how every interview is stored internally in the database.
Think of it as the "master copy" of an interview. Every other representation — what you see in the editor UI, what runs during a live Pipecat session, or what gets exported to Qualtrics — is generated from this single format on the fly. This means the NormalizedInterview is always the source of truth.
The Big Picture

All of these are generated from the NormalizedInterview. None of them are stored separately. If someone edits an interview in the UI, the changes are converted back into this format before saving.
Format Overview
A NormalizedInterview JSON file has three top-level fields:
{
"version": "2.0",
"questions": [ ... ],
"metadata": { ... }
}
| Field |
|---|
When exported via the UI, there's also a small _export block with tracking info (interview ID, name, export timestamp). This is not part of the core format — it's just for bookkeeping.
Questions
Each question in the questions array looks like this:
{
"id": "QID1",
"type": "multiple_choice_single",
"text": "What is your age range?",
"choices": [
{ "id": "1", "display": "18-24", "order": 0 },
{ "id": "2", "display": "25-34", "order": 1 },
{ "id": "3", "display": "35-44", "order": 2 }
],
"required": true,
"order": 0,
"sectionId": "SID1",
"next": "__continue__"
}
Question Fields
Question Types
Choices (for Multiple Choice Questions)
Each choice in the choices array:
Scale (for Likert and Rating Questions)
{
"scale": {
"min": 1,
"max": 5,
"minLabel": "Strongly Disagree",
"maxLabel": "Strongly Agree"
}
}
Validation (for Voice/Text Input Questions)
{
"validation": {
"minLength": 10,
"maxLength": 500,
"pattern": "^[A-Za-z]+"
}
}
Metadata
The metadata object contains interview-level settings:
{
"metadata": {
"name": "Generation Z Research Study",
"description": "A study exploring Gen Z attitudes toward...",
"preamble": "You are a friendly research interviewer conducting...",
"language": "en",
"customStartMessage": "Hello! This is an interview for Generation Z. Are you ready to begin?",
"customEndMessage": "Thank you for participating!",
"sections": [
{ "id": "SID1", "name": "Demographics", "order": 0 },
{ "id": "SID2", "name": "Media Consumption", "order": 1 }
],
"voiceId": "a0e99841-438c-4a64-b679-ae501e7d6091",
"vadParams": { "stop_secs": 0.5 },
"maxSessionDuration": 1800,
"conversationSpeed": "normal"
}
}
Sections
Sections are optional groupings that organize questions into logical blocks (e.g. "Demographics", "Product Feedback", "Closing"). Each section has:
Flow Control
By default, the interview moves through questions sequentially — question 1, then 2, then 3, and so on. This is the "__continue__" behavior.
But you can create more complex flows:
The next Field (on Questions)
Every question has an optional next field that controls where to go after it's answered:
The transitionTo Field (on Choices)
For multiple choice questions, individual choices can override the question's next field. This enables conditional branching — e.g., "If they answer 'Yes', go to the follow-up section; if 'No', skip ahead."
{
"id": "QID5",
"type": "multiple_choice_single",
"text": "Have you used our product before?",
"choices": [
{ "id": "1", "display": "Yes", "order": 0, "transitionTo": "SID3" },
{ "id": "2", "display": "No", "order": 1, "transitionTo": "SID4" }
],
"required": true,
"order": 4,
"next": "__continue__"
}
In this example, even though the question's default next is "__continue__", if the participant chooses "Yes", the interview jumps to section SID3. If they choose "No", it jumps to SID4.
Priority: Choice transitionTo > Question next > Default (__continue__)
Full Example
Here's a minimal but complete NormalizedInterview:
{
"version": "2.0",
"questions": [
{
"id": "QID1",
"type": "voice_input_single",
"text": "What is your name?",
"required": true,
"order": 0,
"sectionId": "SID1",
"next": "__continue__"
},
{
"id": "QID2",
"type": "multiple_choice_single",
"text": "What is your age range?",
"choices": [
{ "id": "1", "display": "18-24", "order": 0 },
{ "id": "2", "display": "25-34", "order": 1 },
{ "id": "3", "display": "35-44", "order": 2 },
{ "id": "4", "display": "45+", "order": 3 }
],
"required": true,
"order": 1,
"sectionId": "SID1",
"next": "__continue__"
},
{
"id": "QID3",
"type": "voice_input_multiline",
"text": "Tell us about your experience with our product.",
"validation": {
"minLength": 20
},
"required": true,
"order": 2,
"sectionId": "SID2",
"next": "__continue__"
},
{
"id": "QID4",
"type": "likert_scale",
"text": "How satisfied are you overall?",
"scale": {
"min": 1,
"max": 5,
"minLabel": "Very Unsatisfied",
"maxLabel": "Very Satisfied"
},
"required": true,
"order": 3,
"sectionId": "SID2",
"next": "__end__"
}
],
"metadata": {
"name": "Product Feedback Interview",
"preamble": "You are a friendly researcher. Ask each question conversationally and listen carefully to responses.",
"language": "en",
"customStartMessage": "Hi there! Thanks for joining us today. Ready to get started?",
"customEndMessage": "That wraps up our interview. Thank you so much for your time!",
"sections": [
{ "id": "SID1", "name": "Demographics", "order": 0 },
{ "id": "SID2", "name": "Product Feedback", "order": 1 }
]
}
}
Importing This Format
A NormalizedInterview JSON file can be imported back into ThirdEar. The system auto-detects the format by checking for version: "2.0" and a questions array. No special steps needed — just upload the JSON file through the import flow. JSON file can be imported back into ThirdEar. The system auto-detects the format by checking for version: "2.0" and a questions array. No special steps needed — just upload the JSON file through the import flow.
The system also supports importing from other formats (Qualtrics QSF, SurveyJS, Google Forms), which are automatically converted to NormalizedInterview on import.
Source Code Reference
For developers who want to dig into the implementation: