Service Requirement Answers

7 views Markdown

The four endpoints that read, add, edit and delete a client's requirement answers on a service.

Overview

A requirement is the question a product asks at order time; what is managed here are the answers on a service. The questions themselves are defined on the product side.

An answer comes from one of two sources. Ones from a definition are tied to a product requirement and carry its type, its options and its module mapping. Free-form ones carry nothing but a label and some content, added by hand later, and they never reach the module.

Reference

Listing the Answers

get/api/v1/admin/services/{id}/requirements
Services/GetServiceRequirements admin

Returns every requirement answer recorded on the service.

Response fields data[] — 8
idintId of the answer record. Update and delete use this one.
requirement_idintId of the product requirement definition behind it. Zero on a free-form one.
keystringproduct comes from a definition, custom was added freely.
namestringThe requirement name or label.
typestringThe field type: text, select, radio, checkbox or file.
responsestring | arrayThe answer given. An array of file objects on the file type, plain text on the rest.
namestringThe name stored on the server.
file_namestringThe original name that was uploaded.
sizeintThe file size in bytes.
pathstringWhere the file is kept.
response_mkeystringWhat the choice maps to in the module. Filled on the choice types only, and resolved for you.
field_optionsarrayThe options the definition offers.
Errors 2
not_found404No such service.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/services/506/requirements' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/services/506/requirements', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506/requirements');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Services()->GetServiceRequirements(['id' => 506]);

// 'response' changes shape by type: an array on file, text on the rest.
foreach ($response['data'] as $req) {
    $answer = $req['type'] === 'file'
        ? array_column($req['response'], 'file_name')
        : $req['response'];
}
Response
{
  "data": [
    {
      "id": 12,
      "requirement_id": 22,
      "key": "product",
      "name": "Game Name",
      "type": "text",
      "response": "Minecraft",
      "response_mkey": "",
      "field_options": []
    }
  ]
}

Adding an Answer

post/api/v1/admin/services/{id}/requirements
Services/CreateServiceRequirement admin two body shapes

Adds a requirement answer to the service, either from a definition or free-form.

Body — shared 1
sourcestringWhich body shape applies: defined ties it to a definition, custom adds it freely. Defaults to defined.
Body — from a definition 3
requirement_idintrequiredId of the product requirement definition to tie it to.
responsestring | arrayThe answer. On the choice types you send the option's id from the definition, not its text; for multiple choice a comma-separated list or an array.
filestringThe file input. Needed instead when the definition is a file type: a base64 data URI or a link that can be fetched.
Body — free-form 4
labelstringrequiredThe requirement label.
typestringtext or file. Defaults to text; a free-form requirement has no choice types.
contentstringThe text answer. Needed on the text type.
filestringThe file input. Needed on the file type.
Response fields data
dataobjectThe answer that was created, returned with 201. Same shape as one row of the list schema.
Errors 8
not_found404No such service.
requirement_id_required422No definition id was given when tying to one.
definition_not_found422The requirement definition was not found.
response_required422The answer was empty.
content_required422The text was empty.
label_required422The label was empty.
file_invalid422The file could not be read or stored.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/services/506/requirements' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"source":"custom","label":"Server Name","type":"text","content":"srv-01"}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/506/requirements', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    source: 'custom',
    label: 'Server Name',
    type: 'text',
    content: 'srv-01',
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506/requirements');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'source'  => 'custom',
        'label'   => 'Server Name',
        'type'    => 'text',
        'content' => 'srv-01',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// On a choice type you send the OPTION'S ID; sending its text records the wrong thing.
$definition = Api::Products()->GetRequirement(['id' => 22])['data'];
$option     = $definition['langs']['en']['options'][0];

Api::Services()->CreateServiceRequirement([
    'id'             => 506,
    'source'         => 'defined',
    'requirement_id' => 22,
    'response'       => $option['id'],
]);

Updating an Answer

patch/api/v1/admin/services/{id}/requirements/{req_id}
Services/UpdateServiceRequirement admin files cannot be edited

Changes a text-based answer. File answers cannot be edited.

Body 1
responsestringThe new answer. Comma-separated option ids on multiple choice.
Response fields data
dataobjectThe answer in its updated state. Same shape as one row of the list schema; on the choice types response_mkey is resolved again.
Errors 3
not_found404No such service or requirement.
file_not_editable422A file requirement cannot be edited. Delete it and add it again.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/services/506/requirements/12' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"response":"Valheim"}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/506/requirements/12', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ response: 'Valheim' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506/requirements/12');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['response' => 'Valheim']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The way to change a file answer is delete-then-add.
Api::Services()->DeleteServiceRequirement(['id' => 506, 'req_id' => 12]);

Api::Services()->CreateServiceRequirement([
    'id'     => 506,
    'source' => 'custom',
    'label'  => 'Contract',
    'type'   => 'file',
    'file'   => 'data:application/pdf;base64,' . base64_encode($pdf),
]);

Deleting an Answer

delete/api/v1/admin/services/{id}/requirements/{req_id}
Services/DeleteServiceRequirement admin the file goes too

Deletes the answer. On a file type the uploaded file is removed from disk as well.

Response fields data — 2
deletedboolWhether the delete succeeded.
idintId of the deleted record.
Errors 2
not_found404No such service or requirement.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/services/506/requirements/12' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/services/506/requirements/12', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506/requirements/12');
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);
$response = Api::Services()->DeleteServiceRequirement([
    'id'     => 506,
    'req_id' => 12,
]);

Pitfalls

Choice types take the id, not the text

On the dropdown, single-choice and multiple-choice types the answer is the option's id from the definition. Sending the option's visible text raises no error but records the wrong value and leaves the module mapping unresolved. Read the ids from the product requirement definition.

A file answer cannot be edited

The update endpoint answers file_not_editable on a file type. The only way to change one is to delete the record and add a new one; the delete also removes the uploaded file from disk, so nothing stale is left behind.

The answer changes shape with the type

In the list the response field is an array on the file type and plain text on the rest. Code expecting one shape breaks on the first service with a file requirement, so check type before reading it.

A free-form requirement never reaches the module

A free-form requirement only sits on the record: with no definition behind it there is no module mapping either. If the answer has to do something on the server, define the requirement on the product side and add it here from that definition.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.