Tools, SDK and errors
Forty-four operations, three ways to call them, and the refusals they share.
Support is headless. There are three ways to reach it and they are all the same surface: the support_* MCP tools, the REST routes under /support, and the support namespace of @lessly/sdk-app. Every operation exists in all three, with the same inputs, the same outputs and the same refusals.
The tools
Forty-four operations. Twenty act on the support model itself — the dictionaries a product configures, then the threads, messages and files that fill it — eight configure and inspect the outbound webhooks that carry events away from it, eleven belong to surveys and the responses collected against them, and five are the delivery rules that say where a survey is shown.
Every REST path below is documented field by field on the Support API reference, and every tool has its own page under MCP tools.
The support model
| Tool | REST | What it does |
|---|---|---|
support_status_list | GET /support/statuses | The status dictionary, seeding the defaults on first read |
support_status_create | POST /support/statuses | Add a status |
support_status_update | PATCH /support/statuses/{id} | Rename, recolour, reorder, re-categorise |
support_status_archive | POST /support/statuses/{id}/archive | Retire a status, moving its threads to a target |
support_agent_create | POST /support/agents | Create an agent profile |
support_agent_list | GET /support/agents | The product’s agents |
support_agent_get | GET /support/agents/{id} | One agent, deactivated ones included |
support_agent_update | PATCH /support/agents/{id} | Edit a profile, including deactivating it |
support_label_create | POST /support/labels | Add a label |
support_label_list | GET /support/labels | The label dictionary |
support_thread_create | POST /support/threads | Open a thread on behalf of an end user |
support_thread_list | GET /support/threads | The queue, with every filter |
support_thread_get | GET /support/threads/{id} | One thread, status joined in |
support_thread_update | PATCH /support/threads/{id} | Title, priority, labels, metadata |
support_thread_status_set | POST /support/threads/{id}/status | Move a thread to a status |
support_thread_assign | POST /support/threads/{id}/assignment | Set or clear the assignee |
support_message_create | POST /support/messages | Post a message into a thread |
support_message_list | GET /support/messages | One conversation, or the product-wide catch-up |
support_attachment_create | POST /support/attachments | Reserve a file and get an upload URL |
support_attachment_finalize | POST /support/attachments/finalize | Confirm the upload landed |
Webhooks
| Tool | REST | What it does |
|---|---|---|
support_webhook_create | POST /support/webhooks | Register an endpoint and mint its signing secret |
support_webhook_list | GET /support/webhooks | The product’s endpoints, never with a secret |
support_webhook_get | GET /support/webhooks/{id} | One endpoint |
support_webhook_update | PATCH /support/webhooks/{id} | URL, events, description, and active against disabled |
support_webhook_delete | DELETE /support/webhooks/{id} | Remove an endpoint and its delivery history |
support_webhook_rotate_secret | POST /support/webhooks/{id}/rotate-secret | Replace the signing secret and return the new one once |
support_webhook_deliveries_list | GET /support/webhook-deliveries | The delivery log, filtered by endpoint, state, event or time |
support_webhook_redeliver | POST /support/webhook-deliveries/redeliver | Send a past delivery again, with its original key |
What each event carries and how to verify its signature is Receive Support events.
Surveys and responses
| Tool | REST | What it does |
|---|---|---|
support_survey_create | POST /support/surveys | Draft a survey with its question set |
support_survey_list | GET /support/surveys | The product’s surveys |
support_survey_get | GET /support/surveys/{id} | One survey, any status |
support_survey_update | PATCH /support/surveys/{id} | Edit a draft, including its questions |
support_survey_publish | POST /support/surveys/{id}/publish | Open it for answers, freezing the structure |
support_survey_unpublish | POST /support/surveys/{id}/unpublish | Return it to draft and stop accepting responses |
support_survey_archive | POST /support/surveys/{id}/archive | Retire a survey for good |
support_survey_stats | GET /support/surveys/{id}/stats | Server-side aggregates, NPS included |
support_response_list | GET /support/responses | The responses collected, with every filter |
support_response_get | GET /support/responses/{id} | One response |
support_response_escalate | POST /support/responses/{id}/escalate | Turn one response into a feedback thread |
Submitting a response is deliberately not a tool: a response is written by a person filling in a form, so POST /support/surveys/{surveyId}/responses is an HTTP route and nothing else. The lifecycle, the question types and the skip rules are Surveys and delivery rules.
Delivery rules
| Tool | REST | What it does |
|---|---|---|
support_rule_create | POST /support/rules | Write a rule: where a survey is shown, on which devices, when and how often |
support_rule_get | GET /support/rules/{id} | One rule, with its whole configuration |
support_rule_list | GET /support/rules | The product’s rules, newest first, filtered by survey, state, page URL or device |
support_rule_update | PATCH /support/rules/{id} | Edit a rule, including switching it off. The survey is not editable |
support_rule_delete | DELETE /support/rules/{id} | Remove a rule for good — a rule is deleted, not archived |
Nothing in a rule is executed here. What each field means, and which half of the decision belongs to your own runtime, is Delivery rules.
The MCP tools are the management plane: this is how an agent, the Workspace app and an operator all drive Support, and it is the surface the platform’s tool catalogue advertises.
The generated SDK namespace
There is no hand-written Support SDK package to install. Support publishes its operation catalogue, and @lessly/sdk-app compiles a support namespace from it — one method per operation above, typed from the same input and output schemas the tools declare. Update the SDK and new operations appear; nothing about Support is maintained twice.
The public perimeter is a narrow subset
Everything above is behind the platform login. The MCP tools, the authenticated REST routes they map to and the generated SDK namespace all require a platform identity, and none of them answers a product public key.
Beside them Support publishes a small keyed perimeter for a client’s own backend: twelve routes on https://public.lessly.com/{productId}/support, reached with a product public key rather than a platform login. They open a thread on behalf of an end user, read the queue and one thread, post and read messages, read the status and label dictionaries, read the agent profiles a reply is signed with, read a published survey and submit a response to it, and ask which delivery rules apply to a page. That is the whole list — see Public HTTP API.
The subset is deliberate. Assignment, status changes, creating and editing agent profiles, attachments and the webhook registry are not published on the public perimeter and answer 404, and no specialist bound to a Lessly identity writes through it — only an unbound agent profile, a bot or the client’s own backend, may author a message there. And the key is a backend credential: an end user’s browser must never talk to Support directly, because everything here is product-scoped support data and an end user is not a caller Support can recognise.
So a thread opened on behalf of an end user is opened by your backend. Your product’s support screen talks to your backend, and your backend talks to Support.
Who is calling
Every operation is authenticated, and the identity always belongs to your side: a specialist signed into the Workspace app, your own backend, or an agent. It answers one question — is this call legitimate, and which product does it belong to — and it never touches the content of a thread. The end users your threads are about are not part of it at all.
The product is sealed to the caller’s identity. It is not a parameter on a single tool, and it is not read from the request body: there is no way to ask Support for another product’s data by naming it, which is why none of the tools above take a product argument.
Errors
| Code | Means |
|---|---|
400 | The input is invalid, or the operation is refused: an archived status as a destination, a deactivated agent as an assignee, an attachment that is not claimable, cursor and offset together |
401 | The call carries no usable identity |
404 | The thing named does not exist — or belongs to another product |
409 | A uniqueness or history conflict: a duplicate status, label or Lessly binding, or re-categorising a status that threads already sit on |
Validation errors name the fields that failed. Refusals say what is wrong and, where there is one, what to do instead — the refusal to re-categorise a status in use tells you to create a status in the target category and move the threads.
An error carries a code and a message; a successful tool call returns exactly the value its output schema describes, so what you get back over HTTP is what you get back over MCP.
Not found, never forbidden
Anything belonging to another product is reported as not found, and the message never says that it exists somewhere else or who owns it. A thread, a status, an agent, a label, an attachment — all the same answer.
This is deliberate. “Forbidden” tells the caller the id is real, which is itself information about another customer’s data. Between “there is no such thread” and “there is a thread and it is not yours”, Support always says the first, so probing for ids learns nothing.
The rule applies at every depth, not only to the object you name: a message authoring against another product’s agent, a thread created with another product’s label, an attachment claimed from another product’s thread — each is a plain not-found.
Where to go next
- Lessly Support: the overview and the model behind these operations.
- Answer your first thread: a first thread, end to end.
- Public HTTP API: the twelve keyed routes a client backend calls.