Skip to main content

Surveys API

Survey campaign management and response collection Base path: /api/v1/surveys Endpoint count: 14

Public survey response (recipient-facing)

These two endpoints power the page a recipient lands on when they open a survey link. They are unauthenticated — the request carries no API key. Access is granted by the single-use token embedded in the link, so you never expose your API key to end users. The token is minted when you send a survey with POST /api/v1/surveys/{id}/send and is baked into the SMS, email, WhatsApp, RCS, or Viber message each recipient receives. Tokens are valid for 30 days. Most teams let recipients use the hosted page below; the JSON form of the submit endpoint is there for when you render your own response UI.
Do not send an X-API-Key header to these endpoints, and do not build the token yourself — always use the link returned by the send endpoint. Both endpoints are rate-limited to 10 requests per minute per IP.

Open the survey response page

GET /api/v1/public/surveys/{token}
string
required
The signed token from the survey link. An invalid or expired token returns a 404 page rather than revealing whether a survey exists.
Returns a self-contained HTML page with a 0–10 rating scale and, when the survey defines a follow-up prompt, a comment box. If the recipient has already answered, the page shows their recorded score instead of the form. The response is served with Cache-Control: private, no-store, so shared links are never cached.

Submit a survey response

POST /api/v1/public/surveys/{token}/submit
string
required
The signed token from the survey link.
integer
The rating, 0–10. Optional when a comment is provided.
string
A free-text comment, up to 2000 characters. Optional when a score is provided.
Send at least one of score or comment; a request with neither is rejected. The endpoint accepts either a JSON body or a standard application/x-www-form-urlencoded form post, so the hosted page and your own integration share the same route. Submitting again with the same token is safe — the first answer is kept and never double-counted. Set Accept: application/json to receive a JSON envelope; otherwise the endpoint returns an HTML thank-you page for browser form posts.
Errors

Reminder waves for non-responders

A survey’s response rate is set by what happens after the first send. Two endpoints close that loop: one lists the contacts who have not answered yet, and one re-sends the survey to them. This is safer than manually re-sending to a stale audience. A contact counts as a non-responder only while their latest send is unanswered — the moment they reply (from any earlier link), they drop out of the list and out of every future wave. Each reminder then runs through the same delivery guardrails as the original send — if a contact was already re-sent the survey (by a concurrent wave, an automation, or a manual resend) inside the 24-hour dedupe window, they come back skipped, never double-sent.

List non-responders

GET /api/v1/surveys/{id}/non-responders
string
required
The survey template id.
integer
How old a contact’s most recent send must be before they surface — defaults to 24. Ranges 1–8760. Bad values fall back to the default rather than erroring, so the list stays loadable.
integer
Page size, 1–200, default 50.
integer
Pagination offset, default 0.
Returns the current non-responders for the survey — each with its contact id, the channel of the contact’s latest send, and that send’s timestamp — plus a total for the full list, so you can show “203 contacts still out” while rendering the first page.

Send a reminder wave

POST /api/v1/surveys/{id}/remind
string
required
The survey template id.
integer
Only contacts whose latest send is at least this old join the wave — default 24, range 1–8760.
array of strings
Restrict the wave to this subset of non-responders (max 1,000). Omit to wave at everyone GET /non-responders would currently list.
integer
Override the cross-survey fatigue cap for this wave (default 72 hours at platform level). Pass 0 to disable cross-survey fatigue capping for this wave.
Responds with requested, sent, and skipped counts — plus a skipped_by_reason breakdown (for example, deduped when the 24-hour same-survey recency check still guards the contact). When nobody is eligible yet, the response is { "requested": 0, "sent": 0, "skipped": 0, "reason": "no_non_responders" }. Every reminder re-uses the channel the contact’s last send used, so a WhatsApp non-responder is chased on WhatsApp, an email non-responder on email. The wave is also auditable — each POST /remind call is written to the audit log with the requested / sent / skipped totals.

Post-conversation scorecards by queue or agent

Post-conversation surveys (the auto-CSAT/NPS close-triggered dispatch and the post-call IVR captures) collect scores per interaction. GET /api/v1/surveys/scorecards rolls the answered responses up into one scorecard per queue (?group_by=queue, the default) or per handling agent (?group_by=agent), so a supervisor can compare teams and agents on the same headline metrics.
GET /api/v1/surveys/scorecards
string
queue (default) buckets by the queue that handled the interaction; agent buckets by the handling agent.
string
csat, nps, or all (default). Restrict the rollup to one metric.
string
Narrow to one queue bucket (with group_by=queue).
string
Narrow to one agent bucket (with group_by=agent).
string
ISO timestamp window lower bound. Only answered responses count.
string
ISO timestamp window upper bound.
integer
Page size, 1–200 (default 50). A bucket with zero answers drops out entirely.
integer
Pagination offset, default 0.
Each row carries responses, satisfied_count, satisfaction_pct (share at score 4–5, the top-two-box), avg_score, csat_avg_score, and nps (promoters minus detractors, over NPS rows only) plus nps_responses. CSAT and NPS averages are kept independent — a blended mean across the two scales is meaningless.
Unanswered sends never distort the numbers: a queue or agent bucket appears only once it has at least one answered response, so sampled-off or not-yet-answered sends never surface as zero-response rows.

Open the survey response page

GET /api/v1/public/surveys/{token}
Recipient-facing landing page for a survey invitation. Verifies the signed token from the link, then renders a self-contained HTML page with a 0-10 score row and an optional comment box — or a thank-you page when the recipient has already answered. Unauthenticated; an invalid or expired token returns a 404 HTML page. Use it as the destination URL for NPS and CSAT survey links.
string
required
—

List all survey templates

GET /api/v1/surveys
List the tenant’s survey templates (NPS / CSAT / CES, SMS / WhatsApp / email / Viber / RCS / push), newest first, each with a sent_count and responded_count rollup. Paginate with ?limit= and &offset=; use it to pick the template to send or inspect. Read access: any authenticated role.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Get a single survey template

GET /api/v1/surveys/{id}
Fetch one survey template by id, including its full multi-question definition (questions, options, skip-branch rules, conjoint / maxdiff designs) and per-locale translations map. Use it to render or edit a template before sending. 404 when the template does not exist for this tenant.
string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Benchmark this survey’s CSAT / NPS / CES score against industry-vertical peer percentiles — percentile rank, quartile rating, and delta vs the industry median. Pick the peer group with ?vertical=

GET /api/v1/surveys/{id}/benchmark
string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

List contacts eligible for a reminder wave — sent this survey, not yet responded, stale for at least ?stale_hours= (default 24)

GET /api/v1/surveys/{id}/non-responders
The CXaaS response-rate lever. A non-responder is a contact whose MOST RECENT send for this survey went out at least ?stale_hours= ago (default 24, matching the same-survey dedupe window) and who has recorded no response since — evaluated per-contact so an already-answered contact is never re-targeted. Returns the page plus a total count and the applied stale_hours. Paginate with ?limit= and &offset=.
string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Aggregate survey rollup — avg score, NPS score, response rate

GET /api/v1/surveys/{id}/results
Aggregate scoring for one survey: total_sent, total_responded, response_rate, avg_score, nps_score (only for NPS templates, promoter% − detractor% over scored responses, rounded to one decimal), and a breakdown_by_score histogram. Use it as the headline metric for one program; for cohort breakout filter it with the VoC endpoint.
string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Voice-of-Customer analytics over verbatim comments — sentiment, themes, drivers, NPS trend over time. Optionally scoped by channel / agent_id / team_id / segment_id for per-breakout scorecards

GET /api/v1/surveys/{id}/voc
Voice-of-Customer text analytics over the survey’s free-text verbatim comments: lexicon sentiment distribution, theme / topic extraction, per-theme driver deltas vs overall, an NPS / score trend over time, and a capped set of sentiment-/theme-tagged verbatims (hard-capped at 5000 rows). Scope by ?granularity= plus channel / agent_id / team_id / segment_id for a per-breakout scorecard. Read-only.
string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Queue- / agent-level CSAT / NPS scorecards across answered survey responses

GET /api/v1/surveys/scorecards
Post-conversation survey scorecards: buckets the tenant’s answered CSAT / NPS survey_responses by queue (call_logs.metadata->>‘queue_id’, joined via call_id) or by handled agent (survey_responses.agent_user_id) and returns one scorecard per bucket with responses, satisfaction_pct (top-2-box score >= 4), avg_score, csat_avg_score, and NPS (promoters − detractors over NPS rows). Narrow to one bucket with ?queue_id= / ?agent_user_id=; restrict survey type with ?survey_type=csat|nps; bound the window with ?from= / ?to=. Paginate with ?limit= / &offset=. Registered as a static route so it wins over /:id. Read access: any authenticated role.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Submit a survey response

POST /api/v1/public/surveys/{token}/submit
Records a recipient’s answer to a survey link: a 0-10 score and/or a free-text comment, keyed off the signed token in the URL. Accepts either a JSON body or an HTML form post and is idempotent per link, so a repeat submission never double-counts. Unauthenticated; an invalid or expired token returns 403. JSON clients receive a { data, meta } envelope while HTML form posts receive a thank-you page.
string
required
—

Create a survey template

POST /api/v1/surveys
Create a survey template: pick the metric type (nps / csat / ces), the 0–10 question copy, and the channels it can be delivered on. Optionally attach a multi-question definition (choice / matrix / open-text / ranking / conjoint / maxdiff, with skip-branch logic), a follow-up prompt, and per-locale translations. The template is inert until you send it; returns 201 with the created template. Requires owner / admin / developer.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the Idempotency-Replay: true response header.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Send a reminder wave to a survey’s non-responders (CXaaS response-rate lever) — re-dispatches through the same channel check, dedupe, and fatigue cap as the original send

POST /api/v1/surveys/{id}/remind
The CXaaS response-rate lever. Resolve the same non-responder set GET /non-responders reports (optionally narrowed to contact_ids, up to 1000), then re-dispatch each through the same single-contact primitive the automation engine uses — so the wave inherits the channel check, 24h dedupe, and fatigue cap of the original send; anyone re-surveyed by a concurrent process simply comes back skipped. Returns sent / skipped / skipped_by_reason. Requires owner / admin / developer.
string
required
—
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the Idempotency-Replay: true response header.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Send a survey to an audience (contact, list, or segment)

POST /api/v1/surveys/{id}/send
Dispatch this survey to one of: a single contact (contact_id), an explicit list (contact_ids, up to 1000), or a dynamic segment (segment_id), on one of the template’s configured channels. Each recipient gets a signed landing URL and passes the 24h same-survey dedupe plus the configurable cross-survey fatigue cap (fatigue_window_hours, default 72; 0 disables). Hard-capped at 5000 contacts per call. Returns sent / skipped counts. Requires owner / admin / developer.
string
required
—
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the Idempotency-Replay: true response header.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Update a survey template

PATCH /api/v1/surveys/{id}
Patch a survey template — any of name, type, question, channels, follow_up_prompt, questions, default_locale, or translations; omitted fields stay unchanged. Pass at least one field or the request 400s; pass questions: null to collapse a multi-question survey back to the legacy single-question shape. A template-level edit only affects FUTURE sends. Requires owner / admin / developer.
string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.

Delete a survey template

DELETE /api/v1/surveys/{id}
Delete a survey template. CASCADE removes every survey_responses row for it, so the results / VoC rollups stop being available immediately; use this only when the program is truly retired. Returns 204 with no body; 404 when the template does not exist. Requires owner / admin / developer.
string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
Response: 204 No Content