Ticket Custom Field Definitions

8 Aufrufe Markdown

The five endpoints that define and change the custom fields on the ticket form.

Overview

These five endpoints define the extra form fields a client fills in when opening a support ticket. The type, whether it is required, where it sits and which department it belongs to are structural; the label, description and option names are kept per language.

What lives here are definitions rather than answers. The values a client types sit encrypted on the ticket's own record and show up in the ticket detail, and these endpoints never touch them.

The write contract is unusual: the update wants the whole definition despite its name. Translations and options are written again for every language.

Reference

Listing the Fields

get/api/v1/admin/tickets/custom-fields
Tickets/GetTicketCustomFields admin

Returns the custom fields on the ticket form, in order.

Query 2
department_idintFilters by department. Zero gives the fields open to every department.
statusstringFilters the live ones or the switched-off ones.
Response fields data[] — 8 + meta
idintThe field id.
department_idintThe department it shows in.
departmentstringThe department name. Empty when the field is open to all.
statusstringWhether the field is live.
rankintWhere it sits on the form.
typestringThe field type: text, textarea, password, select, radio, checkbox.
namestringThe field label. Only in the panel's current language.
requiredboolWhether it has to be filled in.
totalintHow many fields there are. It comes back under meta.
Errors 2
invalid_status422The status filter is not recognised.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/tickets/custom-fields?status=active' \
  -H "Authorization: Bearer $API_KEY"
const url = new URL('https://panel.example.com/api/v1/admin/tickets/custom-fields');
url.searchParams.set('status', 'active');

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/custom-fields?' . http_build_query(['status' => 'active']));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The list carries ONE LANGUAGE; read a field on its own to see every translation.
$fields = Api::Tickets()->GetTicketCustomFields([], ['status' => 'active'])['data'];

Creating a Field

post/api/v1/admin/tickets/custom-fields
Tickets/CreateTicketCustomField admin every language needed

Adds a field to the ticket form and writes its translations.

Body 7
typestringreqThe field type: text, textarea, password, select, radio, checkbox.
department_idintThe department it shows in. Zero means every department.
statusstringWhether the field is live. Live by default.
requiredboolWhether the client has to fill it in.
rankintWhere it sits on the form. Zero appends it to the end.
translationsobjectreqThe label and description per language. The label is needed in every live language.
optionsobjectThe option labels per language. Needed for the choice types, and the labels pair across languages by position.
Response fields 201 — data + meta
dataobjectThe field created. Same shape as the detail endpoint.
created_idintThe new field id. It comes back under meta.
Errors 6
invalid_type422The field type is not recognised.
name_required422The label is missing in one of the live languages.
option_label_required422An option row is filled in one language and empty in another.
options_required422A choice-type field has no options.
vetoed422A hook refused the save.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/tickets/custom-fields' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"select","required":true,"translations":{"tr":{"name":"Sunucu bolgesi"}},"options":{"tr":["Avrupa","Kuzey Amerika"]}}'
const res = await fetch('https://panel.example.com/api/v1/admin/tickets/custom-fields', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'select',
    required: true,
    translations: { en: { name: 'Server location', description: '' } },
    options: { en: ['Europe', 'North America'] },
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/custom-fields');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'         => 'select',
        'required'     => true,
        'translations' => ['en' => ['name' => 'Server location', 'description' => '']],
        'options'      => ['en' => ['Europe', 'North America']],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The ORDER of options is their identity: write the same option at the same place in each language.
Api::Tickets()->CreateTicketCustomField([
    'type'         => 'select',
    'translations' => [
        'en' => ['name' => 'Server location'],
        'tr' => ['name' => 'Sunucu bolgesi'],
    ],
    'options' => [
        'en' => ['Europe', 'North America'],
        'tr' => ['Avrupa', 'Kuzey Amerika'],
    ],
]);

Reading One Field

get/api/v1/admin/tickets/custom-fields/{fid}
Tickets/GetTicketCustomField admin

Returns a field with all of its translations.

Response fields data — 8
idintThe field id.
department_idintThe department it shows in.
statusstringWhether the field is live.
rankintWhere it sits on the form.
typestringThe field type: text, textarea, password, select, radio, checkbox.
requiredboolWhether it has to be filled in.
has_optionsboolWhether the type carries options.
translationsobjectThe label, description and options per language. Options arrive with their own ids and names.
Errors 2
not_found404No such field.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/tickets/custom-fields/4' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch(`https://panel.example.com/api/v1/admin/tickets/custom-fields/${fid}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/custom-fields/' . $fid);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Read this BEFORE updating: the write call wants the WHOLE definition.
$field = Api::Tickets()->GetTicketCustomField(['fid' => $fid])['data'];

Updating a Field

patch/api/v1/admin/tickets/custom-fields/{fid}
Tickets/UpdateTicketCustomField admin wants the whole definition

Writes a field's definition again.

Body 7
typestringreqThe field type: text, textarea, password, select, radio, checkbox.
department_idintThe department it shows in. Zero means every department.
statusstringWhether the field is live. Live by default.
requiredboolWhether the client has to fill it in.
rankintWhere it sits on the form. Zero appends it to the end.
translationsobjectreqThe label and description per language. The label is needed in every live language.
optionsobjectThe option labels per language. Needed for the choice types, and the labels pair across languages by position.
Response fields data — 8
dataobjectThe field as it now stands. Same shape as the detail endpoint.
Errors 6
not_found404No such field.
invalid_type422The field type is not recognised.
name_required422The label is missing in one of the live languages.
option_label_required422An option row is filled in one language and empty in another.
options_required422A choice-type field has no options.
vetoed422A hook refused the save.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/tickets/custom-fields/4' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"select","status":"inactive","translations":{"tr":{"name":"Sunucu bolgesi"}},"options":{"tr":["AB","ABD","Asya"]}}'
const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/custom-fields/${fid}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'select',
    status: 'inactive',
    translations: { en: { name: 'Server region', description: '' } },
    options: { en: ['EU', 'US', 'Asia'] },
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/custom-fields/' . $fid);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'         => 'select',
        'status'       => 'inactive',
        'translations' => ['en' => ['name' => 'Server region', 'description' => '']],
        'options'      => ['en' => ['EU', 'US', 'Asia']],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Even to flip one switch, send the WHOLE definition; whatever you leave out is dropped.
$cur = Api::Tickets()->GetTicketCustomField(['fid' => $fid])['data'];
$cur['fid']    = $fid;
$cur['status'] = 'inactive';

Api::Tickets()->UpdateTicketCustomField($cur);

Deleting a Field

delete/api/v1/admin/tickets/custom-fields/{fid}
Tickets/DeleteTicketCustomField admin

Removes a field and every one of its language records.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the field removed.
Errors 3
not_found404No such field.
vetoed422A hook refused the delete.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/tickets/custom-fields/4' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/tickets/custom-fields/${fid}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tickets/custom-fields/' . $fid);
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);
// To take it off the form, SWITCH IT OFF rather than delete: old answers keep their label.
Api::Tickets()->UpdateTicketCustomField($cur + ['status' => 'inactive']);

Pitfalls

The update is not partial

Whatever the method name suggests, the body is the whole definition. Translations and options are written again for every language, so a language or an option you leave out is dropped. To change one key, read the detail first, adjust it and send the whole thing back.

Options pair by position

An option's identity is where it sits: the first label in each language names the same option. Reordering one language renames a different option in that language, and nothing warns you. A row filled in one language and empty in another is refused outright.

The label is needed in every live language

A save wants a label in every live language on the installation. A script that sends only your own language stops working the day a second one is switched on. Read the list from the settings rather than writing it into the code, because it changes.

Two zeros, two different meanings

Zero in the department field means every department, so the field shows on every ticket form. Zero in the rank field does not mean first; it appends to the end. Mixing them up gives you a field everyone sees, sitting at the bottom of the list.

Deleting a definition does not delete the answers

A delete removes only the definition and its translations. Values clients filled in earlier stay on their own ticket records, with no label left to name them. When you want the field off the form, switch it off rather than delete it: past tickets stay readable that way.

War das hilfreich?

Vielen Dank für Ihre Rückmeldung!

Brauchen Sie weitere Hilfe?

Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.