Resources

Tickets

.md

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

  1. open -- new or reopened ticket, awaiting an agent response.
  2. pending -- an agent replied; waiting on the customer.
  3. review -- the customer replied to a pending ticket; needs agent attention again.
  4. resolved -- the issue is resolved.
  5. 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

FieldAllowed values
statusopen, pending, review, resolved, closed
prioritylow, normal, medium, high, urgent
channeldirect, email
POST /v1/tickets and PATCH /v1/tickets/{id} do not validate these fields before writing them. A value outside the list is rejected by the database and the request fails with 500 instead of 400. Only PATCH /v1/tickets/{id}/manage validates status and priority (400 on invalid values).

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.

FieldTypeRequiredDescription
subjectstringYesThe ticket subject line.
messagestringYesThe initial message body (plain text). Line breaks are converted to <br> for the HTML body.
customer_idstringConditionalExisting customer id (cus_...). Provide customer_id OR customer_email. Must belong to your workspace (404 otherwise).
customer_emailstringConditionalCustomer email. Creates the customer if it does not exist; updates name and phone of an existing customer when provided.
customer_namestringNoCustomer display name. Defaults to the local part of the email when creating.
customer_phonestringNoCustomer phone number.
customer_groupstringNoContact group name (exact, case-sensitive match on an active group). Adds the customer to the group if found; ignored otherwise.
prioritystringNolow, normal (default), medium, high, or urgent.
channelstringNodirect (default) or email.
inbox_idstringNoTarget inbox id (inb_...). Uses the oldest active inbox of the workspace if no inbox field is sent.
inbox_emailstringNoTarget inbox by email address (alternative to inbox_id).
inboxstringNoAlias: accepts an inbox email or an inb_ id. Priority: inbox_id, then inbox_email, then inbox.
assigned_tostringNoEmail of a workspace member to assign (case-insensitive match). If no member matches, the ticket is created unassigned without an error.
metadataobjectNoArbitrary JSON metadata.
attachmentsarrayNoArray of { fileName, fileData (base64), contentType }. Attached to the first message.
terminal
Bash
01curl https://api.tickos.dev/v1/tickets \
02 -H 'Authorization: Bearer sk_your_api_key' \
03 -H 'Content-Type: application/json' \
04 -d '{
05 "subject": "Cannot access my account",
06 "message": "Hi, I am unable to log in since yesterday.",
07 "customer_email": "jane@example.com",
08 "customer_name": "Jane Doe",
09 "priority": "high",
10 "assigned_to": "agent@company.com",
11 "metadata": { "source": "api", "plan": "pro" }
12 }'
UTF-8 · LF · Bashtickos.dev

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.

StatusCause
400Missing subject, message, or both customer_id and customer_email; inbox identifier that is neither an email nor an inb_ id; workspace without any inbox.
403The customer (by customer_id or customer_email) is blocked.
404customer_id or inbox not found in your workspace.
429Plan ticket or storage limit reached. Body: { error: "Limit exceeded", message, current, limit, percentage, recommended_plan }.
Blocked customers (is_blocked: true) cannot open new tickets -- the API returns 403.

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.

ParameterTypeDefaultDescription
statusstring--Comma-separated: open, pending, review, resolved, closed.
prioritystring--Comma-separated: low, normal, medium, high, urgent.
archivedstring--true or false. Omit to include both.
is_readstring--true or false. Omit to include both.
snoozedstringallonly_snoozed, exclude_snoozed, or all.
assigned_tostring--User UUID or "unassigned".
inbox_idstring--Filter by inbox id (inb_...).
tag_idstring--Filter by tag id (tag_...).
has_scheduledstring--true: only tickets with at least one scheduled (not yet sent) email.
has_draftstring--true: only tickets with at least one draft message.
searchstring--Free-text search on subject, client ID, customer email and name.
updated_afterstring--ISO 8601 date for delta sync (400 if not a valid date).
sort_orderstringdescasc or desc. Sorts by last_message_at.
limitnumber50Max 100.
offsetnumber0Pagination 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).

The embedded messages array is a lightweight summary (id, body_text, body_html, direction, is_customer, from_email, from_name, created_at). It is not ordered, does not include attachments, and may include draft messages. Use GET /v1/tickets/{id}/messages for the ordered thread with attachments.

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.

FieldTypeDescription
subjectstringNew subject line.
descriptionstringTicket description.
statusstringopen, pending, review, resolved, or closed. Unlike the manage action, does not set closed_at.
prioritystringlow, normal, medium, high, or urgent.
assigned_tostring | nullUser UUID of a workspace member (not an email, unlike POST). 404 if the user is not a member. null unassigns.
metadataobjectArbitrary 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.

ActionDataDescription
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.
terminal
Bash
01curl -X PATCH https://api.tickos.dev/v1/tickets/{id}/manage \
02 -H 'Authorization: Bearer sk_your_api_key' \
03 -H 'Content-Type: application/json' \
04 -d '{ "action": "snooze", "data": { "until": "2025-03-15T09:00:00Z" } }'
UTF-8 · LF · Bashtickos.dev

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).

response.json
JSON
01{
02 "total": 284,
03 "unread": 12,
04 "archived": 45,
05 "snoozed": 3,
06 "active": 156,
07 "by_status": { "open": 80, "pending": 40, "resolved": 20, "closed": 16 },
08 "by_priority": { "low": 30, "normal": 100, "medium": 40, "high": 10, "urgent": 2 },
09 "recent_activity": { "today": 15, "this_week": 67, "this_month": 210 }
10}
UTF-8 · LF · JSONtickos.dev

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.

response.json
JSON
01{
02 "agents": [
03 {
04 "agent_id": "uuid", "name": "Jane Doe", "email": "jane@acme.com",
05 "total": 120, "open": 10, "pending": 5, "review": 2, "resolved": 80, "closed": 23,
06 "urgent": 3, "unread": 4,
07 "avg_first_response_seconds": 5400, "avg_resolution_seconds": 86400
08 }
09 ],
10 "totals": { "total": 250, "open": 30, "pending": 12, "review": 5, "resolved": 150, "closed": 53, "urgent": 6, "unread": 9 },
11 "timeline": { "days": [ { "date": "2026-07-01", "agents": { "uuid": 12, "unassigned": 3 } } ] }
12}
UTF-8 · LF · JSONtickos.dev

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.

ParameterTypeDefaultDescription
start_datestring--ISO 8601. Tickets created on or after this date.
end_datestring--ISO 8601. Tickets created before this date (exclusive).
include_timelinestringfalsetrue adds a daily series (by ticket creation date) of tickets per tag.
inbox_idstring--One or more inbox ids, comma-separated.
response.json
JSON
01{
02 "tags": [
03 {
04 "tag_id": "tag_7g8h...", "name": "Billing", "color": "#22c55e", "inbox_id": "inb_2b3c...",
05 "total": 42, "open": 5, "pending": 3, "review": 1, "resolved": 30, "closed": 3
06 }
07 ],
08 "totals": { "tickets": 120, "tagged": 80, "untagged": 40 },
09 "timeline": { "days": [ { "date": "2026-07-01", "tags": { "tag_7g8h...": 4 } } ] }
10}
UTF-8 · LF · JSONtickos.dev

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.

FieldTypeRequiredDescription
inbox_idstringConditionalInbox id (inb_...). Provide inbox_id, inbox_email, or inbox.
inbox_emailstringConditionalInbox email address.
inboxstringConditionalAlias for inbox_id or inbox_email.
to_emailstringYesRecipient email address.
subjectstringYesEmail subject.
body_textstringYesPlain text email body.
body_htmlstringNoHTML email body.
ccstringNoCC recipients, comma-separated.
bccstringNoBCC recipients, comma-separated.
from_namestringNoSender display name.
filesarrayNoArray of { fileName, fileData (base64), contentType }.

Returns 201 with { "success": true, "ticket": { id, client_id, subject, status }, "message_id", "email_status", "attachments_uploaded" }.

Use the ticket id (tkv_...) as the {id} path parameter. Client ids with the SUP- prefix are also accepted on most ticket endpoints -- see Identifiers.