empirio.ai Developer Docs
empirio.ai offers four integration surfaces:
- MCP Server — connect AI agents (ChatGPT, Claude, and compatible hosts). Available on every plan, including Free.
- REST API — manage surveys and pull responses over HTTPS. Requires a Pro or Enterprise plan.
- Webhooks — receive new responses in real time. Requires a Pro or Enterprise plan.
- CLI —
@empirio-ai/clinpm package; OAuth 2.1 login and every REST operation on the command line. Requires a Pro or Enterprise plan.
Beta — This API is in beta. Endpoints, request/response shapes, and behavior may change without prior notice.
REST API
Overview
Use the REST API to manage surveys and read response data over HTTPS.
Base URL
https://platform.empirio.ai/rest/v1
All requests must be made over HTTPS. HTTP requests are rejected with a 308 redirect.
Authentication
Include a Bearer token in every request. Two token types are accepted:
Authorization: Bearer sk_live_… # REST API key (dashboard)
Authorization: Bearer <oauth-token> # OAuth 2.1 access token (used by the CLI)
API keys are created in Settings → Integrations. OAuth access tokens are issued via the /oauth/token endpoint and are used automatically by the empirio CLI after empirio login. Both token types carry one or both scopes:
| Scope | Access |
|---|---|
surveys | Create, read, edit, delete, publish, unpublish, duplicate surveys |
responses | List, aggregate, export, delete responses; cross-tabulation; chart export |
The GET /me endpoint is exempt from the scope and plan gates so any authenticated caller can inspect their own identity and plan tier.
Rate limits
| Metric | Limit |
|---|---|
| Read operations | 60 / minute per key |
| Write operations | 20 / minute per key |
| Pre-auth (per IP) | 30 / minute |
POST /surveys (survey_create) | 10 / hour and 50 / day per account |
When a limit is exceeded the API returns 429 with a Retry-After header indicating seconds until the window resets.
Write operations include POST, PATCH, and DELETE. Read operations are GET requests.
The per-account survey_create limit is applied in addition to the global write rate limit and is intended to prevent account-wide runaway creation. It is not affected by idempotency replays — re-sending a request with the same Idempotency-Key does not consume additional quota.
Admin exemption
API keys owned by accounts with the admin role bypass every per-key and per-account rate limit (read, write, and the survey_create hourly/daily caps). The pre-auth IP limit still applies because admin status is only known after the API key is validated. This exemption is intended for internal tooling and test automation — regular Pro/Enterprise accounts remain subject to the limits documented above.
Idempotency
Write operations require an idempotency key via header:
- Header:
Idempotency-Key: <value>(max 128 characters)
Repeat requests with the same key within the idempotency window return the original response without re-executing the operation.
Error responses
All errors follow a consistent envelope:
{
"ok": false,
"error": {
"code": "not_found",
"message": "Survey not found."
}
}
| Code | Status | Description |
|---|---|---|
not_authorized | 401 | Missing or invalid API key |
insufficient_scope | 403 | Key does not have the required scope |
plan_required | 403 | Account requires Pro or Enterprise plan |
forbidden | 403 | Action not permitted |
not_found | 404 | Resource or route not found |
method_not_allowed | 405 | HTTP method not allowed for this endpoint |
validation_error | 400 | Request parameters failed validation |
idempotency_required | 400 | Idempotency key required but not provided |
idempotency_in_progress | 409 | Duplicate request is still being processed |
rate_limited | 429 | Rate limit exceeded |
internal_error | 500 | Unexpected server error |
Recommended survey flow
POST /surveys— create a draftPATCH /surveys/{survey_id}— optional iterative editsPOST /surveys/{survey_id}/publish— publish the draft
Question Types
Write endpoints accept questions as an array of objects. Every question needs a type and question text plus any type-specific fields listed below. The backend assigns internal IDs automatically — clients only send labels.
Selection
Single choice — multiple-choice
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
options | string[] | No | Answer labels (max 100) |
randomizeOptions | boolean | No | Shuffle option order per respondent |
allowOther | boolean | No | Allow a free-text "other" answer |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "multiple-choice",
"question": "How did you hear about us?",
"options": ["Social media", "Search engine", "Friend"],
"allowOther": true
}
Checkboxes — checkbox
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
options | string[] | No | Answer labels (max 100) |
randomizeOptions | boolean | No | Shuffle option order per respondent |
allowOther | boolean | No | Allow a free-text "other" answer |
minSelections | integer | No | Minimum options the respondent must select (≥ 1). Implies the question is required when set. |
maxSelections | integer | No | Maximum options the respondent may select (≥ 1, ≥ minSelections, ≤ options.length). |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "checkbox",
"question": "Which features do you use?",
"options": ["Dashboard", "Reports", "API"],
"randomizeOptions": true,
"minSelections": 1,
"maxSelections": 2
}
Dropdown — dropdown
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
options | string[] | No | Answer labels (max 100) |
randomizeOptions | boolean | No | Shuffle option order per respondent |
allowOther | boolean | No | Allow a free-text "other" answer |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "dropdown",
"question": "Select your country",
"options": ["Germany", "Austria", "Switzerland"]
}
Yes / No — yes-no
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "yes-no",
"question": "Would you use this product again?",
"required": true
}
Rating
Star rating — rating
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
min | number | No | Fixed at 1 |
max | number | No | 2–10 (default 5) |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "rating",
"question": "How would you rate our service?",
"required": true,
"min": 1,
"max": 5
}
Thumbs — thumbs
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
max | number | No | 2–10 (default 5) |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "thumbs",
"question": "Did you enjoy this experience?",
"required": true,
"max": 5
}
Number rating — scale
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
min | number | No | Fixed at 1 |
max | number | No | 2–20 (default 10) |
scaleLabels | { min?: string, max?: string } | No | Label texts for scale ends |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "scale",
"question": "How satisfied are you?",
"min": 1,
"max": 10,
"scaleLabels": {
"min": "Not at all",
"max": "Extremely"
}
}
Text rating — text-rating
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
matrixColumns | string[] | No | Label texts shown on each rating button |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "text-rating",
"question": "How do you feel?",
"matrixColumns": ["Bad", "Neutral", "Good", "Great"]
}
NPS — nps
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "nps",
"question": "How likely are you to recommend us?",
"required": true
}
Text & Input
Short text — text
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "text",
"question": "What is your name?",
"required": true
}
Long text — text-long
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "text-long",
"question": "Please share additional feedback"
}
Email — email
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "email",
"question": "Your email address",
"required": true
}
Phone — phone
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "phone",
"question": "Your contact number"
}
Number — number
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "number",
"question": "How many employees does your company have?",
"required": true
}
Date — date
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "date",
"question": "When did you first use our product?"
}
Advanced
Matrix — matrix
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
matrixRows | string[] | No | Row labels |
matrixColumns | string[] | No | Column labels |
randomizeRows | boolean | No | Shuffle row order per respondent |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "matrix",
"question": "Rate each feature",
"matrixRows": ["Ease of use", "Performance", "Design"],
"matrixColumns": ["Poor", "Fair", "Good", "Excellent"]
}
Ranking — ranking
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Question text |
required | boolean | No | Whether an answer is required |
options | string[] | No | Items to rank |
randomizeOptions | boolean | No | Shuffle option order per respondent |
subtitle | string | No | Optional subtitle text |
showSubtitle | boolean | No | Whether to display the subtitle |
{
"type": "ranking",
"question": "Rank by importance",
"options": ["Speed", "Reliability", "Price", "Support"]
}
Content — content
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Title / label |
content | string | No | Body text |
{
"type": "content",
"question": "Section 2: Demographics",
"content": "This section collects demographic information."
}
Privacy Policy — privacy
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | Yes | Policy title |
required | boolean | No | Whether consent is required to proceed |
content | string | No | Policy text shown to the respondent |
privacyCheckboxLabel | string | No | Label displayed next to the consent checkbox |
{
"type": "privacy",
"question": "Privacy Policy",
"content": "I agree to the processing of my data.",
"privacyCheckboxLabel": "I accept",
"required": true
}
Surveys
Create, edit, publish, duplicate, and delete surveys.
get/surveys
List surveys
List all surveys owned by or shared with the authenticated user.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| status | query | "all" | "draft" | "published" | No | Filter by publication status |
| limit | query | integer | No | Max results to return (default 50) |
| offset | query | integer | No | Number of results to skip (default 0) |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- surveys array of objectrequired
- id string (uuid)required
Survey ID (UUID)
- title stringrequired
- description string (nullable)required
- is_published booleanrequired
- role "owner" | "editor" | "viewer"required
- response_count integerrequired
- created_at stringrequired
ISO 8601 timestamp
- updated_at stringrequired
ISO 8601 timestamp
- id string (uuid)required
- total integerrequired
Total number of accessible surveys, ignoring pagination.
Example response
{
"surveys": [
{
"id": "9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"title": "Customer Satisfaction Q1",
"description": "Quick 3-minute check-in with our Pro customers.",
"is_published": true,
"role": "owner",
"response_count": 142,
"created_at": "2026-01-12T09:24:00.000Z",
"updated_at": "2026-04-02T16:03:18.000Z"
},
{
"id": "c1b2a3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"title": "Onboarding feedback",
"description": null,
"is_published": false,
"role": "editor",
"response_count": 0,
"created_at": "2026-03-20T11:00:00.000Z",
"updated_at": "2026-03-21T08:30:00.000Z"
}
],
"total": 2
}post/surveys
Create survey draft
Create a new survey draft (unpublished). Questions are provided as an array — each needs a type and question text plus type-specific fields. See the Question Types section for the full list of supported types and their fields.
All field-level details are documented directly in the request schema attributes.
Logic rules and translations require question_id references — use survey_edit after creation to add them.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Request body
Required
- mode "manual" | "ai"required
Creation mode: 'manual' for explicit content, 'ai' for prompt-based generation
- questions array of object
Array of question objects (each must have a valid type)
- type "rating" | "thumbs" | "text-rating" | "text" | "text-long" | "nps" | "multiple-choice" | "checkbox" | "dropdown" | "yes-no" | "scale" | "date" | "email" | "phone" | "number" | "ranking" | "matrix" | "content" | "privacy"required
Question type.
- question string
Question text.
- required boolean
Whether an answer is required.
- options array of string
Answer option labels (max 100).
- subtitle string
Optional subtitle.
- showSubtitle boolean
Whether the subtitle is shown.
- content string
Body content for content/privacy questions.
- min 1
Lower bound. Only supported by rating and scale, where it is always 1. Ignored for other types.
- max integer
Upper bound. rating/thumbs: 2–10, scale: 2–20. Ignored for types without max support.
- scaleLabels object
Scale endpoint labels.
- min string
Minimum scale label.
- max string
Maximum scale label.
- min string
- allowOther boolean
Allow an additional free-text 'other' answer.
- randomizeOptions boolean
Randomize option order for each respondent.
- minSelections integer
Minimum number of options the respondent must select. checkbox-only. Implies the question is required when ≥ 1.
- maxSelections integer
Maximum number of options the respondent may select. checkbox-only. Must be ≥ minSelections (if set) and ≤ options.length.
- matrixRows array of string
Matrix row labels.
- matrixColumns array of string
Matrix column labels.
- randomizeRows boolean
Randomize matrix row order for each respondent.
- privacyCheckboxLabel string
Label shown next to the privacy consent checkbox.
- translations map of object
Per-locale question translations keyed by locale (for example `de-DE`).
- type "rating" | "thumbs" | "text-rating" | "text" | "text-long" | "nps" | "multiple-choice" | "checkbox" | "dropdown" | "yes-no" | "scale" | "date" | "email" | "phone" | "number" | "ranking" | "matrix" | "content" | "privacy"required
- ai_prompt string
Prompt text for AI-driven survey creation (ai mode)
- metadata object
Survey metadata object. Field-level descriptions define title, description, and display mode.
- title string
Survey title shown to respondents and in the dashboard.
- description string
Optional survey description shown in intro contexts.
- display_mode "all" | "single"
Question rendering mode: `all` shows all questions on one page; `single` shows one question per page.
- title string
- welcome_page object
Welcome page configuration shown before the first question.
- enabled booleanrequired
Whether the welcome page is shown before the first question.
- title string
Welcome page title.
- description string
Welcome page description text.
- enabled booleanrequired
- end_page object
End page configuration shown after the last question.
- enabled booleanrequired
Whether the end page is shown after the final question.
- title string
Custom end-page title. If omitted, the default title is used.
- description string
Custom end-page description text. If omitted, the default text is used.
- show_title boolean
Explicit title visibility toggle. Use `false` to hide the title even if `title` is set.
- show_description boolean
Explicit description visibility toggle. Use `false` to hide the description even if it exists.
- show_button boolean
Whether to show a call-to-action button on the end page.
- button_text string
Button label shown on the end page.
- button_url string
Destination URL opened by the button and used as redirect target when `auto_redirect` is enabled.
- auto_redirect boolean
When `true`, respondents are redirected automatically to `button_url` after `redirect_delay`.
- redirect_delay string
Auto-redirect delay as a duration-like string.
- enabled booleanrequired
- design object
Design customization settings for survey styling (for example template and primary color).
- template "standard" | "modern" | "cosmos" | "aurora" | "nebula" | "eclipse" | "forest" | "amber"
Visual survey template. Available: `standard` (light, default), `modern` (dark), `cosmos` (dark, depth), `aurora` (light, gradient), `nebula` (dark, purple), `eclipse` (dark, purple accent), `forest` (dark, green), `amber` (dark, blue).
- primary_color string
Primary accent color as hex (for example `#4F46E5`).
- template "standard" | "modern" | "cosmos" | "aurora" | "nebula" | "eclipse" | "forest" | "amber"
- settings object
Survey behavior settings. Field-level descriptions define the supported allowlisted keys.
- show_progress boolean
Whether to show a progress indicator during completion.
- show_question_numbers boolean
Whether to show numeric question labels (1, 2, 3...).
- auto_advance boolean
Whether the respondent flow automatically advances after a question is answered when the question type supports it. Defaults to true.
- hide_branding boolean
Whether to hide empirio branding in the survey footer.
- master_locale string
Primary survey locale in BCP 47 format (for example `en-US`, `de-DE`). Defaults to the user's preferred locale when omitted during creation.
- multi_language_enabled boolean
Whether multi-language answering is enabled.
- additional_locales array of string
Additional enabled locales in BCP 47 format.
- show_progress boolean
Example — Creates a survey with common question types: rating, multiple-choice, NPS, and free text.
{
"mode": "manual",
"metadata": {
"title": "Customer Feedback Survey",
"description": "Help us improve our service"
},
"settings": {
"master_locale": "en-US"
},
"questions": [
{
"type": "rating",
"question": "How would you rate our service?",
"required": true,
"min": 1,
"max": 5
},
{
"type": "multiple-choice",
"question": "How did you hear about us?",
"options": [
"Social media",
"Friend or colleague",
"Search engine",
"Advertisement"
],
"allowOther": true
},
{
"type": "nps",
"question": "How likely are you to recommend us to a friend?",
"required": true
},
{
"type": "text-long",
"question": "Any additional comments or suggestions?",
"required": false
}
]
}Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- id string (uuid)
Survey ID (UUID)
- title string
- is_published boolean
- public_url string
- public_url_note string
- preview_url string
Preview URL showing the current working draft. Responses submitted via this link are not counted.
Example response
{
"id": "9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"title": "Customer Satisfaction Q1",
"is_published": false,
"public_url": "https://www.empirio.ai/s/9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"public_url_note": "Survey is not published yet. This link will only work after publishing."
}get/surveys/{survey_id}
Get survey details
Get full details of a specific survey including questions and settings.
Options, matrixRows, and matrixColumns are returned as {option_id, label} objects. Use option_id to reference individual options in survey_edit option_operations.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | The survey ID |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- id string (uuid)required
Survey ID (UUID)
- title stringrequired
- description string (nullable)required
- questions array of objectrequired
- question_id string (nullable)required
Stable question ID. `null` only for legacy rows without an assigned id.
- type stringrequired
Question type (e.g. text, multiple-choice, matrix, rating).
- question stringrequired
Question title (human-readable prompt).
- required boolean
- subtitle string
- showSubtitle boolean
- options array of object
Answer options for selection questions. Each option has a stable option_id and display label.
- option_id stringrequired
Stable opaque option identifier. Use in option_operations to rename, delete, or reorder.
- label stringrequired
Display label for this option.
- option_id stringrequired
- matrixRows array of object
Matrix row options with stable identifiers.
- option_id stringrequired
Stable opaque option identifier. Use in option_operations to rename, delete, or reorder.
- label stringrequired
Display label for this option.
- option_id stringrequired
- matrixColumns array of object
Matrix column options with stable identifiers.
- option_id stringrequired
Stable opaque option identifier. Use in option_operations to rename, delete, or reorder.
- label stringrequired
Display label for this option.
- option_id stringrequired
- min number
- max number
- scaleLabels object
Optional labels for the min and max endpoints of a scale/rating question.
- min string
- max string
- allowOther boolean
- randomizeOptions boolean
- randomizeRows boolean
- maxSelections number
- content string
- privacyCheckboxLabel string
- translations map of object
Optional per-locale translation payload keyed by BCP-47 locale.
- question_id string (nullable)required
- design_settings objectrequired
Design + settings block. Individual fields depend on the survey template and which editor sections have been configured.
- template string
- displayMode "all" | "single"
- primaryColor string
- logicRules array of map of object
- welcomePage map of object
- endPage map of object
- settings map of object
- is_published booleanrequired
- access_mode "open" | "invite_only" (nullable)required
- role "owner" | "editor" | "viewer"required
- public_url stringrequired
Public survey URL.
- preview_url string
Preview URL showing the current working draft. Responses submitted via this link are not counted.
- has_pending_draft_changes booleanrequired
True when there are unpublished edits in the working draft.
- created_at stringrequired
ISO 8601 timestamp
- updated_at stringrequired
ISO 8601 timestamp
Example response
{
"id": "9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"title": "Customer Satisfaction Q1",
"description": "Quick 3-minute check-in with our Pro customers.",
"questions": [
{
"question_id": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"type": "multiple-choice",
"question": "Which product area do you use most?",
"required": true,
"options": [
{
"option_id": "opt_a1b2c3d4",
"label": "Dashboard"
},
{
"option_id": "opt_bbbb2222",
"label": "Reports"
},
{
"option_id": "opt_cccc3333",
"label": "API"
}
],
"allowOther": true
},
{
"question_id": "q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8",
"type": "checkbox",
"question": "Which features matter most to you?",
"options": [
{
"option_id": "opt_dddd4444",
"label": "Pricing"
},
{
"option_id": "opt_eeee5555",
"label": "Support"
},
{
"option_id": "opt_ffff6666",
"label": "Speed"
}
],
"randomizeOptions": true
},
{
"question_id": "q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34",
"type": "rating",
"question": "How would you rate our service?",
"required": true,
"min": 1,
"max": 5
},
{
"question_id": "q-6e2c9a74-5b38-4f1d-92e7-8a4c7b3d1f05",
"type": "nps",
"question": "How likely are you to recommend us?",
"required": true
}
],
"design_settings": {
"template": "standard",
"displayMode": "single",
"primaryColor": "#4F46E5",
"logicRules": [],
"welcomePage": {
"enabled": true,
"title": "Welcome!"
},
"endPage": {
"enabled": true,
"title": "Thank you!"
},
"settings": {
"showProgress": true,
"showQuestionNumbers": true,
"autoAdvance": true,
"surveyLocale": "en-US"
}
},
"is_published": true,
"access_mode": "open",
"role": "owner",
"public_url": "https://www.empirio.ai/s/9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"has_pending_draft_changes": false,
"created_at": "2026-01-12T09:24:00.000Z",
"updated_at": "2026-04-02T16:03:18.000Z"
}patch/surveys/{survey_id}
Edit survey draft
Apply changes to a survey's working draft (does not publish). Include only the sections you want to change as top-level fields.
All field-level details and supported operation variants are documented directly in the request schema attributes.
See the request-body Examples for complete manual editing scenarios.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | The survey ID to edit |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Request body
Required
- mode "manual" | "ai"required
Edit mode: 'manual' applies provided changes, 'ai' generates changes from ai_prompt
- question_operations array of object | object | object | object
Question operations (`add`, `update`, `delete`, `reorder`).
- option_operations array of object | object | object | object
Option operations (`add`, `rename`, `delete`, `reorder`).
- metadata object
Partial metadata patch.
- title string
Survey title shown to respondents and in the dashboard.
- description string
Optional survey description shown in intro contexts.
- display_mode "all" | "single"
Question rendering mode: `all` shows all questions on one page; `single` shows one question per page.
- title string
- welcome_page object
Partial welcome-page patch.
- enabled boolean
Whether the welcome page is shown before the first question.
- title string
Welcome page title.
- description string
Welcome page description text.
- enabled boolean
- end_page object
Partial end-page patch.
- enabled boolean
Whether the end page is shown after the final question.
- title string
Custom end-page title. If omitted, the default title is used.
- description string
Custom end-page description text. If omitted, the default text is used.
- show_title boolean
Explicit title visibility toggle. Use `false` to hide the title even if `title` is set.
- show_description boolean
Explicit description visibility toggle. Use `false` to hide the description even if it exists.
- show_button boolean
Whether to show a call-to-action button on the end page.
- button_text string
Button label shown on the end page.
- button_url string
Destination URL opened by the button and used as redirect target when `auto_redirect` is enabled.
- auto_redirect boolean
When `true`, respondents are redirected automatically to `button_url` after `redirect_delay`.
- redirect_delay string
Auto-redirect delay as a duration-like string.
- enabled boolean
- logic_rules array of object | object | object
Logic-rule operations (`add`, `update`, `delete`).
- design object
Partial design patch.
- template "standard" | "modern" | "cosmos" | "aurora" | "nebula" | "eclipse" | "forest" | "amber"
Visual survey template. Available: `standard` (light, default), `modern` (dark), `cosmos` (dark, depth), `aurora` (light, gradient), `nebula` (dark, purple), `eclipse` (dark, purple accent), `forest` (dark, green), `amber` (dark, blue).
- primary_color string
Primary accent color as hex (for example `#4F46E5`).
- template "standard" | "modern" | "cosmos" | "aurora" | "nebula" | "eclipse" | "forest" | "amber"
- settings object
Partial settings patch.
- show_progress boolean
Whether to show a progress indicator during completion.
- show_question_numbers boolean
Whether to show numeric question labels (1, 2, 3...).
- auto_advance boolean
Whether the respondent flow automatically advances after a question is answered when the question type supports it. Defaults to true.
- hide_branding boolean
Whether to hide empirio branding in the survey footer.
- master_locale string
Primary survey locale in BCP 47 format (for example `en-US`, `de-DE`). Defaults to the user's preferred locale when omitted during creation.
- multi_language_enabled boolean
Whether multi-language answering is enabled.
- additional_locales array of string
Additional enabled locales in BCP 47 format.
- show_progress boolean
- translations object
Translation patches for metadata and question content.
- metadata map of object
Per-locale metadata translation patches keyed by locale.
- questions map of map of object
Per-locale question translation patches keyed by locale then `question_id`.
- metadata map of object
- ai_prompt string
Prompt text for AI-driven survey editing (ai mode)
Example — Adds a dropdown question at the end of the survey.
{
"mode": "manual",
"question_operations": [
{
"op": "add",
"position": {
"type": "end"
},
"question": {
"type": "dropdown",
"question": "Select your department:",
"options": [
"Engineering",
"Marketing",
"Sales",
"Support",
"HR"
],
"required": true
}
}
]
}Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- id string (uuid)required
Survey ID (UUID)
- warnings array of string
- applied_changes objectrequired
Summary of what the edit actually changed. Added/updated/deleted entries carry `question_id` so callers can correlate applied changes against cached references.
- question_changes objectrequired
- added array of objectrequired
- question_id string (nullable)required
- question stringrequired
- type stringrequired
- position map of objectrequired
- updated array of objectrequired
- question_id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- changed_fields array of stringrequired
- question_id stringrequired
- deleted array of objectrequired
- question_id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- question stringrequired
- type stringrequired
- question_id stringrequired
- reordered booleanrequired
- added array of objectrequired
- logic_rule_changes objectrequired
- added integerrequired
- updated integerrequired
- deleted integerrequired
- metadata_changes map of object
- welcome_page_changes map of object
- end_page_changes map of object
- design_changes map of object
- settings_changes map of object
- translation_changes_summary map of object
- option_changes object
Summary of option-level changes applied by option_operations.
- added array of objectrequired
Options added (server-assigned option_id available via survey_get).
- question_id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- label stringrequired
- question_id stringrequired
- renamed array of objectrequired
- question_id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- option_id stringrequired
- label stringrequired
- question_id stringrequired
- deleted array of objectrequired
- question_id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- option_id stringrequired
- question_id stringrequired
- reordered array of objectrequired
- question_id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- field stringrequired
- question_id stringrequired
- added array of objectrequired
- question_changes objectrequired
Example response
{
"id": "9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"applied_changes": {
"question_changes": {
"added": [
{
"question_id": "q-new-b1c4d5e6-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
"question": "Any other feedback?",
"type": "text",
"position": {
"type": "end"
}
}
],
"updated": [
{
"question_id": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"changed_fields": [
"question",
"required"
]
}
],
"deleted": [],
"reordered": false
},
"logic_rule_changes": {
"added": 0,
"updated": 0,
"deleted": 0
},
"option_changes": {
"added": [
{
"question_id": "q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8",
"label": "New Feature"
}
],
"renamed": [
{
"question_id": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"option_id": "opt_a1b2c3d4",
"label": "Main Dashboard"
}
],
"deleted": [],
"reordered": []
},
"metadata_changes": {
"title": "Customer Satisfaction Q1 — Updated"
}
}
}delete/surveys/{survey_id}
Delete survey
Permanently delete a survey. Owner only.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID to delete. |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- deleted truerequired
Example response
{
"deleted": true
}post/surveys/{survey_id}/publish
Publish survey
Publish the existing survey working draft to live. If already published, updates live survey from the current working draft. Does not create new surveys or accept inline content edits. Returns a public_url — always show this link to the user after publishing.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Existing survey ID to publish |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- id string (uuid)
Survey ID (UUID)
- is_published boolean
- public_url stringrequired
Public survey URL. Always show this link to the user after publishing.
Example response
{
"id": "9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"is_published": true,
"public_url": "https://www.empirio.ai/s/9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b"
}post/surveys/{survey_id}/unpublish
Unpublish survey
Unpublish a survey and move it back to draft status (owner only).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID to unpublish. |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- id string (uuid)
Survey ID (UUID)
- is_published false
Example response
{
"id": "9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b",
"is_published": false
}post/surveys/{survey_id}/duplicate
Duplicate survey
Create a copy of an existing survey (unpublished draft).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID to duplicate |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- id string (uuid)
Survey ID (UUID)
- title string
Example response
{
"id": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70",
"title": "Customer Satisfaction Q1 (Copy)"
}Responses
List, aggregate, export, cross-tabulate, and delete survey responses.
get/surveys/{survey_id}/responses
List responses
List survey participations with global row ranking (row_no) for pagination/deletion workflows. Returns full answers and response metadata without internal IDs.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
| date_from | query | string | No | ISO 8601 date filter start |
| date_to | query | string | No | ISO 8601 date filter end |
| include_incomplete_participations | query | boolean | No | Include incomplete participations with meaningful saved progress |
| limit | query | integer | No | Default 100 |
| offset | query | integer | No | Zero-based pagination offset. |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- responses array of objectrequired
- row_no integer (nullable)required
Global row ranking (1-based). Stable within a survey for pagination.
- answers map of objectrequired
Flat record keyed by `question_id`. Answer values are resolved to labels (not internal option IDs). Matrix answers are `{rowLabel: columnLabel}`. Free-text 'other' answers appear under `<question_id>_other` keys.
- created_at string (nullable)required
- completed_at string (nullable)required
- duration_seconds number (nullable)required
- ended_by_logic booleanrequired
- locale string (nullable)required
- participation_type "response" | "incomplete"required
- row_no integer (nullable)required
- total_count integerrequired
- has_more booleanrequired
Example response
{
"responses": [
{
"row_no": 1,
"answers": {
"q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42": "Dashboard",
"q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8": [
"Pricing",
"Support"
],
"q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34": 5,
"q-6e2c9a74-5b38-4f1d-92e7-8a4c7b3d1f05": 9
},
"created_at": "2026-04-05T14:22:31.000Z",
"completed_at": "2026-04-05T14:25:38.000Z",
"duration_seconds": 187,
"ended_by_logic": false,
"locale": "de-DE",
"participation_type": "response"
},
{
"row_no": 2,
"answers": {
"q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42": "API",
"q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42_other": null,
"q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8": [
"Speed"
],
"q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34": 4,
"q-6e2c9a74-5b38-4f1d-92e7-8a4c7b3d1f05": 7
},
"created_at": "2026-04-05T15:10:02.000Z",
"completed_at": "2026-04-05T15:13:45.000Z",
"duration_seconds": 223,
"ended_by_logic": false,
"locale": "en-US",
"participation_type": "response"
}
],
"total_count": 142,
"has_more": true
}delete/surveys/{survey_id}/responses
Delete responses
Delete responses by ranked row numbers (from responses_list), or delete all responses for a survey. Owner only. Matching responses are permanently soft-deleted.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Request body
Required
- mode "rows" | "all"required
Delete only selected ranked rows ('rows') or delete all responses ('all').
- row_numbers array of integer
Global response row numbers from responses_list (1-based) when mode='rows'. Max 1000 per request.
- date_from string
Date filter used for rank resolution (must match responses_list)
- date_to string
Date filter used for rank resolution (must match responses_list)
- include_incomplete_participations boolean
Incomplete participation filter used for rank resolution (must match responses_list)
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- deleted_count integerrequired
Number of responses that were soft-deleted.
- unresolved_row_numbers array of integerrequired
Row numbers from the request that could not be resolved to an existing response.
Example response
{
"deleted_count": 3,
"unresolved_row_numbers": []
}get/surveys/{survey_id}/responses/aggregates
Get response aggregates
Get aggregate statistics (counts per answer) for survey questions.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
| question_ids | query | array of string | No | Specific question IDs (omit for all, max 200) |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- aggregates objectrequired
Aggregate statistics produced by the analytics core. Field names use camelCase (`questionId`, `totalAnswered`, etc.) because this payload mirrors the analytics core's internal contract — the `questionId` value is the same opaque identifier other endpoints expose as `question_id`, so you can still use it as a join key.
- totalFiltered integerrequired
Total number of responses after filters, before per-question answer-counting.
- questions array of objectrequired
- questionId stringrequired
Stable question identifier. Same value as `question_id` emitted by `survey_get` and webhooks — camelCased here because this payload comes directly from the analytics core, which uses camelCase for its internal contract.
- questionText stringrequired
- questionType stringrequired
- totalAnswered integerrequired
- skipped integerrequired
- buckets array of objectrequired
- value objectrequired
Display label or numeric value for this bucket (option IDs are resolved to labels).
- count integerrequired
- percentage numberrequired
Share of total answered responses for this bucket (one decimal, e.g. 42.5).
- value objectrequired
- questionId stringrequired
- totalFiltered integerrequired
Example response
{
"aggregates": {
"totalFiltered": 142,
"questions": [
{
"questionId": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"questionText": "Which product area do you use most?",
"questionType": "multiple-choice",
"totalAnswered": 142,
"skipped": 0,
"buckets": [
{
"value": "Dashboard",
"count": 78,
"percentage": 54.9
},
{
"value": "Reports",
"count": 41,
"percentage": 28.9
},
{
"value": "API",
"count": 23,
"percentage": 16.2
}
]
},
{
"questionId": "q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34",
"questionText": "How would you rate our service?",
"questionType": "rating",
"totalAnswered": 142,
"skipped": 0,
"buckets": [
{
"value": 5,
"count": 71,
"percentage": 50
},
{
"value": 4,
"count": 48,
"percentage": 33.8
},
{
"value": 3,
"count": 15,
"percentage": 10.6
},
{
"value": 2,
"count": 6,
"percentage": 4.2
},
{
"value": 1,
"count": 2,
"percentage": 1.4
}
]
}
]
}
}get/surveys/{survey_id}/responses/crosstab
Cross-tabulate responses
Cross-tabulate answers between two survey questions.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
| question_x | query | string | Yes | First question ID (rows) |
| question_y | query | string | Yes | Second question ID (columns) |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- crosstab objectrequired
Cross-tabulation between the two requested questions. Field names are camelCase because this payload mirrors the analytics core's internal contract; row/column labels are already label-resolved.
- rowQuestion objectrequired
Row question definition. `id` is the same stable question identifier as `question_id` elsewhere.
- id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- text stringrequired
- id stringrequired
- colQuestion objectrequired
Column question definition.
- id stringrequired
Stable opaque question identifier. Same value emitted by webhooks and returned by survey_get — safe to use as a join key across REST and webhook consumers.
- text stringrequired
- id stringrequired
- matrix array of objectrequired
- rowValue stringrequired
Row label.
- rowTotal integerrequired
- columns array of objectrequired
- colValue stringrequired
Column label for this cell.
- count integerrequired
- rowPercentage numberrequired
Row-normalised percentage (one decimal, e.g. 66.7).
- colValue stringrequired
- rowValue stringrequired
- truncated booleanrequired
True when the row/column set was truncated to stay under the max-categories limit.
- rowQuestion objectrequired
Example response
{
"crosstab": {
"rowQuestion": {
"id": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"text": "Which product area do you use most?"
},
"colQuestion": {
"id": "q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34",
"text": "How would you rate our service?"
},
"matrix": [
{
"rowValue": "Dashboard",
"rowTotal": 78,
"columns": [
{
"colValue": "1",
"count": 0,
"rowPercentage": 0
},
{
"colValue": "2",
"count": 2,
"rowPercentage": 2.6
},
{
"colValue": "3",
"count": 3,
"rowPercentage": 3.8
},
{
"colValue": "4",
"count": 18,
"rowPercentage": 23.1
},
{
"colValue": "5",
"count": 55,
"rowPercentage": 70.5
}
]
},
{
"rowValue": "Reports",
"rowTotal": 41,
"columns": [
{
"colValue": "1",
"count": 1,
"rowPercentage": 2.4
},
{
"colValue": "2",
"count": 2,
"rowPercentage": 4.9
},
{
"colValue": "3",
"count": 6,
"rowPercentage": 14.6
},
{
"colValue": "4",
"count": 15,
"rowPercentage": 36.6
},
{
"colValue": "5",
"count": 17,
"rowPercentage": 41.5
}
]
},
{
"rowValue": "API",
"rowTotal": 23,
"columns": [
{
"colValue": "1",
"count": 1,
"rowPercentage": 4.3
},
{
"colValue": "2",
"count": 2,
"rowPercentage": 8.7
},
{
"colValue": "3",
"count": 6,
"rowPercentage": 26.1
},
{
"colValue": "4",
"count": 6,
"rowPercentage": 26.1
},
{
"colValue": "5",
"count": 8,
"rowPercentage": 34.8
}
]
}
],
"truncated": false
}
}get/surveys/{survey_id}/responses/questions
Get question definitions
Get the question definitions for a survey.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- questions array of objectrequired
- question_id string (nullable)required
Stable question identifier. Identical to the `question_id` returned by `survey_get` and emitted by webhooks, so the three surfaces share one join key.
- title string (nullable)required
- type string (nullable)required
- is_required booleanrequired
- has_open_text booleanrequired
- is_draft_only boolean
Present and true only for questions that exist in the working draft but not the published survey.
- question_id string (nullable)required
- total_questions integerrequired
Example response
{
"questions": [
{
"question_id": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"title": "Which product area do you use most?",
"type": "multiple-choice",
"is_required": true,
"has_open_text": true
},
{
"question_id": "q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8",
"title": "Which features matter most to you?",
"type": "checkbox",
"is_required": false,
"has_open_text": false
},
{
"question_id": "q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34",
"title": "How would you rate our service?",
"type": "rating",
"is_required": true,
"has_open_text": false
},
{
"question_id": "q-6e2c9a74-5b38-4f1d-92e7-8a4c7b3d1f05",
"title": "How likely are you to recommend us?",
"type": "nps",
"is_required": true,
"has_open_text": false
}
],
"total_questions": 4
}get/surveys/{survey_id}/responses/stats
Get survey statistics
Get survey-level statistics (views, starts, completions, incompletes, and average duration).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- views integer
- starts integer
- completions integer
- incompletes integer
- average_duration_seconds number
Example response
{
"views": 412,
"starts": 198,
"completions": 142,
"incompletes": 56,
"average_duration_seconds": 192
}post/surveys/{survey_id}/responses/export/responses
Export responses
Export survey responses as CSV, XLSX, JSON, or SPSS. Returns a temporary signed download URL. By default only completed participations are exported unless include_incomplete_participations=true.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Request body
- format "csv" | "xlsx" | "json" | "spss"
Export format (default: csv)
- date_from string
ISO 8601 date filter start
- date_to string
ISO 8601 date filter end
- include_incomplete_participations boolean
Include incomplete participations (default: false = completed only).
- time_zone string
IANA timezone for exported date/time fields (e.g. Europe/Berlin).
- answer_filters array of object
Answer filters (max 100) with the same semantics as the frontend results filters.
- question_id stringrequired
Question ID to filter by
- answer objectrequired
Answer value to match (scalar or array depending on question type)
- question_id stringrequired
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
- format "csv" | "xlsx" | "json" | "spss"required
- download_url stringrequired
Temporary signed download URL (valid for ~1 hour).
- expires_at stringrequired
ISO timestamp when the signed download URL expires.
Example response
{
"format": "xlsx",
"download_url": "https://storage.empirio.ai/exports/9f3a8b12.../responses-1712345678.xlsx?token=...",
"expires_at": "2026-04-10T17:45:00.000Z"
}post/surveys/{survey_id}/responses/export/charts
Export charts
Export survey aggregate charts in one of six formats: native editable PowerPoint (pptx), image-based PowerPoint (pptx_images), landscape A4 PDF (pdf), Word document (docx), ZIP of PNG charts (zip_images), or structured chart data (chart_json). For binary formats, returns a temporary signed download URL (valid for 1 hour). For chart_json, returns structured chart data inline. By default only completed participations are included unless include_incomplete_participations=true.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| survey_id | path | string (uuid) | Yes | Survey ID. |
| Idempotency-Key | header | string | Yes | Required idempotency key for write operations. Reuse the same value when retrying the same request. |
Request body
- format "pptx" | "pptx_images" | "pdf" | "docx" | "zip_images" | "chart_json"
Chart export format (default: pptx). 'pptx' = native editable PowerPoint, 'pptx_images' = PowerPoint with raster slides, 'pdf' = landscape A4 PDF, 'docx' = Word document, 'zip_images' = ZIP archive of PNG charts, 'chart_json' = structured chart data.
- date_from string
ISO 8601 date filter start
- date_to string
ISO 8601 date filter end
- include_incomplete_participations boolean
Include incomplete participations in chart export (default: false = completed only).
- answer_filters array of object
Answer filters (max 100) with the same semantics as the frontend results filters.
- question_id stringrequired
Question ID to filter by
- answer objectrequired
Answer value to match (scalar or array depending on question type)
- question_id stringrequired
- time_zone string
Reserved parity field for timezone-aware chart export pipelines.
Responses
| Status | Description |
|---|---|
| 200 | Successful response |
| 400 | Validation error |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 409 | Conflict |
| 429 | Rate limited |
| 500 | Internal error |
200 response schema
object | object
Example response
{
"format": "pptx",
"download_url": "https://storage.empirio.ai/exports/9f3a8b12.../charts-1712345678.pptx?token=...",
"expires_at": "2026-04-10T17:45:00.000Z"
}Webhooks
Overview
empirio.ai can push survey responses to your server as soon as they are submitted. Webhooks complement the REST API — you register endpoints once, and empirio delivers signed JSON payloads every time a respondent finishes a survey.
| Property | Value |
|---|---|
| Direction | Outbound (empirio → your server) |
| Trigger | survey.response.submitted |
| Transport | HTTPS only (private IP ranges rejected) |
| Signing | HMAC-SHA256 over the raw request body |
| Content type | application/json |
| Max endpoints per survey | 3 |
| Retries | 3 attempts with exponential backoff (immediate → +1 min → +10 min) |
| Plan requirement | Pro or Enterprise |
Webhook endpoints are managed in Settings → Integrations in the empirio app. The signing secret is returned only once on creation — store it immediately.
Signing & Verification
Request headers
Every delivery arrives with these headers:
Content-Type: application/json
X-Empirio-Signature: sha256=<hex>
X-Empirio-Survey-Id: <uuid>
X-Empirio-Delivery-Id: <uuid>
User-Agent: empirio-webhook/1.0
Verifying the signature
The signature is an HMAC-SHA256 of the raw request body using your endpoint's signing secret. Always verify before trusting the payload.
import crypto from "node:crypto";
function verifyEmpirioSignature(rawBody, headerValue, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
const received = (headerValue || "").replace(/^sha256=/, "");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex"),
);
}
Payload Structure
Envelope
{
"event": "survey.response.submitted",
"survey_id": "…",
"survey_title": "Customer Satisfaction Q1",
"responded_at": "2026-04-05T14:22:31.000Z",
"response": {
"duration_seconds": 187,
"response_locale": "de-DE",
"ended_by_logic": false,
"answers": [ /* cell entries — see below */ ]
}
}
Answer entries
Each element of response.answers represents one "cell". The shape mirrors how empirio's CSV export lays out columns, so one response produces the same decomposition in both formats.
Every entry includes question_id, question_title, and question_type. The rest of the shape depends on what kind of question the entry belongs to — entries are distinguished by field presence, not an explicit discriminator:
| Emitted for | Extra field | value | Identify by |
|---|---|---|---|
| text, text-long, email, phone, date, number, rating, scale, nps, thumbs, yes-no, privacy, multiple-choice, dropdown, image-choice, text-rating | — | string / number / boolean / null | no extra field present |
| checkbox, image-checkbox (one entry per defined option) | option_label | boolean (true if selected) | option_label present |
| matrix (one entry per defined row) | row_label | string / null (column label) | row_label present |
| ranking (one entry per rank position 1..N) | rank_position | string / null (option label at this position) | rank_position present |
any question with allowOther: true (free-text companion) | other_text (replaces value) | — | other_text present |
Note: a multiple-choice, checkbox, or dropdown question with allowOther: true emits its normal entries plus one trailing companion entry in the same response — so a single multiple-choice question can contribute two entries, and a checkbox with N options contributes N+1.
A consumer can route entries with a single switch:
for (const entry of response.answers) {
if ("other_text" in entry) { /* free-text "other" answer */ }
else if ("option_label" in entry) { /* checkbox one-hot */ }
else if ("row_label" in entry) { /* matrix row */ }
else if ("rank_position" in entry){ /* ranking position */ }
else { /* scalar / single-select */ }
}
Value conventions
- No localization. Booleans are real JSON
true/false(including yes-no and privacy); localize for presentation on your side. question_idis stable and persistent. Each question is assigned a UUID-based ID (formatq-<uuid>) at creation time and keeps it for the survey's lifetime. Inserting, removing, or reordering questions does not shift existing IDs. Usequestion_idas your join key for longitudinal or repeated-response analysis.- Unanswered cells are always emitted. Every defined matrix row, rank position, and checkbox option produces an entry — unanswered ones carry
value: null(orvalue: falsefor checkbox options). The schema is stable across edits to the same survey. - Survey order. Entries follow the survey's question order. Within a question, checkbox / matrix / ranking entries follow the order of the corresponding
options/matrixRowsarrays; theother_textcompanion comes last. - Detecting "Other" on single-select questions. For
multiple-choice/dropdownwithallowOther: true, read the main entry and its paired companion together:- Companion
other_text: null→ the respondent picked a regular option; the main entry'svalueis that option's label. - Companion
other_text: "<string>"→ the respondent picked "Other"; the main entry'svaluecarries the same free text (or the literal string"Other"for legacy records that stored only the Other marker without a free-text side channel).
- Companion
- Orphaned answers are dropped. If a stored answer refers to a question that no longer exists, it is skipped — you never see it.
- No scale bounds. Rating/scale/nps/thumbs entries do not include
min/max; read these from the survey definition viaGET /surveys/{survey_id}if you need them.
Question Types
Concrete entry shapes for every question type — the groups and ordering mirror the REST API → Question Types section so you can jump back and forth between the create-payload shape and the webhook-receive shape for the same type.
Every example shows what one question of that type contributes to the response.answers array. Any multiple-choice, checkbox, or dropdown question with allowOther: true emits one additional other_text entry after its main entries — see the dedicated example at the bottom of this page.
question_id values shown in the examples are real q-<uuid> strings — that is the actual format your webhook will receive. They stay stable across edits, so you can use them as join keys.
| Question type | Entries per response | Value | Distinguishing field |
|---|---|---|---|
| text, text-long, email, phone, date | 1 | string or null | — |
| number, rating, scale, nps, thumbs | 1 | number or null | — |
| yes-no, privacy | 1 | boolean or null | — |
| multiple-choice, dropdown, image-choice, text-rating | 1 | option label or null | — |
| checkbox, image-checkbox | N (one per defined option) | boolean | option_label |
| matrix | R (one per defined row) | column label or null | row_label |
| ranking | N (one per rank position 1..N) | option label at this rank or null | rank_position |
…plus allowOther: true | +1 | — | other_text (replaces value) |
| content | — (not emitted) | — | — |
Selection
Single choice — multiple-choice
One entry whose value is the picked option's label (or the free-text answer when the user picked "Other").
{
"question_id": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"question_title": "How did you hear about us?",
"question_type": "multiple-choice",
"value": "Social media"
}
Checkboxes — checkbox
N entries — one per defined option — each carrying the option's label and a boolean value indicating whether the respondent selected it.
[
{
"question_id": "q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8",
"question_title": "Which features do you use?",
"question_type": "checkbox",
"option_label": "Dashboard",
"value": true
},
{
"question_id": "q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8",
"question_title": "Which features do you use?",
"question_type": "checkbox",
"option_label": "Reports",
"value": false
},
{
"question_id": "q-7b2f4e11-9c3a-4a1d-8e22-5f9d3b0a71e8",
"question_title": "Which features do you use?",
"question_type": "checkbox",
"option_label": "API",
"value": true
}
]
Dropdown — dropdown
One entry whose value is the picked option's label (or the free-text answer when the user picked "Other").
{
"question_id": "q-d94b1a6f-2e77-4b8e-87c1-3d4f9a2e05bd",
"question_title": "Select your country",
"question_type": "dropdown",
"value": "Germany"
}
Yes / No — yes-no
One entry with a boolean value (or null if unanswered).
{
"question_id": "q-5e3f8c29-7a14-4d6b-91e0-2a7c4b8f3d91",
"question_title": "Would you use this product again?",
"question_type": "yes-no",
"value": true
}
Rating
Star rating — rating
One entry with a numeric value from 1 to max (or null if unanswered).
{
"question_id": "q-ac8d0f12-5b43-4f2a-8c7e-1e9f6b3a7d42",
"question_title": "How would you rate our service?",
"question_type": "rating",
"value": 4
}
Thumbs — thumbs
One entry with a numeric value from 1 to max (or null if unanswered).
{
"question_id": "q-3b9e2c47-8f15-4a3d-92c6-7d1f8e4b5a09",
"question_title": "Did you enjoy this experience?",
"question_type": "thumbs",
"value": 4
}
Number rating — scale
One entry with a numeric value from 1 to max (or null if unanswered).
{
"question_id": "q-6d4a8f13-2e51-4b7c-83f9-5a2c9d6e1f78",
"question_title": "How satisfied are you?",
"question_type": "scale",
"value": 8
}
Text rating — text-rating
One entry whose value is the label of the picked column (or null if unanswered).
{
"question_id": "q-9c2e5a81-6b73-4d1f-8a4e-3b7f2d9c4e15",
"question_title": "How do you feel?",
"question_type": "text-rating",
"value": "Good"
}
NPS — nps
One entry with a numeric value from 0 to 10 (or null if unanswered).
{
"question_id": "q-1f7b4e92-5c86-4a3d-91f2-6e4a8c7d9b31",
"question_title": "How likely are you to recommend us?",
"question_type": "nps",
"value": 9
}
Text & Input
Short text — text
One entry with a string value (or null if unanswered).
{
"question_id": "q-4a8e2f16-3d95-4b7c-82a1-9f5e6c3d1b74",
"question_title": "What is your name?",
"question_type": "text",
"value": "Jane Doe"
}
Long text — text-long
One entry with a string value (or null if unanswered).
{
"question_id": "q-8d3c1e74-9f42-4b5a-87e6-2a1f7b4c9d53",
"question_title": "Please share additional feedback",
"question_type": "text-long",
"value": "Great product, very easy to use."
}
Email — email
One entry with a string value (or null if unanswered).
{
"question_id": "q-2e9f6a38-4c71-4d8b-93a5-1b7e4f2c8a69",
"question_title": "Your email address",
"question_type": "email",
"value": "jane@example.com"
}
Phone — phone
One entry with a string value (or null if unanswered).
{
"question_id": "q-7c4b9e12-5a38-4f6d-81e9-3a2f8b7c4d61",
"question_title": "Your contact number",
"question_type": "phone",
"value": "+49 30 1234567"
}
Number — number
One entry with a numeric value (or null if unanswered).
{
"question_id": "q-0b3e8a57-6d21-4f9c-84b7-5e1a9c3d7f28",
"question_title": "How many employees does your company have?",
"question_type": "number",
"value": 42
}
Date — date
One entry with an ISO-8601 date string as value (or null if unanswered).
{
"question_id": "q-5a7f2c83-1e96-4b4d-87c3-9d4e6a1f8b52",
"question_title": "When did you first use our product?",
"question_type": "date",
"value": "2024-03-15"
}
Advanced
Matrix — matrix
R entries — one per defined row. Each entry carries the row's label and the column label the respondent picked as value. Unanswered rows still produce an entry with value: null.
[
{
"question_id": "q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34",
"question_title": "Rate each feature",
"question_type": "matrix",
"row_label": "Ease of use",
"value": "Good"
},
{
"question_id": "q-3f8d4b21-7a95-4c6e-89b1-2d5e7f1c9a34",
"question_title": "Rate each feature",
"question_type": "matrix",
"row_label": "Performance",
"value": null
}
]
Ranking — ranking
N entries — one per rank position 1..N, with the option placed at that rank as value.
[
{
"question_id": "q-6e2c9a74-5b38-4f1d-92e7-8a4c7b3d1f05",
"question_title": "Rank by importance",
"question_type": "ranking",
"rank_position": 1,
"value": "Speed"
},
{
"question_id": "q-6e2c9a74-5b38-4f1d-92e7-8a4c7b3d1f05",
"question_title": "Rank by importance",
"question_type": "ranking",
"rank_position": 2,
"value": "Reliability"
},
{
"question_id": "q-6e2c9a74-5b38-4f1d-92e7-8a4c7b3d1f05",
"question_title": "Rank by importance",
"question_type": "ranking",
"rank_position": 3,
"value": "Price"
}
]
Content — content
Content blocks (headings, dividers, static text) are not emitted — they are display-only and never appear in response.answers.
Privacy Policy — privacy
One entry with a boolean value — true if the respondent consented, false if they did not.
{
"question_id": "q-9d1e4a68-3c72-4b5f-86a9-5d7c2f8e1b43",
"question_title": "Privacy Policy",
"question_type": "privacy",
"value": true
}
allowOther: true companion
Any multiple-choice, checkbox, or dropdown question that was created with allowOther: true emits one additional entry after its main entries. This companion uses the key other_text in place of value — a consumer can identify it unambiguously by checking "other_text" in entry. The content is the respondent's free-text answer, or null if they did not use the "other" field.
{
"question_id": "q-a3f8d1b2-4e5c-4c2a-9d11-0f8b7e6a1c42",
"question_title": "How did you hear about us?",
"question_type": "multiple-choice",
"other_text": "From a conference"
}
Delivery Guarantees
- At-least-once delivery. On any failure (non-2xx response, connection error, timeout) empirio retries up to 3 times total with exponential backoff. Ensure your handler is idempotent — use
X-Empirio-Delivery-Idto deduplicate. - 12-second delivery timeout. Return a 2xx status within 12 seconds; longer work should be queued asynchronously on your side.
MCP
Overview
The Model Context Protocol (MCP) server allows AI agents — ChatGPT, Claude, and any MCP-compatible host — to interact with your empirio.ai surveys and response data.
MCP is available on every plan, including Free — no paid plan is required. Access is gated only by OAuth authentication and per-tool scope. (The REST API, CLI, and Webhooks remain Pro/Enterprise features.)
Server URL
https://platform.empirio.ai/mcp
Authentication
OAuth 2.1 with PKCE. Clients auto-discover endpoints via .well-known metadata — no manual configuration needed.
| Property | Value |
|---|---|
| Protocol | MCP Streamable HTTP (stateless) |
| Auth | OAuth 2.1 + PKCE (auto-discovered) |
| Scopes | surveys, responses |
Scopes
| Scope | Access |
|---|---|
surveys | Create, read, edit, delete, publish, unpublish, duplicate surveys |
responses | List, aggregate, export, delete responses; cross-tabulation; chart export |
All tools are listed regardless of the current token scope. If a tool requires a scope the token doesn't have, the server responds with HTTP 403 plus WWW-Authenticate: Bearer ... error="insufficient_scope" scope="<required-scope>" so the client can re-authorize with additional permissions. The advisory JSON-RPC body also includes error.data.code = "insufficient_scope" with the required and current scopes.
Write tools require idempotency_key in their MCP input payload.
Rate limits
| Metric | Limit |
|---|---|
| Read tools | 60 / minute per user |
| Write tools | 20 / minute per user |
survey_create | 10 / hour and 50 / day per account |
Quick start
ChatGPT (Responses API)
{
"model": "gpt-4o",
"tools": [{
"type": "mcp",
"server_label": "empirio",
"server_url": "https://platform.empirio.ai/mcp",
"require_approval": "never"
}],
"input": "List my surveys"
}
Claude (Settings → Connectors)
Add as a remote MCP server:
- URL:
https://platform.empirio.ai/mcp - Auth: OAuth 2.1 (auto-discovered via
.well-known)
Examples
Once connected, your AI agent can handle prompts like these. Each example shows the tool sequence the model will typically follow.
-
"Show me the responses from my latest customer-satisfaction survey." →
survey_list→responses_survey_stats→responses_aggregates -
"Create a 5-question NPS survey for our new product and publish it." →
survey_create(mode: "ai") →survey_publish -
"Cross-tabulate 'How satisfied are you?' against 'Would you recommend us?' for survey X." →
responses_questions→responses_crosstab -
"Export the responses from survey X as an editable PowerPoint deck." →
responses_export_charts(format: "pptx") -
"Delete the last 10 incomplete responses from survey X." →
responses_list(include_incomplete_participations: true) →responses_delete(mode: "rows")
Error codes
Tool responses use a consistent envelope. On failure the response is { "ok": false, "error": { "code": ..., "message": ... } }.
| Code | Meaning | Typical resolution |
|---|---|---|
insufficient_scope | Token is missing the surveys or responses scope | Re-authorize the client requesting the additional scope |
not_authorized | Access token is missing, invalid, or expired | Refresh or re-issue the token |
forbidden | Caller has no access to the target resource | Verify ownership or collaboration role |
not_found | Survey, response, or related resource does not exist | Check the survey_id / filters |
validation_error | Input does not match the tool's schema | Inspect error.message (and error.details if present) for the offending field and retry |
idempotency_required | Mutating call did not supply idempotency_key | Provide a fresh UUID per new mutation |
idempotency_in_progress | A call with the same idempotency_key is still running | Wait briefly, then retry with the same key |
rate_limited | Per-user rate limit exceeded | Back off and retry after the indicated window |
edge_error | Upstream platform service returned a 5xx | Safe to retry with the same idempotency key |
internal_error | Unexpected server-side failure | Retry; contact support if it persists |
Support
- Email: info@empirio.ai
- Privacy policy: empirio.ai/privacy-policy
- Terms of service: empirio.ai/terms-of-service
Tools
Survey
| Tool | Description | Write | Destructive |
|---|---|---|---|
survey_list | List all surveys owned by or shared with the authenticated user | ||
survey_get | Get full survey details including questions, settings, and design | ||
survey_create | Create a new survey draft | ✓ | |
survey_edit | Edit the working draft | ✓ | |
survey_publish | Publish an existing draft or update a live survey from the working draft | ✓ | |
survey_duplicate | Clone a survey as a new unpublished draft | ✓ | |
survey_delete | Permanently delete a survey and all its data | ✓ | ✓ |
survey_unpublish | Unpublish a live survey and move it back to draft status | ✓ |
All survey tools require the surveys scope.
Response
| Tool | Description | Write | Destructive |
|---|---|---|---|
responses_list | List paginated responses with optional date and answer filters | ||
responses_aggregates | Per-question aggregate statistics (counts per answer) | ||
responses_crosstab | Cross-tabulate answers between two survey questions | ||
responses_questions | Get question definitions for a survey | ||
responses_survey_stats | Survey statistics — views, starts, completions, incompletes, avg. duration | ||
responses_delete | Delete responses by ranked row numbers or delete all | ✓ | ✓ |
responses_export | Export responses as CSV, XLSX, JSON, or SPSS | ✓ | |
responses_export_charts | Export charts as PowerPoint (editable or image-based), PDF, Word, ZIP of PNGs, or chart JSON | ✓ |
All response tools require the responses scope.
CLI
Overview
The empirio CLI (@empirio-ai/cli) is a command-line surface over the REST API. It ships as an npm package and delegates every call to the REST endpoints documented in this reference — so CLI and REST stay in parity by construction.
Package
npm install -g @empirio-ai/cli
# or
npx @empirio-ai/cli --help
Binary name: empirio. Requires Node.js ≥ 20. Source at github.com/empirio-de/empirio-ai/tree/main/cli.
See Commands (next in the sidebar) for the full reference of every
empirio …subcommand, its parameters, and usage examples.
Authentication
Two modes. Precedence per invocation: EMPIRIO_API_KEY env var → OS keychain → ~/.config/empirio/credentials.json.
| Mode | Command | When |
|---|---|---|
| OAuth 2.1 (PKCE) | empirio login | Interactive developer workstations |
| API key | EMPIRIO_API_KEY=sk_… empirio … | CI / headless / automation |
The OAuth flow opens the default browser, the user approves consent on www.empirio.ai, and the CLI receives the authorization code on an ephemeral http://127.0.0.1:<port>/callback listener (RFC 8252 §7.3). Tokens are stored in the OS keychain via keytar and fall back to a chmod 0600 file on systems where keytar is unavailable. Refresh tokens are rotated on every use; the access-token TTL is one hour.
Plan gate
All CLI operations require a Pro or Enterprise plan — with one exception: empirio whoami (GET /me) is exempt so that free-plan users can confirm their identity and current plan tier after login.
Global flags
| Flag | Effect |
|---|---|
--json | Emit machine-readable JSON instead of pretty tables |
--quiet | Suppress non-error output |
--idempotency-key <uuid> | Override the auto-generated idempotency key (write operations only) |
Object-valued body fields (metadata, design, settings, welcome_page, logic_rules, translations, …) accept a JSON string on the command line: empirio survey edit --metadata '{"title":"Renamed"}' ….
Environment overrides
For staging or local development:
EMPIRIO_PLATFORM_URL=https://platform.staging.empirio.ai \
empirio login
EMPIRIO_APP_URL is auto-derived from EMPIRIO_PLATFORM_URL (platform.* → app host) unless overridden explicitly.
Support
- Email: info@empirio.ai
- Privacy policy: empirio.ai/privacy-policy
- Terms of service: empirio.ai/terms-of-service
Commands
This section documents every empirio … subcommand with its parameters and a usage example. Flags use kebab-case (e.g. --survey-id, --ai-prompt); object-valued flags accept a JSON string.
All examples assume you are authenticated (either via empirio login or EMPIRIO_API_KEY).
Authentication
empirio login
Start the browser-based OAuth 2.1 (PKCE) flow. On success, tokens are written to the OS keychain (or ~/.config/empirio/credentials.json fallback).
| Parameter | Type | Required | Description |
|---|---|---|---|
--scope | string | No | Requested OAuth scopes, space-separated. Default: surveys responses. |
empirio login
# or request a narrower scope:
empirio login --scope "surveys"
empirio logout
Revoke the stored access + refresh tokens server-side and clear the local credential store.
empirio logout
empirio whoami
Return the authenticated user, plan, and active scopes. Exempt from the Pro/Enterprise plan gate so every authenticated caller can inspect their own state.
empirio whoami
empirio whoami --json
Survey
empirio survey list
List surveys owned by or shared with the authenticated user.
| Parameter | Type | Required | Description |
|---|---|---|---|
--status | draft | published | all | No | Filter by publication status. Default: all. |
--limit | integer (1–100) | No | Page size. Default: 50. |
--offset | integer (≥ 0) | No | Zero-based pagination offset. |
empirio survey list
empirio survey list --status published --limit 10
empirio survey list --json | jq '.surveys[].id'
empirio survey get
Fetch full survey details: metadata, questions (with stable question_id + option_id), settings, design, logic.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey to retrieve. |
empirio survey get --survey-id 9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b
empirio survey create
Create a new (unpublished) survey draft in either manual mode (you supply title + questions) or AI mode (you supply a prompt and the server generates the survey).
| Parameter | Type | Required | Description |
|---|---|---|---|
--mode | manual | ai | Yes | Creation mode. |
--title | string | In manual | Survey title (manual mode only). |
--ai-prompt | string | In AI | Prompt that describes the survey to generate (AI mode only). |
--questions | JSON array | No | Pre-built question objects (manual mode). See Question Types in the REST sidebar for the per-type schema. |
--metadata | JSON object | No | Survey-level metadata (title, description, display_mode, …). |
--welcome-page | JSON object | No | Welcome page configuration. |
--end-page | JSON object | No | End page configuration. |
--design | JSON object | No | Design customization (template, primary color, …). |
--settings | JSON object | No | Survey behavior settings (including master_locale and auto_advance). |
--idempotency-key | UUID | No | Override the auto-generated idempotency key. |
Manual mode:
empirio survey create \
--mode manual \
--title "Customer Satisfaction Q2" \
--questions '[
{"type":"nps","question":"How likely are you to recommend us?","required":true},
{"type":"text-long","question":"What could we improve?"}
]' \
--settings '{"master_locale":"en-US","auto_advance":true}'
AI mode:
empirio survey create \
--mode ai \
--ai-prompt "Five-question NPS survey for a mobile banking app, in German, with a content block explaining data-protection"
AI mode with design override:
empirio survey create \
--mode ai \
--ai-prompt "Short post-purchase CSAT survey" \
--design '{"template":"modern","primary_color":"#ff5a5f"}' \
--settings '{"master_locale":"de-DE","auto_advance":false}'
Rate limit:
survey_createis additionally capped at 10 / hour and 50 / day per account. On overflow the call returns429withRetry-After.
empirio survey edit
Apply changes to the survey's working draft (does not publish). Manual mode accepts targeted section patches; AI mode regenerates or adjusts via prompt.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey to edit. |
--mode | manual | ai | Yes | Edit mode. |
--ai-prompt | string | In AI | Prompt describing the change (AI mode only). |
--question-operations | JSON array | No | Per-question ops: add / update / delete / reorder. |
--option-operations | JSON array | No | Per-option ops inside a question. |
--metadata | JSON object | No | Patch survey title / description / display_mode. |
--welcome-page | JSON object | No | Replace welcome-page config. |
--end-page | JSON object | No | Replace end-page config. |
--logic-rules | JSON object | No | Conditional-logic rules (rule_id keyed). |
--design | JSON object | No | Design override. |
--settings | JSON object | No | Survey behavior settings, including auto_advance for respondent auto-navigation. |
--translations | JSON object | No | Per-locale translation patches. |
--idempotency-key | UUID | No | Override the auto-generated idempotency key. |
Rename a question via manual mode:
empirio survey edit \
--survey-id 9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b \
--mode manual \
--question-operations '[
{"op":"update","question_id":"q-a3f8d1b2","changes":{"question":"Updated wording","required":true}}
]'
Add a new question at the end:
empirio survey edit --survey-id <uuid> --mode manual \
--question-operations '[
{"op":"add","position":{"type":"end"},"question":{"type":"text","question":"Any other feedback?"}}
]'
AI mode:
empirio survey edit --survey-id <uuid> --mode ai \
--ai-prompt "Add a demographics section with age range and country"
empirio survey publish
Publish a draft, or update a live survey from its working draft. Owner-only.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey to publish. |
--idempotency-key | UUID | No | Override the auto-generated idempotency key. |
empirio survey publish --survey-id 9f3a8b12-4c5d-4e6f-8a1b-0c2d3e4f5a6b
empirio survey unpublish
Move a published survey back to draft state. Owner-only.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey to unpublish. |
empirio survey unpublish --survey-id <uuid>
empirio survey duplicate
Clone a survey as a new unpublished draft.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Source survey. |
empirio survey duplicate --survey-id <uuid>
empirio survey delete
Permanently delete a survey and all its data. Owner-only. Destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey to delete. |
--idempotency-key | UUID | No | Override the auto-generated idempotency key. |
empirio survey delete --survey-id <uuid>
Responses
empirio responses list
List individual responses for a survey with per-question answers and rank-based row_no.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey whose responses to list. |
--date-from | ISO 8601 | No | Filter start. |
--date-to | ISO 8601 | No | Filter end. |
--include-incomplete-participations | boolean | No | Include incomplete participations with saved progress. Default false. |
--limit | integer (1–1000) | No | Default 100. |
--offset | integer (0–100000) | No | Zero-based offset. |
empirio responses list --survey-id <uuid>
empirio responses list --survey-id <uuid> --date-from 2026-04-01 --limit 500
empirio responses aggregates
Get aggregate counts and percentages per question.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey to aggregate. |
--question-ids | comma-separated list | No | Limit to specific questions (max 200). Omit for all. |
empirio responses aggregates --survey-id <uuid>
empirio responses aggregates --survey-id <uuid> --question-ids q-a3f8d1b2,q-7b2f4e11
empirio responses crosstab
Cross-tabulate two questions (rows × columns).
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey. |
--question-x | question_id | Yes | Row question. |
--question-y | question_id | Yes | Column question. |
empirio responses crosstab \
--survey-id <uuid> \
--question-x q-a3f8d1b2 \
--question-y q-7b2f4e11
empirio responses questions
Return question definitions as used by the response endpoints (with question_id / option_id).
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey. |
empirio responses questions --survey-id <uuid>
empirio responses survey-stats
Summary statistics: total responses, completion rate, average duration, last-response-at.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey. |
empirio responses survey-stats --survey-id <uuid>
empirio responses delete
Delete individual responses by rank (--mode rows with --row-numbers from responses list) or wipe all responses (--mode all). Destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey. |
--mode | rows | all | Yes | Targeting mode. |
--row-numbers | comma-separated integers | In rows mode | Ranks from responses list (1-based, max 1000). |
--date-from / --date-to | ISO 8601 | No | Must mirror the responses list filter used for rank resolution. |
--include-incomplete-participations | boolean | No | Must mirror the responses list filter. |
--idempotency-key | UUID | No | Override the auto-generated idempotency key. |
# Delete responses at row 3, 7, 12 (from the last `responses list` you ran):
empirio responses delete --survey-id <uuid> --mode rows --row-numbers 3,7,12
# Wipe everything:
empirio responses delete --survey-id <uuid> --mode all
empirio responses export
Export responses as CSV, XLSX, JSON, or SPSS. Returns a temporary signed download URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey. |
--format | csv | xlsx | json | spss | No | Default csv. |
--date-from / --date-to | ISO 8601 | No | Filter window. |
--include-incomplete-participations | boolean | No | Default false. |
--time-zone | IANA TZ | No | e.g. Europe/Berlin for exported timestamps. |
--answer-filters | JSON array | No | Filter by specific answer values (max 100). |
--idempotency-key | UUID | No | Override the auto-generated idempotency key. |
empirio responses export --survey-id <uuid> --format xlsx
empirio responses export --survey-id <uuid> --format csv --time-zone Europe/Berlin
empirio responses export-charts
Export aggregate charts in one of six formats: native editable PowerPoint (pptx), image-based PowerPoint (pptx_images), landscape A4 PDF (pdf), Word document (docx), ZIP of PNG charts (zip_images), or structured chart JSON (chart_json).
| Parameter | Type | Required | Description |
|---|---|---|---|
--survey-id | UUID | Yes | Survey. |
--format | pptx | pptx_images | pdf | docx | zip_images | chart_json | No | Default pptx. Binary formats return a 1h signed download URL; chart_json returns inline content. |
--date-from / --date-to | ISO 8601 | No | Filter window. |
--include-incomplete-participations | boolean | No | Default false. |
--time-zone | IANA TZ | No | e.g. Europe/Berlin. |
--answer-filters | JSON array | No | Same as responses export. |
--idempotency-key | UUID | No | Override the auto-generated idempotency key. |
empirio responses export-charts --survey-id <uuid> --format pptx
empirio responses export-charts --survey-id <uuid> --format pdf
empirio responses export-charts --survey-id <uuid> --format docx
empirio responses export-charts --survey-id <uuid> --format zip_images
empirio responses export-charts --survey-id <uuid> --format chart_json --json > charts.json