ticketNumber path parameter is that per-agent number, not a global id. A ticket’s channel records where it originated, such as email, the chat widget, or WhatsApp. Tickets created through this API always have channel: "api".
Statuses
Each ticket has a status, and each status belongs to one of six fixed categories:new, on_you, on_customer, on_hold, closed, cancelled. The statuses themselves are configured per agent in the dashboard; every category has exactly one default status.
Write endpoints accept a status in one of two forms, at most one per request:
List ticket statuses returns the AI agent’s active statuses with their ids, categories, and labels. Each status has two labels:
externalLabel is what the customer sees, internalLabel is what the dashboard shows.
Assignment and routing
When you create a ticket, the assignee fields decide whether auto-assignment runs.assigneeId and assigneeEmail are a mutually exclusive pair; sending both is a 400.
Update a ticket never auto-assigns. Omitted fields keep their current value,
assigneeId: null unassigns the ticket, and teamId: null clears the team.
List teams returns the AI agent’s teams; exactly one is the default.
Messages
A ticket’s thread contains three message types:metadata.eventType names the kind of change. Changes made by a trigger carry triggerId, triggerName, and runId, and every event from the same trigger run shares its runId. Webhook events report deliveryStatus (pending, delivered, or failed) and the statusCode your endpoint returned. New event kinds may be added, so treat an unknown eventType as a generic change rather than an error.
Add a message to a ticket currently accepts only type: "reply", attributed to a team member via authorId or authorEmail. The body is GitHub-flavored Markdown; raw HTML is stripped. Delivery to the customer is asynchronous, so a 201 means the reply was recorded, not that it reached the customer.
Attendance and activity
Two read-only endpoints return your operators’ Helpdesk Attendance report and Activity timeline. Use them to load your operators’ hours into your own reporting on a schedule, instead of exporting files by hand. Attendance and Activity explain what each value means.
Both take the same query parameters:
The values are typed rather than CSV text. Durations are whole minutes, percentages are numbers, and times are ISO 8601 instants in UTC. An operator in several teams is one attendance row with a
teams array sorted by name, where the CSV repeats the row for each team.
Both include only the AI agent’s current Helpdesk operators, the same as the dashboard.
A day that hasn’t ended yet returns what has happened so far. Pull days that have ended when you want numbers that won’t change.
Activity segments
Activity segments are the raw timeline the Activity tab is built from, so their totals can differ slightly from the Activity export:- Two segments with 2 minutes or less between them stay separate. The dashboard joins them.
- Times aren’t rounded.
- A segment that crosses midnight isn’t split. It’s listed only in the range where it started.
timeLoggedMinutes and productiveTimeMinutes from the attendance rows, or the Activity export.
A segment lasts at most 24 hours. When the same activity goes on longer, a new segment starts the moment the previous one ends, so a long stretch comes as several segments with no gap between them.
A segment that is still open has endedAt: null, and its lastSeenAt is refreshed every few minutes while the Helpdesk is open, also when the operator is idle or the tab is in the background. So a daily pull of yesterday sees a segment that was still open at midnight without its end. Pull the last two days each time and keep the newest copy of each segment by id. A segment ends within about a day of its start, so the next day’s pull gets its end.
Access
The AI agent must have the Helpdesk turned on, or both endpoints return 403HELPDESK_NOT_ENABLED. A key or connected app limited to certain permissions needs analytics:read, shown as Analytics on the consent screen. helpdesk_tickets:read doesn’t include it.
Rate limit
The two endpoints share a limit of 30 calls per 10 seconds per account. It counts every API key and connected app of the account together, and applies on top of the general rate limit. Over it, they return 429RATE_LIMIT_TOO_MANY_REQUESTS with a Retry-After header that gives the seconds to wait.
Reading one page at a time stays under the limit, at about 10 to 20 calls per 10 seconds. You only reach it by running several reads at once, or several tools against the same account.
Paging
These two lists differ from the others in two ways:- There is no
pagination.total. Keep requesting untilpagination.cursorisnull. - A cursor only works with the
startDate,endDateandtimezoneit came from. Changing any of them returns 400VALIDATION_CURSOR_QUERY_MISMATCH.
Pull yesterday’s data every day
Endpoints
List tickets
Filterable, sortable, paginated list
Search tickets
Free-text search over ticket messages
Create a ticket
Open a ticket on behalf of a customer
Get a ticket
Retrieve a single ticket by number
Update a ticket
Change status, assignee, or team
List ticket messages
Read a ticket’s thread
Add a message to a ticket
Post an agent reply
List teams
The AI agent’s teams and the default
List ticket statuses
Configured statuses with ids and labels
List attendance rows
Each operator’s day against their shift
List activity segments
Ticket, untracked and idle time per operator
Error codes
Helpdesk-specific error codes beyond the standard authentication and rate-limiting errors:| Code | HTTP | Description |
|---|---|---|
HELPDESK_NOT_ENABLED | 403 | The Helpdesk is not turned on for this AI agent. |
VALIDATION_CURSOR_QUERY_MISMATCH | 400 | On attendance and activity, the cursor came from a request with a different startDate, endDate or timezone. |
TICKET_NOT_FOUND | 404 | No ticket matches this number for the AI agent. |
CONVERSATION_NOT_TAKEN_OVER | 409 | The ticket is linked to a live conversation that has not been taken over from the AI agent, so a human reply cannot be posted. Take over the conversation from the dashboard first. |
MESSAGE_CONTENT_NOT_RENDERABLE | 422 | The message body rendered to empty HTML. This happens when it consists only of raw HTML, which is stripped. Send Markdown or plain text. |
TICKET_INVALID_STATUS | 422 | statusId does not belong to a status for this AI agent. |
TICKET_ARCHIVED_STATUS | 422 | statusId refers to an archived status, which cannot be applied. |
TICKET_TEAM_MEMBER_NOT_FOUND | 422 | Neither assigneeId nor assigneeEmail resolved to a team member on this account. |
TICKET_TEAM_NOT_FOUND | 422 | teamId does not belong to a team for this AI agent. |
TEAM_MEMBER_NOT_FOUND | 422 | On message creation, neither authorId nor authorEmail resolved to a team member on this account. |