Model Session Details & Demographics
Introduction
This tutorial explains how to work with session-level details and participant demographics returned by the Results API. These data structures are rich and flexible, but they can be challenging to interpret and model correctly without guidance.
By the end of this tutorial, you will understand how to normalize session data, interpret task results, and safely join demographics to outcomes for enriched qualitative and quantitative analysis.
What you'll build
A clear data model and processing approach that:
- Retrieves detailed session data
- Interprets participant demographics correctly
- Understands task-group metadata and task response schemas
- Normalizes responses for analysis
- Avoids common pitfalls with nullable and optional fields
Target audience
- UX researchers
- Data analysts
- Research operations teams
- Analytics engineers supporting research
Prerequisites
- A valid access token (
ACCESS_TOKEN). Go to Authorization for details. - A known session ID, referred to as
SESSION_IDorsessionId. Use the GET /api/v2/sessionResults endpoint to find completed sessions within a test. - Familiarity with JSON data structures
Steps
Step 1 - Retrieve session details
Endpoint
GET /api/v3/sessionResults/SESSION_ID
Request sample
curl --location 'https://api.use2.usertesting.com/api/v3/sessionResults/SESSION_ID' \
--header 'Authorization: Bearer ACCESS_TOKEN'This endpoint returns all structured data collected during a session, including:
- Participant identifier
- Demographic answers
- Task-group metadata
- Task-by-task responses
- Test and audience metadata
Step 2 - Understand the session details structure
At a high level, the response contains:
{
"sessionId": "UUID",
"audienceId": "UUID",
"testPlanId": "UUID",
"sessionParticipant": { ... },
"taskGroups": [ ... ],
"tasks": [ ... ]
}Each section serves a different analytical purpose and should be modeled separately.
Step 3 - Interpreting participant demographics
Demographics are located under:
sessionParticipant.demographicsInfo[]Each demographic item includes:
id- demographic item identifiercode- standardized category (for example,GENDER,AGE_GROUP)label- human-readable questionvalue- participant's selected answer(s)type- single or multiple choice
Important characteristics
- All demographic fields may be nullable
- Multiple answers may be comma-separated
- Not all sessions include demographics
participantIdis its own field and should be treated independently fromsessionId
Recommended normalized table
demographics
- session_id
- participant_id
- demographic_code
- label
- value
- question_typeStep 4 - Understanding task-group metadata
Task-group metadata is stored in:
taskGroups[]Each task-group item includes:
uuid- task-group identifiertype- normalized task-group type such asQX_SCOREorBALANCED_COMPARISONresponse- type-specific metadata payload
Examples
QX_SCORE->response.scoreBALANCED_COMPARISON->response.sequence
Use taskGroups[] when you want session-level score or comparison metadata rather than a single task response.
Recommended normalized table
task_groups
- session_id
- participant_id
- task_group_uuid
- task_group_type
- task_group_response_jsonStep 5 - Understanding task responses
Per-task responses are stored in:
tasks[]Each task item includes:
uuid- task item identifiertext- task prompt texttype- normalized task type such asRATING_SCALE,MULTIPLE_CHOICE,WRITTEN,NPS,IMAGE,URL,RANK_ORDER,FIGMA, orMATRIXresponse- task response payload, including timing and answer data
When present, tasks[].response includes:
startTimeMsendTimeMsskippedanswer
The answer object is task-type-specific and should be treated as semi-structured JSON.
Step 6 - Mapping v3 types to responses
The examples below show common normalized type values in the v3 payload:
RATING_SCALEMULTIPLE_CHOICEWRITTENNPS
Task-group type values are separate and include:
QX_SCOREBALANCED_COMPARISON
Key rule
Never assume a fixed schema for tasks[].response.answer.
Instead:
- Inspect
type - Parse the corresponding answer shape dynamically
- Store raw answers as JSON when possible
Recommended normalized table
tasks
- session_id
- participant_id
- task_uuid
- task_text
- task_type
- start_time_ms
- end_time_ms
- skipped
- answer_jsonStep 7 - Joining demographics to outcomes
To analyze outcomes by demographic segment:
- Carry
session_idandparticipant_idinto your flattened task rows - Join
tasks.participant_idortasks.session_idto the corresponding demographic or session tables
Do not join task UUIDs to participant identifiers. Task IDs identify tasks, not people.
Example questions enabled
- Do younger participants struggle more with Task A?
- Does sentiment differ by income group?
- Which demographic segments fail navigation tasks most often?
Step 8 - Handling nullable and optional fields
The Results API is intentionally flexible. As a result:
- IDs may be
null - Arrays may be empty
- Fields may be missing depending on test design
- Some tasks may have
response: null
Best practices:
- Treat all fields as optional
- Use defensive parsing
- Avoid hard assumptions in ETL logic
Common pitfalls
| Pitfall | Recommendation |
|---|---|
| Assuming all sessions have demographics | Always check for nulls |
Mixing up taskGroups and tasks | Use taskGroups for metadata and tasks for work |
| Hard-coding task response schemas | Branch logic by type |
| Losing join keys during flattening | Persist session_id and participant_id in rows |
| Losing raw responses | Store original JSON |
What you can build next
Once session details are modeled correctly, you can:
- Segment insights by demographic group
- Combine task outcomes with QXscore metrics
- Feed structured responses into AI pipelines
- Power detailed dashboards and reports
Summary
You now have a clear approach to:
- Interpret the v3 session-details structure
- Normalize demographics, task-group metadata, and task responses
- Join participant context to outcomes safely
- Avoid common modeling and parsing errors
