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

EndpointPurpose
GET /v1/customersList/search (filter by search text or externalId)
POST /v1/customersCreate explicitly (409 on duplicate email)
GET /v1/customers/:idCustomer + interaction history (tickets, feedback)
PATCH /v1/customers/:idPartial update (email immutable; null clears a field)
DELETE /v1/customers/:idRemove 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.