-
-
Notifications
You must be signed in to change notification settings - Fork 0
Deals
Manage sales deals in Teamleader Focus.
The Deals resource provides full CRUD operations for deal records, plus lifecycle methods (win, lose, move). Deals represent sales opportunities moving through your pipeline from open to won or lost.
Access via Teamleader::deals().
There are no tag methods on Deals.
deals.taganddeals.untagdo not exist in the Teamleader API, and there is notagsfilter ondeals.list. The SDK'stag(),untag()andwithTags()were removed, along with thetagsfilter key. Tagging is available on Companies and Contacts, where the endpoints are real.Unknown filter keys throw since v2.2.0. The API ignores filter keys it does not recognise and answers
200with the full unfiltered set, so the SDK rejects them before sending.Sorting worked for the first time in v2.2.0.
list()called abuildSort()method that was never defined on this class, so passing asortoption raisedError: Call to undefined method. It now validates the field and builds the object shape the API expects.
deals
| Capability | Supported |
|---|---|
| Pagination | β Supported |
| Filtering | β Supported |
| Sorting | β
Supported (created_at, weighted_value) |
| Sideloading | β Supported |
| Creation | β Supported |
| Update | β Supported |
| Deletion | β Supported |
use McoreServices\TeamleaderSDK\Facades\Teamleader;
// All deals
$deals = Teamleader::deals()->list();
// Open deals only β status must be an array
$deals = Teamleader::deals()->list(['status' => ['open']]);
// With pagination and sorting
$deals = Teamleader::deals()->list([], [
'page_size' => 50,
'page_number' => 1,
'sort' => 'created_at',
'sort_order' => 'desc',
]);
// With sideloading
$deals = Teamleader::deals()->list([], ['include' => 'lead.customer,responsible_user']);Status filter: pass status as an array β
['open'],['won'],['lost']. A plain string is coerced to an array for you, but the array form is what the API declares.Sideloading: the
includeoption is sent asincludes(plural) in the request body. Before v2.2.0 this method sent the singular form, which the API silently ignores β so['include' => 'custom_fields']returned deals with no custom fields and no error, while the fluent->withCustomFields()form worked. The two now produce identical requests.
$deal = Teamleader::deals()->info('deal-uuid');
$deal = Teamleader::deals()->info('deal-uuid', 'lead.customer,responsible_user');
$deal = Teamleader::deals()
->withCustomer()
->withCurrentPhase()
->info('deal-uuid');Required fields (validated before the request):
-
lead.customer.typeβcontactorcompany(throwsInvalidArgumentExceptionfor other values) -
lead.customer.idβ customer UUID -
titleβ deal title
Other validated fields:
-
estimated_probabilityβ must be between0and1inclusive -
estimated_value.currencyβ must be a supported currency code -
currencyβ must include bothcodeandexchange_rate -
custom_fields[]β each entry must includeidandvalue
$deal = Teamleader::deals()->create([
'title' => 'New Business Deal',
'lead' => [
'customer' => ['type' => 'company', 'id' => 'company-uuid'],
],
'phase_id' => 'phase-uuid',
'estimated_value' => ['amount' => 10000, 'currency' => 'EUR'],
'estimated_probability' => 0.75,
'estimated_closing_date' => '2025-12-31',
'responsible_user_id' => 'user-uuid',
'source_id' => 'source-uuid',
'purchase_order_number' => 'PO-2025-042',
]);The id is injected into the request body before posting to deals.update.
Teamleader::deals()->update('deal-uuid', [
'title' => 'Updated Deal Title',
'estimated_probability' => 0.90,
'estimated_value' => ['amount' => 15000, 'currency' => 'EUR'],
]);Teamleader::deals()->delete('deal-uuid');Marks a deal as won.
Teamleader::deals()->win('deal-uuid');Marks a deal as lost. Both $reasonId and $extraInfo are optional and are omitted from the request when null.
// Without reason
Teamleader::deals()->lose('deal-uuid');
// With reason UUID from lostReasons resource
Teamleader::deals()->lose('deal-uuid', 'lost-reason-uuid');
// With reason and additional notes
Teamleader::deals()->lose('deal-uuid', 'lost-reason-uuid', 'Price too high for budget');Moves a deal to a different phase. Throws InvalidArgumentException if $phaseId is empty.
Teamleader::deals()->move('deal-uuid', 'target-phase-uuid');All accept an optional $additionalFilters array.
$deals = Teamleader::deals()->open();
$deals = Teamleader::deals()->won();
$deals = Teamleader::deals()->lost();
// With extra filters
$deals = Teamleader::deals()->open(['responsible_user_id' => 'user-uuid']);| Method | Filter applied |
|---|---|
search(string $term) |
term (title, reference, customer name) |
forCustomer(string $type, string $id) |
customer β throws if type is not contact or company
|
byPhase(string $phaseId) |
phase_id |
byIds(array $ids) |
ids |
forUser(string $userId) |
responsible_user_id |
updatedSince(string $date) |
updated_since |
closingBetween(string $from, string $until) |
estimated_closing_date_from + estimated_closing_date_until
|
$deals = Teamleader::deals()->forCustomer('company', 'company-uuid');
$deals = Teamleader::deals()->byPhase('phase-uuid');
$deals = Teamleader::deals()->closingBetween('2025-07-01', '2025-09-30');
$deals = Teamleader::deals()->forUser('user-uuid', ['status' => ['open']]);
withTags()was removed in v2.2.0 βdeals.listhas notagsfilter, so it returned every deal regardless of the tags passed.
| Method | Include |
|---|---|
withCustomer() |
lead.customer |
withResponsibleUser() |
responsible_user |
withDepartment() |
department |
withCurrentPhase() |
current_phase |
withSource() |
source |
withCustomFields() |
custom_fields |
withAll() |
All of the above except custom_fields
|
$deals = Teamleader::deals()
->withCustomer()
->withResponsibleUser()
->withCurrentPhase()
->list(['status' => ['open']]);These are queued and consumed when the request is built. Before v2.2.0 they raised
Error: Call to undefined method Deals::with()β the fluent interface had never worked in any released version, on any resource.
Verified against @teamleader/focus-api-specification. This is the complete set β anything else throws.
| Filter | Type | Description |
|---|---|---|
ids |
array | Filter by UUIDs. A single string is wrapped for you |
term |
string | Searches title, reference, customer name |
status |
array |
open, won, lost β a string is wrapped for you |
customer |
array | {type: contact|company, id: uuid} |
phase_id |
string | Phase UUID |
pipeline_ids |
array | Pipeline UUIDs. A single string is wrapped for you |
responsible_user_id |
string or array | User UUID(s) β the API accepts both |
estimated_closing_date |
string | Exact closing date |
estimated_closing_date_from |
string | Closing date from (inclusive) |
estimated_closing_date_until |
string | Closing date until (inclusive) |
updated_since |
string | ISO 8601 datetime |
created_before |
string | ISO 8601 datetime |
newis a deal status but not a filter value. Thestatusfield on a deal can benew,open,wonorlost, butdeals.listonly accepts the last three as filter values. Filtering by['new']is not something the API supports.There is no
tagsfilter. See the note in the Overview.
| Field | Description |
|---|---|
created_at |
Deal creation date |
weighted_value |
Probability Γ estimated value |
$deals = Teamleader::deals()->list([], [
'sort' => 'weighted_value',
'sort_order' => 'desc',
]);Several input forms are accepted β a field name, a list of field names, a single ['field' => ..., 'order' => ...] entry, or a list of those:
$deals = Teamleader::deals()->list([], [
'sort' => [
['field' => 'weighted_value', 'order' => 'desc'],
['field' => 'created_at', 'order' => 'asc'],
],
]);Any other field throws, rather than being silently coerced to a default.
| Include | Description |
|---|---|
lead.customer |
Customer record (company or contact) |
responsible_user |
Responsible user |
department |
Assigned department |
current_phase |
Current pipeline phase |
source |
Deal source |
custom_fields |
Custom field values |
Unlike Companies, the same includes apply to both list() and info().
See Sideloading for general patterns.
[
'data' => [
[
'id' => 'deal-uuid',
'title' => 'New Business Deal',
'status' => 'open',
'reference' => 'D-2025-001',
'lead' => [
'customer' => ['type' => 'company', 'id' => 'company-uuid'],
'contact_person' => null,
],
'estimated_value' => ['amount' => 10000.0, 'currency' => 'EUR'],
'estimated_probability' => 0.75,
'estimated_closing_date' => '2025-12-31',
'weighted_value' => ['amount' => 7500.0, 'currency' => 'EUR'],
'current_phase' => ['type' => 'dealPhase', 'id' => 'phase-uuid'],
'responsible_user' => ['type' => 'user', 'id' => 'user-uuid'],
'created_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 Deals does not. There is no total count, so the end of a list is a page shorter than the requested page size.
$deal = Teamleader::deals()->create([
'title' => 'Cloud Migration Project',
'lead' => ['customer' => ['type' => 'company', 'id' => 'company-uuid']],
'phase_id' => 'qualification-phase-uuid',
'estimated_value' => ['amount' => 25000, 'currency' => 'EUR'],
'estimated_probability' => 0.3,
]);
// Move forward
Teamleader::deals()->move($deal['data']['id'], 'proposal-phase-uuid');
Teamleader::deals()->update($deal['data']['id'], ['estimated_probability' => 0.65]);
Teamleader::deals()->move($deal['data']['id'], 'closing-phase-uuid');
Teamleader::deals()->win($deal['data']['id']);$reasons = Teamleader::lostReasons()->list();
$priceReason = collect($reasons['data'])->firstWhere('name', 'Price');
Teamleader::deals()->lose(
'deal-uuid',
$priceReason['id'],
'Budget was 30% below our minimum'
);$deals = Teamleader::deals()
->withCustomer()
->closingBetween('2025-07-01', '2025-09-30', ['status' => ['open']]);$deal = Teamleader::deals()
->withCustomFields()
->info('deal-uuid');
foreach ($deal['data']['custom_fields'] ?? [] as $field) {
$definitionId = $field['definition']['id'];
$value = $field['value'];
}Deal custom field definitions come back from the API with
context: sale, which the SDK normalises todeal. See Custom Fields.
$all = [];
$page = 1;
do {
$response = Teamleader::deals()->list(
['status' => ['open']],
['page_size' => 100, 'page_number' => $page]
);
$all = array_merge($all, $response['data']);
$page++;
} while (count($response['data']) === 100);use InvalidArgumentException;
use McoreServices\TeamleaderSDK\Exceptions\TeamleaderException;
// Unsupported filter key β thrown before the request
try {
Teamleader::deals()->list(['tags' => ['VIP']]);
} catch (InvalidArgumentException $e) {
// 'Unsupported filter key for deals.list: tags. Supported: ids, term,
// customer, phase_id, estimated_closing_date, estimated_closing_date_from,
// estimated_closing_date_until, responsible_user_id, updated_since,
// created_before, status, pipeline_ids.'
}
// Unsupported sort field
try {
Teamleader::deals()->list([], ['sort' => 'title']);
} catch (InvalidArgumentException $e) {
// 'Invalid sort field: title. deals.list accepts: created_at, weighted_value.'
}
// Invalid customer type on create
try {
Teamleader::deals()->create([
'title' => 'Test',
'lead' => ['customer' => ['type' => 'lead', 'id' => 'uuid']],
]);
} catch (InvalidArgumentException $e) {
// "Invalid customer type: lead. Must be 'contact' or 'company'"
}
// move() with empty phase
try {
Teamleader::deals()->move('deal-uuid', '');
} catch (InvalidArgumentException $e) {
// 'Phase ID is required to move a deal'
}
// Invalid probability
try {
Teamleader::deals()->create([/* ... */ 'estimated_probability' => 1.5]);
} catch (InvalidArgumentException $e) {
// 'Estimated probability must be a number between 0 and 1 (inclusive)'
}- Quotations β Quotations belong to deals
- Orders β Orders created from accepted quotations
- Deal Phases β Phases deals move through
- Deal Pipelines β Pipelines containing phases
- Deal Sources β Source reference list
- Lost Reasons β Lost reason reference list
- Companies β Companies as deal customers, and where tagging does exist
- Contacts β Contacts as deal customers
-
Custom Fields β Definitions for the
custom_fieldssideload -
Files β Use
files()->forDeal()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