With the same workspace API key, your backend can read a ticket’s current status and its customer-visible conversation, or list all of one customer’s tickets. The read-back is exactly what the customer sees — agents’ private notes and AI summaries are never included.
Read one ticket
Send a GET to /api/tickets/{id-or-ref}. You can use either the Mongo id or the short, human-facing ref (case-insensitive), so the value you stored from the create response works directly:
curl https://{slug}.essential.support/api/tickets/A7K2M9Q \
-H "Authorization: Bearer es_live_…"
The response is the ticket plus its customer-visible thread. Each message carries its kind (customer, agent_reply, and instant/auto AI replies), a plain-text and HTML body, and the author’s display name:
{
"ticket": {
"id": "66b2f0a1c3d4e5f6a7b8c9d0",
"number": 1042,
"ref": "A7K2M9Q",
"subject": "Invoice charged twice",
"status": "open",
"priority": "high",
"source": "api",
"createdAt": "2026-08-09T14:03:00.000Z",
"lastMessageAt": "2026-08-09T15:20:00.000Z",
"customer": { "id": "66b2efaa1122334455667788", "email": "dana@customer.com", "externalId": "u_9", "name": "Dana Lee" }
},
"messages": [
{ "id": "66b2f0a1…", "kind": "customer", "body": { "text": "I was billed twice this month.", "html": "<p>I was billed twice this month.</p>" },
"author": { "type": "endUser", "displayName": "Dana Lee", "avatarUrl": null }, "attachments": [], "createdAt": "2026-08-09T14:03:00.000Z" },
{ "id": "66b2f5c2…", "kind": "agent_reply", "body": { "text": "We've refunded the duplicate charge.", "html": "<p>We've refunded the duplicate charge.</p>" },
"author": { "type": "agent", "displayName": "Sam", "avatarUrl": null }, "attachments": [], "createdAt": "2026-08-09T15:20:00.000Z" }
]
}
An unknown id or ref returns 404 with Ticket not found.
List a customer’s tickets
Send a GET to /api/tickets. A keyed caller must name exactly one customer, so you must pass email or externalId (a request with neither returns 400):
curl "https://{slug}.essential.support/api/tickets?externalId=u_9&status=open&limit=25" \
-H "Authorization: Bearer es_live_…"
emailorexternalId(one required) — the customer to look up.externalIdtakes precedence when both are given.status(optional) —openorclosed.limit(optional) — 1 to 100; defaults to 25.skip(optional) — offset for paging through more thanlimitresults.
Tickets come back newest-activity first (by lastMessageAt), with a total for paging:
{
"total": 3,
"tickets": [
{ "id": "66b2f0a1…", "ref": "A7K2M9Q", "subject": "Invoice charged twice", "status": "open", "priority": "high", "source": "api",
"customer": { "email": "dana@customer.com", "externalId": "u_9", "name": "Dana Lee" } }
]
}
An unknown customer is not an error — you get { "total": 0, "tickets": [] }. The list is never an upsert: it only finds an existing customer.
Discover your ticket types
Call GET /api/ticket-types with your key to list your workspace’s types (id and name), so you can set type by id when creating a ticket.
Pairs with webhooks
The recommended pattern is thin webhook payloads (ids and state) that trigger a read-back: your app receives ticket.agent_replied, then calls GET /api/tickets/{ref} for the detail. See Set up webhooks.