Skip to content

Companies

MC0RE edited this page Aug 18, 2026 · 5 revisions

Companies

Manage companies in Teamleader Focus CRM.

Overview

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().

list and info accept different includes. companies.list takes only custom_fields. companies.info takes related_companies and related_contacts, and does not take custom_fields. Passing the wrong one throws.

price_list is not an include. It is returned automatically whenever the account has access to price lists, and is null when no price list is set on the company. Link one by passing price_list_id on create or update.

There is no name filter, and byName() throws. Use search() β€” see Searching by name.

Before v2.2.0 this page listed seven includes. Six of them β€” addresses, business_type, responsible_user, added_by, tags and price_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.

Endpoint

companies

Capabilities

Capability Supported
Pagination βœ… Supported
Filtering βœ… Supported
Sorting βœ… Supported (name, added_at, updated_at)
Sideloading βœ… Supported (see caveat above)
Creation βœ… Supported
Update βœ… Supported
Deletion βœ… Supported

Methods

list(array $filters = [], array $options = [])

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.

info(string $id, string|array|null $includes = null)

$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.


create(array $data)

$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'],
]);

update(string $id, array $data)

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).


delete(string $id)

Teamleader::companies()->delete('company-uuid');

uploadLogo(string $id, string|null $image)

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

Tag Methods

tag(string $id, string|array $tags)

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 accepted

untag(string $id, string|array $tags)

Removes one or more tags.

Teamleader::companies()->untag('company-uuid', ['Trial']);

manageTags(string $id, array $tagsToAdd = [], array $tagsToRemove = [])

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.tag and companies.untag are real endpoints. The equivalent methods were removed from Deals because no such endpoint exists there.


Searching by name

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 a filter.name the API ignores, so it returned every company in the account with HTTP 200 and the caller had no way to tell. It now throws an InvalidArgumentException naming search() as the replacement, and is removed in v3.0. The name and company_number filter keys throw for the same reason.


Helper Methods

Search and filter helpers

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() or deactivated() helper on Companies, despite earlier versions of this page listing them. Filter on status directly. (Contacts does have them.)

Fluent include methods

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.


Filters

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.

status is 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.

name and company_number were removed in v2.2.1. Neither exists on companies.list; both were silently dropped by the API, so filtering by them returned every company.


Sorting

Field Description
name Company name
added_at Date added
updated_at Date last updated
$companies = Teamleader::companies()->list([], [
    'sort'       => 'name',
    'sort_order' => 'asc',
]);

name is a valid sort field, even though it is not a valid filter. Sorting and filtering have separate vocabularies on this endpoint.


Sideloading

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.


Response Structure

list() response

[
    '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 meta block. Earlier versions of this page showed one with page and matches β€” the API returns pagination metadata only when a resource sends includes=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_list is present only when the account has access to price lists, and is null when none is set on the company.


Usage Examples

Upsert by VAT number

$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']);
}

Paginate all active companies

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

Load with custom fields

$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(), not info(). custom_fields is a companies.list include; companies.info returns custom fields without being asked and rejects the include.

Link a company to a price list

$priceLists = Teamleader::priceLists()->list();

Teamleader::companies()->update('company-uuid', [
    'price_list_id' => $priceLists['data'][0]['id'],
]);

Error Handling

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
}

Related Resources

  • Contacts β€” Contacts can be linked to companies
  • Business Types β€” Legal structures used on company creation
  • Price Lists β€” Link via price_list_id on 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

Clone this wiki locally