Skip to main content

Clients

The Clients API provides CRUD access to clients, plus ancillary actions for setting tags, changing the status, and changing the account owner.
New to the API? Start with Getting started (base URL, response envelope, errors, pagination) and Authentication (API keys). Those conventions apply to every endpoint below and are not repeated here.
Identifier note: unlike most resources, clients are addressed by their numeric client_id (the clients table has no unique-id column). Wherever {id} appears below it is the client_id.

The client object

All endpoints that return a client use this shape:
{
  "id": 12,
  "company_name": "Acme Inc",
  "description": "Key account.",
  "status": "active",
  "phone": "+1 555 0100",
  "website": "https://acme.example",
  "vat": "GB123456789",
  "category": { "id": 2, "name": "Standard" },
  "owner": { "id": 34, "name": "Jane Doe" },
  "billing": {
    "street": "1 Market St",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94105",
    "country": "United States",
    "invoice_due_days": "7"
  },
  "shipping": {
    "street": null,
    "city": null,
    "state": null,
    "zip": null,
    "country": null
  },
  "dates": {
    "created": "2026-06-15T09:30:00.000000Z",
    "updated": "2026-06-15T09:30:00.000000Z"
  },
  "tags": ["priority", "retainer"]
}
FieldTypeNotes
idintegerClient id. Used in all URLs.
company_namestringClient company name.
descriptionstring|nullClient description.
statusstringactive or suspended.
phonestring|null
websitestring|null
vatstring|nullVAT / tax number.
categoryobject{ id, name }.
ownerobjectPrimary account owner { id, name } — set via the owner action.
billingobjectBilling address + invoice_due_days.
shippingobjectShipping address.
datesobjectcreated, updated.
tagsarrayTag titles.

List / search clients

GET /api/clients

Query parameters

ParameterTypeDescription
statusstringFilter by client status (active|suspended).
category_idintegerFilter by category id.
searchstringFree-text search on the company name.
sortstringclient_company_name, client_status, client_created, client_updated.
orderstringasc or desc (default desc).
limitintegerResults per page (max 100).
pageintegerPage number.

Example request

curl -G https://your-domain/api/clients \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d status=active \
  -d limit=25
<?php
$query = http_build_query(['status' => 'active', 'limit' => 25]);
$ch = curl_init("https://your-domain/api/clients?$query");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Example response — 200 OK

{
  "data": [ { "id": 12, "company_name": "Acme Inc" } ],
  "meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 },
  "message": "Clients retrieved successfully."
}

Get a client

GET /api/clients/{id}

Example request

curl https://your-domain/api/clients/12 \
  -H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init('https://your-domain/api/clients/12');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
Returns the client (200) with message “Client retrieved successfully.”

Create a client

POST /api/clients
Creating a client also provisions its primary account-owner user from first_name, last_name and email (these are required). If a contact-type user already exists with that email, it is promoted to the client’s primary user. A welcome email is not sent unless you pass send_email=yes. The account owner can later be changed with the owner action.

Body parameters

ParameterTypeRequiredNotes
first_namestringyesPrimary user’s first name.
last_namestringyesPrimary user’s last name.
emailstringyesPrimary user’s email. Must be unique among client/team users.
client_company_namestringnoDefaults to “first_name last_name” if omitted.
client_categoryidintegernoExisting category id. Defaults to the system default category.
client_descriptionstringno
client_phonestringno
client_websitestringno
client_vatstringno
client_billing_streetstringno
client_billing_citystringno
client_billing_statestringno
client_billing_zipstringno
client_billing_countrystringno
client_billing_invoice_due_daysintegerno
tagsarray of stringsnoTag titles to attach on create.
send_emailstringnoyes to send the welcome email. Default no.
Required custom fields configured for clients must also be supplied (using their field name). Missing required custom fields return a 422.

Example request

curl -X POST https://your-domain/api/clients \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d first_name=Jane \
  -d last_name=Doe \
  -d email=jane@acme.example \
  -d client_company_name="Acme Inc" \
  -d client_categoryid=2
<?php
$ch = curl_init('https://your-domain/api/clients');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
    CURLOPT_POSTFIELDS     => http_build_query([
        'first_name'          => 'Jane',
        'last_name'           => 'Doe',
        'email'               => 'jane@acme.example',
        'client_company_name' => 'Acme Inc',
        'client_categoryid'   => 2,
    ]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Example response — 201 Created

{
  "data": { "id": 12, "company_name": "Acme Inc" },
  "message": "Client created successfully."
}

Update a client

PATCH /api/clients/{id}
client_company_name is required. The primary user / account owner is not changed here — use the owner action.

Body parameters

ParameterTypeRequiredNotes
client_company_namestringyes
client_categoryidintegernoExisting category id.
client_descriptionstringno
client_phonestringno
client_websitestringno
client_vatstringno
client_billing_streetstringno
client_billing_citystringno
client_billing_statestringno
client_billing_zipstringno
client_billing_countrystringno
client_billing_invoice_due_daysintegerno
As with create, required client custom fields must be supplied.

Example request

curl -X PATCH https://your-domain/api/clients/12 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d client_company_name="Acme Incorporated" \
  -d client_categoryid=2
<?php
$ch = curl_init('https://your-domain/api/clients/12');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
    CURLOPT_POSTFIELDS     => http_build_query([
        'client_company_name' => 'Acme Incorporated',
        'client_categoryid'   => 2,
    ]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Example response — 200 OK

{
  "data": { "id": 12, "company_name": "Acme Incorporated" },
  "message": "Client updated successfully."
}

Delete a client

DELETE /api/clients/{id}
Deletes the client and all of its related records (users, projects, invoices, files, etc.).

Example request

curl -X DELETE https://your-domain/api/clients/12 \
  -H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init('https://your-domain/api/clients/12');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Example response — 200 OK

{
  "data": { "id": 12 },
  "message": "Client deleted successfully."
}

Set tags

PUT /api/clients/{id}/tags
Sets the client’s tags. The tags array is the full set — it replaces existing tags; an empty array clears them.
ParameterTypeRequiredNotes
tagsarray of stringsyesTag titles. Empty array clears all.
curl -X PUT https://your-domain/api/clients/12/tags \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d "tags[]=priority" -d "tags[]=retainer"
<?php
$ch = curl_init('https://your-domain/api/clients/12/tags');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
    CURLOPT_POSTFIELDS     => http_build_query(['tags' => ['priority', 'retainer']]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
Returns the client (200) with message “Client tags updated successfully.”

Change status

PUT /api/clients/{id}/status
ParameterTypeRequiredNotes
statusstringyesactive or suspended.
curl -X PUT https://your-domain/api/clients/12/status \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d status=suspended
<?php
$ch = curl_init('https://your-domain/api/clients/12/status');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
    CURLOPT_POSTFIELDS     => http_build_query(['status' => 'suspended']),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
Returns the client (200) with message “Client status updated successfully.”

Change account owner

PUT /api/clients/{id}/owner
Sets the client’s primary account owner. The owner must be the id of a user that already belongs to this client; it replaces the previous account owner.
ParameterTypeRequiredNotes
ownerintegeryesId of a user belonging to this client.
curl -X PUT https://your-domain/api/clients/12/owner \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d owner=34
<?php
$ch = curl_init('https://your-domain/api/clients/12/owner');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY'],
    CURLOPT_POSTFIELDS     => http_build_query(['owner' => 34]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
Returns the client (200) with message “Client account owner updated successfully.”

Errors

See Getting started for the shared error format. Client-specific:
StatusMeaning
404 Not FoundThe client id does not exist.
422 Unprocessable EntityValidation failed (see below).
{
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email has already been taken."],
    "first_name": ["The first name field is required."]
  }
}