Tickets
A ticket is a customer request. Through the API you can read, create, update, close and reopen tickets. Tickets can’t be deleted through the API.
- Statuses:
new,open,in_progress,pending(snoozed),resolved,closed. - Priorities:
low,normal,high,urgent. channel_typeis the channel the ticket came from (telegram,widget,email,vk,whatsapp,billmgr…). Tickets created through the API havenullchannel_idandchannel_type.
/api/v1/ticketsList tickets
All of the project’s tickets, paginated, newest first (by creation date).
Query parameters:
status,priority: one of the values above; anything else gives 422contact_id: a contact’s UUID, only that contact’s ticketssince: ISO 8601, tickets created at or after that moment. A time with an offset is converted to UTC (13:30+02:00is 11:30 UTC); a time without one is taken as UTCinclude_history:trueaddslast_message_preview(the first 100 characters of the latest message) andmessage_countto every ticket; internal notes aren’t countedpage(default 1),page_size(default 20, max 100)
curl "https://api.support.forestsnet.com/api/v1/tickets?status=open&page_size=10&include_history=true" \
-H "Authorization: Bearer sk_xxx"{
"items": [
{
"id": "8a3f...",
"workspace_id": "1c2b...",
"contact_id": "44e1...",
"channel_id": "c7d0...",
"channel_type": "widget",
"assigned_to": null,
"department_id": null,
"subject": "The confirmation code never arrives",
"status": "open",
"priority": "normal",
"tags": ["billing"],
"first_response_at": null,
"resolved_at": null,
"closed_at": null,
"rating": null,
"created_at": "2026-04-06T10:12:33.123456",
"updated_at": "2026-04-06T10:12:33.123456",
"last_message_preview": "Hi, the code never arrives",
"message_count": 4
}
],
"total": 137,
"page": 1,
"page_size": 10
}/api/v1/tickets/{ticket_id}A ticket with its latest messages
Returns the ticket, its latest messages (in order, oldest first) and the contact. Operators’ internal notes are in the list too, with is_internal: true. The message format is on the Messages page.
Query parameters:
last_messages: how many of the latest messages to return (1–200, default 20)
curl "https://api.support.forestsnet.com/api/v1/tickets/8a3f...?last_messages=50" \
-H "Authorization: Bearer sk_xxx"{
"ticket": { "id": "8a3f...", "status": "open", ... },
"messages": [ { "id": "f1...", "sender_type": "contact", "content": "Hello", ... } ],
"contact": { "id": "44e1...", "full_name": "John", "email": "john@example.com", ... }
}/api/v1/ticketsCreate a ticket
Always creates a new ticket, even when the contact already has an open one. The contact is looked up by telegram_id, then by email, and created if not found. contact needs at least one of the two; the name can go in name or full_name (it updates the name of a contact that was found).
contact: required,telegram_id(a number) and/oremail, plusnamemessage: the first message’s text, saved as a message from the customersubject: if missing, the first 120 characters ofmessagepriority:normalby defaulttags: an array of strings
{
"subject": "Payment doesn't go through",
"priority": "high",
"tags": ["billing", "vip"],
"contact": {
"email": "john@example.com",
"name": "John Smith"
},
"message": "I'm trying to pay for the plan and get error 500."
}curl -X POST https://api.support.forestsnet.com/api/v1/tickets \
-H "Authorization: Bearer sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"priority": "high",
"contact": {"email": "john@example.com", "name": "John"},
"message": "The code never arrives"
}'The response is 201 with the ticket object (as in the list, without the history fields) in status new. Errors: 400 without telegram_id and email, 422 for an unknown priority, 403 TICKET_LIMIT_REACHED when the monthly ticket limit is used up, 403 for a blocked contact.
/api/v1/tickets/{ticket_id}Update a ticket
Changes status, priority, tags (the whole list is replaced) and subject. Send only the fields you need; null leaves a value unchanged. An unknown status or priority gives 422 and the ticket stays as it was.
{ "priority": "urgent", "tags": ["vip", "p1"] }ticket.updated webhook (ticket_id, workspace_id). It doesn’t set closed_at and doesn’t send the customer a closing message. A status change shows up in /updates as ticket.status_change. To close or reopen a ticket, use the endpoints below./api/v1/tickets/{ticket_id}/closeClose a ticket
Sets the closed status and closed_at (and resolved_at if it wasn’t set yet), records the resolution SLA, posts a system message about the closing in the conversation and sends it to the customer’s channel. Sends the ticket.closed webhook. An already closed ticket gives 409 TICKET_ALREADY_CLOSED. The response is the ticket object.
A close through the API is your side’s action, so the customer reads that support closed the conversation, with no operator name: «🔒 Conversation closed» or «🔒 Обращение закрыто оператором», in the customer’s language (the widget’s language, then the contact’s, then the project’s default). Widget tickets use the widget builder’s “ticket closed” text if it’s set; if that message is turned off there, none is posted. If ratings are on for the channel, the customer is asked to rate the conversation.
/api/v1/tickets/{ticket_id}/reopenReopen a ticket
Works only for tickets in closed or resolved; otherwise 422 TICKET_INVALID_STATUS_TRANSITION. Sets the open status, clears closed_at, resolved_at and first_response_at and restarts the SLA. The customer gets a notification if the channel’s settings have it on. Sends the ticket.reopened webhook.
More on tickets: a contact’s ticket history, ticket rating.

