Access survey analytics including response counts, completion rates, timing data, and question-level statistics.
/surveys/:id/analyticsRetrieve comprehensive analytics for a survey.
curl -X GET "https://app.surventrics.ai/api/v1/surveys/SURVEY_ID/analytics" \
-H "Authorization: Bearer YOUR_API_KEY"{
"surveyId": "550e8400-e29b-41d4-a716-446655440000",
"responseStats": {
"total": 250,
"completed": 198,
"abandoned": 42,
"inProgress": 10,
"completionRate": 79.2,
"responseCount": 196,
"viaAgents": 3
},
"timing": {
"averageCompletionSeconds": 245,
"medianCompletionSeconds": null
},
"questions": [
{
"questionId": "q_001",
"questionTitle": "How satisfied are you with our service?",
"questionType": "single_choice",
"responseCount": 195,
"skipRate": 1.5,
"skippedOfShown": { "skipped": 3, "shown": 198 },
"skipRateOfShown": 1.5,
"skipsAvailability": "available"
},
{
"questionId": "q_002",
"questionTitle": "How likely are you to recommend us?",
"questionType": "nps",
"responseCount": 198,
"skipRate": 0.0,
"skippedOfShown": null,
"skipRateOfShown": null,
"skipsAvailability": "not_skippable"
}
],
"daily": [
{ "date": "2024-01-15", "responses": 12, "completions": 10 },
{ "date": "2024-01-16", "responses": 25, "completions": 22 },
{ "date": "2024-01-17", "responses": 18, "completions": 15 }
],
"generatedAt": "2024-01-21T09:00:00Z"
}analytics:read| Field | Type | Description |
|---|---|---|
total | integer | Total response sessions started |
completed | integer | Fully completed responses |
abandoned | integer | Started but never finished |
inProgress | integer | Currently in progress |
completionRate | number | Percentage of completions (0-100) |
responseCount | integer | The survey's responses: completed sessions that are not excluded. The same figure as responseCount on GET /surveys/:id, so it can be below completed |
viaAgents | integer | Of responseCount, how many came through a person's declared AI agent ("196 responses, of which 3 via agents"). Always present, 0 when none |
| Field | Type | Description |
|---|---|---|
averageCompletionSeconds | integer | Mean time to complete in seconds |
medianCompletionSeconds | integer | Median time to complete (may be null) |
Array of per-question statistics:
| Field | Type | Description |
|---|---|---|
questionId | string | Question UUID |
questionTitle | string | Question text |
questionType | string | Question type |
responseCount | integer | Number of responses |
skipRate | number | Deprecated: use skipRateOfShown. Completed sessions without an answer to the question, as a percentage of completed sessions (0-100), for every question, so a question logic hid, a required one or one that takes no answer reads as skipped. It will be removed in a later API version. |
skippedOfShown | object | null | { skipped, shown }: of the sessions shown the question that moved past it, how many left it blank. A session that left the survey at the question is dropout, in neither count. Null unless skipsAvailability is available. |
skipRateOfShown | number | null | skipped as a percentage of shown (0-100, one decimal). Null when there is no figure. |
skipsAvailability | string | available; not_skippable (required, or takes no answer); or not_available (no session recorded every question it was shown, as embed sessions do not yet). Never read not_available as 0. |
Daily response counts for the last 30 days:
| Field | Type | Description |
|---|---|---|
date | string | Date in YYYY-MM-DD format |
responses | integer | Total responses started |
completions | integer | Completed responses |
With include=devices or include=geography: sessions by device type or by country.
| Field | Type | Description |
|---|---|---|
devices[] / geography[] | array | Deprecated shape: sessions with no device or country recorded appear as an "unknown" (devices) or "Unknown" (geography) row, and every percentage is over all sessions. Kept until a later API version. Use devicesByValue / geographyByValue and the excluded counts instead. |
devicesByValue / geographyByValue | array | Sessions WITH a device or country recorded, by value, each percentage over them: the app's figures. Read these instead of devices and geography. |
devicesExcludedCount | integer | With include=devices: sessions with no device recorded. They are not a segment, so leave them out of any comparison and say how many. |
geographyExcludedCount | integer | With include=geography: sessions with no country recorded, as devicesExcludedCount. |
Read a survey's banners (saved sets of breaks) and run them: every question against the same columns, with significance letters. Banners are built in the app; the API reads and runs them. Requires the bannerTables feature, on the Business plan and above.
/surveys/:id/bannersThe survey's banners, each with its columns resolved (group, label, letter) and any group that no longer fits. Runs nothing and takes no query.
/surveys/:id/banners/:bannerId/tablesA run's contents: the base, the weighting used, the columns, and one line per table with its Total base. No cells; fetch a table for those. Computes every table, so it spends your organisation's contents allowance (30 an hour), its own: browsing a run never spends a tab book.
/surveys/:id/banners/:bannerId/tables/:tableKeyOne table: bases per column, and per row the count, proportion, mean where relevant and the letters of the columns it is significantly higher than. :tableKey is a question id, or questionId~rowId for one statement of a grid.
/surveys/:id/banners/:bannerId/exportThe run as an Excel workbook (Contents, Banner and Tables sheets). Spends your organisation's tab book allowance (30 an hour), shared with the in-app download and separate from the contents allowance.
curl -X GET "https://app.surventrics.ai/api/v1/surveys/SURVEY_ID/banners/BANNER_ID/tables?confidence=90&status=completed" \
-H "Authorization: Bearer YOUR_API_KEY"The last three endpoints. The query is strict: an unrecognised value, a parameter given twice, or from after to is a 400 listing every bad parameter in details.invalid.
| Parameter | Type | Description |
|---|---|---|
weighted | boolean | Weight by the survey's active scheme. Default: weighted when your plan includes weighting and a scheme is active. true without the weighting feature is refused, never silently unweighted. |
confidence | integer | 90 or 95. Default: the banner's saved level. |
from, to | date | Inclusive session start dates (YYYY-MM-DD). |
device | string | mobile, tablet or desktop. |
country | string | Two-letter country code. |
status | string | A session status: completed, in_progress, abandoned, … |
Every label comes with a code beside it. A group carries source (question or attribute) and questionId or attribute; a column its own key; a label the product words carries labelKey and labelParams. In a run (the contents call and one table) the Total column is always first, with the id total and no letter; the banner list returns the breaks' columns only, without Total. Letters name columns in the same group that the figure is significantly higher than (pairwise, uncorrected). A column with fewer than 5 respondents is marked lowBase and never tested.
A run is refused before anything is computed, as { error: { code, message, details: { reason } } }. Reasons are codes, never sentences. A refused run is not counted against an allowance.
| Reason | Status | Description |
|---|---|---|
too_many_tables | 422 | More tables than a run takes, with details.count and details.max (200). |
all_groups_broken | 422 | Every group refers to a question that has changed. |
too_large | 413 | Too large to compute in one request; the contents call and the workbook only. One table is never refused as too large. |
no_scheme, scheme_not_on_survey, scheme_not_applied | 400 | A weighted run the survey's weighting cannot serve. Ask for weighted=false. |
all_groups_hidden | 403 | Every break is on a question marked as personal data and hidden from the key's role. |
(allowance) | 429 | The key's per-minute limit, or one of the organisation's two hourly allowances: contents (30 an hour, the contents call) or tab books (30 an hour, the workbook and the in-app download). Retry-After says when to try again. Listing banners and fetching one table spend neither. |
Also 401, 403 (scope, plan or feature) and 404 (a survey, banner or table key that is not there), as on every endpoint.
analytics:readPoll the analytics endpoint to display real-time metrics on your dashboard:
// JavaScript example
async function refreshMetrics(surveyId) {
const response = await fetch(
`https://app.surventrics.ai/api/v1/surveys/${surveyId}/analytics`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`
}
}
);
const data = await response.json();
console.log(`Completion rate: ${data.responseStats.completionRate}%`);
console.log(`Total responses: ${data.responseStats.total}`);
console.log(`Avg completion: ${Math.round(data.timing.averageCompletionSeconds / 60)} min`);
}Find questions with high skip rates that may need improvement:
// Find questions people skip: shown, left blank and moved past, > 10%
const problemQuestions = data.questions
.filter(q => q.skipsAvailability === "available" && q.skipRateOfShown > 10)
.sort((a, b) => b.skipRateOfShown - a.skipRateOfShown);
console.log('Questions needing attention:', problemQuestions);Use the daily data to visualize response trends:
// Calculate 7-day average
const recentDays = data.daily.slice(-7);
const avgDaily = recentDays.reduce((sum, d) => sum + d.completions, 0) / 7;
console.log(`7-day avg: ${avgDaily.toFixed(1)} completions/day`);