Skip to content
MC0RE edited this page Aug 18, 2026 · 11 revisions

Teamleader Focus SDK

Laravel SDK for the Teamleader Focus API.

Latest Version PHP Laravel License


Getting Started

Guide Notes
Installation & Configuration Composer install, .env setup, OAuth flow
Usage Common patterns, filtering, pagination, error handling
Token Storage & Security How tokens are stored, refreshed, and how to harden for production

Resources

General

Resource SDK key Notes
Closing Days closingDays Always requests pagination metadata; two filters only, no sorting; getCommonHolidays() needs ext-calendar for Easter Monday
Currencies currencies
Custom Fields customFields Endpoint customFieldDefinitions; deal fields return context: sale, normalised to deal
Day Off Types dayOffTypes list() takes no arguments and throws if given any
Days Off daysOff
Departments departments
Document-Templates documentTemplates Requires department_id + document_type
Email Tracking emailTracking
Notes notes delete() supported since v1.2.8; meeting subject type; no info()
Teams teams
User Schedules userSchedules Read-only; max 7-day range; supersedes deprecated users.getWeekSchedule
Users users me() response now includes teams
Work Types workTypes

CRM

Resource SDK key Notes
Addresses addresses
Business Types businessTypes
Companies companies Only custom_fields is a valid list include; info takes related_companies / related_contacts. No name filter β€” byName() throws, use search()
Contacts contacts Only custom_fields is a valid list include; info takes none
Tags tags No filters; sort is fixed to tag ascending

Deals

Resource SDK key Notes
Deal Phases dealPhases
Deal Pipelines dealPipelines Requests pagination metadata
Deal Sources dealSources
Deals deals create()/update() accept purchase_order_number; sorts on created_at / weighted_value only
Lost Reasons lostReasons
Orders orders
Quotations quotations No client-side validation on update()

Calendar

Resource SDK key Notes
Activity Types activityTypes
Call Outcomes callOutcomes
Calls calls delete() supported since v1.2.8
Calendar Events calenderEvents Note: typo in SDK key β€” one 'a'
Meetings meetings group_id filter; project_id/group_id linkage (not with milestone_id)

Invoicing

Resource SDK key Notes
Commercial Discounts commercialDiscounts Response objects carry id since July 2026
Credit Notes creditnotes Note: lowercase 'n' in the SDK key. Read-only β€” no write endpoints exist
Invoices invoices expected_payment_method validation; ubl/xrechnung; listDrafts(); untitled grouped_lines sections must omit the section key
Payment Methods payment_methods Note: underscore SDK key
Payment Terms payment_terms Note: underscore SDK key. list() takes no arguments; meta.default is always returned
Subscriptions subscriptions
Tax Rates taxRates
Withholding Tax Rates withholdingTaxRates

Expenses

Resource SDK key Notes
Bookkeeping Submissions bookkeepingSubmissions Requires a subject filter
Expenses expenses Read-only aggregate view
Incoming Credit-Notes incomingCreditNotes create() posts to .add
Incoming Invoices incomingInvoices create() posts to .add
Receipts receipts create() posts to .add

Products

Resource SDK key Notes
Price Lists priceLists
Product Categories productCategories
Products products create() posts to .add
Units of Measure unitsOfMeasure list() takes no arguments and throws if given any; no info() β€” use findById(). All helpers are client-side

Projects (v2)

Requires Teamleader::accounts()->isUsingProjectsV2() to return true.

Resource SDK key Notes
External Parties external_parties Note: underscore SDK key
Groups groups update() billing_method uses {value, update_strategy}
Materials materials No delete(); create() returns HTTP 201; parent_fixed_price billing
Project Lines projectLines list() throws without project_id
Project Tasks projectTasks work_type_rate requires work_type_id
Projects projects Endpoint projects-v2/projects; webhooks are nextgenProject.*; alias nextgenProjects

Legacy Projects

For accounts that have not yet migrated to Projects v2.

Resource SDK key Notes
Legacy Milestones legacyMilestones
Legacy Projects legacyProjects Endpoint is the bare projects.*; webhooks are project.*. forCustomer() is (id, type) β€” id first; sorts on due_on / title / created_at only

Planning

Resource SDK key Notes
Plannable Items plannable_items Read-only; use infoBySource() when task UUID known
Reservations reservations Duration unit must be minutes
User Availability user_availability list() throws; use daily() / total()

Files

Resource SDK key Notes
Files files upload() / download() return URLs β€” you transfer the file. Subject types differ between list and upload

Tasks

Resource SDK key Notes
Tasks tasks Standalone tasks β€” not the same as Project Tasks

Templates

Resource SDK key Notes
Mail Templates mailTemplates list() throws without type

Tickets

Resource SDK key Notes
Ticket Status ticketStatus Read-only; all helpers client-side
Tickets tickets Assignee must be user; links to milestone_id or project_id, not both

Time Tracking

Resource SDK key Notes
Time Tracking timeTracking create() posts to .add; three recording variants; sorts on starts_on only; nextgenTask is valid for writes but not as a filter
Timers timers stop() creates a time tracking entry

Other

Resource SDK key Notes
Accounts accounts list() and info() throw; use getProjectsVersion() / isUsingProjectsV2(). There is no getDefaultId()
Cloud Platforms cloudPlatforms Valid types: invoice, quotation, ticket
Migrate migrate list() throws; three translation methods
Webhooks webhooks URL must be HTTPS; payload at subject.id; list() takes no arguments

Recurring Gotchas

A few behaviours that appear across many resources and are worth knowing before you start.

Since v2.2.0, most of these fail loudly instead of silently. The API answers 200 to unrecognised filter keys, unknown includes and wrong-shaped sort objects, returning plausible but wrong data. The SDK now rejects them client-side.

  • Unsupported filter keys throw β€” the endpoint would have ignored them and returned everything
  • Unsupported sort fields throw β€” likewise ignored by the API, which then returns records in its own order
  • Filtering and sorting have separate vocabularies β€” a field being sortable does not make it filterable. name sorts on Companies but cannot be filtered on
  • status filters are arrays β€” except on Companies and Contacts, where the API declares a string enum
  • The API returns no pagination metadata by default β€” no total, no page count. The end of a list is a page shorter than the page size, so a full final page costs one extra empty request. Resources that send includes=pagination get a meta block; the rest do not
  • update() injects id into the body β€” you don't need to include it in $data
  • Sideloading parameter is includes (plural) β€” not include. Passing the singular form is silently ignored by the API
  • Includes are per-endpoint β€” list and info frequently accept different sets, and some endpoints accept none at all
  • array_filter() without flags strips null β€” use explicit null-preservation if clearing fields (fixed in v1.2.6)
  • Project linkage is exclusive β€” on Meetings and Tickets, group_id/project_id (new projects) cannot be combined with milestone_id (legacy)
  • 429 always throws RateLimitExceededException β€” catch it before the generic TeamleaderException
  • The rate limiter waits, then throws β€” up to rate_limiting.max_wait_ms (default 5000). In a queue worker, catch and release($e->getRetryAfter()) rather than raising the cap
  • Rate limiter state must be Redis-backed β€” file cache does not coordinate across Horizon workers

SDK Version History

See CHANGELOG for the full history.

Version Highlights
v2.2.1 Removed the phantom name and company_number filters on Companies (byName() returned every company); added the missing filter guards on Closing Days and Legacy Projects; getCommonHolidays() no longer fatals without ext-calendar; README, SECURITY and CONTRIBUTING corrected
v2.2.0 Silent-failure sweep: unsupported filters, sort fields, subject types and includes now throw; sideloading parameters corrected; rate limiter waits for a slot, counts failed requests and reads X-RateLimit-Remaining; July 2026 API changes (price_list_id, commercialDiscounts id)
v2.1.1 Fixed TokenService caching live Carbon objects (__PHP_Incomplete_Class on every entry point); completed facade annotations
v2.1.0 Laravel 13 and PHP 8.5 support
v2.0.0 Breaking: dropped Laravel 10 and 11 (unpatched CVEs; Composer advisories block installation)
v1.2.8 API changelog catch-up (Mar–Jun 2026): new calls.delete, notes.delete, and userSchedules resource; field/enum additions across Meetings, Notes, Materials, Invoices, Tickets
v1.2.7 Fixed new-Projects Groups resource base path (404 on all group calls)
v1.2.6 Fixed array_filter() null-stripping bug (field clears now work correctly)
v1.2.5 Fixed strict_types TypeError on rate limit reset time
v1.2.4 Redis-backed rate limiter; 429 always throws regardless of config
v1.2.3 Fixed includes (plural) sideloading parameter
v1.1.5 Fixed token storage schema mismatch; removed migration requirement

Clone this wiki locally