Account Level API

Account Level API Documentation

Overview

The Account Level API lets you build, manage, and analyse surveys from code: create and edit surveys, move them through their lifecycle (start / pause / complete), pull aggregate results, and read individual respondent sessions.

Key Capabilities

  • Create, read, update, and delete surveys
  • Validate a survey payload without saving it
  • Start, pause, and complete surveys
  • Pull aggregate results with per-question distributions, NPS score, and word clouds
  • Read respondent profiles and their sessions across every survey in the account

API Availability

The Account Level API is available on the Standard Plan and above. Upgrade from your account settings.

Account Level API vs. Embeds Integration

Feature Account Level API Embeds Integration
Primary Use Case Programmatic survey management and analytics Real-time survey deployment and interaction
Integration Method RESTful API endpoints HTML snippets or SDKs
Direction Pull / push data from your code to Polling.com Embed surveys into your application
Timing Any point in the survey lifecycle During survey deployment and completion
Capabilities Manage surveys, read results, respondent data, sessions Display surveys, send events, trigger surveys

Use the Account Level API to drive surveys from your own systems or agents; use Embeds when you need the survey rendered inside your app.


Quick Access


Base URL

https://api.polling.com

Authentication

All requests authenticate with your account API key, sent either as:

  • Header: token: <your-api-key>
  • Query parameter: ?token=<your-api-key>

Find your key under Integrations > API Integration > API Key. image_2025-10-02_184038211

Rate Limiting

60 requests per minute per API key. Exceeding the limit returns 429 Too Many Requests.

Common write response

POST /surveys, POST /surveys/{uuid}, and the three lifecycle endpoints (start, pause, complete) all return the same identity payload:

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "status": "ACTIVE",
  "share_url": "https://app.polling.com/forms/550e8400-.../share"
}

DELETE returns 204 No Content.

Question identifiers: hash vs id

Each question has a stable 10-character hash. That's the identifier returned on reads, and the one you send back on update to preserve a question and the answers already collected for it.

id is an optional write-only convenience used at create time, so you can reference a question in a later question's logic.target before hashes exist (for example id: "screener" then logic.target: "screener"). The server resolves id to the generated hash and discards the id — it is never persisted and never returned.

On update, logic.target values must be hashes (either an existing hash on the survey or the id of a new question in the same payload). Use the hashes returned by GET /surveys/{uuid} when round-tripping.

Survey Settings

Every survey has a settings object. All fields are optional; omitted keys fall back to their defaults.

Field Type Default Options / Notes
stop_criteria string forever forever | datetime | responses. Pairs with stop_value.
stop_value string | integer | null null ISO 8601 datetime for datetime; response count for responses. Required when stop_criteria ≠ forever.
start_trigger string now now | scheduled. Pairs with start_at.
start_at datetime | null null ISO 8601. Required when start_trigger = scheduled.
is_anonymous boolean false When true, respondent identifiers are not stored.
redirect_type string none none | custom | results. Pairs with redirect_url.
redirect_url string | null null Redirect target after completion. Required when redirect_type = custom. Must start with http:// or https://.
redirect_url_disqualified string | null null Redirect target for disqualified respondents. Must start with http:// or https://.
user_hash_method string cookie Restrict Repeat Submissions setting: cookie (browser cookie) | ip (IP-based dedup) | none (allow unlimited).
reward_amount number | null null Reward amount offered for completion.
reward_name string | null null Reward description, e.g. "Amazon Gift Card".
complete_html string | null null Custom HTML rendered on the thank-you page.
complete_json object | null null Structured completion-screen config (dashboard-driven).
screenout_enabled boolean false Enable geographic / language screening.
screenout_countries string[] [] ISO 3166-1 alpha-2 country codes allowed to take the survey.
screenout_languages string[] [] ISO 639-1 language codes allowed to take the survey.

Endpoints

List Surveys

List every survey in the account, newest first.

Endpoint:

GET /api/account/surveys

Query parameters:

  • status (optional): filter by status — DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, or DISABLED (case-insensitive).

Example request:

GET /api/account/surveys?status=ACTIVE HTTP/1.1
token: YOUR_API_KEY

Example response (200):

[
  {
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "status": "ACTIVE",
    "share_url": "https://app.polling.com/forms/550e8400-.../share",
    "name": "Customer Satisfaction Survey",
    "description": "Quarterly CSAT survey",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-15T12:00:00Z"
  }
]

Example response (422) — unknown status value:

{
  "error": "Unknown status 'GARBAGE'.",
  "allowed": ["DRAFT", "SCHEDULED", "ACTIVE", "PAUSED", "COMPLETED", "DISABLED"]
}

Create Survey

Create a survey. New surveys start in DRAFT; use the lifecycle endpoints to move them to ACTIVE when you're ready to collect responses.

Endpoint:

POST /api/account/surveys

Example request:

POST /api/account/surveys HTTP/1.1
token: YOUR_API_KEY
Content-Type: application/json

{
  "name": "Customer Satisfaction Survey",
  "description": "Quarterly CSAT survey",
  "questions": [
    {
      "id": "country",
      "type": "radio",
      "title": "Where are you located?",
      "choices": ["US", "Canada", "Other"],
      "required": true,
      "logic": [
        { "if": "is", "value": "Other", "then": "disqualify" }
      ]
    },
    {
      "type": "radio",
      "title": "How satisfied are you?",
      "choices": ["Very satisfied", "Satisfied", "Neutral", "Dissatisfied"],
      "required": true
    },
    {
      "type": "textarea",
      "title": "What could we improve?"
    }
  ],
  "settings": {
    "is_anonymous": true,
    "user_hash_method": "none",
    "stop_criteria": "responses",
    "stop_value": 500
  }
}

Example response (201):

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "status": "DRAFT",
  "share_url": "https://app.polling.com/forms/550e8400-.../share"
}

Example response (422) — validation failure:

{
  "valid": false,
  "errors": [
    { "path": "questions[0].choices", "message": "Choices are required and must be a non-empty array", "severity": "error" }
  ]
}

Every supported type and its type-specific fields are listed in Question Types Reference. For branching / skip / disqualify / URL rules see Logic Rules.


Validate Survey

Validates a survey payload and returns the same errors the create endpoint would, without saving anything.

Endpoint:

POST /api/account/surveys/validate

Example request: same body shape as Create Survey.

Example response (200) — valid (may include warnings):

{
  "valid": true,
  "errors": [
    { "path": "questions[2].title", "message": "Question has no title", "severity": "warning" }
  ]
}

Example response (200) — invalid:

{
  "valid": false,
  "errors": [
    { "path": "name", "message": "Survey name is required", "severity": "error" },
    { "path": "questions[0].choices", "message": "Choices are required and must be a non-empty array", "severity": "error" }
  ]
}

Get Survey Details

Fetch a survey's full configuration. The response can be edited and sent straight back to the update endpoint.

Endpoint:

GET /api/account/surveys/{uuid}

Parameters:

  • uuid (path, required): survey UUID.

Example request:

GET /api/account/surveys/550e8400-e29b-41d4-a716-446655440000 HTTP/1.1
token: YOUR_API_KEY

Example response (200):

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Customer Satisfaction Survey",
  "description": "Quarterly CSAT survey",
  "status": "ACTIVE",
  "share_url": "https://app.polling.com/forms/550e8400-.../share",
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-15T12:00:00Z",
  "questions": [
    {
      "hash": "aB3xY9kL2q",
      "type": "radio",
      "title": "Where are you located?",
      "required": true,
      "choices": ["US", "Canada", "Other"],
      "logic": [
        { "if": "is", "value": "Other", "then": "disqualify" }
      ]
    },
    {
      "hash": "mN7pQ4rS8t",
      "type": "textarea",
      "title": "What could we improve?"
    }
  ],
  "settings": {
    "stop_criteria": "responses",
    "stop_value": 500,
    "start_trigger": "now",
    "start_at": null,
    "is_anonymous": true,
    "redirect_type": "none",
    "redirect_url": null,
    "redirect_url_disqualified": null,
    "user_hash_method": "none",
    "reward_amount": null,
    "reward_name": null,
    "complete_html": null,
    "complete_json": null,
    "screenout_enabled": false,
    "screenout_countries": [],
    "screenout_languages": []
  }
}

Update Survey

Replace the survey's name, description, questions, and settings. Questions are matched by hash: include a question's hash to keep it (and its collected answers) intact, omit it to delete it, or add a new entry (without hash) to create one. See Logic Rules for logic.target semantics during updates.

Endpoint:

POST /api/account/surveys/{uuid}

Body flags:

  • confirm_delete_questions_with_answers (boolean, default false): required when removing a question that already has collected answers. Without it the request returns 409.

Example request — reorder and add a new question, preserving hashes:

POST /api/account/surveys/550e8400-e29b-41d4-a716-446655440000 HTTP/1.1
token: YOUR_API_KEY
Content-Type: application/json

{
  "name": "Customer Satisfaction Survey",
  "description": "Added an NPS question and reordered.",
  "questions": [
    {
      "hash": "aB3xY9kL2q",
      "type": "radio",
      "title": "Where are you located?",
      "choices": ["US", "Canada", "Other"],
      "required": true,
      "logic": [{ "if": "is", "value": "Other", "then": "disqualify" }]
    },
    {
      "type": "nps",
      "title": "How likely are you to recommend us?",
      "rate_max": 10
    },
    {
      "hash": "mN7pQ4rS8t",
      "type": "textarea",
      "title": "What could we improve?"
    }
  ]
}

Example response (200):

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "status": "ACTIVE",
  "share_url": "https://app.polling.com/forms/550e8400-.../share"
}

Example response (409) — removing a question with collected answers:

{
  "message": "Some questions you are removing already have collected responses.",
  "questions_with_answers": [
    { "id": 42, "title": "How satisfied are you?", "answer_count": 57 }
  ]
}

Pass confirm_delete_questions_with_answers: true in the body to proceed anyway.

Example response (422) — hash from another survey:

{
  "valid": false,
  "errors": [
    { "path": "questions", "message": "hash 'aB3xY9kL2q' does not reference a question on this survey", "severity": "error" }
  ]
}

Every hash in an update payload must belong to the survey being updated. Mixing hashes from another survey (even one in the same account) is rejected — fetch the target survey's current hashes with GET /surveys/{uuid} and round-trip them.

The survey status is not modified here — use the lifecycle endpoints below.


Delete Survey

Permanently delete a survey along with every session and answer. Irreversible; use /complete instead if you just want to stop collecting responses.

Endpoint:

DELETE /api/account/surveys/{uuid}

Example request:

DELETE /api/account/surveys/550e8400-e29b-41d4-a716-446655440000 HTTP/1.1
token: YOUR_API_KEY

Example response: 204 No Content.


Lifecycle: Start, Pause, Complete

Move a survey through its lifecycle. Invalid transitions (e.g. reactivating a COMPLETED survey) return 409.

POST /api/account/surveys/{uuid}/start     → ACTIVE    (from DRAFT, SCHEDULED, or PAUSED)
POST /api/account/surveys/{uuid}/pause     → PAUSED    (from ACTIVE)
POST /api/account/surveys/{uuid}/complete  → COMPLETED (terminal; from any non-DISABLED status)

Example request:

POST /api/account/surveys/550e8400-e29b-41d4-a716-446655440000/start HTTP/1.1
token: YOUR_API_KEY

Example response (200):

{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "status": "ACTIVE",
  "share_url": "https://app.polling.com/forms/550e8400-.../share"
}

Example response (409) — transition not allowed:

{
  "error": "Cannot start a survey in status 'COMPLETED'.",
  "current_status": "COMPLETED",
  "allowed_from": ["DRAFT", "SCHEDULED", "PAUSED", "ACTIVE"]
}

Status values: DRAFT, SCHEDULED, ACTIVE, PAUSED, COMPLETED, DISABLED. DISABLED is a staff-imposed lock — contact support if you land in it.


Get Survey Results

Aggregate stats, per-question summaries, and respondent demographics for a survey. Each question summary carries common fields (hash, question, type, total_answers, avg_time_seconds) plus only the extra fields that apply to its type — see Question Types Reference for every type's result shape.

Endpoint:

GET /api/account/surveys/{uuid}/results

Query parameters (all optional):

  • country — ISO country code
  • language — language code
  • from, toYYYY-MM-DD date range, applied to session start
  • session_statusonly_completed | only_incompleted | only_disqualified | except_disqualified
  • exclude_botstrue to exclude bot-flagged respondents

Per-question-type fields:

Question types Extra fields
radio, checkbox, boolean, confirmation choices: [{value, count, percent}]
rating rate_format, rate_max, avg_rating, choices
nps rate_max, avg_score, nps_score, detractors, passives, promoters, choices
ranking choices: [{value, avg_rank, position_counts}]
matrix_radio, matrix_checkbox, matrix_linear matrix: {rows: [{value, columns: [{value, count, percent}]}]}
signature signed_count, not_signed_count
click_heatmap image_url, clicks: [{x, y, count}]
text, textarea, address word_cloud: [{word, count}] — top 20 words, stopwords removed; empty array when there are no answers.

Example request:

GET /api/account/surveys/550e8400-.../results?country=US&from=2026-01-01&session_status=only_completed HTTP/1.1
token: YOUR_API_KEY

Example response (200 — abridged):

{
  "survey": { "uuid": "550e8400-...", "name": "CSAT Q1", "status": "ACTIVE" },
  "stats": {
    "sessions": { "completed": 423, "incompleted": 0, "disqualified": 0, "total": 423 },
    "completion_rate_percent": 100.0,
    "median_completion_seconds": 187,
    "questions_count": 3
  },
  "demographics": {
    "countries": [{ "code": "US", "count": 423 }],
    "languages": [{ "code": "en", "count": 389 }, { "code": "es", "count": 34 }]
  },
  "questions": [
    {
      "hash": "aB3xY9kL2q",
      "question": "How satisfied are you?",
      "type": "radio",
      "total_answers": 423,
      "avg_time_seconds": 4.2,
      "choices": [
        { "value": "Very satisfied", "count": 201, "percent": 47.5 },
        { "value": "Satisfied",      "count": 158, "percent": 37.4 }
      ]
    },
    {
      "hash": "xY2zA6bC9d",
      "question": "How likely are you to recommend us?",
      "type": "nps",
      "total_answers": 423,
      "avg_time_seconds": 2.1,
      "rate_max": 10,
      "avg_score": 7.8,
      "nps_score": 31.7,
      "detractors": { "count": 67,  "percent": 15.8 },
      "passives":   { "count": 155, "percent": 36.6 },
      "promoters":  { "count": 201, "percent": 47.5 },
      "choices": [
        { "value": "10", "count": 89, "percent": 21.0 }
      ]
    },
    {
      "hash": "mN7pQ4rS8t",
      "question": "What could we improve?",
      "type": "textarea",
      "total_answers": 318,
      "avg_time_seconds": 22.8,
      "word_cloud": [
        { "word": "documentation", "count": 42 },
        { "word": "pricing",       "count": 38 }
      ]
    }
  ],
  "filters_applied": {
    "country": "US", "language": null, "from": "2026-01-01", "to": null,
    "session_status": "only_completed", "exclude_bots": false
  }
}

Get Sessions by Respondent

Every session a given respondent has on a single survey.

Endpoint:

GET /api/account/surveys/{uuid}/sessions-by-respondent/{respondent_id}

Parameters:

  • uuid (path, required): survey UUID.
  • respondent_id (path, required): the external_id you passed via the Embed integration's customer_id.

Example request:

GET /api/account/surveys/550e8400-.../sessions-by-respondent/user_12345 HTTP/1.1
token: YOUR_API_KEY

Example response (200):

[
  {
    "session_id": 456,
    "is_completed": true,
    "is_disqualified": false,
    "started_at": "2024-01-20T14:30:00Z",
    "completed_at": "2024-01-20T14:45:00Z",
    "answers": [
      {
        "question_hash": "aB3xY9kL2q",
        "question": "How satisfied are you with our service?",
        "answer": "Very satisfied"
      }
    ]
  }
]

Get Respondent Details

A respondent's profile plus every session they have across every survey in the account. Each session includes a survey_uuid so you can identify which survey it belongs to.

Endpoint:

GET /api/account/respondents/{respondent_id}

Parameters:

  • respondent_id (path, required): the external_id you passed via the Embed integration's customer_id.

Example request:

GET /api/account/respondents/user_12345 HTTP/1.1
token: YOUR_API_KEY

Example response (200):

{
  "external_id": "user_12345",
  "country": "US",
  "region": "California",
  "city": "San Francisco",
  "language": "en",
  "is_anonymous": false,
  "is_mobile": true,
  "lat": 37.7749,
  "lon": -122.4194,
  "last_access_at": "2024-01-20T14:45:00Z",
  "created_at": "2024-01-10T08:00:00Z",
  "sessions": [
    {
      "survey_uuid": "550e8400-e29b-41d4-a716-446655440000",
      "session_id": 456,
      "is_completed": true,
      "is_disqualified": false,
      "started_at": "2024-01-20T14:30:00Z",
      "completed_at": "2024-01-20T14:45:00Z",
      "answers": [
        {
          "question_hash": "aB3xY9kL2q",
          "question": "How satisfied are you with our service?",
          "answer": "Very satisfied"
        }
      ]
    }
  ]
}

List Survey Respondents

The external ids of every respondent that completed a survey. Respondents who started but didn't finish aren't included. Capped at 1000 entries.

Endpoint:

GET /api/account/surveys/{uuid}/respondents/list

Parameters:

  • uuid (path, required): survey UUID.

Example request:

GET /api/account/surveys/550e8400-.../respondents/list HTTP/1.1
token: YOUR_API_KEY

Example response (200):

[
  "user_12345",
  "user_67890",
  "customer_abc",
  "customer_def"
]

Status Codes

  • 200 OK — successful read or update.
  • 201 Created — survey created.
  • 204 No Content — successful delete.
  • 401 Unauthorized — missing or invalid API key.
  • 403 Forbidden — the account's plan doesn't include API access (Standard or above required).
  • 404 Not Found — resource doesn't exist or doesn't belong to the account.
  • 409 Conflict — destructive update without confirm_delete_questions_with_answers=true, or a lifecycle transition that isn't allowed from the survey's current status.
  • 422 Unprocessable Entity — payload validation failed.
  • 429 Too Many Requests — rate limit exceeded (60 requests per minute).
  • 500 Internal Server Error — server error. The response includes a code you can reference in support requests.

Logic Rules

A question can carry a logic array. Each rule matches an if condition against the respondent's answer and, on the first match, applies a then action. Rules are evaluated top-down; the first match wins.

{
  "logic": [
    { "if": "is",    "value": "Other", "then": "disqualify" },
    { "if": "lower", "value": 7,       "then": "go_question", "target": "aB3xY9kL2q" },
    { "if": "higher","value": 6,       "then": "finish" }
  ]
}

if conditions

Condition What it checks value shape
is Answer equals value string | number
is_filled Answer is not empty (no value)
is_empty Answer is empty (no value)
contains_any Selected choices include at least one value from the list string[]
contains_all Selected choices include every value in the list string[]
doesnt_contains_any None of the listed values are selected string[]
doesnt_contains_all At least one listed value is not selected string[]
contains Free-text answer contains the substring string
doesnt_contains Free-text answer doesn't contain the substring string
between Numeric / date answer is within range (inclusive) { "from": ..., "to": ... }
higher Numeric / date answer is strictly greater than value number | string
lower Numeric / date answer is strictly less than value number | string

Which conditions work with which question types

Condition radio checkbox ranking text textarea rating nps boolean confirmation matrix_* address
is / is_filled / is_empty
contains_any / doesnt_contains_any
contains_all / doesnt_contains_all
contains / doesnt_contains ✓¹
between / higher / lower ✓²

¹ Only for text questions with input_type of text, email, or url. ² Only for text questions with input_type of number, date, time, or datetime-local.

Logic is not supported on captcha, signature, or click_heatmap. Using a condition that isn't valid for a question's type returns 422 at create / update time.

then actions

Action What it does Extra field
go_question Jump to another question, skipping anything in between target — question hash
make_required Mark another question as required target — question hash
make_not_required Mark another question as optional target — question hash
disable_question Hide another question (pair with default_disabled: true) target — question hash
enable_question Show another question that was default_disabled target — question hash
finish End the survey and render the thank-you page
disqualify End the survey and render the disqualified page
open_url Redirect the respondent to a URL url — must start with http:// or https://

target — identifying a question inside logic

target accepts either:

  • A hash — the preferred form. Use this on any rule that references an existing question (always available on read).
  • An id — a create-time convenience. If you set id: "screener" on a question, a later rule can say target: "screener" before hashes exist. Ids are not persisted, so on update you must use hashes.


Question Types Reference

Every question shares the common fields from Create Survey: type, optional id (write-only) or hash (on update), title, helper_text, required, default_disabled, logic. The snippets below show the type-specific input fields and the matching object you'll see inside the Results response's questions[] array.

radio — Single-select

Create / update payload:

{
  "type": "radio",
  "title": "How did you hear about us?",
  "choices": ["Search engine", "Social media", "Friend", "Other"],
  "randomize": false,
  "other_choice": true,
  "other_choice_text": "Somewhere else…",
  "image_choices": false
}

Choices can be plain strings or {text, value} objects (or {text, image} when image_choices: true).

Results JSON:

{
  "hash": "aB3xY9kL2q",
  "question": "How did you hear about us?",
  "type": "radio",
  "total_answers": 423,
  "avg_time_seconds": 4.2,
  "choices": [
    { "value": "Search engine", "count": 180, "percent": 42.6 },
    { "value": "Social media",  "count": 120, "percent": 28.4 },
    { "value": "Friend",        "count":  95, "percent": 22.5 },
    { "value": "Other",         "count":  28, "percent":  6.6 }
  ]
}

checkbox — Multi-select

Create / update payload:

{
  "type": "checkbox",
  "title": "Which features do you use?",
  "choices": ["Dashboard", "Reports", "Integrations", "API"],
  "min_choices": 1,
  "max_choices": 3,
  "randomize": false,
  "other_choice": false
}

Supports everything radio does, plus min_choices / max_choices.

Results JSON:

{
  "hash": "cD4eF6gH8i",
  "question": "Which features do you use?",
  "type": "checkbox",
  "total_answers": 398,
  "avg_time_seconds": 6.8,
  "choices": [
    { "value": "Dashboard",    "count": 312, "percent": 78.4 },
    { "value": "Reports",      "count": 287, "percent": 72.1 },
    { "value": "Integrations", "count": 164, "percent": 41.2 },
    { "value": "API",          "count":  91, "percent": 22.9 }
  ]
}

Counts are per-selection (not per-session), so choices[].count can sum above total_answerstotal_answers is the number of sessions that answered at all.

ranking — Drag-to-rank

Create / update payload:

{
  "type": "ranking",
  "title": "Rank these priorities",
  "choices": ["Reliability", "Performance", "Ease of use", "Price"],
  "randomize": true,
  "image_choices": false
}

All defined choices must be ranked — there is no "Other".

Results JSON:

{
  "hash": "jK5lM7nP9q",
  "question": "Rank these priorities",
  "type": "ranking",
  "total_answers": 341,
  "avg_time_seconds": 18.4,
  "choices": [
    { "value": "Reliability", "avg_rank": 1.47, "position_counts": [189, 87, 43, 22] },
    { "value": "Performance", "avg_rank": 2.12, "position_counts": [87, 152, 78, 24] },
    { "value": "Ease of use", "avg_rank": 2.88, "position_counts": [43, 68, 141, 89] },
    { "value": "Price",       "avg_rank": 3.53, "position_counts": [22, 34, 79, 206] }
  ]
}

position_counts[i] is how many sessions placed the choice at position i+1.

text — Single-line input

Create / update payload:

{
  "type": "text",
  "title": "What's your email?",
  "input_type": "email",
  "placeholder": "you@example.com",
  "min": "5",
  "max": "100",
  "regex": "^[^@]+@[^@]+\\.[^@]+$"
}

input_type is one of text (default), email, url, number, date, time, datetime-local. min / max mean character length for text inputs and numeric / temporal bounds for the rest. regex is only applied to input_type: "text".

Results JSON:

{
  "hash": "lM8nO1pQ3r",
  "question": "What's your email?",
  "type": "text",
  "total_answers": 372,
  "avg_time_seconds": 5.1,
  "word_cloud": [
    { "word": "gmail",   "count": 198 },
    { "word": "com",     "count": 201 },
    { "word": "outlook", "count":  47 },
    { "word": "yahoo",   "count":  28 }
  ]
}

Top 20 words, stopwords removed. Empty array when the question has no answers.

textarea — Multi-line input

Create / update payload:

{
  "type": "textarea",
  "title": "Any other feedback?",
  "placeholder": "Share your thoughts…"
}

Results JSON:

{
  "hash": "mN7pQ4rS8t",
  "question": "Any other feedback?",
  "type": "textarea",
  "total_answers": 318,
  "avg_time_seconds": 22.8,
  "word_cloud": [
    { "word": "documentation", "count": 42 },
    { "word": "pricing",       "count": 38 },
    { "word": "mobile",        "count": 29 },
    { "word": "notifications", "count": 21 }
  ]
}

rating — Stars / smileys / labels

Create / update payload:

{
  "type": "rating",
  "title": "Rate our service",
  "rate_format": "stars",
  "rate_max": 5
}

rate_format is stars (default), smileys, or labels. rate_max is 1–20.

Results JSON:

{
  "hash": "rS2tU4vW6x",
  "question": "Rate our service",
  "type": "rating",
  "total_answers": 419,
  "avg_time_seconds": 2.9,
  "rate_format": "stars",
  "rate_max": 5,
  "avg_rating": 4.2,
  "choices": [
    { "value": "1", "count":   6, "percent":  1.4 },
    { "value": "2", "count":  15, "percent":  3.6 },
    { "value": "3", "count":  58, "percent": 13.8 },
    { "value": "4", "count": 142, "percent": 33.9 },
    { "value": "5", "count": 198, "percent": 47.3 }
  ]
}

Distribution covers 1..rate_max.

nps — Net Promoter Score

Create / update payload:

{
  "type": "nps",
  "title": "How likely are you to recommend us?",
  "rate_max": 10,
  "rate_format": "labels"
}

rate_max is 5 or 10 (default). Standard NPS is 0–10.

Results JSON:

{
  "hash": "xY2zA6bC9d",
  "question": "How likely are you to recommend us?",
  "type": "nps",
  "total_answers": 423,
  "avg_time_seconds": 2.1,
  "rate_max": 10,
  "avg_score": 7.8,
  "nps_score": 31.7,
  "detractors": { "count":  67, "percent": 15.8 },
  "passives":   { "count": 155, "percent": 36.6 },
  "promoters":  { "count": 201, "percent": 47.5 },
  "choices": [
    { "value": "0",  "count":   1, "percent":  0.2 },
    { "value": "1",  "count":   2, "percent":  0.5 },
    { "value": "6",  "count":  30, "percent":  7.1 },
    { "value": "7",  "count":  61, "percent": 14.4 },
    { "value": "8",  "count":  94, "percent": 22.2 },
    { "value": "9",  "count": 112, "percent": 26.5 },
    { "value": "10", "count":  89, "percent": 21.0 }
  ]
}

nps_score = promoters_percent - detractors_percent. Distribution covers 0..rate_max.

boolean — Yes / No

Create / update payload:

{
  "type": "boolean",
  "title": "Have you used the new dashboard?",
  "label_true": "Yes",
  "label_false": "No"
}

Results JSON:

{
  "hash": "eF3gH5iJ7k",
  "question": "Have you used the new dashboard?",
  "type": "boolean",
  "total_answers": 405,
  "avg_time_seconds": 1.8,
  "choices": [
    { "value": "No",  "count": 118, "percent": 29.1 },
    { "value": "Yes", "count": 287, "percent": 70.9 }
  ]
}

choices[].value reflects the label_true / label_false strings.

confirmation — Agree / Disagree (consent gate)

Create / update payload:

{
  "type": "confirmation",
  "title": "Do you consent to the terms?",
  "label_agree": "I consent",
  "label_disagree": "I do not consent"
}

A disqualify-on-disagree logic rule is auto-injected — custom logic rules are allowed and merged with it.

Results JSON:

{
  "hash": "pQ1rS3tU5v",
  "question": "Do you consent to the terms?",
  "type": "confirmation",
  "total_answers": 511,
  "avg_time_seconds": 3.5,
  "choices": [
    { "value": "I do not consent", "count":  12, "percent":  2.3 },
    { "value": "I consent",        "count": 499, "percent": 97.7 }
  ]
}

matrix_radio — Grid, single-select per row

Create / update payload:

{
  "type": "matrix_radio",
  "title": "Rate each area",
  "rows": [
    { "value": "quality", "text": "Quality" },
    { "value": "speed",   "text": "Speed"   }
  ],
  "columns": [
    { "value": "good", "text": "Good" },
    { "value": "bad",  "text": "Bad"  }
  ]
}

rows / columns can be plain strings or {text, value} objects.

Results JSON:

{
  "hash": "uV6wX8yZ0a",
  "question": "Rate each area",
  "type": "matrix_radio",
  "total_answers": 381,
  "avg_time_seconds": 14.2,
  "matrix": {
    "rows": [
      {
        "value": "Quality",
        "columns": [
          { "value": "Good", "count": 340, "percent": 89.2 },
          { "value": "Bad",  "count":  41, "percent": 10.8 }
        ]
      },
      {
        "value": "Speed",
        "columns": [
          { "value": "Good", "count": 298, "percent": 78.2 },
          { "value": "Bad",  "count":  83, "percent": 21.8 }
        ]
      }
    ]
  }
}

matrix_checkbox — Grid, multi-select per row

Same payload shape as matrix_radio; respondents can tick multiple columns per row.

Results JSON: same matrix.rows[].columns shape as matrix_radio. Counts are per-selection, so percent values within a single row can sum above 100%.

matrix_linear — Likert grid

Create / update payload:

{
  "type": "matrix_linear",
  "title": "To what extent do you agree?",
  "rows": [
    "The team is responsive",
    "The product is reliable",
    "The docs are clear"
  ],
  "columns": [
    "Strongly disagree",
    "Disagree",
    "Neutral",
    "Agree",
    "Strongly agree"
  ]
}

Results JSON:

{
  "hash": "aB2cD4eF6g",
  "question": "To what extent do you agree?",
  "type": "matrix_linear",
  "total_answers": 412,
  "avg_time_seconds": 17.1,
  "matrix": {
    "rows": [
      {
        "value": "The team is responsive",
        "columns": [
          { "value": "Strongly disagree", "count":   4, "percent":  1.0 },
          { "value": "Disagree",          "count":  17, "percent":  4.1 },
          { "value": "Neutral",           "count":  59, "percent": 14.3 },
          { "value": "Agree",             "count": 182, "percent": 44.2 },
          { "value": "Strongly agree",    "count": 150, "percent": 36.4 }
        ]
      }
    ]
  }
}

signature — Canvas signature capture

Create / update payload:

{
  "type": "signature",
  "title": "Please sign below"
}

Plan requirement: Standard+. Logic rules are not supported.

Results JSON:

{
  "hash": "bC4dE7fG1h",
  "question": "Please sign below",
  "type": "signature",
  "total_answers": 142,
  "avg_time_seconds": 8.7,
  "signed_count": 110,
  "not_signed_count": 32
}

click_heatmap — Clickable image

Create / update payload:

{
  "type": "click_heatmap",
  "title": "Where would you click first?",
  "image_url": "https://cdn.example.com/screenshots/homepage.png"
}

Plan requirement: Standard+. Logic rules are not supported.

Results JSON:

{
  "hash": "hK8lM2nO5p",
  "question": "Where would you click first?",
  "type": "click_heatmap",
  "total_answers": 89,
  "avg_time_seconds": 5.4,
  "image_url": "https://cdn.example.com/screenshots/homepage.png",
  "clicks": [
    { "x": 0.45, "y": 0.62, "count": 28 },
    { "x": 0.80, "y": 0.15, "count": 17 },
    { "x": 0.12, "y": 0.88, "count":  9 }
  ]
}

x and y are fractions of the image's width and height (0.01.0).

address — Structured address input

Create / update payload:

{
  "type": "address",
  "title": "Shipping address",
  "address_placeholders": {
    "address_line_1": "Street address",
    "address_line_2": "Apt, suite, unit…",
    "city": "City",
    "state": "State / Province",
    "zip_code": "ZIP / Postal code",
    "country": "Country"
  }
}

All address_placeholders keys are optional.

Results JSON:

{
  "hash": "dE6fG8hI0j",
  "question": "Shipping address",
  "type": "address",
  "total_answers": 204,
  "avg_time_seconds": 19.7,
  "word_cloud": [
    { "word": "street",  "count": 78 },
    { "word": "avenue",  "count": 42 },
    { "word": "new",     "count": 35 },
    { "word": "york",    "count": 29 }
  ]
}

Address lines are tokenised as free text.

captcha — Bot-check challenge

Create / update payload:

{
  "type": "captcha",
  "title": "Verify you're human"
}

Plan requirement: Standard+. No answer is persisted, so captcha never appears in results. Logic rules are not supported.

page — Static HTML element between questions

Create / update payload:

{
  "type": "page",
  "content": "<h2>Section 2</h2><p>The following questions are about billing.</p>"
}

Plan requirement: Essentials+. Pages don't collect responses and never appear in results.