-
-
Notifications
You must be signed in to change notification settings - Fork 0
Orders
Retrieve orders in Teamleader Focus.
The Orders resource provides read-only access to orders. Orders are created when a quotation is accepted, or entered manually in Teamleader β there is no create, update or delete endpoint.
list() returns a summary per order. info() returns full detail including grouped_lines.
Access via Teamleader::orders().
list()paginates since v2.2.3. Before that the resource sent nopageparameter and there was no way past the first twenty orders β nometablock, no page count, no error. An account with 1,100 orders returned 20 and looked complete. If you are enumerating orders, useall().Pagination is not in the API specification.
@teamleader/focus-api-specificationdeclares onlyfilterandincludesonorders.list, where 40 of the 58.listendpoints declarepage. The endpoint honours it regardless β verified live:size: 100returned all 30 orders in a test account,size: 5returned five on page 1 and five different records on page 2. An ignored parameter cannot do that.
idsis the only filter.department_id,updated_since,order_date_after,termandstatusare each accepted by the API and ignored, returning the full unfiltered set with HTTP 200. Since v2.2.3 the SDK throws instead of sending them.Sorting is accepted by the API and discarded. Sorting by
order_datereturns the same first record as no sort at all. Since v2.2.3 a sort option throws rather than silently doing nothing.There is no
withCustomFields()method, despite earlier versions of this page showing one. The fluent method iswith('custom_fields').
orders
| Capability | Supported |
|---|---|
| Pagination | β Supported (undeclared in the spec β see above) |
| Filtering | β
Supported (ids only) |
| Sorting | β Not supported β passing one throws |
| Sideloading | β
Supported (custom_fields) |
| Creation | β Not supported |
| Update | β Not supported |
| Deletion | β Not supported |
use McoreServices\TeamleaderSDK\Facades\Teamleader;
// First page β 20 records, the API default
$orders = Teamleader::orders()->list();
// Explicit page
$orders = Teamleader::orders()->list([], ['page_size' => 100, 'page_number' => 1]);
// Specific orders by ID
$orders = Teamleader::orders()->list(['ids' => ['order-uuid-1', 'order-uuid-2']]);
// Sideloading β both option keys work since v2.2.2
$orders = Teamleader::orders()->list([], ['include' => 'custom_fields']);
Passing neither page option sends no page key, so existing calls behave exactly as before v2.2.3.
Unknown filter keys throw since v2.2.3:
Teamleader::orders()->list(['updated_since' => '2026-01-01T00:00:00+00:00']);
// InvalidArgumentException: Unsupported filter key for orders.list: updated_since.
// Supported: ids. The API ignores every other filter key and returns the full set.
Added in v2.2.3. Walks every page and returns one data array.
$orders = Teamleader::orders()->all();
// With custom fields on every page
$orders = Teamleader::orders()->all([], ['include' => 'custom_fields']);
count($orders['data']); // every order in the account
The endpoint returns no total count, so the end of the list is inferred from a page shorter than the one requested. A complete final page therefore costs one extra empty request, and the number of requests a full pass needs cannot be known in advance.
$maxPages is a runaway guard, not a limit. Reaching it with a full page still coming throws a TeamleaderException rather than returning a partial set that looks complete β which is the failure this method exists to prevent. The default of 100 pages of 100 covers 10,000 orders.
The signature differs from
PaymentMethods::all()andTaxRates::all(), which take(array $filters, int $maxPages).$optionssits in the middle so a sideload can be applied to every page: the fluent include state is consumed by the first request, sowith('custom_fields')->all()would otherwise sideload page 1 and nothing after it.all()resolves the include once and replays it on each page.
Returns the full order including grouped_lines.
$order = Teamleader::orders()->info('order-uuid');
$order = Teamleader::orders()->info('order-uuid', 'custom_fields');
// Fluent β the method is with(), not withCustomFields()
$order = Teamleader::orders()->with('custom_fields')->info('order-uuid');
Unlike Companies,
info()does not validate includes. An unrecognised value is sent, ignored by the API, and returns 200 with no sideloaded data.custom_fieldsis the only include either endpoint accepts.
$orders = Teamleader::orders()->byIds(['order-uuid-1', 'order-uuid-2']);
Sends no page parameter, so it returns at most 20 records. Add pagination via list() directly if you are requesting more ids than that.
Returns the local list of payment term types β no API call. Matches the enum in the API specification.
Teamleader::orders()->getPaymentTermTypes();
// ['cash', 'end_of_month', 'after_invoice_date']
Returns the local list of supplier types β no API call. Matches the enum in the API specification.
Teamleader::orders()->getSupplierTypes();
// ['company', 'contact']
Verified against @teamleader/focus-api-specification. This is the complete set β anything else throws.
| Filter | Type | Description |
|---|---|---|
ids |
array or string | Filter by order UUIDs. A lone string wraps |
The omission is deliberate, not an oversight in this SDK.
orders.listdeclares no other filter, and the API silently ignores the ones people reach for β a deliberately invented filter key was accepted too, which is how the others were shown to be ignored rather than merely unhelpful.There is no
deal_idfilter and noupdated_sincefilter, and Teamleader publishes no order webhooks. A full pass withall()is the only mechanism available for keeping a local copy in step.
| Include | Endpoint | Description |
|---|---|---|
custom_fields |
orders.list, orders.info
|
Custom field values on the order |
Pass as options['include'] on list() and all(), as the $includes argument on info(), or via the fluent with('custom_fields'). Both include and includes are accepted as the option key since v2.2.2.
See Sideloading for general patterns.
[
'data' => [
[
'id' => 'order-uuid',
'name' => 'Project Services Q1',
'status' => 'delivered', // see note below
'order_date' => '2025-01-15', // nullable
'order_number' => 32, // integer, nullable
'delivery_date' => '2025-01-22', // nullable
'payment_term' => [
'type' => 'after_invoice_date', // cash | end_of_month | after_invoice_date
'days' => 30,
],
'total' => [
'tax_exclusive' => ['amount' => 1000.0, 'currency' => 'EUR'],
'tax_inclusive' => ['amount' => 1210.0, 'currency' => 'EUR'],
'purchase_price_tax_exclusive' => ['amount' => 750.0, 'currency' => 'EUR'],
'purchase_price_tax_inclusive' => ['amount' => 907.5, 'currency' => 'EUR'],
'taxes' => [
[
'rate' => 0.21,
'taxable' => ['amount' => 1000.0, 'currency' => 'EUR'],
'tax' => ['amount' => 210.0, 'currency' => 'EUR'],
],
],
],
'supplier' => ['type' => 'company', 'id' => 'company-uuid'], // company | contact
'department' => ['type' => 'department', 'id' => 'department-uuid'],
'deal' => ['type' => 'deal', 'id' => 'deal-uuid'],
'project' => ['type' => 'project', 'id' => 'project-uuid'], // legacy projects only
'assignee' => ['type' => 'user', 'id' => 'user-uuid'],
'web_url' => 'https://focus.teamleader.eu/order_detail.php?id=order-uuid',
'custom_fields' => [ // only with the include
['definition' => ['type' => 'customFieldDefinition', 'id' => 'definition-uuid'], 'value' => 'Q1'],
],
],
],
'headers' => ['X-RateLimit-Remaining' => ['199'], /* ... */],
]
There is no
metablock.orders.listdeclares none and takes nopaginationinclude, so there is no total count and no page count. The only end-of-list signal is a short page.
statusis not in the API specification. It is present on live records β"status": "delivered"β but absent from both thelistandinfoschemas, so the full set of values is unconfirmed. Treat it as observed rather than documented.
order_numberis an integer, not a string.
projectrefers to the old projects module only. Next-gen project links appear per line item oninfo().
Everything from list(), plus a grouped_lines array of sections with line items. Each line item carries product, quantity, description, unit, unit_price, tax, discount, total, product_category, and β for next-gen projects β project, group and purchase_price.
$orders = Teamleader::orders()->all();
foreach ($orders['data'] as $order) {
// ...
}
Prefer this over list() in any sync or reconciliation job. list() without page options returns twenty records and gives no indication that more exist.
orders.list has no deal_id filter, so filter in PHP β but page through the full set first, or you are filtering twenty records rather than all of them.
$all = Teamleader::orders()->all();
$dealOrders = array_filter(
$all['data'],
fn ($order) => ($order['deal']['id'] ?? null) === $dealId
);
If you need to process orders in batches rather than holding them all in memory:
$page = 1;
do {
$response = Teamleader::orders()->list([], ['page_size' => 100, 'page_number' => $page]);
foreach ($response['data'] as $order) {
// ...
}
$page++;
} while (count($response['data']) === 100);
$order = Teamleader::orders()->with('custom_fields')->info('order-uuid');
foreach ($order['data']['custom_fields'] ?? [] as $field) {
echo $field['definition']['id'].': '.$field['value']."\n";
}
use InvalidArgumentException;
use McoreServices\TeamleaderSDK\Exceptions\{NotFoundException, TeamleaderException};
// Unsupported filter key β thrown before the request
try {
Teamleader::orders()->list(['status' => 'delivered']);
} catch (InvalidArgumentException $e) {
// 'Unsupported filter key for orders.list: status. Supported: ids.
// The API ignores every other filter key and returns the full set.'
}
// Sorting β thrown before the request
try {
Teamleader::orders()->list([], ['sort' => 'order_date']);
} catch (InvalidArgumentException $e) {
// 'orders.list does not support sorting. Passed: sort.
// Records come back in the order the API chooses.'
}
// all() hit its page cap with records still pending
try {
$orders = Teamleader::orders()->all([], [], 5);
} catch (TeamleaderException $e) {
// 'orders.list still had records after 5 pages of 100 (500 retrieved).
// Raise $maxPages if the account is genuinely this large β returning a
// partial set here would look complete to the caller.'
}
try {
Teamleader::orders()->info('order-uuid');
} catch (NotFoundException $e) {
// Order does not exist
}
- Quotations β Orders are created when quotations are accepted
- Deals β Orders reference a deal
-
Products β Line items on
info()reference products - Product-Categories β Line items reference a product category
-
Custom-Fields β Definitions behind
custom_fields - 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