# The Address Book

https://dev.wisecp.com/es/the-address-book

The six endpoints managing the account's billing profiles.

## Overview

The address book holds the account's **billing profiles**. Each record carries a contact, an address and the tax rate that address falls under, and the invoices are raised from it.

One profile is always the **default** and the account never sits without one: the first added becomes it, and removing the default promotes the next in line.

The location fields are filled from the reference chain: a country code, a state number and a city number. Where the platform holds no data the same fields take free text.

## Reference

### Listing the Addresses

get/api/v1/client/addresses

`Addresses/GetAddresses` the key's owner

Returns the account's billing profiles.

Response fields data[] — 18

idintThe address id.

labelstringA free label.

full_namestringThe contact's full name.

namestringThe first name.

surnamestringThe last name.

kindstringThe contact kind: a person or a company.

emailstringThe contact's e-mail.

phonestringThe contact's phone.

identitystringThe identity number.

companyobjectThe company details. It carries the name, tax number and tax office.

countrystringThe country code.

stateobjectThe state. It carries a number and a name, and the number is zero when it was typed free.

cityobjectThe city. It carries a number and a name, and the number is zero when it was typed free.

addressstringThe street address.

zipcodestringThe postcode.

tax_ratefloatThe tax rate this address falls under. The server works it out from the address.

is_defaultboolWhether it is the default billing profile.

notificationsarrayThe notification channels per contact. An e-mail and a message flag for each of the six categories.

Errors 1

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

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

```javascript
const res = await fetch('https://panel.example.com/api/v1/client/addresses', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
const billing = data.find((a) => a.is_default);
```

```php
$ch = curl_init('https://panel.example.com/api/v1/client/addresses');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The default contact ALWAYS comes first; take the first row rather than searching for it.
$rows = Kernel::internal('client:Addresses/GetAddresses', ['owner_id' => $uid])['data'];
$default = $rows[0] ?? null;
```

### Reading One Address

get/api/v1/client/addresses/{id}

`Addresses/GetAddress` the key's owner

Returns one of the account's addresses.

Response fields data — 18

dataobjectThe address record. Same shape as an item in the listing.

Errors 2

not_found404No such address, or it belongs to another customer.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

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

```javascript
const res = await fetch(`https://panel.example.com/api/v1/client/addresses/${id}`, {
  headers: { Authorization: `Bearer ${clientKey}` },
});

if (res.status === 404) return notYours();

const { data } = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/client/addresses/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Someone else's address also answers NOT FOUND: this endpoint never says whether it exists.
$a = Kernel::internal('client:Addresses/GetAddress', ['owner_id' => $uid, 'id' => $id]);
```

### Adding an Address

post/api/v1/client/addresses

`Addresses/CreateAddress` a billing profile

Adds a new billing profile to the account.

Body 16

namestringreqThe contact's first name.

surnamestringreqThe last name.

emailstringreqA valid e-mail.

countrystringreqThe country code. Taken from the reference endpoint.

statestringreqThe state. A number from the list, or free text when the list is empty.

citystringreqThe city. A number from the list, or free text when the list is empty.

addressstringreqThe street address.

zipcodestringreqThe postcode. Twenty characters at most.

kindstringThe contact kind. A person by default.

companyobjectThe company details. The name is required on a company contact.

labelstringA free label.

phonestringThe contact's phone.

identitystringThe identity number.

notificationsobjectThe channel matrix. A full replace, and leaving it out opens every one.

is_defaultboolMake this the default profile.

overwrite_invoicesboolRewrite the open invoices to this address.

Response fields data — 18

dataobjectThe address made. Same shape as the read endpoint.

Errors 6

name_required422A required field is missing or invalid. The last name, e-mail, country, state, city, address and postcode are refused the same way.

company_name_required422The company name is empty on a company contact.

notifications_invalid422The notification matrix is broken or holds an unknown category.

contact_rejected422A hook refused the save.

address_add_failed500The address could not be added.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/client/addresses' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Jane","surname":"Cooper","email":"jane@example.com","country":"TR","state":"34","city":"1441","address":"Sample St 42","zipcode":"34710"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/client/addresses', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Jane', surname: 'Cooper', email: 'jane@example.com',
    country: 'TR', state: String(stateId), city: String(cityId),
    address: 'Sample St 42', zipcode: '34710',
  }),
});

const { data } = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/client/addresses');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($contact),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The FIRST address becomes the DEFAULT by itself; the account never sits without a profile.
$a = Kernel::internal('client:Addresses/CreateAddress', ['owner_id' => $uid] + $contact)['data'];
$isFirst = $a['is_default'];
```

### Updating an Address

put/api/v1/client/addresses/{id}

`Addresses/UpdateAddress` the whole body

Rewrites the address in full.

Body 16

namestringreqThe contact's first name.

surnamestringreqThe last name.

emailstringreqA valid e-mail.

countrystringreqThe country code. Taken from the reference endpoint.

statestringreqThe state. A number from the list, or free text when the list is empty.

citystringreqThe city. A number from the list, or free text when the list is empty.

addressstringreqThe street address.

zipcodestringreqThe postcode. Twenty characters at most.

kindstringThe contact kind. A person by default.

companyobjectThe company details. The name is required on a company contact.

labelstringA free label.

phonestringThe contact's phone.

identitystringThe identity number.

notificationsobjectThe channel matrix. A full replace, and leaving it out opens every one.

is_defaultboolMake this the default profile.

overwrite_invoicesboolRewrite the open invoices to this address.

Response fields data — 18

dataobjectThe address as it now stands. Same shape as the read endpoint.

Errors 5

not_found404No such address, or it belongs to another customer.

name_required422A required field is missing or invalid.

company_name_required422The company name is empty on a company contact.

contact_rejected422A hook refused the save.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/client/addresses/12' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Jane","surname":"Cooper","email":"jane@example.com","country":"TR","state":"34","city":"1441","address":"New St 7","zipcode":"34710"}'
```

```javascript
const cur = await fetch(`https://panel.example.com/api/v1/client/addresses/${id}`, {
  headers: { Authorization: `Bearer ${clientKey}` },
}).then((r) => r.json());

const body = {
  ...cur.data,
  state: String(cur.data.state.id || cur.data.state.name),
  city:  String(cur.data.city.id  || cur.data.city.name),
  address: 'New St 7',
};

await fetch(`https://panel.example.com/api/v1/client/addresses/${id}`, {
  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/addresses/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($contact),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The state and city are read as OBJECTS and sent as PLAIN VALUES: pull the number out before writing back.
$a = Kernel::internal('client:Addresses/GetAddress', ['owner_id' => $uid, 'id' => $id])['data'];
$a['state'] = (string) ($a['state']['id'] ?: $a['state']['name']);
$a['city']  = (string) ($a['city']['id']  ?: $a['city']['name']);

Kernel::internal('client:Addresses/UpdateAddress', ['owner_id' => $uid, 'id' => $id] + $a);
```

### Moving the Default

post/api/v1/client/addresses/{id}/default

`Addresses/SetDefaultAddress` the account country follows

Makes this address the default billing profile.

Body —

——No body is needed, send an empty one. The address comes from the id in the path.

Response fields data — 18

dataobjectThe address that is now the default.

Errors 2

not_found404No such address, or it belongs to another customer.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/client/addresses/12/default' \
  -H "Authorization: Bearer $CLIENT_KEY"
```

```javascript
const res = await fetch(`https://panel.example.com/api/v1/client/addresses/${id}/default`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/client/addresses/' . $id . '/default');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// There is no way to UNSET a default: making another address the default is the only road.
Kernel::internal('client:Addresses/SetDefaultAddress', ['owner_id' => $uid, 'id' => $other]);
```

### Removing an Address

delete/api/v1/client/addresses/{id}

`Addresses/DeleteAddress` the key's owner

Removes an address and hands the default on where needed.

Response fields data — 2

deletedboolWhether the delete ran.

idintThe id of the address removed.

Errors 2

not_found404No such address, or it belongs to another customer.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

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

```javascript
const res = await fetch(`https://panel.example.com/api/v1/client/addresses/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${clientKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/client/addresses/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Removing the default is NOT refused: the next address is promoted to default by itself.
Kernel::internal('client:Addresses/DeleteAddress', ['owner_id' => $uid, 'id' => $id]);
$now = Kernel::internal('client:Addresses/GetAddresses', ['owner_id' => $uid])['data'][0] ?? null;
```

## Pitfalls

> **The state and city read as objects and write as plain values**
> 
> The read endpoints return the state and city as an **object carrying a number and a name**, while the write endpoints expect a plain value. Sending a record straight back fails validation on those two fields. Pull the number out before writing, and send the name where the number is zero.

> **The update wants the whole record**
> 
> The update body is the **whole address**: the fields required on create are required here as well. Send the full record even to change the postcode alone. The one ease is the notification matrix, which keeps its saved value when left out.

> **Moving the default changes the account country**
> 
> Making an address the default pulls the account's country to **that address's country**. The country decides the tax rate and what some products show, so a simple-looking change can move prices. Think it through before defaulting a profile in another country.

> **Rewriting the open invoices is a choice**
> 
> While adding an address you can ask for the open invoices to move to it. Without that the invoices raised on the **old address** stay as they are and the customer sees two different addresses. Consider it on a change such as a new tax number.

> **Someone else's address answers not found**
> 
> Asking for another customer's address id answers `404` rather than a permission error. That is deliberate: the answer never reveals **whether that id exists**. Do not read a not-found as "it was removed".

> **Every contact carries its own notification matrix**
> 
> The notification choices are kept at the account level and **per contact**: invoices can reach one person and support another. With the matrix left out a create opens every channel while an update keeps what was saved.

## Related Articles

- [The Account Behind the Key](https://dev.wisecp.com/en/the-account-of-the-key)
- [Client API First Calls](https://dev.wisecp.com/en/client-api-first-calls)
- [Paying an Invoice](https://dev.wisecp.com/en/paying-an-invoice)
