The Account Behind the Key

4 views Markdown

The five endpoints giving the profile, choices and security state of the key's owner.

Overview

On the client API the account is always resolved from the key. There is no customer parameter: these five endpoints read and write the record of whoever owns the key.

What may be changed on a profile is the operator's decision. The read endpoint returns that decision alongside the data: which fields are open and which cannot be emptied.

Account security is deliberately left out. The password, the e-mail, two-step sign-in and ending a session stay in the panel. The API shows their state alone.

Reference

Reading the Profile

get/api/v1/admin/client/me
Account/GetMe the key's owner

Returns the profile of the customer the key belongs to.

Response fields data — 25 + meta — 2
idintThe customer id.
kindstringThe account kind: a person or a company.
full_namestringThe first and last name together.
namestringThe first name.
surnamestringThe last name.
companyobjectThe company details.
namestringThe company name.
tax_numberstringThe tax number.
tax_officestringThe tax office.
avatar_urlstringThe address of the profile picture. It comes empty when there is none.
emailstringThe e-mail address.
email_verifiedboolWhether the e-mail was verified.
phonestringThe mobile number. It comes in international form.
phone_country_codestringThe phone's country code.
phone_verifiedboolWhether the number was verified.
landline_phonestringThe landline number.
identitystringThe identity number.
birthdaystringThe date of birth.
languagestringThe language chosen.
countrystringThe country code.
currencystringThe display currency. It can differ from the wallet's.
group_idintThe customer group id.
balanceobjectThe wallet balance. It carries an amount and a currency, and that currency is the wallet's own.
timezonestringThe time zone chosen. Empty means the installation's is used.
date_formatstringThe date format chosen.
custom_fieldsarrayThe extra fields the operator defined. Each carries an id, name, type, whether it is required, whether it is editable, its choices and its value.
created_atstringWhen the account was opened.
last_login_atstringWhen they last signed in.
editablearrayThe field names that can be changed now. It comes back under meta.
requiredarrayThe fields that cannot be emptied once sent.
Errors 4
missing_token401The key was not sent or is not known.
insufficient_scope403The key lacks the required scope.
audience_mismatch403An admin key was used on the client surface.
not_found404The account behind the key is gone.
Request
curl 'https://panel.example.com/api/v1/client/me' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/me', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data, meta } = await res.json();
renderForm(data, meta.editable, meta.required);
$ch = curl_init('https://panel.example.com/api/v1/client/me');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Build the form from meta.editable: a field the operator closed gives 422 rather than passing quietly.
$r = Kernel::internal('client:Account/GetMe', ['owner_id' => $uid]);
$form = array_intersect_key($r['data'], array_flip($r['meta']['editable']));

Updating the Profile

patch/api/v1/admin/client/me
Account/UpdateMe an operator gate

Changes the profile fields you send.

Body 14
namestringThe first name. It cannot be emptied.
surnamestringThe last name. It cannot be emptied.
kindstringThe account kind. Moving to a person clears the company details.
companyobjectThe company details. It wants the account to be a company.
phonestringThe mobile number. Changing it drops the verified mark.
birthdaystringThe date of birth.
identitystringThe identity number.
landline_phonestringThe landline number.
languagestringThe language code. It has to exist on the installation.
countrystringThe country code.
currencystringThe display currency code.
timezonestringThe time zone. Sending it empty clears the account's own choice.
date_formatstringThe date format. Sending it empty clears the account's own choice.
custom_fieldsobjectThe values of the extra fields. A tick field takes a list.
avatarstringThe profile picture. Encoded content alone is taken and an address is refused.
Response fields data — 25 + meta — 1
dataobjectThe profile as it now stands. Same shape as the read endpoint.
changedarrayThe fields truly written. It comes back under meta.
Errors 12
field_not_editable422The field is closed by an operator setting.
nothing_to_update422The body holds no field that is known.
name_required422The first or last name cannot be emptied.
kind_invalid422The account kind is neither of the two values.
company_requires_corporate422Company details were sent to a personal account.
company_name_required422A required part of the company is empty.
phone_required422The phone was emptied while required.
phone_taken422The number is used on another account.
birthday_required422A required field was emptied.
language_invalid422An unknown preference value. The country, currency, time zone and date format are refused the same way.
custom_field_required422An unknown or required extra field.
avatar_url_not_allowed422An address was sent as the picture.
Request
curl -X PATCH 'https://panel.example.com/api/v1/client/me' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"timezone":"Europe/Istanbul","date_format":"d/m/Y"}'
const res = await fetch('https://panel.example.com/api/v1/client/me', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ timezone: 'Europe/Istanbul' }),
});

const { meta } = await res.json();
console.log(meta.changed);
$ch = curl_init('https://panel.example.com/api/v1/client/me');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['timezone' => 'Europe/Istanbul']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The E-MAIL and PASSWORD do not change here: both want a verification flow and stay in the panel.
$r = Kernel::internal('client:Account/UpdateMe', ['owner_id' => $uid, 'timezone' => $tz]);
$written = $r['meta']['changed'];

Reading the Notification Choices

get/api/v1/admin/client/me/notifications
Account/GetNotifications client

Returns which notifications the account takes and by which channel.

Response fields data — 1
categoriesarrayOne row per category.
categorystringThe category name: general, invoices, support, product, domain or marketing.
emailboolWhether the e-mail channel is on.
smsboolWhether the message channel is on.
lockedboolWhether the category can be closed. The general one is always on.
Errors 3
missing_token401The key was not sent or is not known.
insufficient_scope403The key lacks the required scope.
audience_mismatch403An admin key was used on the client surface.
Request
curl 'https://panel.example.com/api/v1/client/me/notifications' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/me/notifications', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
const editable = data.categories.filter((c) => ! c.locked);
$ch = curl_init('https://panel.example.com/api/v1/client/me/notifications');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Read BEFORE writing: the write is a FULL REPLACE and a category left out closes on both channels.
$cur = Kernel::internal('client:Account/GetNotifications', ['owner_id' => $uid])['data'];

Writing the Notification Choices

put/api/v1/admin/client/me/notifications
Account/UpdateNotifications a full replace

Rewrites the notification choices in full.

Body 5
invoicesobjectThe invoice notifications. It carries the e-mail and message channels, and a channel left out counts as off.
supportobjectThe support notifications.
productobjectThe product and service notifications.
domainobjectThe domain notifications.
marketingobjectThe marketing notifications.
Response fields data — 1
dataobjectThe choices as they now stand. Same shape as the read endpoint.
Errors 3
missing_token401The key was not sent or is not known.
insufficient_scope403The key lacks the required scope.
audience_mismatch403An admin key was used on the client surface.
Request
curl -X PUT 'https://panel.example.com/api/v1/client/me/notifications' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"invoices":{"email":true,"sms":false},"support":{"email":true,"sms":true}}'
const cur = await fetch('https://panel.example.com/api/v1/client/me/notifications', {
  headers: { Authorization: `Bearer ${clientKey}` },
}).then((r) => r.json());

const body = {};
for (const c of cur.data.categories)
  if (! c.locked) body[c.category] = { email: c.email, sms: c.sms };

body.marketing = { email: false, sms: false };

await fetch('https://panel.example.com/api/v1/client/me/notifications', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
});
$ch = curl_init('https://panel.example.com/api/v1/client/me/notifications');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($prefs),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A category you leave out CLOSES: send the full set even to change one choice.
$cur = Kernel::internal('client:Account/GetNotifications', ['owner_id' => $uid])['data'];
$body = [];
foreach ($cur['categories'] as $c)
    if (! $c['locked']) $body[$c['category']] = ['email' => $c['email'], 'sms' => $c['sms']];

$body['marketing'] = ['email' => false, 'sms' => false];
Kernel::internal('client:Account/UpdateNotifications', ['owner_id' => $uid] + $body);

Reading the Security Summary

get/api/v1/admin/client/me/security
Account/GetSecurity read only

Returns the verification state, the two-step sign-in and the recent sessions.

Response fields data — 5
email_verifiedboolWhether the e-mail was verified.
phone_verifiedboolWhether the number was verified.
two_factorobjectWhere two-step sign-in stands.
enabledboolWhether it is on.
methodstringWhich method. An app, e-mail or a message.
sessionsarrayThe sign-ins of the last thirty days. 25 records at most, newest first.
ipstringThe address it came from.
citystringThe city caught at sign-in.
country_codestringThe country code.
user_agentstringThe browser string. It comes raw and parsing is left to you.
created_atstringWhen they signed in.
last_login_atstringWhen they last signed in.
Errors 3
missing_token401The key was not sent or is not known.
insufficient_scope403The key lacks the required scope.
audience_mismatch403An admin key was used on the client surface.
Request
curl 'https://panel.example.com/api/v1/client/me/security' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/me/security', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
if (! data.two_factor.enabled) nudgeToEnable();
$ch = curl_init('https://panel.example.com/api/v1/client/me/security');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// It is READ ONLY: signing a session out, the password and setting two-step up are ABSENT here and stay in the panel.
$sec = Kernel::internal('client:Account/GetSecurity', ['owner_id' => $uid])['data'];
$stale = array_filter($sec['sessions'], fn ($s) => $s['country_code'] !== $home);

Pitfalls

Writing the notifications is a full replace

The notification write reads the body as the whole set of choices. A category you leave out closes on both the e-mail and the message channel. Read the current list first and write over it to change one. The general category is locked and stays as it is even when sent.

A closed field is not ignored quietly

Sending a field the operator closed for editing is refused with 422. Building a form from a fixed field list makes the whole save fail the day one of them is closed. Always build the list from the editable set the read endpoint returns.

The wallet currency is not the display choice

The currency field on the profile says how amounts are shown, while the wallet's own currency comes separately inside the balance object. The two can differ, and reading the balance in the display currency gives a wrong figure. Read the balance in its own currency.

The profile picture goes as content alone

The picture field takes no address. The server never fetches one you give, because the outgoing request would give away its real address. Send the image as encoded content, and send it empty to remove the picture.

Changing the phone drops its verification

The verified mark is cleared when the number changes. The flows resting on the phone stop until the new one is verified. The same holds for the e-mail, which cannot be changed here at all. Send the customer to verification after a number change.

The security endpoint is not a management tool

The security summary lists the sessions and cannot end them, and no session token is ever returned. The password, two-step sign-in and identity documents stay in the panel for the same reason: each wants an unbroken human step.

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.