The Support area publishes 53 REST endpoints. Paths are relative to https://api.lepsto.com; see Authentication for the required header.
GET/support/agents
List this product support agents by display name. Deactivated agents are hidden unless asked for: they keep every message they wrote but cannot take new threads. Through the public edge the profiles come back without lesslyUserId — the binding never leaves the authenticated plane
MCP tool: support_agent_list
Parameters
| Name | In | Type | Required | Description |
|---|
includeInactive | query | boolean
| — | Also return deactivated agents (default false) |
Response
Returns object.
| Field | Type | Always | Description |
|---|
agents | object[]
| • | The product support agents, by display name |
POST/support/agents
Create a support agent profile for this product. Binding a Lepsto identity is optional and the value is stored verbatim — nothing resolves it or checks that the user exists. An unbound profile is a bot or the client's own backend, and it replies exactly like a human agent does
MCP tool: support_agent_create
Parameters
| Name | In | Type | Required | Description |
|---|
displayName | body | string
| • | Name the end user sees next to this agent replies |
title | body | string
| — | Job title shown under the name, e.g. "Support engineer" |
avatarUrl | body | string (uri)
| — | Absolute URL of the agent avatar image |
lesslyUserId | body | string
| — | Lepsto identity to bind this profile to, stored verbatim as an opaque string. It is never resolved, validated against the platform or enriched. Omit it for a bot or a backend profile. At most one agent per product may claim a given Lepsto user |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Agent id |
title | string | null
| • | Job title shown under the name, or null if unset |
isActive | boolean
| • | A deactivated agent keeps its history but can no longer be put on a thread |
avatarUrl | string | null
| • | Avatar image URL, or null if unset |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
displayName | string
| • | Name shown next to the agent replies |
lesslyUserId | string | null
| • | Bound Lepsto identity, stored verbatim and resolved by nobody. Null for an unbound profile — a bot or the client's own backend, which replies just like anyone else |
GET/support/agents/:id
Get one support agent by id, deactivated ones included. An agent belonging to another product is reported as not found. Through the public edge the profile comes back without lesslyUserId — the binding never leaves the authenticated plane
MCP tool: support_agent_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Agent id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Agent id |
title | string | null
| • | Job title shown under the name, or null if unset |
isActive | boolean
| • | A deactivated agent keeps its history but can no longer be put on a thread |
avatarUrl | string | null
| • | Avatar image URL, or null if unset |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
displayName | string
| • | Name shown next to the agent replies |
lesslyUserId | string | null
| • | Bound Lepsto identity, stored verbatim and resolved by nobody. Null for an unbound profile — a bot or the client's own backend, which replies just like anyone else |
PATCH/support/agents/:id
Edit an agent profile. Pass null to clear the title, the avatar or the Lepsto binding; omit a field to leave it alone. Setting isActive to false deactivates the agent: it keeps every message it wrote and every thread it already sits on, but cannot be assigned to another one
MCP tool: support_agent_update
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Agent id |
displayName | body | string
| — | Name the end user sees next to this agent replies |
title | body | string | null
| — | New job title, or null to clear it |
avatarUrl | body | string (uri) | null
| — | New avatar URL, or null to clear it |
lesslyUserId | body | string | null
| — | Lepsto identity to bind, or null to unbind. Stored verbatim, resolved by nobody |
isActive | body | boolean
| — | Deactivate or reactivate. A deactivated agent keeps every message it wrote and every thread it already sits on, but cannot be assigned to another one |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Agent id |
title | string | null
| • | Job title shown under the name, or null if unset |
isActive | boolean
| • | A deactivated agent keeps its history but can no longer be put on a thread |
avatarUrl | string | null
| • | Avatar image URL, or null if unset |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
displayName | string
| • | Name shown next to the agent replies |
lesslyUserId | string | null
| • | Bound Lepsto identity, stored verbatim and resolved by nobody. Null for an unbound profile — a bot or the client's own backend, which replies just like anyone else |
POST/support/attachments
Reserve a file on a support thread and get a short-lived signed URL to upload it to. The bytes never pass through this API: PUT them to the returned uploadUrl with the same Content-Type AND the header "x-goog-content-length-range: 0,26214400", both of which the signature covers — omit either and storage refuses the PUT. Then call support_attachment_finalize, which checks that the first bytes really are the type you declared. Only the content types listed on the contentType field are accepted. A thread may hold at most 20 attachments waiting for their bytes at once; past that the call is refused with 429 and a message beginning "too_many_pending_attachments". Until finalized the attachment is pending — it cannot be attached to a message and is invisible in message output. A thread belonging to another product is reported as not found
MCP tool: support_attachment_create
Parameters
| Name | In | Type | Required | Description |
|---|
threadId | body | string (uuid)
| • | Thread the file belongs to. An attachment is created against a thread first and claimed by one of its messages later, when that message is posted |
fileName | body | string
| • | Display name of the file, e.g. "invoice.pdf". Only the base name is used for the storage key; any directory part is discarded |
contentType | body | stringone of: image/jpeg, image/png, image/gif, image/webp, image/heic, video/mp4, video/webm, video/quicktime, application/pdf, text/plain, application/zip
| • | MIME type of the file, which must be one of: image/jpeg, image/png, image/gif, image/webp, image/heic, video/mp4, video/webm, video/quicktime, application/pdf, text/plain, application/zip. Exact strings only — a parameter such as "; charset=utf-8" is refused. The upload URL is signed for exactly this type, so the PUT must send the same Content-Type header, and finalize refuses the file unless its first bytes really are this type |
sizeBytes | body | integer
| • | Size of the file in bytes, at most 26214400. Declared up front so an oversized upload is refused before a URL is issued; finalize checks the real size again |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Attachment id |
status | stringone of: pending, uploaded, rejected
| • | pending: the row exists and an upload URL was issued, but no bytes have been confirmed. uploaded: finalize saw the object in storage and its first bytes matched the declared type. rejected: they did not, the object was deleted, and this attachment is finished — the state is terminal. Only uploaded attachments can be attached to a message or downloaded |
fileName | string
| • | Display name of the file |
threadId | string (uuid)
| • | Thread this attachment belongs to |
createdAt | string
| • | ISO-8601 creation timestamp |
messageId | string (uuid) | null
| • | The message that carries this attachment, or null while nothing claims it yet |
sizeBytes | integer
| • | Size in bytes: what the caller declared while the attachment is pending, what storage reported once it is uploaded |
uploadUrl | string
| • | Short-lived signed URL. PUT the file bytes here with the same Content-Type, then call support_attachment_finalize. The URL is a bearer credential for this one object |
uploadedAt | string | null
| • | ISO-8601 timestamp of the confirmed upload, or null while pending |
contentType | string
| • | MIME type declared when the attachment was created |
rejectedReason | string | null
| • | Why finalize refused the file — signature_mismatch, not_utf8, nul_byte or markup_in_text — or null in any state but rejected |
uploadExpiresAt | string
| • | ISO-8601 moment the upload URL stops working |
DELETE/support/attachments/:id
Delete one attachment that no message carries — pending, uploaded or rejected — and its file in storage. The file is removed at once: a download URL issued before the delete stops working. Use it when a user removes a file from a message they have not sent, or to erase personal data. An attachment a message carries is refused with 409 and a message beginning "attachment_claimed": delete the message instead with support_message_delete (your own) or support_message_delete_any (admin), which removes its files with it. If storage fails the call answers 503 with a message beginning "attachment_cleanup_failed", nothing is lost, and repeating it is safe. Deleting the same attachment again answers not found. An attachment belonging to another product is reported as not found
MCP tool: support_attachment_delete
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | The attachment to delete, as returned by the create call. It must not be carried by a message |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | The attachment that was deleted |
status | stringone of: pending, uploaded, rejected
| • | The state the attachment was in when it was deleted: pending, uploaded or rejected |
threadId | string (uuid)
| • | Thread the attachment belonged to |
objectDestroyed | boolean
| • | true when storage held the file and removed it; false when there was nothing to remove — a pending attachment whose bytes never arrived, or a rejected one whose file was already deleted at finalize |
POST/support/attachments/finalize
Confirm that an upload landed. Storage is asked whether the object exists and how large it is; if it is not there the call is refused and the attachment stays pending. Its first 4 KiB are then read and checked against the content type the attachment was created with: if the bytes are something else — an svg uploaded as a png, html uploaded as text — the object is DELETED, the attachment becomes rejected, and the call answers 400 with a message beginning "attachment_rejected" and naming the reason. That state is terminal: calling finalize again returns the same 400, and the file must be created and uploaded afresh. On success the attachment becomes uploaded — it can now be named in support_message_create and carries a download URL served under the verified type. Calling it again on an uploaded attachment is safe and simply returns a fresh download URL
MCP tool: support_attachment_finalize
Parameters
| Name | In | Type | Required | Description |
|---|
attachmentId | body | string (uuid)
| • | The attachment whose upload should be confirmed, as returned by the create call |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Attachment id |
status | stringone of: pending, uploaded, rejected
| • | pending: the row exists and an upload URL was issued, but no bytes have been confirmed. uploaded: finalize saw the object in storage and its first bytes matched the declared type. rejected: they did not, the object was deleted, and this attachment is finished — the state is terminal. Only uploaded attachments can be attached to a message or downloaded |
fileName | string
| • | Display name of the file |
threadId | string (uuid)
| • | Thread this attachment belongs to |
createdAt | string
| • | ISO-8601 creation timestamp |
messageId | string (uuid) | null
| • | The message that carries this attachment, or null while nothing claims it yet |
sizeBytes | integer
| • | Size in bytes: what the caller declared while the attachment is pending, what storage reported once it is uploaded |
uploadedAt | string | null
| • | ISO-8601 timestamp of the confirmed upload, or null while pending |
contentType | string
| • | MIME type declared when the attachment was created |
downloadUrl | string
| • | Short-lived signed URL to read the uploaded file |
rejectedReason | string | null
| • | Why finalize refused the file — signature_mismatch, not_utf8, nul_byte or markup_in_text — or null in any state but rejected |
POST/support/attachments/purge
Delete every attachment of a thread that no message carries — pending, uploaded and rejected alike — and their files in storage, for example to erase an end user's data. Attachments carried by messages are left alone and counted in skippedClaimed; remove them by deleting their messages with support_message_delete or support_message_delete_any, then purge. On a thread that is still in use this can also remove a file a user is attaching right now — that is intended for erasure, and there is no "older than" option; the user's message then fails to attach it and the file must be uploaded again. If storage fails part of the way the call answers 503 with a message beginning "attachment_cleanup_failed": files already deleted stay deleted, and repeating the call finishes the rest. A repeat after success answers zeros. A thread belonging to another product is reported as not found
MCP tool: support_attachment_purge
Parameters
| Name | In | Type | Required | Description |
|---|
threadId | body | string (uuid)
| • | Thread whose attachments that no message carries should all be deleted |
Response
Returns object.
| Field | Type | Always | Description |
|---|
deleted | object
| • | Attachments deleted by this call, by the state they were in. Zeros on a repeat |
threadId | string (uuid)
| • | The thread that was purged |
objectsMissing | integer
| • | Deleted attachments whose file storage no longer held, or never received |
skippedClaimed | integer
| • | Attachments of the thread carried by messages and therefore left alone. They are removed by deleting their message with support_message_delete or support_message_delete_any |
objectsDestroyed | integer
| • | Files storage held and removed |
GET/support/labels
List this product label dictionary by name. A product that has invented no tags yet gets an empty list — unlike statuses, there is nothing to seed
MCP tool: support_label_list
Parameters
No parameters.
Response
Returns object.
| Field | Type | Always | Description |
|---|
labels | object[]
| • | The product label dictionary, by name |
POST/support/labels
Add a label to this product label dictionary. A label is a free-form tag: nothing automates off it and a thread may carry any number of them. Names are unique per product
MCP tool: support_label_create
Parameters
| Name | In | Type | Required | Description |
|---|
name | body | string
| • | Label name as the customer wants to see it. Unique within the product |
color | body | string
| — | Optional display colour as a six-digit hex string, e.g. #2563eb |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Label id |
name | string
| • | Label name, unique within the product |
color | string | null
| • | Display colour as a hex string, or null if uncoloured |
createdAt | string
| • | ISO-8601 creation timestamp |
GET/support/labels/:id
Get one label of this product label dictionary by id, in the same shape a list element has. A label belonging to another product is reported as not found
MCP tool: support_label_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Label id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Label id |
name | string
| • | Label name, unique within the product |
color | string | null
| • | Display colour as a hex string, or null if uncoloured |
createdAt | string
| • | ISO-8601 creation timestamp |
GET/support/messages
List messages oldest first — the conversation reads forward. Name a threadId to read one thread, or omit it to read every thread of this product, which is how a client catches up on what it missed: pass since with the timestamp of the last message it has, then follow nextCursor until it comes back null. Paging is stable across messages that share a timestamp, so a walk neither skips nor repeats one. Filter by direction and by visibility; omit visibility to get public messages and internal notes together. A thread belonging to another product is reported as not found
MCP tool: support_message_list
Parameters
| Name | In | Type | Required | Description |
|---|
threadId | query | string (uuid)
| — | Thread whose messages to list. Omit to list the messages of every thread of this product — the catch-up read |
direction | query | stringone of: inbound, outbound
| — | Only messages in this direction |
visibility | query | stringone of: public, internal
| — | Only messages with this visibility. Omit to get both public and internal |
since | query | string (date-time)
| — | ISO-8601 timestamp. Only messages created strictly after it — pass the createdAt of the last message you already have |
cursor | query | string
| — | Opaque position from a previous nextCursor. Resumes exactly where that page ended, with no gap and no repeat even when messages share a timestamp. Never construct one, and do not combine it with offset |
limit | query | integer
| — | Page size, 1..100 (default 50) |
offset | query | integer
| — | Number of messages to skip. The older pagination, kept for callers that already use it; prefer cursor, which cannot drift as messages arrive |
Response
Returns object.
| Field | Type | Always | Description |
|---|
limit | integer
| • | Page size that was applied |
offset | integer
| • | Offset that was applied |
messages | object[]
| • | The requested page of messages, oldest first — the conversation reads forward |
nextCursor | string | null
| • | Position to resume from: pass it back as cursor for the next page. Null when this page is the last one |
POST/support/messages
Post a message into a support thread. A message has exactly one author: either an opaque external identifier from the client's own user base, or a support agent — never both and never neither. Direction, authorship and visibility are independent: an internal note may be inbound or outbound. Posting a message moves the parent thread's last reply timestamp. A message may carry files: create and finalize them first, then name them in attachmentIds. A message that names at least one attachment may leave body out entirely — a screenshot posted with nothing to say. Without attachments the body is required, and an empty body is refused either way. Through the public edge an agent author is accepted only if that agent carries no Lepsto binding — a bot or the calling backend; an agent bound to a Lepsto identity may author only from a signed-in session
MCP tool: support_message_create
Parameters
| Name | In | Type | Required | Description |
|---|
threadId | body | string (uuid)
| • | Id of the thread this message belongs to |
body | body | string
| — | The message text, stored verbatim. Omit it only on a message that carries at least one attachment — a picture posted with nothing to say. An empty string is never a body: leave the field out instead |
direction | body | stringone of: inbound, outbound
| • | inbound: written by the external end user. outbound: written by support, going out to them |
authorExternalId | body | string
| — | Opaque identifier of the external end user in the client's own user base, stored verbatim. Set this exactly when the message comes from outside; leave authorAgentId empty |
authorAgentId | body | string (uuid)
| — | The support agent of this product who wrote the message. Set this exactly when the message comes from inside the support contour; leave authorExternalId empty. An agent of another product is reported as not found |
visibility | body | stringone of: public, internal
| — | Defaults to public when omitted. Internal notes are never shown to the end user |
attachmentIds | body | string (uuid)[]
| — | Files this message carries, created with support_attachment_create and confirmed with support_attachment_finalize beforehand. Each must be an uploaded attachment of the same thread that no other message already carries; otherwise the whole message is refused. At most 10 per message |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Message id |
body | string
| • | The message text, byte-identical to what was stored |
editedAt | string | null
| • | ISO-8601 time of the last edit, or null when the message was never edited |
threadId | string (uuid)
| • | Thread this message belongs to |
createdAt | string
| • | ISO-8601 creation timestamp |
deletedAt | string | null
| • | ISO-8601 time the message was deleted, or null. A deleted message stays in the thread as a placeholder: its body is empty and it carries no attachments |
direction | stringone of: inbound, outbound
| • | inbound: written by the external end user. outbound: written by support, going out to them |
visibility | stringone of: public, internal
| • | public: part of the conversation the external end user sees. internal: a note between agents that never leaves the support contour |
attachments | object[]
| • | Files this message carries, each with a freshly signed download URL. Empty when there are none. Attachments whose upload was never confirmed are never listed here |
attachmentIds | string (uuid)[]
| • | The same files as a flat list of ids, in the order attachments[] has them. This is the value support_message_create takes back; attachments.0.id names only the first file |
authorAgentId | string (uuid) | null
| • | The agent author, or null when the external end user wrote the message |
attachmentCount | integer
| • | How many files this message carries, 0 when it carries none. A dot-path has no length, so this is the only way a step can branch on whether there are files at all — and a message may carry a file and no body, so it is what tells those two messages apart |
authorExternalId | string | null
| • | The external author, or null when an agent wrote the message |
createdByIdentityId | string | null
| • | The Lepsto identity that posted the message — the one allowed to edit or delete it. Null for a message from the public edge or from before authorship was recorded: nobody owns those, and only support_message_delete_any removes them |
DELETE/support/messages/:id
Delete a message you posted yourself. The message stays in the thread as a placeholder: its body becomes empty, it carries no attachments, deletedAt is set and its edit history is destroyed. Its attachment files are removed from storage immediately, and download URLs issued before the delete stop working. If removing a file fails, the delete answers an error and is safe to retry. Deleting an already deleted message succeeds again, removes any file still left and changes nothing else. Deleting a public message recomputes the thread last reply timestamp from the messages left; the thread status does not change. A message posted by someone else is refused as forbidden — an admin uses support_message_delete_any. A message belonging to another product is reported as not found
MCP tool: support_message_delete
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Message id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Message id |
body | string
| • | The message text, byte-identical to what was stored |
editedAt | string | null
| • | ISO-8601 time of the last edit, or null when the message was never edited |
threadId | string (uuid)
| • | Thread this message belongs to |
createdAt | string
| • | ISO-8601 creation timestamp |
deletedAt | string | null
| • | ISO-8601 time the message was deleted, or null. A deleted message stays in the thread as a placeholder: its body is empty and it carries no attachments |
direction | stringone of: inbound, outbound
| • | inbound: written by the external end user. outbound: written by support, going out to them |
visibility | stringone of: public, internal
| • | public: part of the conversation the external end user sees. internal: a note between agents that never leaves the support contour |
attachments | object[]
| • | Files this message carries, each with a freshly signed download URL. Empty when there are none. Attachments whose upload was never confirmed are never listed here |
attachmentIds | string (uuid)[]
| • | The same files as a flat list of ids, in the order attachments[] has them. This is the value support_message_create takes back; attachments.0.id names only the first file |
authorAgentId | string (uuid) | null
| • | The agent author, or null when the external end user wrote the message |
attachmentCount | integer
| • | How many files this message carries, 0 when it carries none. A dot-path has no length, so this is the only way a step can branch on whether there are files at all — and a message may carry a file and no body, so it is what tells those two messages apart |
authorExternalId | string | null
| • | The external author, or null when an agent wrote the message |
createdByIdentityId | string | null
| • | The Lepsto identity that posted the message — the one allowed to edit or delete it. Null for a message from the public edge or from before authorship was recorded: nobody owns those, and only support_message_delete_any removes them |
GET/support/messages/:id
Get one message by id, in the same shape a list element has — the files it carries included, each with a freshly signed download URL. An internal note is returned like any other message: visibility narrows the list, it does not hide a message from this tool. A message belonging to another product is reported as not found
MCP tool: support_message_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Message id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Message id |
body | string
| • | The message text, byte-identical to what was stored |
editedAt | string | null
| • | ISO-8601 time of the last edit, or null when the message was never edited |
threadId | string (uuid)
| • | Thread this message belongs to |
createdAt | string
| • | ISO-8601 creation timestamp |
deletedAt | string | null
| • | ISO-8601 time the message was deleted, or null. A deleted message stays in the thread as a placeholder: its body is empty and it carries no attachments |
direction | stringone of: inbound, outbound
| • | inbound: written by the external end user. outbound: written by support, going out to them |
visibility | stringone of: public, internal
| • | public: part of the conversation the external end user sees. internal: a note between agents that never leaves the support contour |
attachments | object[]
| • | Files this message carries, each with a freshly signed download URL. Empty when there are none. Attachments whose upload was never confirmed are never listed here |
attachmentIds | string (uuid)[]
| • | The same files as a flat list of ids, in the order attachments[] has them. This is the value support_message_create takes back; attachments.0.id names only the first file |
authorAgentId | string (uuid) | null
| • | The agent author, or null when the external end user wrote the message |
attachmentCount | integer
| • | How many files this message carries, 0 when it carries none. A dot-path has no length, so this is the only way a step can branch on whether there are files at all — and a message may carry a file and no body, so it is what tells those two messages apart |
authorExternalId | string | null
| • | The external author, or null when an agent wrote the message |
createdByIdentityId | string | null
| • | The Lepsto identity that posted the message — the one allowed to edit or delete it. Null for a message from the public edge or from before authorship was recorded: nobody owns those, and only support_message_delete_any removes them |
PATCH/support/messages/:id
Edit the body of a message you posted yourself. Only the identity that created the message may edit it; a message posted by someone else, from the public edge or before authorship was recorded is refused as forbidden. The previous body is kept as a revision and editedAt is set. A deleted message cannot be edited (conflict). Sending the same body changes nothing. An edit does not move the thread's last reply timestamp. A message belonging to another product is reported as not found
MCP tool: support_message_update
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Id of the message to edit |
body | body | string
| • | The new message text, stored verbatim. It replaces the whole body. An empty string is accepted only on a message that carries at least one attachment — a picture may stand alone, a message with nothing to show may not |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Message id |
body | string
| • | The message text, byte-identical to what was stored |
editedAt | string | null
| • | ISO-8601 time of the last edit, or null when the message was never edited |
threadId | string (uuid)
| • | Thread this message belongs to |
createdAt | string
| • | ISO-8601 creation timestamp |
deletedAt | string | null
| • | ISO-8601 time the message was deleted, or null. A deleted message stays in the thread as a placeholder: its body is empty and it carries no attachments |
direction | stringone of: inbound, outbound
| • | inbound: written by the external end user. outbound: written by support, going out to them |
visibility | stringone of: public, internal
| • | public: part of the conversation the external end user sees. internal: a note between agents that never leaves the support contour |
attachments | object[]
| • | Files this message carries, each with a freshly signed download URL. Empty when there are none. Attachments whose upload was never confirmed are never listed here |
attachmentIds | string (uuid)[]
| • | The same files as a flat list of ids, in the order attachments[] has them. This is the value support_message_create takes back; attachments.0.id names only the first file |
authorAgentId | string (uuid) | null
| • | The agent author, or null when the external end user wrote the message |
attachmentCount | integer
| • | How many files this message carries, 0 when it carries none. A dot-path has no length, so this is the only way a step can branch on whether there are files at all — and a message may carry a file and no body, so it is what tells those two messages apart |
authorExternalId | string | null
| • | The external author, or null when an agent wrote the message |
createdByIdentityId | string | null
| • | The Lepsto identity that posted the message — the one allowed to edit or delete it. Null for a message from the public edge or from before authorship was recorded: nobody owns those, and only support_message_delete_any removes them |
POST/support/messages/:id/delete-any
Delete any message of this product as an admin — including an inbound message from the end user and a message nobody owns. Same soft delete as support_message_delete: the message stays as a placeholder with an empty body and no attachments, its edit history is destroyed, its attachment files are removed from storage immediately and download URLs issued before the delete stop working. If removing a file fails, the delete answers an error and is safe to retry; a repeated delete removes any file still left and changes nothing else. A message belonging to another product is reported as not found
MCP tool: support_message_delete_any
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Message id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Message id |
body | string
| • | The message text, byte-identical to what was stored |
editedAt | string | null
| • | ISO-8601 time of the last edit, or null when the message was never edited |
threadId | string (uuid)
| • | Thread this message belongs to |
createdAt | string
| • | ISO-8601 creation timestamp |
deletedAt | string | null
| • | ISO-8601 time the message was deleted, or null. A deleted message stays in the thread as a placeholder: its body is empty and it carries no attachments |
direction | stringone of: inbound, outbound
| • | inbound: written by the external end user. outbound: written by support, going out to them |
visibility | stringone of: public, internal
| • | public: part of the conversation the external end user sees. internal: a note between agents that never leaves the support contour |
attachments | object[]
| • | Files this message carries, each with a freshly signed download URL. Empty when there are none. Attachments whose upload was never confirmed are never listed here |
attachmentIds | string (uuid)[]
| • | The same files as a flat list of ids, in the order attachments[] has them. This is the value support_message_create takes back; attachments.0.id names only the first file |
authorAgentId | string (uuid) | null
| • | The agent author, or null when the external end user wrote the message |
attachmentCount | integer
| • | How many files this message carries, 0 when it carries none. A dot-path has no length, so this is the only way a step can branch on whether there are files at all — and a message may carry a file and no body, so it is what tells those two messages apart |
authorExternalId | string | null
| • | The external author, or null when an agent wrote the message |
createdByIdentityId | string | null
| • | The Lepsto identity that posted the message — the one allowed to edit or delete it. Null for a message from the public edge or from before authorship was recorded: nobody owns those, and only support_message_delete_any removes them |
GET/support/messages/:id/revisions
List the earlier bodies of one message, newest first — one entry per edit, each with the body as it read before that edit and who made the edit. Empty for a message never edited, and empty for a deleted one: deleting a message destroys its history with it. A message belonging to another product is reported as not found
MCP tool: support_message_revisions_list
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Message id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
revisions | object[]
| • | Every earlier body of the message, newest first. Empty when it was never edited, and empty again once the message is deleted — a removed body does not survive in its history |
GET/support/responses
List the responses of ONE survey, newest first — surveyId is required, because a page mixing two surveys would carry answers keyed by two different question sets. Filters: score range (scoreMin/scoreMax, inclusive — a response with no score matches neither bound), a creation window (from/to), completed, plus pagination
MCP tool: support_response_list
Parameters
| Name | In | Type | Required | Description |
|---|
surveyId | query | string (uuid)
| • | Required: responses are always read one survey at a time |
scoreMin | query | integer
| — | Only responses scored at or above this value (inclusive). A response with no score matches neither bound — not even at the bottom of the scale |
scoreMax | query | integer
| — | Only responses scored at or below this value (inclusive). A response with no score matches neither bound — not even at the bottom of the scale |
from | query | string (date-time)
| — | Only responses created at or after this ISO-8601 timestamp |
to | query | string (date-time)
| — | Only responses created at or before this ISO-8601 timestamp |
completed | query | boolean | stringone of: true, false
| — | true: only responses that reached the end. false: only partial ones |
limit | query | integer
| — | Page size, 1..100 (default 25) |
offset | query | integer
| — | Number of responses to skip |
Response
Returns object.
| Field | Type | Always | Description |
|---|
limit | integer
| • | Page size that was applied |
offset | integer
| • | Offset that was applied |
responses | object[]
| • | The requested page of responses, newest first |
GET/support/responses/:id
Get one survey response by id, with its answers exactly as they were submitted. A response belonging to another product is reported as not found
MCP tool: support_response_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Response id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Response id |
score | integer | null
| • | The answer of the first nps or rating question, raw and never rescaled, or null when neither was answered. Null is not zero — 0 is a real score at the bottom of an NPS scale |
answers | object
| • | Answers keyed by question id, exactly as stored |
metadata | object
| • | Free-form client-owned JSON |
surveyId | string (uuid)
| • | The survey that was answered |
completed | boolean
| • | Whether the respondent reached the end |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
authorExternalId | string
| • | The respondent, byte-identical to what was stored |
escalatedThreadId | string (uuid) | null
| • | The feedback thread this response was escalated into, or null while it has not been |
POST/support/responses/:id/escalate
Turn a survey response into a feedback thread so a person can follow up. The thread opens on the product’s default open status, titled with the survey’s name, authored by the same external respondent, carrying the response metadata plus surveyResponseId, and rated with the response score when it fits 0..10. No message is written — the answers stay on the response, and neither the end user nor an agent typed anything. A response that was already escalated is refused with a conflict naming the thread it went to
MCP tool: support_response_escalate
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Response id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
thread | object
| • | The feedback thread that was opened |
response | object
| • | The response, with escalatedThreadId now set |
GET/support/rules
List this product’s delivery rules, newest first. Supply surveyId or enabled to filter by survey or live state; omit both to include all surveys and both states. Supply url to match a page and device to match its device type. With url but no device, only rules targeting any device match; omit both to skip page and device matching. The client runtime reads the public edge, which requires url and returns only live rules on published surveys.
MCP tool: support_rule_list
Parameters
| Name | In | Type | Required | Description |
|---|
surveyId | query | string (uuid)
| — | Only rules delivering this survey |
enabled | query | boolean | stringone of: true, false
| — | true: only live rules. false: only switched-off ones |
url | query | string
| — | Only rules whose URL match applies to this page URL |
device | query | stringone of: desktop, mobile
| — | The device the page is rendered on. Without it only rules targeting any device match |
limit | query | integer
| — | Page size, 1..100 (default 25) |
offset | query | integer
| — | Number of rules to skip |
Response
Returns object.
| Field | Type | Always | Description |
|---|
limit | integer
| • | Page size that was applied |
rules | object[]
| • | The requested page of rules, newest first |
offset | integer
| • | Offset that was applied |
POST/support/rules
Write a delivery rule: where a survey should be shown (a URL match), on which devices, when (immediately, after a delay, or on the client’s own event) and how often. Nothing here runs on this side — the rule is configuration a client’s own runtime reads back through the public edge and acts on. A rule reaches that edge only while its survey is published, so a rule may be written against a draft and simply waits
MCP tool: support_rule_create
Parameters
| Name | In | Type | Required | Description |
|---|
surveyId | body | string (uuid)
| • | The survey this rule delivers. It must belong to this product |
urlMatch | body | object
| • | Which pages this rule applies to |
trigger | body | object
| • | When to show the survey once the page matches |
frequency | body | object
| • | How often the same person may see this survey |
device | body | stringone of: any, desktop, mobile
| — | Which devices this rule applies to (default any) |
enabled | body | boolean
| — | Whether the rule is live. A rule is created enabled unless this says otherwise |
attributeConditions | body | object[]
| — | Conditions on the visitor’s attributes. Omit for a rule that applies to everyone |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Rule id |
device | stringone of: any, desktop, mobile
| • | Which devices the rule applies to. any is the rule that does not care and is the default |
enabled | boolean
| • | Whether the rule is live |
trigger | object
| • | When the client’s runtime shows the survey once the page matches: immediately, after a delay, or on an event of its own |
surveyId | string (uuid)
| • | The survey this rule delivers |
urlMatch | object
| • | Which pages match |
createdAt | string (date-time)
| • | ISO-8601 creation timestamp |
frequency | object
| • | How often the same person may see this survey. The client’s runtime enforces it — this toolkit never sees the end user |
updatedAt | string (date-time)
| • | ISO-8601 last-modification timestamp |
attributeConditions | object[]
| • | Conditions on the visitor’s attributes, ALL of which must hold |
DELETE/support/rules/:id
Delete a delivery rule for good. Unlike a survey, a rule is not archived — it has no responses behind it and nothing outlives it, so the row is removed and cannot be recovered. The survey it targeted is untouched. To stop delivery reversibly, set enabled to false instead
MCP tool: support_rule_delete
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Rule id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Id of the rule that was deleted |
deleted | booleanone of: true
| • | Always true — the rule is gone |
GET/support/rules/:id
Get one delivery rule by id, with its whole configuration. A rule belonging to another product is reported as not found
MCP tool: support_rule_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Rule id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Rule id |
device | stringone of: any, desktop, mobile
| • | Which devices the rule applies to. any is the rule that does not care and is the default |
enabled | boolean
| • | Whether the rule is live |
trigger | object
| • | When the client’s runtime shows the survey once the page matches: immediately, after a delay, or on an event of its own |
surveyId | string (uuid)
| • | The survey this rule delivers |
urlMatch | object
| • | Which pages match |
createdAt | string (date-time)
| • | ISO-8601 creation timestamp |
frequency | object
| • | How often the same person may see this survey. The client’s runtime enforces it — this toolkit never sees the end user |
updatedAt | string (date-time)
| • | ISO-8601 last-modification timestamp |
attributeConditions | object[]
| • | Conditions on the visitor’s attributes, ALL of which must hold |
PATCH/support/rules/:id
Edit a delivery rule: its URL match, device, trigger, frequency, conditions, or whether it is live at all. Every value replaces the old one whole rather than merging into it. The survey is not editable — a rule is the targeting OF one survey, and retargeting it silently would change what every matching page shows; delete it and write the one you meant. Switching a rule off is the quiet way to stop delivering without touching the survey
MCP tool: support_rule_update
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Rule id |
urlMatch | body | object
| — | The new URL match, replacing the old one |
trigger | body | object
| — | The new trigger, replacing the old one |
frequency | body | object
| — | The new frequency, replacing the old one |
device | body | stringone of: any, desktop, mobile
| — | The new device targeting |
enabled | body | boolean
| — | Turn the rule on or off |
attributeConditions | body | object[]
| — | The complete new condition set, replacing the old one. Pass [] to clear it |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Rule id |
device | stringone of: any, desktop, mobile
| • | Which devices the rule applies to. any is the rule that does not care and is the default |
enabled | boolean
| • | Whether the rule is live |
trigger | object
| • | When the client’s runtime shows the survey once the page matches: immediately, after a delay, or on an event of its own |
surveyId | string (uuid)
| • | The survey this rule delivers |
urlMatch | object
| • | Which pages match |
createdAt | string (date-time)
| • | ISO-8601 creation timestamp |
frequency | object
| • | How often the same person may see this survey. The client’s runtime enforces it — this toolkit never sees the end user |
updatedAt | string (date-time)
| • | ISO-8601 last-modification timestamp |
attributeConditions | object[]
| • | Conditions on the visitor’s attributes, ALL of which must hold |
GET/support/statuses
List this product status dictionary in display order. A product that never opened settings gets the four defaults seeded on the first call. Archived statuses are hidden unless asked for
MCP tool: support_status_list
Parameters
| Name | In | Type | Required | Description |
|---|
includeArchived | query | boolean
| — | Also return archived statuses (default false) |
Response
Returns object.
| Field | Type | Always | Description |
|---|
statuses | object[]
| • | The product status dictionary, in display order |
POST/support/statuses
Add a status to this product status dictionary. The name is what people see; the category is what automation and reporting read, and it cannot change once threads sit on the status
MCP tool: support_status_create
Parameters
| Name | In | Type | Required | Description |
|---|
name | body | string
| • | Status name as the customer wants to see it. Unique within the product |
color | body | string
| • | Display colour as a six-digit hex string, e.g. #2563eb |
order | body | integer
| • | Sort order within the status dictionary, lowest first |
category | body | stringone of: open, pending, resolved, closed
| • | Lifecycle bucket the status belongs to. Automation and reporting read the category, never the name — so it cannot be changed once threads sit on the status |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Status id |
name | string
| • | Status name, unique within the product |
color | string
| • | Display colour as a hex string |
order | integer
| • | Sort order within the status dictionary |
archived | boolean
| • | Archived statuses stay readable but are not offered as targets |
category | stringone of: open, pending, resolved, closed
| • | Lifecycle bucket the status belongs to |
GET/support/statuses/:id
Get one status of this product status dictionary by id, in the same shape a list element has. An archived status is returned like any other — hiding it is the list job, not this one. A status belonging to another product is reported as not found
MCP tool: support_status_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Status id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Status id |
name | string
| • | Status name, unique within the product |
color | string
| • | Display colour as a hex string |
order | integer
| • | Sort order within the status dictionary |
archived | boolean
| • | Archived statuses stay readable but are not offered as targets |
category | stringone of: open, pending, resolved, closed
| • | Lifecycle bucket the status belongs to |
PATCH/support/statuses/:id
Rename, recolour or reorder a status. The category may only change while no thread references the status: changing it later would rewrite the history of every metric derived from it. A status belonging to another product is reported as not found
MCP tool: support_status_update
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Status id |
name | body | string
| — | Status name as the customer wants to see it. Unique within the product |
color | body | string
| — | Display colour as a six-digit hex string, e.g. #2563eb |
order | body | integer
| — | Sort order within the status dictionary, lowest first |
category | body | stringone of: open, pending, resolved, closed
| — | New lifecycle bucket. Refused while any thread references the status |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Status id |
name | string
| • | Status name, unique within the product |
color | string
| • | Display colour as a hex string |
order | integer
| • | Sort order within the status dictionary |
archived | boolean
| • | Archived statuses stay readable but are not offered as targets |
category | stringone of: open, pending, resolved, closed
| • | Lifecycle bucket the status belongs to |
POST/support/statuses/:id/archive
Retire a status. There is no way to delete one: every thread on it moves to the target status in the same transaction, so no thread is ever left on an archived status. The target must be a different, live status of this product
MCP tool: support_status_archive
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Status id |
targetStatusId | body | string (uuid)
| • | Live status every thread on the archived status moves to, in the same transaction |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Status id |
name | string
| • | Status name, unique within the product |
color | string
| • | Display colour as a hex string |
order | integer
| • | Sort order within the status dictionary |
archived | boolean
| • | Archived statuses stay readable but are not offered as targets |
category | stringone of: open, pending, resolved, closed
| • | Lifecycle bucket the status belongs to |
GET/support/surveys
List this product’s surveys, newest first, with pagination. Supply status to return only draft, published or archived surveys; omit status to include all three states.
MCP tool: support_survey_list
Parameters
| Name | In | Type | Required | Description |
|---|
status | query | stringone of: draft, published, archived
| — | Only surveys in this lifecycle state |
limit | query | integer
| — | Page size, 1..100 (default 25) |
offset | query | integer
| — | Number of surveys to skip |
Response
Returns object.
| Field | Type | Always | Description |
|---|
limit | integer
| • | Page size that was applied |
offset | integer
| • | Offset that was applied |
surveys | object[]
| • | The requested page of surveys, newest first |
POST/support/surveys
Open a new survey. It starts as a draft — editable and not yet answerable — whatever questions travel with it. There is no way to create one already published: publishing runs the structure validator, and a create that skipped it would be a second way in
MCP tool: support_survey_create
Parameters
| Name | In | Type | Required | Description |
|---|
name | body | string
| • | What this survey is called. It becomes the title of an escalated feedback thread |
questions | body | object[]
| — | The initial question set. Omit it to open an empty draft and fill it in later |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Survey id |
name | string
| • | What this survey is called |
status | stringone of: draft, published, archived
| • | draft: editable, not answerable. published: answerable, frozen. archived: terminal, neither editable nor answerable, and there is no way back |
createdAt | string
| • | ISO-8601 creation timestamp |
questions | object[]
| • | The ordered question set, exactly as stored |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
questionIds | string[]
| • | The same questions as a flat list of ids, in the order they are asked |
questionCount | integer
| • | How many questions this survey asks — the same field support/survey.published carries, so a workflow built from the trigger and one built from a get-step bind one shape |
GET/support/surveys/:id
Get one survey by id, with its whole question set. A survey belonging to another product is reported as not found. Through the public edge only a PUBLISHED survey is readable — a draft or an archived one is reported as not found there
MCP tool: support_survey_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Survey id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Survey id |
name | string
| • | What this survey is called |
status | stringone of: draft, published, archived
| • | draft: editable, not answerable. published: answerable, frozen. archived: terminal, neither editable nor answerable, and there is no way back |
createdAt | string
| • | ISO-8601 creation timestamp |
questions | object[]
| • | The ordered question set, exactly as stored |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
questionIds | string[]
| • | The same questions as a flat list of ids, in the order they are asked |
questionCount | integer
| • | How many questions this survey asks — the same field support/survey.published carries, so a workflow built from the trigger and one built from a get-step bind one shape |
PATCH/support/surveys/:id
Edit a survey’s name or question set. Only a draft is editable — publishing freezes the structure, because answers are keyed by question id and rewriting the questions under them would change what every collected response was answering. To edit a live survey, unpublish it first. questions replaces the whole set; pass an empty array to clear it
MCP tool: support_survey_update
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Survey id |
name | body | string
| — | New name |
questions | body | object[]
| — | The complete new question set, replacing the old one. Pass [] to empty the draft |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Survey id |
name | string
| • | What this survey is called |
status | stringone of: draft, published, archived
| • | draft: editable, not answerable. published: answerable, frozen. archived: terminal, neither editable nor answerable, and there is no way back |
createdAt | string
| • | ISO-8601 creation timestamp |
questions | object[]
| • | The ordered question set, exactly as stored |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
questionIds | string[]
| • | The same questions as a flat list of ids, in the order they are asked |
questionCount | integer
| • | How many questions this survey asks — the same field support/survey.published carries, so a workflow built from the trigger and one built from a get-step bind one shape |
POST/support/surveys/:id/archive
Switch a survey off for good, from either draft or published. Archiving is terminal — there is no way back in v2 — and it deletes nothing: every response already collected stays readable
MCP tool: support_survey_archive
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Survey id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Survey id |
name | string
| • | What this survey is called |
status | stringone of: draft, published, archived
| • | draft: editable, not answerable. published: answerable, frozen. archived: terminal, neither editable nor answerable, and there is no way back |
createdAt | string
| • | ISO-8601 creation timestamp |
questions | object[]
| • | The ordered question set, exactly as stored |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
questionIds | string[]
| • | The same questions as a flat list of ids, in the order they are asked |
questionCount | integer
| • | How many questions this survey asks — the same field support/survey.published carries, so a workflow built from the trigger and one built from a get-step bind one shape |
POST/support/surveys/:id/publish
Publish a draft survey, which is what makes it answerable and what freezes its structure. Publishing validates the whole question set: it must be non-empty, question ids must be unique, radio and checkbox questions must offer options, a statement may not be required, and every skip must target a later question or the literal "end". A survey that is already published or archived is refused
MCP tool: support_survey_publish
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Survey id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Survey id |
name | string
| • | What this survey is called |
status | stringone of: draft, published, archived
| • | draft: editable, not answerable. published: answerable, frozen. archived: terminal, neither editable nor answerable, and there is no way back |
createdAt | string
| • | ISO-8601 creation timestamp |
questions | object[]
| • | The ordered question set, exactly as stored |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
questionIds | string[]
| • | The same questions as a flat list of ids, in the order they are asked |
questionCount | integer
| • | How many questions this survey asks — the same field support/survey.published carries, so a workflow built from the trigger and one built from a get-step bind one shape |
GET/support/surveys/:id/stats
Aggregate one survey’s responses over a time window: how many came in and how many reached the end, then per question — option counts for radio and checkbox, mean and distribution for rating and nps, an answered count for the rest. For the FIRST nps question it also returns promoters (9-10), passives (7-8), detractors (0-6) and the NPS score: percentage of promoters minus percentage of detractors over everyone who answered, rounded once. The window defaults to the last 30 days — omitting from does not mean "since the beginning"
MCP tool: support_survey_stats
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Survey id |
from | query | string (date-time)
| — | Start of the window, ISO-8601, inclusive. Defaults to 30 days ago — omitting it means "recently", never "since the beginning" |
to | query | string (date-time)
| — | End of the window, ISO-8601, inclusive. Defaults to now |
Response
Returns object.
| Field | Type | Always | Description |
|---|
to | string
| • | ISO-8601 end of the window that was actually applied |
nps | object | null
| • | The NPS block, or null when the survey asks no nps question |
from | string
| • | ISO-8601 start of the window that was actually applied |
surveyId | string (uuid)
| • | The survey these numbers describe |
questions | object[]
| • | Every question of the survey, answered or not |
responses | object
| • | The response counts over the window |
POST/support/surveys/:id/unpublish
Return a published survey to draft so it can be edited. It stops accepting responses immediately; the responses already collected are untouched. A survey that is not published is refused
MCP tool: support_survey_unpublish
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Survey id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Survey id |
name | string
| • | What this survey is called |
status | stringone of: draft, published, archived
| • | draft: editable, not answerable. published: answerable, frozen. archived: terminal, neither editable nor answerable, and there is no way back |
createdAt | string
| • | ISO-8601 creation timestamp |
questions | object[]
| • | The ordered question set, exactly as stored |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
questionIds | string[]
| • | The same questions as a flat list of ids, in the order they are asked |
questionCount | integer
| • | How many questions this survey asks — the same field support/survey.published carries, so a workflow built from the trigger and one built from a get-step bind one shape |
GET/support/threads
List support threads for this product, newest first — by creation time by default, or by last modification with sort='updated'. Every filter lives here: type, status, status category, assignee, external author, creation window, rating range (ratingMin/ratingMax, inclusive — a thread nobody rated matches neither bound), plus pagination
MCP tool: support_thread_list
Parameters
| Name | In | Type | Required | Description |
|---|
type | query | stringone of: ticket, feedback
| — | Only threads of this type |
statusId | query | string (uuid)
| — | Only threads sitting on this exact status |
category | query | stringone of: open, pending, resolved, closed
| — | Only threads whose status belongs to this category. Resolved through a join on the status dictionary — the category is never stored on the thread |
assignedAgentId | query | string (uuid)
| — | Only threads assigned to this agent |
authorExternalId | query | string
| — | Only threads opened by this external author, matched verbatim |
createdFrom | query | string (date-time)
| — | Only threads created at or after this ISO-8601 timestamp |
createdTo | query | string (date-time)
| — | Only threads created at or before this ISO-8601 timestamp |
ratingMin | query | integer
| — | Only threads rated at or above this score (0..10, inclusive). An unrated thread never matches a rating filter — not even at the bottom of the scale |
ratingMax | query | integer
| — | Only threads rated at or below this score (0..10, inclusive). An unrated thread never matches a rating filter — not even at the bottom of the scale |
labelIds | query | string (uuid) | string (uuid)[]
| — | Only threads carrying ALL of these labels. Every other filter here narrows the result and so does this one: each additional label id makes the answer smaller, never bigger |
sort | query | stringone of: created, updated
| — | Ordering of the page, newest first either way (default 'created') |
limit | query | integer
| — | Page size, 1..100 (default 25) |
offset | query | integer
| — | Number of threads to skip |
Response
Returns object.
| Field | Type | Always | Description |
|---|
limit | integer
| • | Page size that was applied |
offset | integer
| • | Offset that was applied |
threads | object[]
| • | The requested page of threads, newest first by the requested sort |
POST/support/threads
Open a support thread on behalf of an external end user. The author is an opaque identifier from the client's own user base and is stored verbatim. The thread opens on the product's default open status; there is no status argument. An optional rating — an integer 0..10, both ends inclusive — may travel with it and is stored verbatim too; omit it for an unrated thread, because 0 is a real score at the bottom of the scale
MCP tool: support_thread_create
Parameters
| Name | In | Type | Required | Description |
|---|
type | body | stringone of: ticket, feedback
| • | ticket: the end user wants something fixed. feedback: unsolicited input |
title | body | string
| • | Short human-readable subject of the thread |
authorExternalId | body | string
| • | Opaque identifier of the external end user in the client's own user base. Stored verbatim: never resolved, normalised or enriched, and it is not a Lepsto identity |
authorDisplay | body | string
| — | Optional display name for the external author, as the client already knows it |
metadata | body | object
| — | Free-form client-owned JSON attached to the thread |
labelIds | body | string (uuid)[]
| — | Labels to put on the new thread |
rating | body | integer
| — | Optional score the client attached when the thread was opened: an integer 0..10, both ends inclusive, stored verbatim and never recomputed. Omit it for an unrated thread — 0 is a real score at the bottom of the scale, not a way of saying "no rating" |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Thread id |
type | stringone of: ticket, feedback
| • | ticket: the end user wants something fixed. feedback: unsolicited input |
title | string
| • | Thread subject |
labels | object[]
| • | Every label on the thread, by name |
rating | integer | null
| • | The score the client supplied at creation, or null while the thread is unrated |
status | object
| • | The status itself, joined in at read time |
labelIds | string (uuid)[]
| • | The same labels as a flat list of ids, in the order labels[] has them. This is the value support_thread_update takes back: a dot-path can name this whole set, while labels.0.id names only the first element |
metadata | object
| • | Free-form client-owned JSON |
priority | stringone of: low, normal, high, urgent
| • | Thread priority |
statusId | string (uuid)
| • | Id of the status the thread sits on |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
labelNames | string[]
| • | The same labels as a flat list of names, in the order labels[] has them |
lastReplyAt | string | null
| • | ISO-8601 timestamp of the last reply, or null while there are none |
authorDisplay | string | null
| • | Display name for the external author, if given |
assignedAgentId | string (uuid) | null
| • | Assigned agent, or null while unassigned |
authorExternalId | string
| • | The external author, byte-identical to what was stored |
GET/support/threads/:id
Get one support thread by id, with its status joined in. A thread belonging to another product is reported as not found
MCP tool: support_thread_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Thread id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Thread id |
type | stringone of: ticket, feedback
| • | ticket: the end user wants something fixed. feedback: unsolicited input |
title | string
| • | Thread subject |
labels | object[]
| • | Every label on the thread, by name |
rating | integer | null
| • | The score the client supplied at creation, or null while the thread is unrated |
status | object
| • | The status itself, joined in at read time |
labelIds | string (uuid)[]
| • | The same labels as a flat list of ids, in the order labels[] has them. This is the value support_thread_update takes back: a dot-path can name this whole set, while labels.0.id names only the first element |
metadata | object
| • | Free-form client-owned JSON |
priority | stringone of: low, normal, high, urgent
| • | Thread priority |
statusId | string (uuid)
| • | Id of the status the thread sits on |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
labelNames | string[]
| • | The same labels as a flat list of names, in the order labels[] has them |
lastReplyAt | string | null
| • | ISO-8601 timestamp of the last reply, or null while there are none |
authorDisplay | string | null
| • | Display name for the external author, if given |
assignedAgentId | string (uuid) | null
| • | Assigned agent, or null while unassigned |
authorExternalId | string
| • | The external author, byte-identical to what was stored |
PATCH/support/threads/:id
Edit a thread: its title, its priority, its labels and its client-owned metadata. Moving a thread between statuses and putting an agent on it have their own tools and are not possible here. labelIds replaces the entire label set — pass an empty array to clear it, and a label belonging to another product is reported as not found
MCP tool: support_thread_update
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Thread id |
title | body | string
| — | New subject of the thread |
priority | body | stringone of: low, normal, high, urgent
| — | New priority |
labelIds | body | string (uuid)[]
| — | The complete new label set. Pass an empty array to remove every label |
metadata | body | object
| — | Replaces the whole client-owned metadata object |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Thread id |
type | stringone of: ticket, feedback
| • | ticket: the end user wants something fixed. feedback: unsolicited input |
title | string
| • | Thread subject |
labels | object[]
| • | Every label on the thread, by name |
rating | integer | null
| • | The score the client supplied at creation, or null while the thread is unrated |
status | object
| • | The status itself, joined in at read time |
labelIds | string (uuid)[]
| • | The same labels as a flat list of ids, in the order labels[] has them. This is the value support_thread_update takes back: a dot-path can name this whole set, while labels.0.id names only the first element |
metadata | object
| • | Free-form client-owned JSON |
priority | stringone of: low, normal, high, urgent
| • | Thread priority |
statusId | string (uuid)
| • | Id of the status the thread sits on |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
labelNames | string[]
| • | The same labels as a flat list of names, in the order labels[] has them |
lastReplyAt | string | null
| • | ISO-8601 timestamp of the last reply, or null while there are none |
authorDisplay | string | null
| • | Display name for the external author, if given |
assignedAgentId | string (uuid) | null
| • | Assigned agent, or null while unassigned |
authorExternalId | string
| • | The external author, byte-identical to what was stored |
POST/support/threads/:id/assignment
Put a support agent on a thread, or pass null to return it to the unassigned queue — clearing an assignment is an ordinary operation, not an error. An agent belonging to another product is reported as not found
MCP tool: support_thread_assign
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Thread id |
agentId | body | string (uuid) | null
| • | Support agent of this product to put on the thread, or null to return it to the unassigned queue. An agent of another product is reported as not found |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Thread id |
type | stringone of: ticket, feedback
| • | ticket: the end user wants something fixed. feedback: unsolicited input |
title | string
| • | Thread subject |
labels | object[]
| • | Every label on the thread, by name |
rating | integer | null
| • | The score the client supplied at creation, or null while the thread is unrated |
status | object
| • | The status itself, joined in at read time |
labelIds | string (uuid)[]
| • | The same labels as a flat list of ids, in the order labels[] has them. This is the value support_thread_update takes back: a dot-path can name this whole set, while labels.0.id names only the first element |
metadata | object
| • | Free-form client-owned JSON |
priority | stringone of: low, normal, high, urgent
| • | Thread priority |
statusId | string (uuid)
| • | Id of the status the thread sits on |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
labelNames | string[]
| • | The same labels as a flat list of names, in the order labels[] has them |
lastReplyAt | string | null
| • | ISO-8601 timestamp of the last reply, or null while there are none |
authorDisplay | string | null
| • | Display name for the external author, if given |
assignedAgentId | string (uuid) | null
| • | Assigned agent, or null while unassigned |
authorExternalId | string
| • | The external author, byte-identical to what was stored |
POST/support/threads/:id/status
Move a thread to a status. Any status may follow any other — there are no transition rules. The category is never a parameter: it comes from the status the thread lands on. A status belonging to another product is reported as not found, and an archived status is refused
MCP tool: support_thread_status_set
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Thread id |
statusId | body | string (uuid)
| • | Destination status of this product. Any status may follow any other — v1 has no transition rules. An archived status is refused |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Thread id |
type | stringone of: ticket, feedback
| • | ticket: the end user wants something fixed. feedback: unsolicited input |
title | string
| • | Thread subject |
labels | object[]
| • | Every label on the thread, by name |
rating | integer | null
| • | The score the client supplied at creation, or null while the thread is unrated |
status | object
| • | The status itself, joined in at read time |
labelIds | string (uuid)[]
| • | The same labels as a flat list of ids, in the order labels[] has them. This is the value support_thread_update takes back: a dot-path can name this whole set, while labels.0.id names only the first element |
metadata | object
| • | Free-form client-owned JSON |
priority | stringone of: low, normal, high, urgent
| • | Thread priority |
statusId | string (uuid)
| • | Id of the status the thread sits on |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-modification timestamp |
labelNames | string[]
| • | The same labels as a flat list of names, in the order labels[] has them |
lastReplyAt | string | null
| • | ISO-8601 timestamp of the last reply, or null while there are none |
authorDisplay | string | null
| • | Display name for the external author, if given |
assignedAgentId | string (uuid) | null
| • | Assigned agent, or null while unassigned |
authorExternalId | string
| • | The external author, byte-identical to what was stored |
GET/support/webhook-deliveries
List webhook delivery attempts for this product, newest first. Filter by endpoint, by state (pending, delivered, failed), by event type or by the time the delivery was opened. Each row carries the exact JSON body that was sent, the last HTTP status and, while attempts remain, when the next one is due
MCP tool: support_webhook_deliveries_list
Parameters
| Name | In | Type | Required | Description |
|---|
webhookId | query | string (uuid)
| — | Only deliveries for this endpoint |
state | query | stringone of: pending, delivered, failed
| — | Only deliveries in this state: pending, delivered or failed |
eventType | query | stringone of: support.message.created, support.message.updated, support.message.deleted, support.thread.status-changed, support.thread.assigned, support.survey.published, support.response.created
| — | Only deliveries of this event type |
createdFrom | query | string (date-time)
| — | Only deliveries opened at or after this ISO-8601 time |
createdTo | query | string (date-time)
| — | Only deliveries opened at or before this ISO-8601 time |
limit | query | integer
| — | Page size, 1..200 (default 50) |
offset | query | integer
| — | Number of deliveries to skip |
Response
Returns object.
| Field | Type | Always | Description |
|---|
limit | integer
| • | Page size that was applied |
offset | integer
| • | Offset that was applied |
deliveries | object[]
| • | The requested page, newest first |
POST/support/webhook-deliveries/redeliver
Send a past delivery to its endpoint again, with the original payload and the original idempotencyKey — a receiver that already processed it can recognise and ignore the repeat. The original delivery row is kept as history and a new one is opened for this attempt. If the endpoint has since been disabled or unsubscribed from the event, nothing is delivered
MCP tool: support_webhook_redeliver
Parameters
| Name | In | Type | Required | Description |
|---|
id | body | string (uuid)
| • | Id of the delivery to send again |
Response
Returns object.
| Field | Type | Always | Description |
|---|
delivery | object
| • | The new delivery, opened in pending state. It carries the original idempotencyKey, so a receiver that already processed the first one can ignore it |
GET/support/webhooks
List the endpoints this product has registered, oldest first. Signing secrets are never included — only the short prefix that tells two endpoints apart
MCP tool: support_webhook_list
Parameters
No parameters.
Response
Returns object.
| Field | Type | Always | Description |
|---|
webhooks | object[]
| • | The endpoints this product has registered, oldest first |
POST/support/webhooks
Register an HTTPS endpoint to receive Support events for this product, and mint its signing secret. The secret is returned here in clear and NOWHERE ELSE — exactly once, so store it now: it cannot be read back and can only be replaced by rotating it. Plain http:// endpoints are rejected
MCP tool: support_webhook_create
Parameters
| Name | In | Type | Required | Description |
|---|
url | body | string (uri)
| • | HTTPS endpoint the event is POSTed to. Plain http:// is rejected |
eventTypes | body | string[]one of: support.message.created, support.message.updated, support.message.deleted, support.thread.status-changed, support.thread.assigned, support.survey.published, support.response.created
| • | Event types this endpoint subscribes to. One or more of support.message.created, support.message.updated, support.message.deleted, support.thread.status-changed, support.thread.assigned, support.survey.published, support.response.created |
description | body | string
| — | Free-form note about what this endpoint is for, up to 1000 characters |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Webhook id |
url | string
| • | HTTPS endpoint events are POSTed to |
secret | string
| • | The signing secret in clear. Shown exactly once, here — store it now; it cannot be read back and can only be replaced by rotating it |
status | stringone of: active, disabled
| • | active or disabled |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-update timestamp |
eventTypes | string[]
| • | Event types this endpoint subscribes to |
description | string | null
| • | Free-form note, or null |
secretPrefix | string
| • | First characters of the signing secret, to tell endpoints apart. Never the whole secret |
DELETE/support/webhooks/:id
Remove a registered endpoint and the delivery history that points at it. To stop delivery without losing the registration, set its status to disabled instead
MCP tool: support_webhook_delete
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Webhook id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Id of the endpoint that was removed |
deleted | booleanone of: true
| • | Always true — the endpoint is gone |
GET/support/webhooks/:id
Get one registered endpoint by id. The signing secret is not part of the answer. An endpoint belonging to another product is reported as not found
MCP tool: support_webhook_get
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Webhook id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Webhook id |
url | string
| • | HTTPS endpoint events are POSTed to |
status | stringone of: active, disabled
| • | active or disabled |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-update timestamp |
eventTypes | string[]
| • | Event types this endpoint subscribes to |
description | string | null
| • | Free-form note, or null |
secretPrefix | string
| • | First characters of the signing secret, to tell endpoints apart. Never the whole secret |
PATCH/support/webhooks/:id
Edit a registered endpoint: its url, the event types it subscribes to, its description or its status. Setting status to disabled keeps the registration and stops delivery. Omit a field to leave it alone; pass null to clear the description. The signing secret is not editable here — rotate it instead
MCP tool: support_webhook_update
Parameters
| Name | In | Type | Required | Description |
|---|
url | body | string (uri)
| — | HTTPS endpoint the event is POSTed to. Plain http:// is rejected |
eventTypes | body | string[]one of: support.message.created, support.message.updated, support.message.deleted, support.thread.status-changed, support.thread.assigned, support.survey.published, support.response.created
| — | Event types this endpoint subscribes to. One or more of support.message.created, support.message.updated, support.message.deleted, support.thread.status-changed, support.thread.assigned, support.survey.published, support.response.created |
description | body | string | null
| — | Free-form note about what this endpoint is for, up to 1000 characters |
status | body | stringone of: active, disabled
| — | active delivers events; disabled keeps the registration and stops delivery |
id | path | string (uuid)
| • | Webhook id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Webhook id |
url | string
| • | HTTPS endpoint events are POSTed to |
status | stringone of: active, disabled
| • | active or disabled |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-update timestamp |
eventTypes | string[]
| • | Event types this endpoint subscribes to |
description | string | null
| • | Free-form note, or null |
secretPrefix | string
| • | First characters of the signing secret, to tell endpoints apart. Never the whole secret |
POST/support/webhooks/:id/rotate-secret
Replace the signing secret of an endpoint and return the new one in clear, exactly once. The old secret stops verifying immediately — there is no grace window, so update the receiver before rotating
MCP tool: support_webhook_rotate_secret
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string (uuid)
| • | Webhook id |
Response
Returns object.
| Field | Type | Always | Description |
|---|
id | string (uuid)
| • | Webhook id |
url | string
| • | HTTPS endpoint events are POSTed to |
secret | string
| • | The signing secret in clear. Shown exactly once, here — store it now; it cannot be read back and can only be replaced by rotating it |
status | stringone of: active, disabled
| • | active or disabled |
createdAt | string
| • | ISO-8601 creation timestamp |
updatedAt | string
| • | ISO-8601 last-update timestamp |
eventTypes | string[]
| • | Event types this endpoint subscribes to |
description | string | null
| • | Free-form note, or null |
secretPrefix | string
| • | First characters of the signing secret, to tell endpoints apart. Never the whole secret |
Was this page helpful?Thanks for your feedback!