-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commercial Discounts
Read commercial discount definitions in Teamleader Focus.
Commercial discounts are named discounts configured in Teamleader settings. They are department-scoped and read-only through the API.
Access via Teamleader::commercialDiscounts().
Response objects now carry an
id. Teamleader added the field tocommercialDiscounts.listin July 2026. Earlier versions of this page β and of the SDK β worked around its absence by using the discount name as an identifier.asOptions()is now keyed by UUID.Applying a discount does not reference these records. When you put a discount on an invoice or credit note you send
{type, value, description}β not a discount id. See Applying a discount.No pagination or sorting.
list()returns all discounts in a single response. Passing sort or page options throws since v2.2.0.
commercialDiscounts
| Capability | Supported |
|---|---|
| Pagination | β Not supported |
| Filtering | β
Supported (department_id) |
| Sorting | β Not supported |
| Sideloading | β Not supported |
| Creation | β Not supported |
| Update | β Not supported |
| Deletion | β Not supported |
use McoreServices\TeamleaderSDK\Facades\Teamleader;
// All discounts
$discounts = Teamleader::commercialDiscounts()->list();
// For a specific department
$discounts = Teamleader::commercialDiscounts()->list(['department_id' => 'dept-uuid']);department_id is the only filter. Unknown filter keys throw, as do sort and page options:
Teamleader::commercialDiscounts()->list(['name' => 'Trade']);
// InvalidArgumentException: Unsupported filter key for commercialDiscounts.list:
// name. Supported: department_id.There is no info() endpoint β use find().
All helpers are client-side: they call list() and filter in PHP. Each makes its own API request.
$discounts = Teamleader::commercialDiscounts()->forDepartment('dept-uuid');Resolves a single discount by UUID from the full list. Added in v2.2.0, alongside the id field.
$discount = Teamleader::commercialDiscounts()->find('discount-uuid');
// ['id' => 'discount-uuid', 'name' => 'Early payment', 'department' => [...]]Returns null if not found.
Case-insensitive. Default is exact match.
$discount = Teamleader::commercialDiscounts()->findByName('Early payment');
$discount = Teamleader::commercialDiscounts()->findByName('early', null, false); // partial
$discount = Teamleader::commercialDiscounts()->findByName('Trade', 'dept-uuid');Partial name match. Returns a plain array of matching discounts.
$matches = Teamleader::commercialDiscounts()->search('discount');Returns [id => name].
$options = Teamleader::commercialDiscounts()->asOptions();
// ['discount-uuid-1' => 'Early payment', 'discount-uuid-2' => 'Trade']
$options = Teamleader::commercialDiscounts()->asOptions('dept-uuid');This changed in v2.2.0. It previously returned
[name => name], because the API returned no id. Two discounts sharing a name across departments collapsed into a single entry; they no longer do. If you were relying on the old keying, this is a breaking change in your code β the values are unchanged.If a record arrives without an
idβ an account whose API has not yet been updated β the method falls back to using the name as the key for that entry.
Returns a plain array of discount names.
$names = Teamleader::commercialDiscounts()->names();
// ['Early payment', 'Trade', 'Partner']Returns [departmentId => ['department' => ..., 'discounts' => [...]]].
$grouped = Teamleader::commercialDiscounts()->groupedByDepartment();Returns true if a discount with that name exists. Calls findByName() internally.
$exists = Teamleader::commercialDiscounts()->exists('Early payment');| Filter | Type | Description |
|---|---|---|
department_id |
string | Filter by department UUID |
That is the complete set. Anything else throws.
[
'data' => [
[
'id' => 'discount-uuid',
'name' => 'Early payment',
'department' => ['type' => 'department', 'id' => 'dept-uuid'],
],
[
'id' => 'discount-uuid',
'name' => 'Trade',
'department' => ['type' => 'department', 'id' => 'dept-uuid'],
],
],
'headers' => ['X-RateLimit-Remaining' => ['199'], /* ... */],
]
idwas added in July 2026. Earlier versions of this page stated there was no id and that the name was the only unique identifier. That is no longer true, and code written around it β keying maps by name, matching records by name across departments β is worth revisiting.There is no
metablock; the complete list is always returned.
Reading a discount and applying one are separate things. The write side does not take a discount id β invoice and credit note endpoints accept a discount object built from scratch:
| Field | Type | Notes |
|---|---|---|
type |
string |
percentage β the only value the API declares |
value |
number | Between 0 and 100 |
description |
string | Free text, e.g. winter promotion
|
Teamleader::invoices()->creditPartially(
'invoice-uuid',
'2026-02-04',
$groupedLines,
[['type' => 'percentage', 'value' => 15.5, 'description' => 'winter promotion']]
);So this resource is a reference list β it tells you which discounts your account has configured, so you can present them or reuse their names and values. It is not a set of records you attach by id.
// Present configured discounts, then send the chosen one's shape
$discount = Teamleader::commercialDiscounts()->findByName('Early payment', 'dept-uuid');
$payload = [
'type' => 'percentage',
'value' => 5.0, // the percentage is yours to supply
'description' => $discount['name'],
];The list response carries
nameanddepartmentonly β no percentage value. The discount amount lives in Teamleader's settings UI and is not exposed here, so you supply the number yourself.
if (Teamleader::commercialDiscounts()->exists('Early payment', 'dept-uuid')) {
// Safe to reference by that name
}$options = Cache::remember("tl_discounts_{$deptId}", 3600, fn () =>
Teamleader::commercialDiscounts()->asOptions($deptId)
);
// ['discount-uuid' => 'Early payment', ...]Each helper is its own request, so if you need more than one thing, fetch once:
$discounts = collect(Teamleader::commercialDiscounts()->forDepartment($deptId)['data']);
$options = $discounts->pluck('name', 'id')->all();
$names = $discounts->pluck('name')->all();
$early = $discounts->firstWhere('name', 'Early payment');use InvalidArgumentException;
use McoreServices\TeamleaderSDK\Exceptions\TeamleaderException;
// Unsupported filter key
try {
Teamleader::commercialDiscounts()->list(['name' => 'Trade']);
} catch (InvalidArgumentException $e) {
// 'Unsupported filter key for commercialDiscounts.list: name.
// Supported: department_id.'
}
// Sorting and pagination are not supported
try {
Teamleader::commercialDiscounts()->list([], ['page_size' => 10]);
} catch (InvalidArgumentException $e) {
// 'commercialDiscounts.list does not support pagination. ...'
}
// API-level errors
try {
Teamleader::commercialDiscounts()->list();
} catch (TeamleaderException $e) {
Log::error('Teamleader error', ['message' => $e->getMessage()]);
}-
Invoices β Discounts applied at invoice level via
creditPartially() - Quotations β Discounts applied at quotation level
- Tax Rates β Per-line tax rates (department-scoped)
-
Departments β
department_idfilter 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