# The Account Behind the Key

https://dev.wisecp.com/es/the-account-of-the-key

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.

companyobject companyThe 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/client/me' \
  -H "Authorization: Bearer $CLIENT_KEY"
```

```javascript
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);
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
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"}'
```

```javascript
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);
```

```php
$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);
```

```php
// 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

categoriesarray categories[]One 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/client/me/notifications' \
  -H "Authorization: Bearer $CLIENT_KEY"
```

```javascript
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);
```

```php
$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);
```

```php
// 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
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}}'
```

```javascript
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),
});
```

```php
$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);
```

```php
// 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_factorobject two_factorWhere two-step sign-in stands.

enabledboolWhether it is on.

methodstringWhich method. An app, e-mail or a message.

sessionsarray sessions[]The 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/client/me/security' \
  -H "Authorization: Bearer $CLIENT_KEY"
```

```javascript
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();
```

```php
$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);
```

```php
// 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.

## Related Articles

- [The Address Book](https://dev.wisecp.com/en/the-address-book)
- [Client API First Calls](https://dev.wisecp.com/en/client-api-first-calls)
- [The Account Balance](https://dev.wisecp.com/en/the-account-balance)
