Feedback conversations
Support tickets tell you what is broken. Feedback conversations tell you how customers actually feel. Ask a customer for feedback (a feedback request), or record feedback your app collected — either way you get an ongoing conversation with an optional 1–5 rating and explanation, kept separate from your support queue. Available on Starter and up; the Free plan gets a 403.
Which project?
Every feedback conversation belongs to one project. A key limited to one project implies it — send nothing extra. A key that reaches several projects (the kind you give a coding agent) names the project on each request with the Helmdesk-Project header (slug or id) or ?project=; without it the API answers 400 project.required with the projects it could have meant. Every object in a response carries projectId and projectSlug, so you can always tell what you are looking at.
curl https://helmdesk.dev/api/v1/feedback/conversations \
-H "Authorization: Bearer sk_live_..." \
-H "Helmdesk-Project: ledgerly"// Pin the project once — or set HELMDESK_PROJECT and omit it.
const helmdesk = new HelmdeskClient({ apiKey, project: 'ledgerly' })
await helmdesk.feedback.conversations.list()See Authentication for how keys and projects fit together.
Request feedback (the "tickle")
A feedback request emails the customer a built-in, project-branded message with a one-click respond link — no form, no login. The request tracks its funnel: sent → opened (they clicked the link) → responded(they answered), so you always know whether a nudge landed. Perfect for "you signed up but never created a project — what stopped you?" automations driven from your own app.
import { HelmdeskClient } from '@helmdesk/sdk'
const helmdesk = new HelmdeskClient({ apiKey: process.env.HELMDESK_API_KEY! })
// e.g. from a lifecycle job: user signed up 3 days ago, no project yet
await helmdesk.feedback.requests.create({
title: 'How is your FireScraper setup going?',
message:
'Hi! We noticed you signed up but have not created a scraping session yet. ' +
'Did something get in the way? Reply and tell us — we read every answer.',
customerEmail: 'jane@example.com',
customerName: 'Jane',
})
// Later: check the funnel
const { requests } = await helmdesk.feedback.requests.list({ status: 'sent,opened' })The email subject is the request title. When the customer responds, a conversation is created from their answer and linked back to the request.
Link-only requests — send it yourself
Sometimes the ask belongs inside your email — a coupon, a renewal notice, a personal note from the founder. Pass channel: 'link' and Helmdesk sends nothing: the created request comes back with respondUrl, the public respond link, for you to embed wherever you like. The request starts as created instead of sent, and the funnel still tracks opened and responded exactly the same way.
// Create the link — no Helmdesk email goes out
const request = await helmdesk.feedback.requests.create({
title: 'How was your first month?',
message: 'Tell us how the first month went — the good and the bad.',
customerEmail: 'jane@example.com',
customerName: 'Jane',
channel: 'link',
// Staff-only context — the customer never sees this
internalNote: 'Sent $50 coupon WELCOME50 alongside this link',
})
// Put request.respondUrl inside your own coupon email
await sendMyCouponEmail({
to: 'jane@example.com',
couponCode: 'WELCOME50',
feedbackLink: request.respondUrl,
})
// The funnel still tracks it: created -> opened -> respondedThe optional internalNote works on both channels — use it to remember why a request went out ("sent $50 coupon"). It is visible to your team in the dashboard and API, never to the customer.
Record feedback from your app
If you collect feedback inside your own product (an in-app form, an NPS widget), push it to Helmdesk so everything lives in one place:
const conversation = await helmdesk.feedback.conversations.create({
title: 'In-app feedback',
body: 'Love the new export feature, but PDF rendering is slow on big jobs.',
customerEmail: 'jane@example.com',
rating: 4,
ratingComment: 'Great overall, exports need work',
})Reply and close
Conversations are two-way. When you reply — from the dashboard, API, or an AI assistant via MCP — the customer gets an email with a link back to the thread. A customer reply to a closed conversation reopens it.
// List open conversations with low ratings — who is unhappy?
const { conversations } = await helmdesk.feedback.conversations.list({
status: 'open',
rating: '1,2,3',
})
// Read the full thread
const thread = await helmdesk.feedback.conversations.get(conversations[0].id)
// Reply (the customer is emailed) and close
await helmdesk.feedback.conversations.reply(thread.id, 'Thanks — PDF speedup ships next week!')
await helmdesk.feedback.conversations.update(thread.id, { status: 'closed' })Scopes & environments
feedback:read— list and view conversations and requests.feedback:write— create requests, create conversations, reply, close/reopen, set ratings.- Requests created with an
sk_sandbox_…key are sandbox data: no real email is sent, and the data stays in the sandbox plane — ideal for integration tests. See Sandbox.
Endpoints
| Endpoint | Purpose |
|---|---|
| GET/POST /v1/feedback/conversations | List (filter by status, rating, customer, date) / create |
| GET/PATCH /v1/feedback/conversations/:id | Full thread / close, reopen, set rating |
| POST /v1/feedback/conversations/:id/messages | Reply (emails the customer) |
| GET/POST /v1/feedback/requests | List with funnel status / create + send the email |
| GET /v1/feedback/requests/:id | One request incl. opened/responded timestamps |
| DELETE /v1/feedback/requests/:id | Delete a request (the conversation it produced is kept) |
Deleting a request
Only the request row goes: a conversation the customer started from it is kept, because the answer is theirs, not the tickle's. The respond link stops working and nobody is emailed. Requires feedback:write.
// SDK
await helmdesk.feedback.requests.delete(request.id)
// curl
curl -X DELETE https://helmdesk.dev/api/v1/feedback/requests/<request-id> \
-H "Authorization: Bearer sk_live_..." \
-H "Helmdesk-Project: abc-salon"
// → { "deleted": true, "id": "...", "projectId": "...", "projectSlug": "abc-salon" }The MCP server exposes the same surface as tools (request_feedback, list_feedback_conversations, reply_to_feedback, …) — see MCP Server.