Overview
The Walnut Customer Data API gives you programmatic, self-service access to your demo analytics data. Query session-level insights, retrieve engagement metrics, and feed data directly into your CRM, BI tools, internal dashboards, or AI-powered workflows.
This initial release exposes demo session data. Additional endpoints (demo metadata, playlist sessions, admin analytics) will be introduced in future releases.
Key Capabilities
- Query individual demo sessions with filtering, pagination, and field selection
- Aggregate session data by date, user type, viewer type, demo, or embed status
- Retrieve pre-built analytics summaries with totals, averages, and top-10 breakdowns
- Integrate demo data into any system that can make HTTP requests
Authentication
All API requests (except the health check endpoint) require authentication via an API key passed in the x-api-key request header.
Making an Authenticated Request
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?limit=10>"
⚠️ Your API key is provided once by the Walnut team and cannot be recovered if lost. Store it securely. If you need a new key, contact your Walnut account representative.
Rate Limits
The API enforces the following rate limits per API key:
| Limit | Value |
|---|---|
| Sustained throughput | 50 requests/second |
| Burst allowance | 100 requests/second |
| Max rows per request | 10,000 |
Requests exceeding the rate limit will receive a 429 Too Many Requests response. Implement exponential backoff in your integration.
Endpoints
| Path | Method | Auth | Description |
|---|---|---|---|
/health | GET | None | Health check |
/demo-sessions | GET | x-api-key | Query demo sessions with filtering, pagination, field selection, and aggregation |
/demo-sessions/summary | GET | x-api-key | Pre-built analytics summary (totals, averages, top demos, top countries) |
GET /demo-sessions
Retrieve demo session records with support for filtering, pagination, field selection, and aggregation.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
fields | string | (all fields) | Comma-separated list of fields to return |
limit | integer | 1,000 | Max rows to return (maximum: 10,000) |
offset | integer | 0 | Pagination offset |
start_date | YYYY-MM-DD | (none) | Include sessions from this date (inclusive) |
end_date | YYYY-MM-DD | (none) | Include sessions up to this date (inclusive) |
user_type | string | all | Filter by user type: "external", "internal", or "all" |
demo_id | UUID | (none) | Filter sessions for a specific demo |
group_by | string | (none) | Aggregate results by: date, user_type, viewer_type, demo_id, is_embed |
Available Fields (29)
Use the fields parameter to select only the data you need. If omitted, all fields are returned.
| Category | Fields |
|---|---|
| Identifiers | id, demo_id, company_id |
| Classification | viewer_type, user_type |
| Timing | started_at, finished_at |
| Session State | is_bounced, is_completed, is_embed, is_offline |
| Engagement | interaction_count, screen_views_count, annotation_views_count, guide_views_count |
| Geography | geo_country_code, geo_country_name, geo_city |
| Context | origin_url, demo_owner_name, demo_name |
| Lead Form | lead_form_exists, lead_form_opened, lead_form_opened_at, lead_form_skipped, lead_form_skipped_at, lead_form_submitted, lead_form_submitted_at |
| Gates | email_gate_exists, access_gate_exists |
Aggregation
Pass one or more dimensions to group_by (comma-separated) to receive aggregated counts instead of individual session records. For example, group_by=date,user_type returns session counts grouped by both date and user type.
Response Schema
Standard response (individual sessions):
{
"data": [
{
"id": "7b4ab219-ab64-4ae5-8a96-601237c212c1",
"demo_id": "bacdb032-c3a2-4a38-9b85-a45a5e3639f8",
"company_id": "3003886f-9618-46b9-9baf-dcbba56a891a",
"viewer_type": "prospect",
"user_type": "external",
"started_at": "2026-03-08T10:37:25.286Z",
"finished_at": null,
"is_bounced": false,
"is_completed": false,
"interaction_count": 0,
"screen_views_count": 1,
"geo_country_code": "USA",
"geo_country_name": "United States",
"geo_city": "Dallas",
"is_embed": true,
"is_offline": false,
"origin_url": "<https://app.teamwalnut.com/player/>...",
"demo_owner_name": "Jane Smith",
"lead_form_exists": false,
"lead_form_opened": false,
"lead_form_opened_at": null,
"lead_form_skipped": false,
"lead_form_skipped_at": null,
"lead_form_submitted": false,
"lead_form_submitted_at": null,
"email_gate_exists": false,
"access_gate_exists": false,
"annotation_views_count": 1,
"guide_views_count": 1,
"demo_name": "Product Overview Demo"
}
],
"count": 1,
"total": 253852,
"limit": 1,
"offset": 0
}
count= rows returned in this response page.total= total matching rows across all pages. Use these for pagination logic.
Single-dimension aggregation (group_by=date):
{
"data": {
"2026-03-01": 669,
"2026-03-02": 822,
"2026-03-03": 471
},
"total": 1962,
"group_by": ["date"]
}
Multi-dimension aggregation (group_by=date,user_type):
{
"data": {
"2026-03-01|external": 669,
"2026-03-02|external": 820,
"2026-03-02|internal": 2,
"2026-03-03|external": 471
},
"total": 1962,
"group_by": ["date", "user_type"]
}
Composite keys use the
|(pipe) delimiter. Dimensions with zero sessions are omitted from the response — absence means zero.
Sort Order
Results are returned newest-first (descending by started_at). There is currently no parameter to change sort order.
Field Types, Values & Definitions
Every field returned by /demo-sessions is documented below with its data type, possible values, and what it means in the context of Walnut demo analytics.
| Field | Type | Values / Format | Definition |
|---|---|---|---|
id | UUID | "7b4ab219-ab64-..." | Unique session identifier |
demo_id | UUID | "bacdb032-c3a2-..." | The demo that was viewed |
company_id | UUID | "3003886f-9618-..." | Your Walnut company/account ID |
viewer_type | string | "prospect" | "owner" |
user_type | string | "external" | "internal" |
started_at | ISO 8601 | "2026-03-08T10:37:25.286Z" | When the session began (always present) |
finished_at | ISO 8601 or null | "2026-03-08T10:42:00.000Z" or null | When the session ended. null means the session is still open or was abandoned without a completion event. |
is_bounced | boolean | true / false | The viewer exited after the first screen with no further interaction. Use bounce rate (bounced / total) to measure first-screen effectiveness and gating friction. |
is_completed | boolean | true / false | The viewer reached the end of the demo flow (viewed all screens). Use completion rate (completed / total) to measure narrative quality and content stickiness. |
is_embed | boolean | true / false | true = demo was viewed inside an embedded iframe (e.g., on your website). false = viewed via direct link or Walnut player. |
is_offline | boolean | true / false | Whether the session was an offline/downloaded demo view |
interaction_count | integer | 0+ | Total interactive clicks during the session — includes FAB (Floating Action Button) clicks, annotation clicks, and guide step advances. A proxy for engagement depth. |
screen_views_count | integer | 0+ | Number of demo screens viewed in the session. Compare to total screens in the demo to calculate screens viewed (%). |
annotation_views_count | integer | 0+ | Number of annotation overlays (tooltips, modals, highlights on screens) the viewer saw. Annotations are the individual visual elements placed on screens to explain features. |
guide_views_count | integer | 0+ | Number of guide chapters (not individual steps) viewed. Guides are the structured narrative flows that walk viewers through a demo story. Measured at chapter level, not step level. |
geo_country_code | string | "USA", "GBR", etc. | ISO 3166-1 alpha-3 country code based on viewer IP |
geo_country_name | string | "United States" | Full country name |
geo_city | string or null | "Dallas" or null | City based on IP geolocation. null when unavailable. |
origin_url | string | Full URL | The URL where the demo was loaded from (Walnut player URL, or the page where the embed lives) |
demo_owner_name | string | Display name | The Walnut team member who owns/created this demo |
demo_name | string | Demo title | The name of the demo as set in the Walnut builder |
lead_form_exists | boolean | true / false | Whether this demo has a lead form configured |
lead_form_opened | boolean | true / false | Whether the lead form was displayed to the viewer |
lead_form_opened_at | ISO 8601 or null | Timestamp or null | When the lead form was displayed |
lead_form_skipped | boolean | true / false | Whether the viewer dismissed the form without submitting |
lead_form_skipped_at | ISO 8601 or null | Timestamp or null | When the form was skipped |
lead_form_submitted | boolean | true / false | Whether the viewer submitted the lead form. Calculate lead form conversion as submitted / opened. |
lead_form_submitted_at | ISO 8601 or null | Timestamp or null | When the form was submitted |
email_gate_exists | boolean | true / false | Whether this demo requires an email gate (viewer must enter email before accessing the demo) |
access_gate_exists | boolean | true / false | Whether this demo has an access gate (password or approval required) |
GET /demo-sessions/summary
Returns a pre-built analytics summary with 7 parallel aggregations, giving you a comprehensive snapshot of your demo performance.
Summary Includes
- Period — the date range covered (auto-scopes to last 12 months if no date filters provided)
- Totals — sessions, completed, bounced
- Averages — duration (seconds), interactions, screen views
- By user type — count distribution across user types (
external,internal) - By viewer type — count distribution across viewer types (
prospect,owner,colleague_member,colleague_non_member) - By embed status —
embeddedvs.directsession counts - Top 10 demos — ranked by session count (includes
demo_id,demo_name,sessions) - Top 10 countries — ranked by session count (includes
country,sessions)
Optional Filters
The summary endpoint accepts start_date and end_date query parameters to scope the summary to a specific date range. When omitted, defaults to the last 12 months.
Response Schema
{
"period": {
"start": "2025-03-08",
"end": "2026-03-08"
},
"totals": {
"sessions": 253852,
"completed": 253835,
"bounced": 218118
},
"averages": {
"duration_seconds": 21,
"interactions": 0.4,
"screen_views": 1.7
},
"by_user_type": {
"external": 245083,
"internal": 8769
},
"by_viewer_type": {
"colleague_member": 5298,
"colleague_non_member": 811,
"owner": 2660,
"prospect": 245083
},
"by_embed": {
"direct": 24589,
"embedded": 229263
},
"top_demos": [
{
"demo_id": "4c1e1a49-ba4d-4951-9f6a-6005648e8eea",
"demo_name": "Product Overview Demo",
"sessions": 31668
}
],
"top_countries": [
{
"country": "United States",
"sessions": 107132
}
]
}
Note:
averages.duration_secondsis an integer.averages.interactionsandaverages.screen_viewsare floats.
Examples
All examples use the production base URL. Replace YOUR_API_KEY with the key provided by your Walnut account team.
Health Check (no authentication required)
curl <https://customer-api.teamwalnut.com/health>
Response:
{ "status": "ok" }
Retrieve Last 10 Sessions
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?limit=10>"
Filter by Date Range and User Type
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?start_date=2025-01-01&end_date=2025-12-31&user_type=external&limit=50>"
Select Specific Fields
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?fields=id,demo_id,started_at,is_completed,user_type&limit=20>"
Aggregate by Date
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?start_date=2025-01-01&group_by=date>"
Multi-Dimensional Aggregation
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?group_by=date,user_type>"
Analytics Summary
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions/summary>"
Summary with Date Filter
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions/summary?start_date=2025-01-01>"
Filter by Specific Demo
curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?demo_id=YOUR_DEMO_UUID&limit=100>"
Pagination
The /demo-sessions endpoint returns up to limit rows per request (default 1,000; maximum 10,000). Use the offset parameter to paginate through larger datasets.
Example: Paginating Through Results
# Page 1 (rows 0-999) curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?limit=1000&offset=0>" # Page 2 (rows 1000-1999) curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?limit=1000&offset=1000>" # Page 3 (rows 2000-2999) curl -H "x-api-key: YOUR_API_KEY" \\ "<https://customer-api.teamwalnut.com/demo-sessions?limit=1000&offset=2000>"
Continue incrementing offset by your limit value until fewer rows than limit are returned, indicating you have reached the end of the dataset.
Error Handling
The API uses standard HTTP status codes. Error responses return a JSON body with a descriptive message.
| Status Code | Meaning | Common Cause |
|---|---|---|
| 200 | Success | Request completed successfully |
| 400 | Bad Request | Invalid date format, unknown field name, or malformed parameter |
| 401 | Unauthorized | Missing or invalid API key |
| 429 | Too Many Requests | Rate limit exceeded; retry with exponential backoff |
| 500 | Internal Server Error | Unexpected server issue; contact support if persistent |
Error response body:
{ "error": "Invalid fields requested" }
ℹ️ Error responses never expose internal details. If you receive a 500 error consistently, contact your Walnut account team with the request URL and timestamp.
What Can You Do With This Data?
The fields returned by the API map directly to actionable metrics. Here are recipes your team can implement immediately.
Key Metrics You Can Calculate
| Metric | Formula | Why It Matters |
|---|---|---|
| Bounce Rate | is_bounced=true count / total sessions | Measures first-screen effectiveness. High bounce = weak opener or too-early gating. |
| Completion Rate | is_completed=true count / total sessions | Measures narrative quality. Higher completion = story resonates and holds attention. |
| Lead Form Conversion | lead_form_submitted / lead_form_opened | Is your form friction too high? Low conversion suggests the form appears too early or asks too much. |
| Engagement Depth | interaction_count • screen_views_count per session | Are prospects exploring or skimming? Higher depth = stronger buying intent. |
| Internal vs External Split | Filter by user_type | High internal usage may mean reps are previewing, not sending to prospects. Track the ratio over time. |
| Embed Performance | Compare is_embed=true vs false sessions | Are website embeds converting better than direct links? Optimize placement accordingly. |
| Identification Coverage | Sessions with email_gate_exists=true or lead_form_exists=true / total | The more viewers you can identify, the more complete your attribution picture becomes. Aim for 70–80% coverage. |
| Top Demos by Engagement | Sort by avg interaction_count, not just session count | Volume ≠ quality. A demo with fewer sessions but high interaction may be your best closer. |
Common Integration Patterns
- CRM sync: Pull sessions daily, match
origin_urlor lead form data to opportunities in Salesforce/HubSpot. Usedemo_nameanddemo_owner_namefor attribution. - BI dashboards: Use
group_by=datefor trend charts,/demo-sessions/summaryfor executive snapshots. Theperiodobject tells you the exact date range covered. - Hot lead alerts: Poll for sessions where
is_completed=trueANDinteraction_count > 5to notify sales of high-intent viewers. - Re-engagement triggers: Flag open deals where recent sessions show
is_bounced=true— send a shorter, more targeted demo or playlist. - Embed optimization: Compare
by_embeddata in the summary to decide whether to invest in more website embeds or direct-link campaigns. - Pagination loop: Increment
offsetby yourlimitvalue untilcount < limit, indicating you’ve reached the end of the dataset.
Current Limitations
This is the initial release of the Customer Data API. Please note the following current constraints:
- Read-only access: the API supports data retrieval only; there are no write endpoints.
- Maximum of 10,000 rows per request. Use pagination for larger datasets.
- Offset-based pagination only. There is no cursor-based pagination. For very large datasets, offset pagination may slow down at high offsets.
- API key changes (including revocations) may take up to 5 minutes to take effect due to authorization caching.
- No self-service key rotation. If you lose your API key, contact your Walnut account representative for a new one.
- Demo session data only. Additional data types (demo metadata, playlist sessions, admin analytics) will be added in future releases.
- Fixed sort order. Results are always returned newest-first by
started_at. Custom sorting is not yet supported. - Summary auto-scopes to 12 months. When no
start_date/end_dateis provided,/demo-sessions/summarydefaults to the last 12 months.
Support
If you need assistance with the Customer Data API, your Walnut account team is here to help.
- API key requests: Contact your Walnut account representative to provision a new key or revoke an existing one.
- Technical issues: Include the request URL, timestamp, and HTTP status code when reporting problems.
- Feature requests: Let your account team know what additional data or capabilities would be valuable for your use case.