The Address Book

3 views Markdown

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 'https://panel.example.com/api/v1/client/addresses' \
  -H "Authorization: Bearer $CLIENT_KEY"
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);
$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);
// 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 'https://panel.example.com/api/v1/client/addresses/12' \
  -H "Authorization: Bearer $CLIENT_KEY"
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();
$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);
// 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 -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":"[email protected]","country":"TR","state":"34","city":"1441","address":"Sample St 42","zipcode":"34710"}'
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: '[email protected]',
    country: 'TR', state: String(stateId), city: String(cityId),
    address: 'Sample St 42', zipcode: '34710',
  }),
});

const { data } = await res.json();
$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);
// 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 -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":"[email protected]","country":"TR","state":"34","city":"1441","address":"New St 7","zipcode":"34710"}'
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),
});
$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);
// 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 -X POST 'https://panel.example.com/api/v1/client/addresses/12/default' \
  -H "Authorization: Bearer $CLIENT_KEY"
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();
$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);
// 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 -X DELETE 'https://panel.example.com/api/v1/client/addresses/12' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch(`https://panel.example.com/api/v1/client/addresses/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${clientKey}` },
});

const body = await res.json();
$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);
// 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.

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.