Data Ingestion
Data ingestion endpoints receive raw signal data captured during a test session. Events, facial analysis frames, and voice segments are sent in batches. All three endpoints are write-only and return a confirmation with counts of accepted and rejected records.
Batch Format
Every ingestion request uses the same top-level structure:
{ "sessionId": "sess_001", "batch": [ ]}Items in batch that fail validation are rejected individually. The rest are accepted. The response always reports both counts.
Endpoints
/api/data/events JWT Ingest a batch of behavioral interaction events for a session
Each item in batch represents one discrete interaction event captured in the tester’s browser or device.
Request body
{ "sessionId": "sess_001", "batch": [ { "eventType": "click", "targetSelector": "#checkout-btn", "timestamp": "2026-03-16T10:14:32.120Z", "x": 540, "y": 210, "viewport": { "width": 1440, "height": 900 } }, { "eventType": "scroll", "targetSelector": "body", "timestamp": "2026-03-16T10:14:35.400Z", "scrollY": 820 }, { "eventType": "input", "targetSelector": "#email-field", "timestamp": "2026-03-16T10:14:40.000Z", "inputLength": 18 } ]}Event schema
| Field | Type | Required | Description |
|---|---|---|---|
eventType | string | Yes | One of: click, scroll, input, focus, blur, navigation, error |
targetSelector | string | Yes | CSS selector of the interacted element |
timestamp | string (ISO 8601) | Yes | When the event occurred |
x | number | No | Horizontal cursor position (pixels from left) |
y | number | No | Vertical cursor position (pixels from top) |
scrollY | number | No | Vertical scroll offset in pixels |
inputLength | number | No | Character count for input events (no raw content) |
viewport | object | No | { width, height } of the browser viewport |
Response
{ "success": true, "data": { "sessionId": "sess_001", "accepted": 3, "rejected": 0, "rejections": [] }, "error": null, "metadata": null}Rejection entries include index (position in batch) and reason:
{ "rejections": [ { "index": 2, "reason": "Missing required field: eventType" } ]}/api/data/facial JWT Ingest a batch of facial analysis frames for a session
Each item in batch represents one analyzed video frame. Raw video is never sent; only pre-processed emotion and attention scores are accepted.
Request body
{ "sessionId": "sess_001", "batch": [ { "frameIndex": 0, "timestamp": "2026-03-16T10:14:30.000Z", "emotions": { "neutral": 0.72, "surprise": 0.14, "confusion": 0.09, "frustration": 0.05 }, "attentionScore": 0.91, "gazeX": 0.53, "gazeY": 0.47 }, { "frameIndex": 1, "timestamp": "2026-03-16T10:14:31.000Z", "emotions": { "neutral": 0.45, "confusion": 0.38, "frustration": 0.17 }, "attentionScore": 0.74, "gazeX": 0.12, "gazeY": 0.80 } ]}Frame schema
| Field | Type | Required | Description |
|---|---|---|---|
frameIndex | integer | Yes | Sequential frame number within the session |
timestamp | string (ISO 8601) | Yes | When the frame was captured |
emotions | object | Yes | Emotion labels mapped to confidence scores (0.0 to 1.0). Scores must sum to approximately 1.0. |
attentionScore | number (0-1) | Yes | Estimated tester attention level |
gazeX | number (0-1) | No | Normalized horizontal gaze position |
gazeY | number (0-1) | No | Normalized vertical gaze position |
Response
{ "success": true, "data": { "sessionId": "sess_001", "accepted": 2, "rejected": 0, "rejections": [] }, "error": null, "metadata": null}/api/data/voice JWT Ingest a batch of voice analysis segments for a session
Each item in batch represents one transcribed and analyzed audio segment. Audio files are never sent; only text transcripts and derived metrics are accepted.
Request body
{ "sessionId": "sess_001", "batch": [ { "segmentIndex": 0, "startTime": "2026-03-16T10:14:30.000Z", "endTime": "2026-03-16T10:14:38.200Z", "transcript": "I'm trying to find the settings but I can't see where it is.", "sentiment": "negative", "sentimentScore": -0.62, "hesitationCount": 2, "wordsPerMinute": 124 }, { "segmentIndex": 1, "startTime": "2026-03-16T10:14:40.000Z", "endTime": "2026-03-16T10:14:48.500Z", "transcript": "Oh I see it now, it was hidden in the profile menu.", "sentiment": "positive", "sentimentScore": 0.71, "hesitationCount": 0, "wordsPerMinute": 138 } ]}Segment schema
| Field | Type | Required | Description |
|---|---|---|---|
segmentIndex | integer | Yes | Sequential segment number |
startTime | string (ISO 8601) | Yes | Segment start timestamp |
endTime | string (ISO 8601) | Yes | Segment end timestamp |
transcript | string | Yes | Transcribed text of the spoken segment |
sentiment | string | Yes | One of: positive, neutral, negative |
sentimentScore | number (-1 to 1) | Yes | Numeric sentiment score |
hesitationCount | integer | No | Count of hesitation markers (um, uh, etc.) |
wordsPerMinute | number | No | Speaking rate |
Response
{ "success": true, "data": { "sessionId": "sess_001", "accepted": 2, "rejected": 0, "rejections": [] }, "error": null, "metadata": null}