-
-
Notifications
You must be signed in to change notification settings - Fork 0
Companies
Manage companies in Teamleader Focus CRM.
The Companies resource provides full CRUD operations for company records. Beyond standard list/info/create/update/delete it exposes tag management, logo upload, and a set of search and filter helpers.
Access via Teamleader::companies().
listandinfoaccept different includes.companies.listtakes onlycustom_fields.companies.infotakesrelated_companiesandrelated_contacts, and does not takecustom_fields. Passing the wrong one throws.
price_listis not an include. It is returned automatically whenever the account has access to price lists, and isnullwhen no price list is set on the company. Link one by passingprice_list_idon create or update.There is no
namefilter, andbyName()throws. Usesearch()β see Searching by name.Before v2.2.0 this page listed seven includes. Six of them β
addresses,business_type,responsible_user,added_by,tagsandprice_listβ are not includes at all. Those fields are returned by default, so requesting them looked like it worked: the API ignores unrecognised include values and the data arrived anyway. The fluent methods for them have been removed. No response data changed β only a no-op parameter was dropped.
companies
| Capability | Supported |
|---|---|
| Pagination | β Supported |
| Filtering | β Supported |
| Sorting | β
Supported (name, added_at, updated_at) |
| Sideloading | β Supported (see caveat above) |
| Creation | β Supported |
| Update | β Supported |
| Deletion | β Supported |
use McoreServices\TeamleaderSDK\Facades\Teamleader;
$companies = Teamleader::companies()->list();
$companies = Teamleader::companies()->list(
['status' => 'active', 'tags' => ['VIP']],
['page_size' => 50, 'page_number' => 1, 'sort' => 'name', 'sort_order' => 'asc']
);
// Sideloading β custom_fields is the only include companies.list accepts
$companies = Teamleader::companies()->list([], ['include' => 'custom_fields']);Unknown filter keys throw since v2.2.1:
Teamleader::companies()->list(['name' => 'Acme']);
// InvalidArgumentException: Invalid filter key 'name' for companies.list.
// Supported filters: ids, email, vat_number, national_identification_number,
// term, tags, updated_since, status, marketing_mails_consent. 'search' and
// 'general_search' are accepted as aliases for 'term'. companies.list has no
// name filter β use 'term', which searches name, VAT, emails and telephones.$company = Teamleader::companies()->info('company-uuid');
// With includes β string or array form
$company = Teamleader::companies()->info('company-uuid', 'related_companies,related_contacts');
// Via fluent interface
$company = Teamleader::companies()
->withRelatedContacts()
->info('company-uuid');Custom fields and the price list are returned automatically on info() β there is no include to request them with. Passing custom_fields here throws, because it belongs to companies.list.
$company = Teamleader::companies()->create([
'name' => 'Acme Corp',
'vat_number' => 'BE0123456789',
'website' => 'https://acme.be',
'language' => 'nl',
'responsible_user_id' => 'user-uuid',
'business_type_id' => 'business-type-uuid',
'price_list_id' => 'price-list-uuid',
'marketing_mails_consent' => true,
'emails' => [['type' => 'primary', 'email' => 'info@acme.be']],
'telephones' => [['type' => 'phone', 'number' => '+32 3 123 45 67']],
'addresses' => [[
'type' => 'primary',
'address' => [
'line_1' => 'Keizerstraat 1',
'postal_code' => '2000',
'city' => 'Antwerp',
'country' => 'BE',
],
]],
'tags' => ['Partner', 'VIP'],
]);Teamleader::companies()->update('company-uuid', [
'name' => 'Acme Corp Ltd',
'responsible_user_id' => 'new-user-uuid',
]);
// Link to a price list
Teamleader::companies()->update('company-uuid', ['price_list_id' => 'price-list-uuid']);null values are preserved in the payload, so passing null clears a nullable field rather than being silently stripped (fixed in v1.2.6).
Teamleader::companies()->delete('company-uuid');Uploads or removes a company logo. The image must be a base64 data URI starting with data:image/. Pass null to remove an existing logo. An InvalidArgumentException is thrown if a non-null value doesn't start with data:image/.
Returns empty array (HTTP 204) on success.
$imageData = base64_encode(file_get_contents('/path/to/logo.png'));
Teamleader::companies()->uploadLogo('company-uuid', 'data:image/png;base64,' . $imageData);
// Remove logo
Teamleader::companies()->uploadLogo('company-uuid', null);Adds one or more tags. Accepts a string or array.
Teamleader::companies()->tag('company-uuid', ['VIP', 'Partner']);
Teamleader::companies()->tag('company-uuid', 'Enterprise'); // string also acceptedRemoves one or more tags.
Teamleader::companies()->untag('company-uuid', ['Trial']);Adds and removes tags in one call. Makes two separate API calls internally β one tag, one untag. Returns ['tagged' => [...], 'untagged' => [...]].
$result = Teamleader::companies()->manageTags(
'company-uuid',
['Active', 'Paid'], // add
['Trial', 'Prospect'] // remove
);Unlike Deals,
companies.tagandcompanies.untagare real endpoints. The equivalent methods were removed from Deals because no such endpoint exists there.
companies.list has no name filter. Use term, which searches the company name along with VAT number, emails and telephones:
$companies = Teamleader::companies()->search('Acme');
// or equivalently
$companies = Teamleader::companies()->list(['term' => 'Acme']);term is a broader match than an exact name lookup. If you need exact-name behaviour, narrow the result set in PHP:
$matches = array_filter(
Teamleader::companies()->search('Acme')['data'],
fn ($company) => strcasecmp($company['name'], 'Acme Corp') === 0
);
byName()throws since v2.2.1. It built afilter.namethe API ignores, so it returned every company in the account with HTTP 200 and the caller had no way to tell. It now throws anInvalidArgumentExceptionnamingsearch()as the replacement, and is removed in v3.0. Thenameandcompany_numberfilter keys throw for the same reason.
| Method | Filter applied |
|---|---|
search(string $term) |
term (searches name, VAT, emails, phones) |
searchAll(string $query) |
term (alias) |
byEmail(string $email) |
email β {type: primary, email: $email}
|
byVatNumber(string $vat) |
vat_number |
byNationalIdentificationNumber(string $n) |
national_identification_number |
withTags(string|array $tags) |
tags |
updatedSince(string $date) |
updated_since |
All helpers accept an optional $options array as their last parameter for pagination/sorting.
$companies = Teamleader::companies()->byEmail('info@acme.be');
$companies = Teamleader::companies()->byVatNumber('BE0123456789');
$companies = Teamleader::companies()->updatedSince('2025-01-01T00:00:00+00:00');There is no
active()ordeactivated()helper on Companies, despite earlier versions of this page listing them. Filter onstatusdirectly. (Contacts does have them.)
| Method | Include added | Valid on |
|---|---|---|
withCustomFields() |
custom_fields |
list() |
withRelatedCompanies() |
related_companies |
info() |
withRelatedContacts() |
related_contacts |
info() |
Removed in v2.2.0: withAddresses(), withBusinessType(), withResponsibleUser(), withAddedBy(), withPriceList(), withCommonRelationships(). None of those values is an include the API accepts.
Verified against @teamleader/focus-api-specification. This is the complete set β anything else throws.
| Filter | Type | Description |
|---|---|---|
ids |
array | Filter by UUIDs |
email |
array or string | Email β string auto-wraps as {type: primary}
|
vat_number |
string | Exact VAT number |
national_identification_number |
string | National ID number |
term |
string | Searches name, VAT, emails, phones |
tags |
array | All specified tags must be present |
updated_since |
string | ISO 8601 datetime |
status |
string |
active or deactivated β a string, not an array |
marketing_mails_consent |
bool | Marketing consent flag |
search and general_search are accepted as aliases for term.
statusis one of the exceptions to the "status filters are arrays" rule on Home. The API declares it as a string enum here. If you pass an array the SDK takes the first element.
nameandcompany_numberwere removed in v2.2.1. Neither exists oncompanies.list; both were silently dropped by the API, so filtering by them returned every company.
| Field | Description |
|---|---|
name |
Company name |
added_at |
Date added |
updated_at |
Date last updated |
$companies = Teamleader::companies()->list([], [
'sort' => 'name',
'sort_order' => 'asc',
]);
nameis a valid sort field, even though it is not a valid filter. Sorting and filtering have separate vocabularies on this endpoint.
| Include | Endpoint | Description |
|---|---|---|
custom_fields |
companies.list |
Custom field values |
related_companies |
companies.info |
Linked companies |
related_contacts |
companies.info |
Linked contacts |
That is the complete list. addresses, business_type, responsible_user, added_by, tags and price_list are returned by default and cannot β and need not β be requested.
See Sideloading for general patterns.
[
'data' => [
[
'id' => 'company-uuid',
'name' => 'Acme Corp',
'status' => 'active',
'vat_number' => 'BE0123456789',
'website' => 'https://acme.be',
'language' => 'nl',
'marketing_mails_consent' => true,
'responsible_user' => ['type' => 'user', 'id' => 'user-uuid'],
'price_list' => ['type' => 'priceList', 'id' => 'price-list-uuid'],
'added_at' => '2025-01-15T10:00:00+00:00',
'updated_at' => '2025-03-01T09:00:00+00:00',
],
],
'headers' => ['X-RateLimit-Remaining' => ['199'], /* ... */],
]There is no
metablock. Earlier versions of this page showed one withpageandmatchesβ the API returns pagination metadata only when a resource sendsincludes=pagination, and Companies does not. There is no total count, so the end of a list is a page shorter than the requested page size.
price_listis present only when the account has access to price lists, and isnullwhen none is set on the company.
$existing = Teamleader::companies()->byVatNumber('BE0123456789');
if (!empty($existing['data'])) {
Teamleader::companies()->update($existing['data'][0]['id'], ['name' => 'Acme Corp Ltd']);
} else {
Teamleader::companies()->create(['name' => 'Acme Corp', 'vat_number' => 'BE0123456789']);
}Because there is no total count, page until a page comes back shorter than the requested size. A complete final page costs one extra empty request.
$all = [];
$page = 1;
do {
$response = Teamleader::companies()->list(
['status' => 'active'],
['page_size' => 100, 'page_number' => $page]
);
$all = array_merge($all, $response['data']);
$page++;
} while (count($response['data']) === 100);$companies = Teamleader::companies()
->withCustomFields()
->list(['status' => 'active']);
foreach ($companies['data'] as $company) {
foreach ($company['custom_fields'] ?? [] as $field) {
$id = $field['definition']['id'];
$value = $field['value'];
}
}Note this uses
list(), notinfo().custom_fieldsis acompanies.listinclude;companies.inforeturns custom fields without being asked and rejects the include.
$priceLists = Teamleader::priceLists()->list();
Teamleader::companies()->update('company-uuid', [
'price_list_id' => $priceLists['data'][0]['id'],
]);use InvalidArgumentException;
use McoreServices\TeamleaderSDK\Exceptions\{NotFoundException, ValidationException, TeamleaderException};
// Unsupported filter key β thrown before the request
try {
Teamleader::companies()->list(['name' => 'Acme']);
} catch (InvalidArgumentException $e) {
// "Invalid filter key 'name' for companies.list. Supported filters: ids,
// email, vat_number, national_identification_number, term, tags,
// updated_since, status, marketing_mails_consent. ... companies.list has
// no name filter β use 'term', which searches name, VAT, emails and
// telephones."
}
// byName() throws since v2.2.1
try {
Teamleader::companies()->byName('Acme');
} catch (InvalidArgumentException $e) {
// 'companies.list has no `name` filter β this method silently returned
// every company. Use search('Acme') instead, which filters on name, VAT
// number, emails and telephones via the `term` filter.'
}
// Include valid for list, not for info
try {
Teamleader::companies()->info('company-uuid', 'custom_fields');
} catch (InvalidArgumentException $e) {
// 'Invalid include for companies.info: custom_fields. Accepts:
// related_companies, related_contacts. custom_fields is a companies.list
// include; companies.info returns custom fields automatically.'
}
try {
Teamleader::companies()->uploadLogo('company-uuid', 'not-a-data-uri');
} catch (InvalidArgumentException $e) {
// 'Image must be a base64 data URI (e.g. data:image/png;base64,...) or null to remove the logo'
}
try {
Teamleader::companies()->info('company-uuid');
} catch (NotFoundException $e) {
// Company does not exist
}
try {
Teamleader::companies()->create(['name' => '']);
} catch (InvalidArgumentException $e) {
// 'Company name is required' β thrown client-side, before the request
}- Contacts β Contacts can be linked to companies
- Business Types β Legal structures used on company creation
-
Price Lists β Link via
price_list_idon create/update - Tags β Tag reference list
- Deals β Deals reference companies as customers
- Invoices β Invoices can be issued to companies
-
Files β Use
files()->forCompany()to list attachments - Sideloading β Loading related data
- Filtering β Filter and pagination reference
Last Updated: August 2026 β’ SDK Version: 2.2.2 β’ Made with β€οΈ by MCore Services
- Departments
- Users
- Teams
- Custom Fields
- Work Types
- Document Templates
- Currencies
- Notes
- Email Tracking
- Closing Days
- Day Off Types
- Days Off
- User Schedules
- Invoices
- Credit Notes
- Subscriptions
- Payment Methods
- Payment Terms
- Tax Rates
- Withholding Tax Rates
- Commercial Discounts
Next Gen Projects
Legacy Projects