Document Scheme

8 views Markdown

The ten endpoints that define which document is asked of whom: the field pool and the filters that use it.

Overview

The document scheme has two layers. Fields are a shared pool — single inputs such as "passport copy" or "tax certificate". Filters pick a set out of that pool and carry the rules that decide who is asked for it.

One field can sit in several filters; that is what the pool is for. Updating a field reaches every filter using it at once.

Reference

Listing Filters

get/api/v1/admin/clients/document-filters
Clients/GetDocumentFilters admin

Returns the document filters. Each one carries a set of fields and the rules that decide who is asked for them.

Query parameters 3
statusstringactive or inactive.
pageintDefaults to 1.
limitintDefaults to 25, maximum 100.
Response fields data[] — 5
idintFilter id.
namestringFilter name.
statusstringactive or inactive.
fieldsint[]An ordered list of ids from the field pool. The order is what the client sees.
rulesobject[]The rules that decide who the filter applies to.
typestringWhat the rule looks at: email_provider, vpn_proxy, account_age, service_count, total_spending, country_mismatch, country.
valuestringDepends on the type: a list of domains, yes, a numeric threshold or a country id.
extrastringAn extra value for some types. Empty on most rules.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -G 'https://panel.example.com/api/v1/admin/clients/document-filters' \
  -H "Authorization: Bearer $API_KEY" \
  -d status=active
const url = new URL('https://panel.example.com/api/v1/admin/clients/document-filters');
url.searchParams.set('status', 'active');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
$url = 'https://panel.example.com/api/v1/admin/clients/document-filters?' . http_build_query(['status' => 'active']);

$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);
$response = Api::Clients()->GetDocumentFilters([], ['status' => 'active']);

Adding a Filter

post/api/v1/admin/clients/document-filters
Clients/CreateDocumentFilter admin

Defines a new filter. The field ids have to exist in the pool.

Body 4
namestringrequiredFilter name.
fieldsint[]requiredOrdered field ids, for example [3, 1].
activeboolDefaults to false; the filter is born inactive.
rulesobject[]The rules that apply it.
typestringWhat the rule looks at: email_provider, vpn_proxy, account_age, service_count, total_spending, country_mismatch, country.
valuestringDepends on the type: a list of domains, yes, a numeric threshold or a country id.
extrastringAn extra value for some types. Empty on most rules.
Response fields data — 5
idintId of the new filter.
namestringFilter name.
statusstringactive or inactive. A new filter is born inactive unless you sent active.
fieldsint[]The ordered field ids, as stored.
rulesobject[]The stored rules — same type / value / extra shape as the listing.
Errors 5
name_required422name was empty.
fields_required422fields was empty or malformed.
fields_invalid422None of the ids exist in the field pool.
filter_add_failed500The record could not be created.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/document-filters' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"High spend","fields":[3,1],"active":true,"rules":[{"type":"total_spending","value":"5000","extra":""}]}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/document-filters', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({"name":"High spend","fields":[3,1],"active":true,"rules":[{"type":"total_spending","value":"5000","extra":""}]}),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-filters');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name'   => 'High spend',
        'fields' => [3, 1],
        'active' => true,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->CreateDocumentFilter([
    'name'   => 'High spend',
    'fields' => [3, 1],
    'active' => true,
    'rules'  => [
        ['type' => 'total_spending', 'value' => '5000', 'extra' => ''],
    ],
]);

Filter Detail

get/api/v1/admin/clients/document-filters/{fid}
Clients/GetDocumentFilter admin

Returns one filter; the schema is the same as in the list.

Response fields data — 5
idintFilter id.
namestringFilter name.
statusstringactive or inactive.
fieldsint[]The ordered field ids, exactly as the listing returns them.
rulesobject[]The rules, same type / value / extra shape as the listing.
Errors 2
not_found404No such record.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/document-filters/7' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/document-filters/7', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-filters/7');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->GetDocumentFilter(['fid' => 7]);

Updating a Filter

patch/api/v1/admin/clients/document-filters/{fid}
Clients/UpdateDocumentFilter admin

Changes the filter name, the field order, the status or the rules.

Body at least one
namestringFilter name.
fieldsint[]The new ordered field list. What you send replaces the old list.
activeboolTurns the filter on or off.
rulesobject[]The new rule list. This replaces as well, it does not append.
Response fields data — 5
idintFilter id.
namestringFilter name after the update.
statusstringactive or inactive.
fieldsint[]The stored field order — read it back to see what the replace actually left.
rulesobject[]The stored rules; an empty array means the filter now applies to everyone.
Errors 5
not_found404No such filter.
name_required422name was sent empty.
fields_required422fields was sent empty or malformed.
fields_invalid422An id does not exist in the field pool.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/clients/document-filters/7' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"active":false}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/document-filters/7', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ active: false }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-filters/7');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['active' => false]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->UpdateDocumentFilter([
    'fid'    => 7,
    'active' => false,
]);

Deleting a Filter

delete/api/v1/admin/clients/document-filters/{fid}
Clients/DeleteDocumentFilter admin

Deletes the filter. The field pool is untouched; the fields stay in whatever other filters use them.

Response fields data — 2
deletedboolWhether the delete succeeded.
idintId of the deleted filter.
Errors 1
not_found404No such filter.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/document-filters/7' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/document-filters/7', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-filters/7');
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::Clients()->DeleteDocumentFilter(['fid' => 7]);

Listing Fields

get/api/v1/admin/clients/document-fields
Clients/GetDocumentFields admin

Returns the shared field pool. Filters pick their fields from here.

Query parameters 4
statusstringactive or inactive.
pageintDefaults to 1.
limitintDefaults to 25, maximum 100.
typestringFilters by input type.
Response fields data[] — 8
idintField id. A filter's fields list points here.
statusstringactive or inactive. An inactive field is not shown to the client.
typestringinput, textarea, selectbox, radio, checkbox, file.
labelsobjectLabel per language, for example {"en": "Passport copy"}.
optionsobjectOptions per language. Only meaningful on the choice types.
allowed_extstringAllowed file extensions. Only on the file type.
max_sizeintMaximum file size in MB. Only on the file type.
used_inobject[]The filters using this field: [{ id, name }].
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -G 'https://panel.example.com/api/v1/admin/clients/document-fields' \
  -H "Authorization: Bearer $API_KEY" \
  -d type=file
const url = new URL('https://panel.example.com/api/v1/admin/clients/document-fields');
url.searchParams.set('type', 'file');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
$url = 'https://panel.example.com/api/v1/admin/clients/document-fields?' . http_build_query(['type' => 'file']);

$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);
$response = Api::Clients()->GetDocumentFields([], ['type' => 'file']);

Adding a Field

post/api/v1/admin/clients/document-fields
Clients/CreateDocumentField admin

Adds a field to the pool. The label has to be given in at least one language.

Body 6
typestringrequiredInput type.
labelsobjectrequiredLabel per language; at least one.
activeboolDefaults to true.
optionsobjectOptions per language. In practice required on the choice types.
allowed_extstringAllowed extensions. Only on the file type.
max_sizeintLimit in MB. Only on the file type.
Response fields data — 7
idintId of the new field.
statusstringactive or inactive.
typestringThe stored input type.
labelsobjectThe stored labels, per language.
optionsobjectThe stored choices, per language.
allowed_extstringAllowed file extensions.
max_sizeintMaximum file size in MB.
used_inNot part of this answer: a newly created field belongs to no filter yet.
Errors 4
type_invalid422type is not one of the allowed input types.
labels_required422No language carried a non-empty label.
field_add_failed500The record could not be created.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/document-fields' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"file","labels":{"en":"Passport copy"},"allowed_ext":"jpg,png,pdf","max_size":5}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/document-fields', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'file',
    labels: {"en":"Passport copy"},
    allowed_ext: 'jpg,png,pdf',
    max_size: 5,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-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'        => 'file',
        'labels'      => ['en' => 'Passport copy'],
        'allowed_ext' => 'jpg,png,pdf',
        'max_size'    => 5,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->CreateDocumentField([
    'type'        => 'file',
    'labels'      => ['en' => 'Passport copy'],
    'allowed_ext' => 'jpg,png,pdf',
    'max_size'    => 5,
]);

Field Detail

get/api/v1/admin/clients/document-fields/{fid}
Clients/GetDocumentField admin

Returns one field. used_in comes with it, showing which filters use it.

Response fields data — 8
idintField id — this is what a filter's fields list refers to.
statusstringactive or inactive. An inactive field is not shown to clients even where a filter still lists it.
typestringInput type: input, textarea, selectbox, radio, checkbox, file.
labelsobjectThe label per language, for example { "en": "Passport copy" }.
optionsobjectPer-language choices for the list types. Empty on the other types.
allowed_extstringAllowed file extensions — file type only.
max_sizeintMaximum file size in MB — file type only.
used_inobject[]The filters referencing this field, as { id, name }. Read it before deleting — every one of them loses the field.
Errors 2
not_found404No such field.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/document-fields/3' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/document-fields/3', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-fields/3');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->GetDocumentField(['fid' => 3]);

// Before deleting: which filters hold this field?
$usedIn = $response['data']['used_in'] ?? [];

Updating a Field

patch/api/v1/admin/clients/document-fields/{fid}
Clients/UpdateDocumentField admin

Updates the field. The change reaches every filter using it at once.

Body 6
typestringrequiredInput type. Required even on an update — this endpoint rewrites the field rather than patching single keys.
labelsobjectrequiredLabel per language; at least one must be non-empty.
activeboolTurns the field on or off. Defaults to true.
optionsobjectChoices per language. In practice required on the choice types.
allowed_extstringAllowed extensions. Only on the file type.
max_sizeintLimit in MB. Only on the file type.
Response fields data — 7
idintField id.
statusstringactive or inactive.
typestringThe stored input type.
labelsobjectThe stored labels, per language.
optionsobjectThe stored choices, per language.
allowed_extstringAllowed file extensions.
max_sizeintMaximum file size in MB.
Errors 4
not_found404No such field.
type_invalid422type is not one of the allowed input types.
labels_required422No language carried a non-empty label.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/clients/document-fields/3' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"max_size":10}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/document-fields/3', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ max_size: 10 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-fields/3');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['max_size' => 10]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->UpdateDocumentField([
    'fid'      => 3,
    'max_size' => 10,
]);

Deleting a Field

delete/api/v1/admin/clients/document-fields/{fid}
Clients/DeleteDocumentField admin affects filters

Removes the field from the pool.

Response fields data — 2
deletedboolWhether the delete succeeded.
idintId of the deleted field.
Errors 1
not_found404No such field.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/document-fields/3' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/document-fields/3', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/document-fields/3');
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::Clients()->DeleteDocumentField(['fid' => 3]);

Pitfalls

The field list is ordered

fields is not a set but an ordered list, and the client sees the fields in that order. On update the list you send replaces the old one; it does not append to it.

A field is shared, not copied

Updating or deleting a field affects every filter that uses it. Look at used_in on the field detail before you delete.

A new filter is born switched off

Leave active out and the filter is created as inactive, asking nobody for anything. Creating one and walking away leaves a verification that never runs.

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.