Ticket Replies
The eight endpoints that read and write a ticket conversation and set how replies are kept.
Overview
A ticket's conversation lives at these eight endpoints. Four deal with the reply itself, two change how a reply is stored and seen, and two handle attachments.
Replies are walked with a cursor rather than a page number. The newest comes first; to reach older ones you hand back the oldest id you hold, and to poll for new ones you hand back the newest.
Two different ideas of privacy live here and should not be confused. Encryption is about how the text sits in the database, and staff still read it. Hiding decides whether the client sees it at all.
Reference
Listing the Replies
Returns a ticket's conversation, newest first.
curl 'https://panel.example.com/api/v1/admin/tickets/402/messages?limit=10' \
-H "Authorization: Bearer $API_KEY"const url = new URL(`https://panel.example.com/api/v1/admin/tickets/${id}/messages`);
url.searchParams.set('limit', '10');
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const { data, meta } = await res.json();
// Bir sonraki sayfa: url.searchParams.set('before_reply_id', meta.oldest_reply_id)$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/messages?' . http_build_query(['limit' => 10]));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// THERE IS NO PAGE NUMBER: you ask for the next page with the oldest id you hold.
$page = Api::Tickets()->GetTicketMessages(['id' => $id], ['limit' => 10]);
$next = Api::Tickets()->GetTicketMessages(['id' => $id], [
'before_reply_id' => $page['meta']['oldest_reply_id'],
]);Writing a Reply
Adds a reply, moves the ticket to replied and tells the client.
curl -X POST 'https://panel.example.com/api/v1/admin/tickets/402/messages' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"message":"Sorununuz cozuldu.","signature":"Destek Ekibi"}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/messages`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'Your issue is resolved. Please confirm.',
signature: 'Support Team',
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'message' => 'Your issue is resolved. Please confirm.',
'signature' => 'Support Team',
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// To leave a note, add it HIDDEN: otherwise the client gets an e-mail.
Api::Tickets()->AddTicketMessage([
'id' => $id,
'message' => 'Checked the server logs, nothing unusual.',
'hidden' => true,
]);Editing a Reply
Changes the text of a reply already written.
curl -X PATCH 'https://panel.example.com/api/v1/admin/tickets/402/messages/846' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"message":"Duzeltilmis yanit metni."}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/messages/${replyId}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'Updated reply text.' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/messages/' . $replyId);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['message' => 'Updated reply text.']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Editing TELLS THE CLIENT NOTHING: the old text stands in the mail already sent.
Api::Tickets()->UpdateTicketMessage([
'id' => $id,
'reply_id' => $replyId,
'message' => 'Updated reply text.',
]);Deleting a Reply
Removes a reply along with its attachments.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tickets/402/messages/846' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/messages/${replyId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/messages/' . $replyId);
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);// The reply's attachments go too, and the ticket's last-reply summary shifts on the next read.
Api::Tickets()->DeleteTicketMessage(['id' => $id, 'reply_id' => $replyId]);Changing the Encryption
Decides whether a reply is stored encrypted.
curl -X PUT 'https://panel.example.com/api/v1/admin/tickets/402/messages/846/encryption' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"encrypted":true}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/messages/${replyId}/encryption`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ encrypted: true }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/messages/' . $replyId . '/encryption');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['encrypted' => true]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Encryption changes only how it is STORED: staff read the text either way.
$r = Api::Tickets()->SetTicketMessageEncryption([
'id' => $id, 'reply_id' => $replyId, 'encrypted' => true,
])['data'];Changing the Visibility
Hides a staff reply from the client, or shows it again.
curl -X PUT 'https://panel.example.com/api/v1/admin/tickets/402/messages/848/visibility' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"hidden":true}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/messages/${replyId}/visibility`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ hidden: true }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/messages/' . $replyId . '/visibility');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['hidden' => true]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Hiding works after the fact, yet it cannot pull back the mail ALREADY SENT.
Api::Tickets()->SetTicketMessageVisibility([
'id' => $id, 'reply_id' => $replyId, 'hidden' => true,
]);Downloading an Attachment
Returns an attachment's details together with its content.
curl 'https://panel.example.com/api/v1/admin/tickets/402/attachments/202' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/attachments/${attId}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const { data } = await res.json();
const bytes = Uint8Array.from(atob(data.content_base64), (c) => c.charCodeAt(0));$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/attachments/' . $attId);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Turning the content into text makes the body roughly A THIRD larger than the file.
$att = Api::Tickets()->GetTicketAttachment(['id' => $id, 'att_id' => $attId])['data'];
file_put_contents($path, base64_decode($att['content_base64']));Deleting an Attachment
Removes an attachment from its record and from disk.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tickets/402/attachments/202' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/attachments/${attId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/attachments/' . $attId);
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);// This endpoint removes REPLY attachments; note attachments have endpoints of their own.
Api::Tickets()->DeleteTicketAttachment(['id' => $id, 'att_id' => $attId]);Pitfalls
This list is walked with a cursor: you ask for the next page with the oldest reply id you hold. Sending a page number does nothing and returns the same first page forever. The meta block hands you both the oldest and the newest id, so keep them both.
The last-reply preview in a ticket list arrives blank for encrypted messages, while this endpoint hands the text back decoded. The two do not contradict: a list sweeps many tickets and keeps ciphertext out of that surface. Encryption is about storage rather than about keeping staff out.
Staff-only replies never reach the client, yet this endpoint returns them alongside the rest. An integration that shows the conversation to a customer as it comes leaks the internal notes with it. Filter the hidden ones out yourself on any customer-facing surface.
Adding a reply moves the ticket to replied and e-mails the client. Fixing the text afterwards does not pull that mail back, and neither does hiding it. When you only mean to record something, add the reply hidden: the status stays put and no mail goes out.
Because the response is a document, the file content arrives turned into text and the body runs about a third larger than the file. A hundred-megabyte attachment means a hundred-and-thirty-megabyte response, and a client that loads it whole stops there. Stream large attachments, and leave them alone unless you need them.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.