Tickets
Create, list, update and manage support tickets.
The Ticket object represents a support conversation with a customer. Tickets belong to an inbox, carry a priority, and contain a thread of messages with optional attachments.
Identifiers
- Ticket ids are prefixed strings such as tkv_4d5e6f... (not UUIDs). Inbox ids start with inb_, tag ids with tag_, message ids with msg_ and customer ids with cus_. User ids (assigned_to, user_id) are UUIDs.
- Every ticket also has a human-readable client_id (e.g. SUP-000123) built from its inbox prefix. Client ids are for display and are not guaranteed to be unique.
- Use the ticket id in the {id} path parameter. Client ids with the SUP- prefix are also accepted on GET/PATCH/DELETE /v1/tickets/{id} and on the /messages and /tags sub-resources. Client ids with any other prefix (inbox prefixes are configurable, default TKT) are treated as ticket ids and return 404. PATCH /v1/tickets/{id}/manage only accepts the ticket id.
- If a SUP- client id matches more than one ticket, GET and PATCH return 404 and DELETE returns 409. Use the ticket id to avoid ambiguity.
Lifecycle
- open -- new or reopened ticket, awaiting an agent response.
- pending -- an agent replied; waiting on the customer.
- review -- the customer replied to a pending ticket; needs agent attention again.
- resolved -- the issue is resolved.
- closed -- archived. Resolved and closed tickets reopen (status open) when the customer replies.
Automatic transitions: an outbound reply moves open or review tickets to pending; a customer (inbound) reply moves pending to review, and resolved or closed to open. Internal notes never change the status.
Allowed values
| Field | Allowed values |
|---|---|
| status | open, pending, review, resolved, closed |
| priority | low, normal, medium, high, urgent |
| channel | direct, email |
Create a ticketPOST/v1/tickets
Permission: write. Creates a ticket (status open) and its first message, which is stored as an inbound message from the customer. If customer_email does not match an existing customer, the customer is created automatically.
| Field | Type | Required | Description |
|---|---|---|---|
| subject | string | Yes | The ticket subject line. |
| message | string | Yes | The initial message body (plain text). Line breaks are converted to <br> for the HTML body. |
| customer_id | string | Conditional | Existing customer id (cus_...). Provide customer_id OR customer_email. Must belong to your workspace (404 otherwise). |
| customer_email | string | Conditional | Customer email. Creates the customer if it does not exist; updates name and phone of an existing customer when provided. |
| customer_name | string | No | Customer display name. Defaults to the local part of the email when creating. |
| customer_phone | string | No | Customer phone number. |
| customer_group | string | No | Contact group name (exact, case-sensitive match on an active group). Adds the customer to the group if found; ignored otherwise. |
| priority | string | No | low, normal (default), medium, high, or urgent. |
| channel | string | No | direct (default) or email. |
| inbox_id | string | No | Target inbox id (inb_...). Uses the oldest active inbox of the workspace if no inbox field is sent. |
| inbox_email | string | No | Target inbox by email address (alternative to inbox_id). |
| inbox | string | No | Alias: accepts an inbox email or an inb_ id. Priority: inbox_id, then inbox_email, then inbox. |
| assigned_to | string | No | Email of a workspace member to assign (case-insensitive match). If no member matches, the ticket is created unassigned without an error. |
| metadata | object | No | Arbitrary JSON metadata. |
| attachments | array | No | Array of { fileName, fileData (base64), contentType }. Attached to the first message. |
Returns 201 with { "ticket": {...}, "message": {...}, "attachments": [...] }. The ticket object includes customer { id, name, email }. Fires the ticket.created webhook (no separate message.created event for the first message). If the inbox has an auto-responder, Slack or forwarding configured, those run as well.
| Status | Cause |
|---|---|
| 400 | Missing subject, message, or both customer_id and customer_email; inbox identifier that is neither an email nor an inb_ id; workspace without any inbox. |
| 403 | The customer (by customer_id or customer_email) is blocked. |
| 404 | customer_id or inbox not found in your workspace. |
| 429 | Plan ticket or storage limit reached. Body: { error: "Limit exceeded", message, current, limit, percentage, recommended_plan }. |
List ticketsGET/v1/tickets
Permission: read. Returns { "data": [...], "pagination": { "limit", "offset", "total" } }. Each ticket includes customer { id, name, email }, assigned_to_user { id, email, full_name }, tags [{ id, name, color }], message_count and attachment_count.
| Parameter | Type | Default | Description |
|---|---|---|---|
| status | string | -- | Comma-separated: open, pending, review, resolved, closed. |
| priority | string | -- | Comma-separated: low, normal, medium, high, urgent. |
| archived | string | -- | true or false. Omit to include both. |
| is_read | string | -- | true or false. Omit to include both. |
| snoozed | string | all | only_snoozed, exclude_snoozed, or all. |
| assigned_to | string | -- | User UUID or "unassigned". |
| inbox_id | string | -- | Filter by inbox id (inb_...). |
| tag_id | string | -- | Filter by tag id (tag_...). |
| has_scheduled | string | -- | true: only tickets with at least one scheduled (not yet sent) email. |
| has_draft | string | -- | true: only tickets with at least one draft message. |
| search | string | -- | Free-text search on subject, client ID, customer email and name. |
| updated_after | string | -- | ISO 8601 date for delta sync (400 if not a valid date). |
| sort_order | string | desc | asc or desc. Sorts by last_message_at. |
| limit | number | 50 | Max 100. |
| offset | number | 0 | Pagination offset. |
Get a ticketGET/v1/tickets/{id}
Permission: read. Returns the bare ticket object (no wrapper) with customer { id, name, email, phone }, assigned_to_user { id, email, full_name }, tags [{ id, name, color }] and an embedded messages array. {id} is the ticket id or a SUP- client id (see Identifiers).
Update a ticketPATCH/v1/tickets/{id}
Permission: write. Updates direct fields; other fields in the body are ignored (400 if none of these is present). Returns the updated ticket object with customer and inbox { id, name, email_address }. Fires the ticket.updated webhook.
| Field | Type | Description |
|---|---|---|
| subject | string | New subject line. |
| description | string | Ticket description. |
| status | string | open, pending, review, resolved, or closed. Unlike the manage action, does not set closed_at. |
| priority | string | low, normal, medium, high, or urgent. |
| assigned_to | string | null | User UUID of a workspace member (not an email, unlike POST). 404 if the user is not a member. null unassigns. |
| metadata | object | Arbitrary JSON. Replaces the existing metadata. |
Delete a ticketDELETE/v1/tickets/{id}
Permission: admin. Permanently deletes the ticket and its attachments from storage. Returns { "deleted": true, "id": "<id as sent in the path>" }. Fires the ticket.deleted webhook. Returns 409 if a SUP- client id matches more than one ticket (nothing is deleted).
Management actionsPATCH/v1/tickets/{id}/manage
Permission: write. Performs a single operation such as snooze, archive or assign. Send { "action": "...", "data": { ... } }. {id} must be the ticket id (client ids are not accepted). Returns { "ticket": {...}, "action": "...", "message": "..." }, where ticket includes customer and assigned_to_user. Management actions do not fire webhooks.
| Action | Data | Description |
|---|---|---|
| mark_read | -- | Mark the ticket as read. |
| mark_unread | -- | Mark the ticket as unread. |
| archive | -- | Archive the ticket. |
| unarchive | -- | Unarchive the ticket. |
| snooze | { "until": "ISO 8601" } | Snooze until the given date (must be in the future, otherwise 400). |
| unsnooze | -- | Cancel the snooze. |
| update_status | { "status": "open" } | open, pending, review, resolved or closed (400 otherwise). Closing sets closed_at; any other status clears it. |
| update_priority | { "priority": "high" } | low, normal, medium, high or urgent (400 otherwise). |
| assign | { "user_id": "uuid" } | Assign to a workspace member. 404 if the user is not a member of the workspace. |
| unassign | -- | Remove assignment. |
An unknown action or missing data returns 400.
Ticket statisticsGET/v1/tickets/stats
Permission: read. Returns counts by status, priority and recent activity (tickets created today, in the last 7 days and this calendar month). Optionally filter by inbox_id. by_status covers open, pending, resolved and closed only (no review count).
Agent performance statisticsGET/v1/tickets/agent-stats
Permission: read. Returns per-agent ticket counts by status (agent_id null = unassigned), plus average first response time (first outbound message) and average resolution time in seconds. Query params: start_date, end_date (ISO 8601, filter by ticket creation) and include_timeline=true to add a daily series of resolved tickets per agent.
Tag statisticsGET/v1/tickets/tag-stats
Permission: read. Counts tickets created in the period, grouped by tag, with a breakdown by status. A ticket with several tags counts once in each tag; tags with no tickets in the period are omitted. Tags are sorted by total, descending.
| Parameter | Type | Default | Description |
|---|---|---|---|
| start_date | string | -- | ISO 8601. Tickets created on or after this date. |
| end_date | string | -- | ISO 8601. Tickets created before this date (exclusive). |
| include_timeline | string | false | true adds a daily series (by ticket creation date) of tickets per tag. |
| inbox_id | string | -- | One or more inbox ids, comma-separated. |
timeline is only present with include_timeline=true. truncated: true is added when the period contains more tickets than the endpoint processes in one request; narrow the date range in that case.
Compose an email ticketPOST/v1/tickets/compose
Permission: write. Creates a ticket and sends the first message as an outbound email via your verified domain. Only works with email-type inboxes.
| Field | Type | Required | Description |
|---|---|---|---|
| inbox_id | string | Conditional | Inbox id (inb_...). Provide inbox_id, inbox_email, or inbox. |
| inbox_email | string | Conditional | Inbox email address. |
| inbox | string | Conditional | Alias for inbox_id or inbox_email. |
| to_email | string | Yes | Recipient email address. |
| subject | string | Yes | Email subject. |
| body_text | string | Yes | Plain text email body. |
| body_html | string | No | HTML email body. |
| cc | string | No | CC recipients, comma-separated. |
| bcc | string | No | BCC recipients, comma-separated. |
| from_name | string | No | Sender display name. |
| files | array | No | Array of { fileName, fileData (base64), contentType }. |
Returns 201 with { "success": true, "ticket": { id, client_id, subject, status }, "message_id", "email_status", "attachments_uploaded" }.