Managing Tickets
The nine endpoints that open, update, delete and reshape support tickets.
Overview
These nine endpoints deal with the ticket itself: opening one, reading it, changing its fields, deleting it and seeing what happened to it. Replies, notes and custom fields live at their own endpoints, and the detail call does not carry them.
Three reshaping operations sit alongside. The bulk call closes or deletes many at once. Merging gathers a scattered conversation into one ticket. Splitting lifts a subject that wandered in out into a ticket of its own.
This area reaches the client. Opening a ticket, marking one solved and notifying on a split each send an e-mail, and assigning staff tells the person assigned. Weigh that before writing a batch script.
Reference
Listing the Tickets
Returns support tickets, filtered and paged.
open, waiting, process, replied, solved.open, waiting, process, replied, solved.curl 'https://panel.example.com/api/v1/admin/tickets?status=waiting&limit=25' \
-H "Authorization: Bearer $API_KEY"const url = new URL('https://panel.example.com/api/v1/admin/tickets');
url.searchParams.set('status', 'waiting');
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets?' . http_build_query(['status' => 'waiting', 'limit' => 25]));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// On an encrypted reply the preview text arrives EMPTY; ciphertext never enters a list.
$rows = Api::Tickets()->GetTickets([], ['status' => 'waiting'])['data'];
$prev = $rows[0]['last_reply']['message'] ?? null;Opening a Ticket
Opens a ticket for a client on behalf of staff and writes the first message.
open, waiting, process, replied, solved.curl -X POST 'https://panel.example.com/api/v1/admin/tickets' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"client_id":2,"subject":"Hos geldiniz","message":"Merhaba {FULL_NAME}, hizmetiniz hazir.","department_id":4}'const res = await fetch('https://panel.example.com/api/v1/admin/tickets', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
client_id: 2,
subject: 'Welcome aboard',
message: 'Hi {FULL_NAME}, your service {SERVICE} is ready.',
department_id: 4,
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'client_id' => 2,
'subject' => 'Welcome aboard',
'message' => 'Hi {FULL_NAME}, your service {SERVICE} is ready.',
'department_id' => 4,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Opening a ticket MAILS THE CLIENT; in an import loop every record means one more message.
Api::Tickets()->CreateTicket([
'client_id' => $uid,
'subject' => $subject,
'message' => 'Hi {FULL_NAME}, your service {SERVICE} is ready.',
]);Reading One Ticket
Returns one ticket with everything it points at resolved.
open, waiting, process, replied, solved.curl 'https://panel.example.com/api/v1/admin/tickets/402' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The detail CARRIES NO MESSAGES; replies, notes and history come from their own endpoints.
$ticket = Api::Tickets()->GetTicket(['id' => $id])['data'];Updating a Ticket
Applies the fields you send, and each changed field leaves its own trail.
open, waiting, process, replied, solved. Solved and in-process both tell the client.curl -X PATCH 'https://panel.example.com/api/v1/admin/tickets/402' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status":"solved","priority":3}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status: 'solved', priority: 3 }),
});
const { meta } = await res.json();
console.log(meta.applied);$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['status' => 'solved', 'priority' => 3]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Sending the same value is a NO-OP; meta.applied tells you what really changed.
$r = Api::Tickets()->UpdateTicket(['id' => $id, 'status' => 'solved']);
$changed = $r['meta']['applied'];Deleting a Ticket
Removes a ticket along with its replies and files.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tickets/404' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// There is NO wastebasket: replies, attachments and custom field files all go with it.
Api::Tickets()->DeleteTicket(['id' => $id]);Reading the History
Returns what happened on a ticket, in order.
curl 'https://panel.example.com/api/v1/admin/tickets/402/history' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/history`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/history');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The event name is a RAW key; turning it into readable text is your screen's job.
$events = Api::Tickets()->GetTicketHistory(['id' => $id])['data'];Acting on Many Tickets
Closes or deletes several tickets in one call.
close marks them solved, delete removes them, while block-close and block-delete also bar the client from opening tickets.curl -X POST 'https://panel.example.com/api/v1/admin/tickets/bulk' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"ids":[405,406],"action":"close"}'const res = await fetch('https://panel.example.com/api/v1/admin/tickets/bulk', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids: [405, 406], action: 'close' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/bulk');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['ids' => [405, 406], 'action' => 'close']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The password step the panel asks for before deleting is NOT here; scope is the only gate.
Api::Tickets()->BulkTicketActions(['ids' => $ids, 'action' => 'close']);Merging Tickets
Gathers several tickets into one.
curl -X POST 'https://panel.example.com/api/v1/admin/tickets/merge' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"ids":[419,418]}'const res = await fetch('https://panel.example.com/api/v1/admin/tickets/merge', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids: [419, 418] }),
});
const { meta } = await res.json();
console.log(meta.primary_id); // 418$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/merge');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['ids' => [419, 418]]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// YOU DO NOT PICK the main ticket: the lowest number wins and the rest are folded in.
$r = Api::Tickets()->MergeTickets(['ids' => [419, 418]]);
$primary = $r['meta']['primary_id']; // 418Splitting a Ticket
Moves the replies you pick into a new ticket.
curl -X POST 'https://panel.example.com/api/v1/admin/tickets/402/split' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"reply_ids":[846,847],"subject":"Fatura sorusu","department_id":4}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/split`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reply_ids: [846, 847],
subject: 'Billing question split out',
department_id: 4,
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/split');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'reply_ids' => [846, 847],
'subject' => 'Billing question split out',
'department_id' => 4,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The replies MOVE rather than copy: they no longer appear on the source ticket.
$new = Api::Tickets()->SplitTicket([
'id' => $id,
'reply_ids' => $ids,
'subject' => 'Billing question split out',
])['data'];Pitfalls
In a merge the ticket with the lowest number becomes the main one, and the order of your array changes nothing. The others have their replies moved across and are then removed. Assuming the number you put first wins gathers the conversation the opposite way round. Read the main number that comes back to see what happened.
The last-reply preview in a ticket list arrives empty when that reply is encrypted. Ciphertext never enters a list, and that is a deliberate line. A client that prints the preview as it comes shows a blank row, so label the empty value as encrypted instead.
On an update each field runs its own path: marking a ticket solved notifies the client, and assigning staff notifies the person assigned. Sending the same value again does nothing, so mail goes out exactly when a record really changes. Read the applied-field list that comes back to see what went.
The two blocking options do more than close and delete: they bar the client from opening tickets. That is a lasting decision which shuts that account out of support, and it is not the same as removing a ticket. When writing a spam clean-up, reach for the plain option rather than the blocking one.
When a ticket goes, its replies, attachments, note attachments and custom field files go with it. There is no wastebasket and no undo. A script that deletes where it meant to close takes the client's history along; marking the status solved is all closing needs.
Related Articles
Merci pour votre retour !
Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.