Your app’s backend can open a support ticket on a customer’s behalf with a workspace API key. This is the server-to-server path: the customer does not need to be signed in, and you identify them by email (and, ideally, your own account id).
Before you start
- Create a key under Settings → Integrations → API keys — see Create and manage API keys.
- Your base URL is your portal host:
https://{slug}.essential.support(or your custom domain). Send every request there.
The request
Send a POST to /api/tickets with your key as a Bearer token and a JSON body:
curl -X POST https://{slug}.essential.support/api/tickets \
-H "Authorization: Bearer es_live_…" \
-H "Content-Type: application/json" \
-d '{
"email": "dana@customer.com",
"externalId": "u_9",
"name": "Dana Lee",
"subject": "Invoice charged twice",
"message": "I was billed twice this month.",
"type": "Billing",
"priority": "high"
}'
Body fields
subject(required) — the ticket title.message(required) — the first message, as plain text. For rich content sendmessageHtmlormessageLexicalinstead; at least one non-empty message is required.email(required) — the customer’s email. It finds or creates the customer and is treated as verified, because your key vouches for it.externalId(optional, recommended) — your own stable customer id. When present it is the primary identity key, so a customer’s email can change without creating a duplicate.name(optional) — the customer’s display name.type(optional) — a ticket type by name (e.g.Billing) or by id. CallGET /api/ticket-typesto list them.priority(optional) —high,med, orlow.locale(optional) — the customer’s language (e.g.es), used for auto-translation of replies.
Setting the type and priority
Both type and priority are validate-or-ignore: a value Essential Support does not recognize is silently dropped rather than returning an error, so a stale type name never blocks ticket creation. priority is honored only on this API-key path — end users cannot set their own priority — and an unknown value falls back to med. Setting a priority here also stops AI auto-triage from overriding your choice.
A successful response
On success you get 201 Created with the ticket in the read-back shape. Store the ref (a short, human-facing code such as A7K2M9Q) or the id so you can link back and read updates later.
{
"ticket": {
"id": "66b2f0a1c3d4e5f6a7b8c9d0",
"number": 1042,
"ref": "A7K2M9Q",
"subject": "Invoice charged twice",
"status": "open",
"createdAt": "2026-08-09T14:03:00.000Z",
"lastMessageAt": "2026-08-09T14:03:00.000Z",
"aiAnswerPending": false,
"priority": "high",
"source": "api",
"customer": {
"id": "66b2efaa1122334455667788",
"email": "dana@customer.com",
"externalId": "u_9",
"name": "Dana Lee"
}
}
}
Errors
400— Subject and message are required (a required field is missing or empty).400— A valid customer email is required.401— API key required or Invalid API key (missing, wrong, or revoked key).429— Rate limit exceeded (over 120 requests per minute for that key).
Related
- Read your tickets with the API
- Set up webhooks — get notified when a ticket changes
- Link customer accounts
- Test your integration in the Sandbox