Customers
Every project has a customer directorythat builds itself: whenever someone opens a ticket or responds to feedback, Helmdesk records (or refreshes) a customer keyed by their email. The API lets you enrich those records from your own app — name, company, your own user id, private notes, arbitrary metadata — and pull a customer's full interaction history in one call.
Sync customers from your app
Set externalIdto your app's user id when you create (or enrich) a customer. From then on your backend can resolve its own users to Helmdesk customers without ever passing an email around:
import { HelmdeskClient } from '@helmdesk/sdk'
const helmdesk = new HelmdeskClient({ apiKey: process.env.HELMDESK_API_KEY! })
// e.g. on signup in YOUR app
await helmdesk.customers.create({
email: user.email,
name: user.name,
company: user.company,
externalId: user.id, // your app's user id
metadata: { plan: 'pro' }, // anything you like, up to 8 KB
})
// Later, anywhere in your app: who is user_42 in Helmdesk?
const { customers } = await helmdesk.customers.list({ externalId: 'user_42' })Emails are unique per project and environment — create returns a 409 if the customer already exists. Customers created automatically from a ticket or feedback start with just email and name; use update to enrich them after the fact.
Who is this person?
get returns the customer plus their interaction history — tickets and feedback conversations joined by email, recent-first, capped at 50 each. One call gives an agent (human or AI) everything the customer has ever asked or said:
const detail = await helmdesk.customers.get(customerId)
detail.history.tickets
// [{ id, number, subject, status, createdAt }, ...]
detail.history.feedbackConversations
// [{ id, title, rating, status, createdAt }, ...]CRM-style notes and updates
internalNote is staff-only — it is returned by the API and shown in the dashboard, but never exposed to the customer. Use it to carry context between interactions. Email is immutable (it is the join key back to tickets and feedback); every other field is patchable, and null clears:
await helmdesk.customers.update(customerId, {
company: 'Acme Corp',
internalNote: 'Churn risk — pricing complaint in March, handled by Sam',
externalId: null, // clears
})
// Deleting only removes the directory entry — tickets and feedback are
// untouched, and a new interaction re-creates the customer.
await helmdesk.customers.delete(customerId)Search the directory
const { customers, total } = await helmdesk.customers.list({
search: 'acme', // substring match on email, name, or company
page: 1,
limit: 25, // max 100; sorted by most recently seen
})Webhooks
Two events keep your systems in sync: customer.created fires when a customer enters the directory (explicitly via the API, or automatically from their first ticket or feedback), and customer.updated fires on profile changes. See Webhooks.
Scopes & environments
customers:read— list and view customers and their interaction history.customers:write— create, update, and delete customers.- Customers live per environment: an
sk_sandbox_…key only sees and touches the sandbox directory — ideal for integration tests. See Sandbox.
Endpoints
| Endpoint | Purpose |
|---|---|
| GET /v1/customers | List/search (filter by search text or externalId) |
| POST /v1/customers | Create explicitly (409 on duplicate email) |
| GET /v1/customers/:id | Customer + interaction history (tickets, feedback) |
| PATCH /v1/customers/:id | Partial update (email immutable; null clears a field) |
| DELETE /v1/customers/:id | Remove from the directory (tickets/feedback untouched) |
The MCP server exposes the same surface as tools (list_customers, get_customer, update_customer, …) — so an AI assistant can answer "who is this person and what have they asked us before?" in one step. See MCP Server.