| name | cognitive3d-public-api |
| description | Expert guide for the Cognitive3D (C3D) REST API — constructing requests, suggesting endpoints, and parsing responses. Use for C3D session data, gaze/fixation tracking, sensors, dynamic objects, objectives, ExitPoll surveys, or building dashboards from the C3D backend. Triggers on c3d, cognitive3d, c3ddev, XR/VR analytics, API key setup, or slicer query construction. |
| tags | ["analytics","data","query","api","xr","vr","gaze","sessions","cognitive3d","c3d"] |
Cognitive3D Public API Skill
Cognitive3D is an XR (VR/AR/MR) analytics platform. Its backend stores session recordings —
gaze, movement, events, sensor data, object interactions — that you can query to build dashboards,
reports, and data pipelines.
Environment & Base URL
| Environment | Base URL |
|---|
| Production | https://api.cognitive3d.com |
| Development | https://api.c3ddev.com |
All endpoints are under /v0/.
Authentication
Every request needs an Authorization header.
| Key format | Header value |
|---|
| New (issued after May 2025) | Authorization: orgkey-{your_key} |
| Legacy (issued before May 2025) | Authorization: APIKEY:ORGANIZATION {your_key} |
Keys are organization-scoped — one key unlocks all projects, scenes, and sessions in that org.
The Authorization header alone is sufficient for all API calls, including write operations.
Using an environment variable
To avoid exposing API keys in commands, before making any API calls, look for a value in the environment variable C3D_API_KEY and use that if it is present. Make sure not to print its value to the console. Check for its existence without printing it. In a curl command for example, use the variable rather than its value.
Using a curl config file
There is an alternate option for configuring the API key. A c3d_curlrc file can be used. If the user wants to use this approach, use it instead of the environment variable with all curl commands:
curl --config ./c3d_curlrc https://api.cognitive3d.com/v0/...
(In this example, the c3d_curlrc would be in our active directory. Use the appropriate path if it is somewhere else.)
If the user wants to take this approach, they should create a file named c3d_curlrc in their working directory with the following content:
# Cognitive3D API credentials
header = "Authorization: orgkey-YOUR_API_KEY_HERE"
This also keeps the API key out of command history and console output. If you ask the user for the API key yourself and add it to curl commands the normal way as a header, it will be visible in console output from the user.
Key IDs to Know
Before constructing any request, identify which IDs you're working with:
| ID | Type | Notes |
|---|
projectId | integer | Found in project URLs and responses |
sceneId | UUID string | Identifies a scene (environment) |
versionId | integer | A specific published version of a scene |
sessionId | SDK string | The ID assigned by the C3D SDK at session creation |
objectId / sdkId | string | Dynamic object identifier — use sdkId field (not id) in gaze queries |
Discovering your organization ID:
If you don't know your organization or project IDs, call:
GET /v0/organizations/apiKeys/whoami
This returns just the organization ID of the API key being used, which you can use to fetch project data as well via the other endpoints.
Choosing the Right Endpoint
Use this decision tree when the user describes what they want:
"List / browse sessions"
→ POST /v0/datasets/sessions/paginatedListQueries (paginated, with filters)
"All data for one specific session"
→ POST /v0/datasets/sessions/singleQueries (scene-scoped)
→ POST /v0/datasets/sessions/singleProjectSessionQueries (project-scoped)
"Raw gaze / fixation / event / sensor / position data for a session"
→ POST /v0/projects/:projectId/sessions/:sessionId/jsonRequests with jsonType
Values: GAZE, FIXATION, EVENTS, DYNAMICS, SENSORS, BOUNDARY, ALL
"Aggregated gaze stats across many sessions (how long did people look at object X?)"
→ POST /v0/datasets/sessions/slicerObjectMetricQueries
"Per-session gaze breakdown for a single object"
→ POST /v0/datasets/sessions/slicerSingleObjectPerSessionMetricQueries
"Analytics query / session counts / aggregations with filters"
→ POST /v0/datasets/sessions/slicerQueries
→ See slicer_query_doc.md for the full filter/aggregation syntax
"What filterable properties exist for this project?"
→ POST /v0/datasets/sessions/slicerPropertyNameQueries
Body: { "entityFilters": { "projectId": <int> } }
Returns lists of textual, numerical, and boolean session/event properties
"Project info + scene list"
→ GET /v0/projects/:projectId
"Training outcomes / pass-fail for a scenario"
→ GET /v0/versions/:versionId/sessions/:sessionId/objectiveData (per-session step results)
→ GET /v0/projects/:projectId/objectiveVersions/:objectiveVersionId/results.csv (bulk export)
"Survey responses (ExitPoll)"
→ GET /v0/projects/:projectId/questionSets/:name/:version/responses
Full endpoint catalog → endpoints.md
Common Request Patterns
Paginated session list
POST /v0/datasets/sessions/paginatedListQueries
{
"entityFilters": {
"projectId": "<int>",
"sceneId": "<uuid>", // optional
"versionId": "<int>" // optional
},
"page": 0,
"limit": 20,
"sort": "desc",
"orderBy": { "fieldName": "date", "fieldParent": "session" }
}
Raw data for a session
POST /v0/projects/:projectId/sessions/:sessionId/jsonRequests
{
"sessionId": "<sdkSessionId>",
"jsonType": "GAZE" // or FIXATION, EVENTS, DYNAMICS, SENSORS, BOUNDARY, ALL
}
Aggregated gaze on dynamic objects
POST /v0/datasets/sessions/slicerObjectMetricQueries
{
"entityFilters": { "projectId": <int>, "sceneId": "<uuid>", "versionId": <int> },
"gazeType": "gaze", // or "fixation"
"aggregations": "all",
"objectIds": ["<sdkId_1>", "<sdkId_2>"] // sdkId is a UUID string like "c34cbd8b-4fb9-..."
}
Object lookup: objectIds takes sdkId values (UUID strings), not friendly names. To find the sdkId for a named object, call:
GET /v0/versions/:versionId/objects
Response array has name (friendly) and sdkId (UUID) fields. Match on name, use sdkId in gaze queries.
Critical Gotchas
- Timestamps and durations are milliseconds —
date field values are Unix ms; duration field is also ms (60 seconds = 60000)
- Use
sdkId (UUID string) for objects in gaze queries, not the integer id field. Users often refer to objects by friendly name — look up the sdkId first via GET /v0/versions/:versionId/objects
- Test sessions are noise — nearly every analytics query should exclude them:
{ "field": { "fieldParent": "session", "nestedFieldName": "booleanSessionProp", "path": "c3d.session_tag.test" }, "op": "eq", "value": false }
sessionType — set to "project" for project-wide queries, "scene" when you have a sceneId
- The slicer filter schema is non-obvious — nested
fieldParent/nestedFieldName/path structure for session properties vs flat fieldName/fieldParent for built-in fields. See the Slicer Query System section below.
- Never guess property names — property paths (e.g.
c3d.app.version, c3d.session_tag.test) are not intuitive enough to infer. Before using a property in a query, either look it up in slicer_fields.yaml or discover it via POST /v0/datasets/sessions/slicerPropertyNameQueries (see slicer_api_guide.md). The built-in field names listed in this file (like date, duration, sessionId) are safe to use directly — it's the property path values that must be verified.
- Always make HTTP requests sequentially, never in parallel — When running queries across multiple projects, sessions, or endpoints, execute requests one at a time. Do not fire parallel requests even if asked to query "all projects" or collate data across many resources. This avoids rate limiting and ensures predictable behavior.
Missing Stuff
- More advanced routes and howto guides for user data are coming soon.
- More advanced routes and howto guides for objective results data are coming soon.
Response Parsing Tips
- Session list response: array of session objects with
sessionId, date, duration, userId, properties (nested object of all session properties)
- jsonRequests ALL response:
{ "data": { "gaze": [...], "fixations": [...], "events": [...], "dynamics": [...], "dynamics.manifest": {...}, "sensors": [...], "boundary": [...] } }
- Slicer query response (with
json0_key_y): For 0D (no slice-bys), the value is at aggregations.<name>.<op_name>.value. For 1D, aggregations.<name>.<op_name>.values is an array of { "x": <bucket>, "value": <number> }. For 2D, each entry has { "x": <bucket>, "values": { "<dim2_key>": <number>, ... } } — dimension keys are named, not positional. See output_types.md for full details.
- Gaze metric response:
{ "sessionCount": N, "metrics": { "<sdkId>": { "averageGazeLength": ..., "totalGazeCount": ..., ... } } }
Code Examples
For Python, JavaScript, and C# request examples → code-examples.md
Full Endpoint Catalog
For all endpoints organized by domain → endpoints.md
For a coverage table showing which endpoints have example responses → endpoint-coverage.md
When adding or updating any endpoint or example response in endpoints.md, update this table to match.
Slicer Query System
The slicer is the main analytics query engine. Endpoint: POST /v0/datasets/sessions/slicerQueries
Minimal request
{
"sessionType": "project",
"entityFilters": { "projectId": 541 },
"sessionFilters": [],
"aggregations": [
{
"name": "main",
"outputType": "json0_key_y",
"operations": [
{ "name": "session_count", "type": "sessionCount" }
]
}
]
}
Always include "sessionType": "project" — the default is scene sessions which is almost never what you want.
Always set "outputType": "json0_key_y" on every aggregation. The default "legacy" format you get if you omit this field uses positional arrays where dimension values are mapped by index to a separate labels array — this is error-prone, especially when a dimension has sparse buckets (e.g. only junk=true sessions exist but no junk=false). The json0_key_y format uses named keys instead, making each bucket self-describing and unambiguous. See output_types.md for all format options.
Field references
There are two patterns depending on whether you're referencing a built-in field or a property:
Built-in fields (top-level fields like date, duration, sessionId):
{ "fieldName": "date", "fieldParent": "session" }
Properties (free-form key/value pairs that vary per project):
{ "nestedFieldName": "textualSessionProp", "fieldParent": "session", "path": "c3d.app.version" }
Always use nestedFieldName for properties. Some commonly indexed properties also support unnestedFieldName, which is faster — but only if the property is marked unnested: true in slicer_fields.yaml. If you use unnestedFieldName on a property that isn't unnested, the query will silently return wrong results. When in doubt, nestedFieldName always works.
The six property field names are:
| nestedFieldName | Type | Parent |
|---|
textualSessionProp | string | session |
numericalSessionProp | number | session |
booleanSessionProp | boolean | session |
textualEventProp | string | event |
numericalEventProp | number | event |
booleanEventProp | boolean | event |
Session filters
sessionFilters is an array of filter objects, ANDed together. Each filter has a field reference, an op, and a value:
{ "field": { "fieldName": "date", "fieldParent": "session" }, "op": "gte", "value": 1747094400000 }
Ops: eq, neq, gt, gte, lt, lte, in (use values array), notIn (use values array), range/outsideRange (use min/max).
Compound filters use "op": "and" / "or" / "none" with a children array:
{
"op": "and",
"children": [
{ "op": "eq", "field": { "nestedFieldName": "booleanSessionProp", "path": "c3d.session_tag.test" }, "value": false },
{ "op": "eq", "field": { "nestedFieldName": "booleanSessionProp", "path": "c3d.session_tag.junk" }, "value": false }
]
}
Aggregation operations
| Type | Requires Field? | Notes |
|---|
sessionCount | No | Count sessions |
eventCount | No | Count events |
min | Yes | Requires numerical/integral field |
max | Yes | Requires numerical/integral field |
average | Yes | Requires numerical/integral field |
sum | Yes | Requires numerical/integral field |
Slice-bys (dimensions)
Operations produce a single number by default. Add sliceBys at the aggregation level (not inside the operation) to bucket the data. See slicer_query_doc.md for full syntax and examples.
Date histogram (data over time):
{ "name": "by_week", "timeUnit": "week", "timeValue": 1, "field": { "fieldName": "date", "fieldParent": "session" } }
Discrete field (split by values of a field):
{ "name": "by_version", "maxTerms": 64, "field": { "unnestedFieldName": "textualSessionProp", "path": "c3d.app.version" } }
When to load reference files
For complex queries (objective filters, user segmentation, event filters, compound filter logic, custom groups, histogram slice-bys) → slicer_query_doc.md
For discovering what fields/properties/values exist for a project before building a query → slicer_api_guide.md
For looking up a specific field name, property path, type, or display metadata → slicer_fields.yaml
For choosing or parsing output formats (legacy, json0_keyed, json0_list, json0_key_y) at different dimensionalities → output_types.md