Internal Notes and Secrets
The nine endpoints for staff-only notes and the secrets kept on a ticket.
Overview
These nine endpoints deal with the side of a ticket the client never sees. Six manage staff-only notes and their files. Three hold labelled secrets, which is where things shared during a conversation, such as server access, belong.
Both live on the ticket's own row, encrypted. Neither has a table of its own, so neither has paging or filtering: the list always arrives whole.
Adding a note raises no notice and leaves the ticket status alone. When you want text that stays out of the client's view yet belongs to the conversation itself, what you want is a hidden reply rather than a note.
Reference
Listing the Notes
Returns the notes staff left on a ticket.
curl 'https://panel.example.com/api/v1/admin/tickets/402/notes' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/notes`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/notes');
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 paging: every note comes at once, decoded from the ticket's own row.
$notes = Api::Tickets()->GetTicketNotes(['id' => $id])['data'];Adding a Note
Leaves a staff-only note on a ticket.
curl -X POST 'https://panel.example.com/api/v1/admin/tickets/402/notes' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"message":"Muhasebeye devredildi.","pinned":true}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/notes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'Escalated to the billing team.',
pinned: true,
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/notes');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'message' => 'Escalated to the billing team.',
'pinned' => true,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A note NEVER REACHES THE CLIENT and raises no notice; only staff see it.
$note = Api::Tickets()->AddTicketNote([
'id' => $id,
'message' => 'Escalated to the billing team.',
])['data'];Updating a Note
Changes a note's text or whether it is pinned.
curl -X PATCH 'https://panel.example.com/api/v1/admin/tickets/402/notes/1d9e35094420c723' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"pinned":false}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/notes/${noteId}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ pinned: false }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/notes/' . $noteId);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['pinned' => false]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// An empty body is an ERROR rather than a no-op; send at least one field.
Api::Tickets()->UpdateTicketNote([
'id' => $id, 'note_id' => $noteId, 'pinned' => false,
]);Deleting a Note
Removes a note along with its files.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tickets/402/notes/1d9e35094420c723' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/notes/${noteId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/notes/' . $noteId);
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 note's attachments go too: dropped from the record and removed from disk.
Api::Tickets()->DeleteTicketNote(['id' => $id, 'note_id' => $noteId]);Downloading a Note Attachment
Returns a file attached to a note, with its content.
curl 'https://panel.example.com/api/v1/admin/tickets/402/notes/1d9e35094420c723/attachments/71c918b7ff525444' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(
`https://panel.example.com/api/v1/admin/tickets/${id}/notes/${noteId}/attachments/${attId}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const { data } = await res.json();$url = 'https://panel.example.com/api/v1/admin/tickets/' . $id . '/notes/' . $noteId . '/attachments/' . $attId;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Note attachments live on the ticket row and are NOT fetched from the reply endpoint.
$att = Api::Tickets()->GetTicketNoteAttachment([
'id' => $id, 'note_id' => $noteId, 'att_id' => $attId,
])['data'];Deleting a Note Attachment
Removes a note attachment from its record and from disk.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tickets/402/notes/1d9e35094420c723/attachments/71c918b7ff525444' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(
`https://panel.example.com/api/v1/admin/tickets/${id}/notes/${noteId}/attachments/${attId}`,
{ method: 'DELETE', headers: { Authorization: `Bearer ${apiKey}` } },
);
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/tickets/' . $id . '/notes/' . $noteId . '/attachments/' . $attId;
$ch = curl_init($url);
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 file goes while the note stays; deleting the note takes its files along.
Api::Tickets()->DeleteTicketNoteAttachment([
'id' => $id, 'note_id' => $noteId, 'att_id' => $attId,
]);Listing the Secrets
Returns the labelled secrets kept on a ticket.
curl 'https://panel.example.com/api/v1/admin/tickets/402/private-data' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/private-data`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/private-data');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The content comes back IN THE CLEAR: a client that logs this call logs the password too.
$items = Api::Tickets()->GetTicketPrivateData(['id' => $id])['data'];Adding a Secret
Adds a labelled secret to a ticket.
curl -X POST 'https://panel.example.com/api/v1/admin/tickets/402/private-data' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"label":"Sunucu erisimi","content":"host: 203.0.113.10"}'const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/private-data`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
label: 'Server SSH',
content: 'host: 203.0.113.10\nuser: admin',
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/private-data');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'label' => 'Server SSH',
'content' => "host: 203.0.113.10\nuser: admin",
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Give a reply id to tie it there; zero leaves it on the ticket as a whole.
Api::Tickets()->AddTicketPrivateData([
'id' => $id,
'label' => 'Server SSH',
'content' => $credentials,
'reply_id' => $replyId,
]);Deleting a Secret
Removes one secret from a ticket.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tickets/402/private-data/pd_65a4f0c8a1b23' \
-H "Authorization: Bearer $API_KEY"const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/${id}/private-data/${itemId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/' . $id . '/private-data/' . $itemId);
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);// REMOVING credentials once a ticket closes is a good habit; the record otherwise stays.
Api::Tickets()->DeleteTicketPrivateData(['id' => $id, 'item_id' => $itemId]);Pitfalls
These items sit encrypted in the database, yet the read endpoint hands the content back decoded. A key carrying this scope reads whatever server passwords are stored there. A client that logs its requests and responses logs the password with them, so keep the key narrow and keep this call's body out of your logs.
Notes and secrets are not separate rows but one encrypted field on the ticket row. Every write saves that whole block again. When two requests add a note at the same moment the second can write over the first, so avoid writing to one ticket in parallel during batch work.
Notes and their attachments are addressed by a string, not by where they sit in the list. The older panel used the position, and adding or removing a note shifted it. Keep a position on your side and one day you will update a different note, so store the id that comes back.
Send no field at all on a note update and the call is refused. The ticket update does not behave that way, where an unchanged field passes quietly. A sync script that forwards whatever it holds hits an error here when nothing changed. Check you really have a field before you send.
Merging tickets moves replies and attachments to the main ticket and then deletes the other ticket rows. Notes and secrets live on those rows, so they go with them. The same holds when you delete a ticket. If the only copy of a server password sits there, read it and write it onto the main ticket before you merge.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.