Skip to content

Surveys and delivery rules

Collect survey responses and configure where your product shows each survey.

A survey collects answers to a questionnaire you define. Delivery rules describe where and when your product should show it. Support stores both; your application renders the questions and enforces visitor-specific rules.

Responses are separate from support conversations: they have no thread status, messages, assignment, or workspace notification. Escalate a response when your team needs a conversation to follow up.

The examples use a first-week survey for your shop. Replace ids, customer details, URLs, and targeting values with your own. Question and option ids belong to your application and must agree across the survey and its answers.

Lifecycle

A survey is in exactly one of three states:

State
Editable
Answerable
draft
Yes
No
published
No
Yes
archived
No
No

support_survey_publish validates a draft and opens it for responses. Published questions cannot be edited. Answers refer to question ids, so keep each id associated with the same question meaning.

support_survey_unpublish returns a published survey to draft for editing and stops new responses immediately. Existing responses remain. The call rejects surveys that are not published.

support_survey_archive permanently archives a draft or published survey. Existing responses remain readable, but the survey cannot return to draft or accept answers.

Create always starts a draft. Publish separately after it passes publication validation.

The survey object

{
  "id": "9f3c1b02-77ad-4e51-b0c9-5a8e2d4f6017",
  "name": "After your first week",
  "status": "published",
  "questions": [
    {
      "id": "q_channel",
      "type": "radio",
      "label": "How did you get started?",
      "required": true,
      "options": [
        { "id": "docs", "label": "Reading the documentation" },
        { "id": "sales", "label": "A call with our team" },
        { "id": "self", "label": "I worked it out myself" }
      ],
      "skip": { "self": "q_blockers" }
    },
    {
      "id": "q_score",
      "type": "nps",
      "label": "How likely are you to recommend this shop?"
    },
    {
      "id": "q_blockers",
      "type": "checkbox",
      "label": "What got in your way?",
      "options": [
        { "id": "billing", "label": "Billing" },
        { "id": "permissions", "label": "Account permissions" }
      ]
    }
  ],
  "createdAt": "2026-08-20T09:14:02.331Z",
  "updatedAt": "2026-08-20T09:20:47.882Z"
}

name must contain 1–255 characters. It becomes the title of a feedback thread when you escalate a response, so use a name your team can recognize in the queue.

questions holds up to 100 questions in display order. support_survey_update replaces the entire set you send. Pass [] to empty a draft.

support_survey_create creates the draft. support_survey_list and support_survey_get read surveys in any state. Creating and editing require product write access; publishing, unpublishing, and archiving require admin access; reads and statistics require read access.

Questions

Give each question a unique, stable id within the survey. Its label is the text the respondent reads. Answers use ids, so reordering questions does not change which answer belongs to which question.

Type
What it collects
Answer shape
reaction
A free string your client owns — an emoji, typically.
String
text
Free prose.
String
radio
One option.
An option id
checkbox
Any number of options.
An array of option ids
rating
An integer on a declared scale.
Integer
nps
The recommendation question, fixed to 0–10.
Integer
statement
Nothing. It is displayed, never answered.
None — never send one

Use these properties only with the supported question type. An incompatible property returns 400 with field errors:

  • options — radio and checkbox only. Each option is an id and a label, both client-owned.
  • scale — rating only. min is 0 or 1; max is an integer from 2 to 10. The default is { "min": 1, "max": 5 }. nps is fixed at 0–10 and accepts no scale.
  • skip — radio only. See Skip logic.

required is enforced when completed is true. Partial responses can omit required answers. A statement cannot be required because it collects no answer.

{
  "id": "q_channel",
  "type": "radio",
  "label": "How did you get started?",
  "required": true,
  "options": [
    { "id": "docs", "label": "Reading the documentation" },
    { "id": "sales", "label": "A call with our team" },
    { "id": "self", "label": "I worked it out myself" }
  ],
  "skip": { "self": "q_blockers" }
}

Skip logic

On a radio question, skip maps an option id to a later question id or "end". Other question types cannot use it.

Targets must come later in the question list or end the survey. Backward jumps and loops are rejected at publication.

Omit skipped and unanswered questions from answers; do not send a special skipped value. The response does not distinguish a question that was hidden from one left blank.

What publication checks

Draft saves validate each field’s shape. Publication also checks relationships between questions. support_survey_publish requires:

  • it has at least one question;
  • question ids are unique, and so are the option ids within a question;
  • every radio and checkbox question offers options;
  • no statement is marked required;
  • every skip names an option the question actually offers, and targets a later question or end.

These checks ensure the published skip path can reach the end without looping.

Answers

answers maps question ids to values of the corresponding type. An unknown question id, an invalid value, or an answer to a statement returns 400.

Escalate a response

support_response_escalate creates a feedback thread from a response. It uses the survey name as the title, the same external respondent as the author, the default open status, and the response metadata plus surveyResponseId. If the response’s score fits 0–10, it becomes the thread’s rating.

The new thread starts without messages; the answers stay on the response.

Escalating the same response again returns a conflict identifying the existing thread instead of creating another one.

Escalation requires product write access through the authenticated API. It is not available with a public key.

Read responses back

A stored response includes its answers, derived score, completion flag, and any escalation id:

{
  "id": "3a7c5e91-2d48-4b06-9f13-8e5a0c7d2b64",
  "surveyId": "9f3c1b02-77ad-4e51-b0c9-5a8e2d4f6017",
  "authorExternalId": "user_8842",
  "answers": { "q_channel": "docs", "q_score": 9 },
  "score": 9,
  "completed": true,
  "metadata": { "locale": "en-GB" },
  "escalatedThreadId": null,
  "createdAt": "2026-08-20T11:31:55.204Z",
  "updatedAt": "2026-08-20T11:31:55.204Z"
}

score contains the raw answer from the first nps or rating question, or null when neither was answered. It is not rescaled. Keep 0 distinct from null.

Read the score with its source question: a 1–5 rating and a 0–10 NPS answer use different scales even though both appear in score.

support_response_list requires surveyId and filters by inclusive scoreMin / scoreMax, creation window from / to, and completed. Unscored responses match neither score bound. support_response_get reads one response.

support_survey_stats returns response and completion counts for a time window. Per-question results include option counts for radio and checkbox, means and distributions for rating and nps, and answered counts for other types. For the first NPS question it also returns promoters (9–10), passives (7–8), detractors (0–6), and the NPS score.

The default window is the last 30 days. Set from explicitly for older results; omitting it does not request all history.

The two public survey routes

Your backend can read published questions and submit responses through two public survey routes. Survey management, collected-response reads, statistics, and escalation require the authenticated API; their paths return 404 on the public API.

Both routes use the product public key and base path in Public HTTP API, with its error shapes, CORS rules, and rate limit. Keep the key on your backend; the browser calls your backend.

Access: no tokens

Public survey requests require a product public key and surveyId. There is no additional respondent token, signed link, expiry, or single-use guard.

surveyId identifies the survey and does not authorize a respondent. Support stores the authorExternalId your backend asserts without checking the person, invitation, or previous responses. It neither deduplicates responses nor applies per-respondent rate limits.

Your backend decides who may answer and how often.

Reading and answering

GET /surveys/{surveyId} returns a published survey and its questions. Draft, archived, unknown, and other-product surveys all return the same 404. Check the status through the authenticated API when diagnosing a not-found response.

POST /surveys/{surveyId}/responses creates one response to the survey in the path. It is the only public survey write.

{
  "authorExternalId": "user_8842",
  "completed": true,
  "answers": {
    "q_channel": "docs",
    "q_blockers": ["billing", "permissions"],
    "q_score": 9
  },
  "metadata": {
    "pageUrl": "https://shop.example.com/welcome",
    "locale": "en-GB"
  }
}
Field
Rules
authorExternalId
Required. 1–255 characters. Your own identifier for the respondent, stored verbatim and never resolved, normalized or enriched.
answers
Required. Keyed by question id, each value fitting that question’s type. A skipped or blank question is absent.
completed
Required. true when the respondent reached the end, false for a partial response. Required questions are enforced only when it is true.
metadata
Optional. Free-form JSON of your own, stored verbatim and never read.

This request has a 256 KiB body cap, shared with POST /messages. Other public routes allow 64 KiB.

Send completed: false for a partial response and true when the respondent reaches the end. Filter by completion when analyzing results. Each submission creates a response; sending a completed response after a partial one does not update the earlier record.

Submitting to a draft, archived, unknown, or other-product survey returns 404 without revealing which condition applies.

The result is the response object shown above, with escalatedThreadId: null. Fields are at the top level; there is no { "success": true, "data": … } wrapper. Read body.answers, for example, rather than body.data.answers.

Response submission has no MCP tool or authenticated API operation. It records a person’s answers from your product through the public API. A request carrying a Lepsto identity is rejected before the handler with 401 and error.code: "public_edge_only"; no response is created. Granting more platform permissions does not enable this route.

Delivery rules

A delivery rule belongs to one survey and specifies page matching, device targeting, a trigger, frequency, and visitor conditions.

Your application executes the rule. Support has no scheduler, widget, impression log, or per-visitor history. It returns matching configuration; your runtime decides whether and when to show it. See The client-execution contract.

The rule object

{
  "id": "3f0a5c11-6b2d-4f8e-9a17-2c4e8d3b5a90",
  "surveyId": "9f3c1b02-77ad-4e51-b0c9-5a8e2d4f6017",
  "enabled": true,
  "urlMatch": { "kind": "prefix", "value": "https://shop.example.com/checkout" },
  "device": "mobile",
  "trigger": { "kind": "delay", "seconds": 15 },
  "frequency": { "kind": "until_answered", "cooldownDays": 7 },
  "attributeConditions": [
    { "key": "plan", "op": "eq", "value": "pro" },
    { "key": "trial_ends_at", "op": "exists" }
  ],
  "createdAt": "2026-08-20T09:14:02.118Z",
  "updatedAt": "2026-08-20T09:14:02.118Z"
}
Field
Rules
id
The rule’s own id.
surveyId
The survey this rule delivers. Required at creation, must belong to your product, and not editable afterwards.
enabled
Whether the rule is live. A rule is created enabled unless you say otherwise.
urlMatch
Required. Which pages the rule applies to.
device
any, desktop or mobile. Defaults to any.
trigger
Required. When to show the survey once the page matches.
frequency
Required. How often the same person may see it.
attributeConditions
Conditions on the visitor, all of which must hold. Defaults to an empty list, which applies to everyone.
createdAt, updatedAt
ISO-8601 timestamps.

You can create rules for a draft survey. They remain hidden from the public API until the survey is published.

URL matching

urlMatch is a kind and a value, and the value is at most 512 characters.

kind
Matches when
exact
The URL is the value, character for character. No case folding and no trailing-slash forgiveness.
prefix
The URL starts with the value.
contains
The value appears anywhere in the URL.
regex
A JavaScript regular expression, applied unanchored.

Regular expressions are unanchored: /checkout matches anywhere in the URL. Add ^ and $ when the match must cover the whole string.

An invalid regular expression returns 400 when you save the rule. Literal exact, prefix, and contains values are not compiled, so a string such as ([ is accepted literally.

Devices

device: "any" is the default and matches either reported device.

When requesting rules, report desktop or mobile; any is a rule setting, not a valid reported device. Omitting the device returns only any rules.

Triggers

Each trigger accepts only the fields for its kind. An extra field, such as seconds on immediate, returns 400.

kind
Field
Means
immediate
—
Show it as soon as the page matches.
delay
seconds, an integer from 0 to 86,400
Wait this long after the page matched, at most a day.
event
name, 1–128 characters
Wait for an event in your own runtime.

Named events belong to your runtime. Support stores the name without checking an event registry or listening for it.

Frequency

frequency is a kind and an optional cooldownDays, an integer from 1 to 365.

kind
Means
once
Show it a single time, ever.
until_answered
Keep showing it until the person responds.
always
Show it every time the rule matches.

once does not accept cooldownDays; sending both returns 400.

Your runtime enforces frequency and keeps the visitor’s impression and response history. Support stores only the policy.

Attribute conditions

A rule accepts up to 20 attribute conditions. Every condition must match (AND); OR conditions are not supported.

op
Holds when
value
eq
The attribute equals value
Required
neq
The attribute does not equal value
Required
contains
value appears in the attribute
Required
exists
The attribute is present at all
Refused

Keys allow up to 128 characters and values up to 512. You define their meaning. Support stores them verbatim without looking up visitors or validating the vocabulary.

An empty condition list applies to everyone. Send attributeConditions: [] to clear it. Your runtime evaluates the conditions against visitor attributes.

Managing rules

Tool
REST
What it does
POST /support/rules
Write a rule against one survey
GET /support/rules/{id}
One rule, with its whole configuration
GET /support/rules
This product’s rules, newest first
PATCH /support/rules/{id}
Edit a rule, including switching it off
DELETE /support/rules/{id}
Remove a rule for good

Rule creation and updates require product write access, reads require read, and deletion requires admin. These tools use platform authentication. The public API only allows the matching-rule read.

Updates replace each supplied field in full, including attributeConditions and urlMatch, and require at least one field. surveyId cannot change: delete the rule and create another to target a different survey. Set enabled: false to stop delivery reversibly without changing the survey.

support_rule_delete permanently removes the rule and leaves its survey untouched. A second delete returns 404. Rules have no archive or restore operation.

support_rule_list returns { "rules": [...], "limit": …, "offset": … }, newest first. Its filters:

Filter
Effect
surveyId
Only rules delivering this survey
enabled
true for live rules, false for switched-off ones
url
Only rules whose URL match applies to this page URL
device
desktop or mobile; without it only any rules match
limit, offset
Page size 1–100 (default 25) and how many to skip

Both authenticated and public reads support URL and device matching. An authenticated read with neither filter returns all rules. Pagination runs after matching, so the page size applies to matched results.

Authenticated reads include rules for surveys in any state, allowing you to configure them before publication.

The rule read at the public edge

The public API exposes GET /rules with these restrictions:

Authenticated
Public edge
url
Optional
Required
enabled
Filter of your choosing
Forced to true; the filter is not honored
Survey status
Any
published only

Send the page’s url; omitting it returns 400. Support applies URL and device matching before returning rules.

Public reads return only enabled rules for published surveys. Unpublishing the survey hides all its rules; disabling one rule hides only that rule.

Public POST /rules, PATCH /rules/{id}, and DELETE /rules/{id} return 404. Use the authenticated API to change rules.

The public read returns the complete matching rule, including its conditions. It does not redact fields.

The client-execution contract

Support selects rules for the page. Your runtime applies the visitor-specific checks and runs the trigger.

Support decides:

  • which rules belong to the product,
  • whether the rule is enabled and its survey published,
  • whether urlMatch applies to the url you sent,
  • whether device admits the device you reported.

Your runtime decides:

  • whether attributeConditions hold, against attributes only you have;
  • when to show the survey, honoring trigger — now, after seconds, or on your own named event;
  • whether this person may be shown it at all, honoring frequency and cooldownDays against the per-visitor history only you keep.

Use your backend for every Support request in this flow; never send the public key to the browser:

  1. On page load, send the current URL and device to your backend, which calls GET /rules?url=…&device=….
  2. Discard rules whose attributeConditions do not match the visitor.
  3. Check frequency and cooldownDays against your visitor history.
  4. Start the trigger: render immediately, wait seconds, or listen for your named event.
  5. When triggered, fetch GET /surveys/{surveyId} through your backend and render the survey. Record that it was shown.
  6. Submit answers through your backend to POST /surveys/{surveyId}/responses and update your local response history.

Fetch rules on page load rather than polling. Recheck survey availability when fetching questions, since the survey may have been unpublished.

Rule errors

The shared error shapes are in Tools, SDK and errors. What is specific to rules:

Code
When
400
The body does not fit — a regex that does not compile, a match value over 512 characters, cooldownDays on once, a value on exists or missing on eq / neq / contains, an update with no field in it, or url missing at the public edge
404
The rule does not exist, or the rule or the survey belongs to another product

Creating a rule for another product’s survey returns not found, without confirming that the survey exists.

If a stored regular expression no longer compiles, it matches nothing. Other rules can still be returned.

No webhook fires for a rule

Rule changes emit no webhooks. The seven supported events include survey publication and response creation, but no rule event.

Where to go next

Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select