---
title: "How to Export Data from Teamleader: Methods, API Limits & Gaps"
slug: how-to-export-data-from-teamleader-methods-api-limits-gaps
date: 2026-08-27
author: Rishabh Makhar
categories: [Migration Guide, CRM]
excerpt: "Complete guide to exporting data from Teamleader Focus. Covers native backup gaps, module exports, API v2 rate limits (200 req/min), pagination, and extraction pitfalls."
tldr: "Teamleader Focus offers backup ZIP, module CSV exports, and API v2 (200 req/min, 100 records/page). The backup excludes activity history, ticket content, files, and old timesheets — full extraction requires API scripting."
canonical: https://clonepartner.com/blog/how-to-export-data-from-teamleader-methods-api-limits-gaps
---

# How to Export Data from Teamleader: Methods, API Limits & Gaps


# How to Export Data from Teamleader: Methods, API Limits & Gaps

Teamleader Focus has **no single "export everything" button** that gives you a complete, full-fidelity copy of your data. The platform offers a backup feature that generates a ZIP of CSV files, but it explicitly excludes activity tracking, ticket content, files and documents, expenses, and timesheets older than one year. For everything the backup misses, you need module-level UI exports or the Teamleader Focus API v2 — an RPC-style API with a 200-request-per-minute rate limit and pagination capped at 100 records per page. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25690929430545-How-can-I-create-a-backup-of-my-data))

This guide covers the constraints of each extraction method, the technical requirements for API extraction, rate limit handling, OAuth scope requirements, error handling, and the specific edge cases that silently corrupt data during a platform exit. If you're extracting data as part of a CRM migration, read our [Teamleader to Nutshell migration guide](https://clonepartner.com/blog/blog/teamleader-to-nutshell-migration-a-technical-guide) for target-side constraints. For general pre-migration planning, see the [CRM data migration checklist](https://clonepartner.com/blog/blog/the-ultimate-crm-data-migration-checklist-a-10-point-plan-for-a-zero-loss-transition).

> [!NOTE]
> **TL;DR:** Teamleader Focus gives you three extraction paths: a native backup ZIP (fast but incomplete), per-module Excel/CSV exports (customizable but manual), and the API v2. The backup excludes activity history, ticket content, documents, expenses, and old timesheets. The API is rate-limited to 200 requests per sliding minute per integration per account, with a max page size of 100. For a 50,000-record account, expect 4–8 hours of sustained API polling to extract contacts, companies, deals, and their related data. These estimates are theoretical maximums derived from rate limits, not measured benchmarks from a specific account.

## Teamleader Focus Data Model: What You're Actually Exporting

Before choosing an extraction method, understand what's inside Teamleader Focus. It's an **all-in-one European business platform** — not just a CRM. The platform organizes data into several pillars:

- **CRM & Sales:** Contacts, Companies, Deals (sales opportunities with pipeline stages), Quotations, and Products.
- **Operations & Work:** Projects, Tasks, Milestones, Meetings, and Time Tracking entries.
- **Financials:** Invoices, Credit Notes, Subscriptions, and booking data.
- **Support:** Tickets with properties and conversation threads.
- **Configuration:** Custom Fields (up to 50 per entity for contacts/companies/deals/projects, 25 for other sections), Tags, and pipeline definitions.

The extraction challenge is preserving the connective tissue between these pillars. A time-tracking entry is linked to a Task, which belongs to a Milestone, which rolls up to a Project, which is billed to a Company. If you export these modules independently without capturing their foreign keys (UUIDs), you cannot reconstruct the relationships in your target system.

If you're migrating to a pure-play CRM, you'll face a structural mismatch. Pure CRMs drop project management and invoicing capabilities entirely. You must decide whether to route financial data to an ERP and CRM data to your sales platform, or archive the non-CRM data. Our [Teamleader to Nutshell migration guide](https://clonepartner.com/blog/blog/teamleader-to-nutshell-migration-a-technical-guide) walks through this in detail.

> [!WARNING]
> **Account-level limits matter for export planning.** The default CRM limit is 10,000 contacts and companies combined, expandable to a hard cap of 200,000. Products cap at 150,000. Default storage is 35 GB. These limits set the upper bound for how long your extraction will take.

## Method 1: Native Backup (ZIP Export)

**What it is:** A one-click backup that generates a ZIP file containing CSV files for each module.

**How to access it:** Go to **Settings > Overview**, search for "Backup," click **My backups**, then hit the plus sign to generate. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25690929430545-How-can-I-create-a-backup-of-my-data))

**Who can use it:** Admin users with export rights only. Not available on trial accounts.

### What the Backup Includes

The backup pulls account-wide data — contacts, companies, deals, invoices, quotations, projects — in flat CSV format. Each module gets its own file.

### What the Backup Excludes

This is where most teams get burned:

| Excluded Data | Workaround |
|---|---|
| Activities/tracking on any entity | API only |
| Article lines on quotations/invoices | Module-level export (Deals > Export or Invoices > Export) |
| Documents/PDFs generated in Teamleader | Invoice PDFs: batch export (max 250/batch). Other files: API or Dropbox sync |
| Files uploaded to customers/projects | API `files.list` + `files.download`, or Dropbox sync |
| Orders | Deals > Orders > Export |
| Targets | No export available |
| Ticket content (conversation body) | API `tickets.listMessages` |
| Timesheets older than 1 year | Timesheets > Export (per year) |
| Expenses | No export available |

### Custom Field Limitation

The backup exports **only the first 10 custom fields per module**, sorted alphabetically. If you have more than 10 custom fields on contacts, companies, deals, or projects, the remaining fields are silently dropped. You must use the module-level export with a custom template — or the API — to get the rest. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25690929430545-How-can-I-create-a-backup-of-my-data))

### Retention

Teamleader keeps a maximum of **3 backup files for 24 hours each**. Generate a fourth, and the oldest is automatically removed. Download and store locally immediately after generation.

## Method 2: Module-Level UI Exports (Excel/CSV)

Each major module has its own export function. These are more configurable than the backup but require manual, per-module execution. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25691186210961-Create-an-Excel-export-in-Teamleader-Focus))

### How Module Exports Work

1. Navigate to the module (Contacts, Deals, Invoices, Projects, etc.)
2. Click **Export to Excel** (or select CSV/PDF from the dropdown)
3. Choose **Basic columns** or **+ Create template** to select specific fields
4. Apply segment filters if you need a subset
5. Download the generated file

### Key Capabilities

- **Custom export templates** — select exactly which columns to include, including custom fields beyond the backup's 10-field limit
- **Segment filtering** — export subsets based on saved segments
- **Invoices** have four export paths: Excel, accounting CSV, PDF batch (max 250 invoices per batch), and bookkeeping application export. These cover outgoing invoices only. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25695218290321-Export-outgoing-invoices-from-Teamleader-Focus))
- **Insights reports** can be exported to Excel, CSV, or PDF, with applied filters and groupings reflected in the export
- **Export log** — Teamleader maintains an export log (accessible from your user icon) that records who exported what, when, and from which segment. You can re-download previous exports from this log.

### Limitations of Module Exports

- **No cross-module relationships.** You cannot export full company details and full linked-contact details in the same combined export. Each module must be exported separately. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25698010805521-How-can-I-select-the-fields-that-need-to-be-exported))
- **No ticket content.** The ticket module only exports ticket properties (status, assignee, dates) — not conversation threads.
- **Incoming invoices and expenses cannot be exported** from the UI.
- **Column names are locale-dependent.** Headers are translated to the language of the exporting user account. Date/time formats follow the company entity's locale. This can break column mappings if your target system expects a specific format.
- **No activity history.** Notes, call logs, and meeting summaries do not export in CRM entity CSVs.
- **No attachments.** Files uploaded to Deals or Contacts, as well as signed PDF Quotations, are excluded.
- **Custom field type fidelity varies.** Boolean fields export as 0/1 or true/false depending on locale. Multi-select fields collapse to semicolon-delimited strings. Date fields follow the account locale format (DD/MM/YYYY for Dutch/French accounts, MM/DD/YYYY for English), which can cause silent parse errors in target systems expecting ISO 8601.

> [!TIP]
> **Switch your user profile language to English before exporting** if your migration target expects English column headers. Dutch or French headers will break every column mapping downstream. Date formats also change with locale — English exports produce MM/DD/YYYY, while Dutch and French produce DD/MM/YYYY.

### Non-Technical Alternative: Zapier and Make Connectors

For teams without API scripting capability, Teamleader has native connectors on Zapier and Make (formerly Integromat). These are viable for extracting individual record types into Google Sheets or an intermediate database, but they operate at Zapier's own rate limits and task caps — not Teamleader's API limits — and cannot perform bulk historical exports efficiently. They are best suited for ongoing sync of new records, not archival of historical data.

## Method 3: Teamleader Focus API v2

For full-fidelity extraction — activity history, deal pipelines with all stages, ticket conversations, linked entity relationships, and binary files — the API is the only path.

### API Architecture

The Teamleader Focus API uses an **RPC-style design**, not REST. Every call is a `POST` request to `https://api.focus.teamleader.eu/resource.action`. All request parameters go in a JSON body, and all responses are JSON keyed under `data`. ([raw.githubusercontent.com](https://raw.githubusercontent.com/teamleadercrm/api/master/apiary.apib))

```bash
# Example: list contacts updated since a specific date
curl -X POST https://api.focus.teamleader.eu/contacts.list \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "updated_since": "2026-08-01T00:00:00+00:00"
    },
    "page": { "size": 100, "number": 1 },
    "sort": [{ "field": "updated_at", "order": "asc" }]
  }'
```

### Authentication (OAuth 2.0) and Required Scopes

Teamleader does not issue static API keys. The API strictly uses **OAuth 2.0**. You must register an application on the Teamleader Marketplace (it can be kept private) to get a `client_id` and `client_secret`. ([developer.focus.teamleader.eu](https://developer.focus.teamleader.eu/docs/authentication))

The flow:

1. Redirect users to the authorization URL with your client ID
2. User grants access, receives an authorization code
3. Exchange the code for an access token and refresh token
4. Access tokens expire — use the refresh token to obtain new ones
5. Refresh tokens are **single-use** — store the new refresh token returned with each access token

For long-running extractions (hours for large accounts), your script must handle automatic token refresh. A failed refresh mid-extraction means restarting from the last checkpoint.

**OAuth Scopes Required for Extraction**

Scope configuration is a common setup blocker. During OAuth authorization, you must request the specific scopes covering the data you intend to extract. Missing a scope causes silent gaps — endpoints return empty results or 403 errors rather than explaining what's missing.

| Data Category | Required Scope(s) |
|---|---|
| Contacts & Companies | `contacts` |
| Deals & Quotations | `deals` |
| Invoices & Credit Notes | `invoices` |
| Projects & Tasks | `projects` |
| Time Tracking | `timetracking` |
| Tickets & Messages | `tickets` |
| Products | `products` |
| Files | `files` |
| Custom Field Definitions | `customfields` |
| Calls & Activity | `calls` |
| Webhooks | `webhooks` |

Always request read scopes for every data category you intend to extract before starting a long-running job. Discovering a missing scope after 6 hours of extraction means re-authenticating and re-running.

### Rate Limits

Teamleader enforces rate limits **per client ID per Teamleader account**, using a **sliding window** of one minute. ([raw.githubusercontent.com](https://raw.githubusercontent.com/teamleadercrm/api/master/apiary.apib))

| Parameter | Value |
|---|---|
| Rate limit | **200 requests per minute** (sliding window) |
| Window type | Sliding — checked against calls in the last 60 seconds |
| Exceeded response | HTTP 429 Too Many Requests |
| Response headers | `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` |

If you have multiple integrations hitting the same account, each has its own 200-request budget. A single integration cannot exceed 200 calls/minute against one account.

Teamleader's documentation explicitly recommends `.list` endpoints, `updated_since` filters, webhooks, and sideloading (the `includes` parameter) before resorting to brute-force polling. Follow that advice — it matters at volume.

### Pagination

Pagination is page-number-based, controlled by `page.size` (max 100) and `page.number` (1-indexed). Responses include pagination metadata under `meta.page` and `meta.matches`. ([raw.githubusercontent.com](https://raw.githubusercontent.com/teamleadercrm/api/master/apiary.apib))

For a 200,000-contact account at max page size, you need at least 2,000 pagination requests just for contacts — consuming 10 minutes of rate limit budget at full speed.

### Key API Endpoints for Data Extraction

| Resource | List Endpoint | Info Endpoint | Notes |
|---|---|---|---|
| Contacts | `contacts.list` | `contacts.info` | Supports `updated_since` filter |
| Companies | `companies.list` | `companies.info` | Supports `updated_since` filter |
| Deals | `deals.list` | `deals.info` | Includes pipeline phase |
| Invoices | `invoices.list` | `invoices.info` | Separate draft vs booked |
| Credit Notes | `creditNotes.list` | `creditNotes.info` | Separate from invoices |
| Quotations | `quotations.list` | `quotations.info` | — |
| Projects | `projects.list` | `projects.info` | Check legacy vs. v2 model |
| Tasks | `tasks.list` | `tasks.info` | — |
| Time Tracking | `timeTracking.list` | — | No year restriction like backup |
| Tickets | `tickets.list` | `tickets.info` | — |
| Ticket Messages | `tickets.listMessages` | — | Returns HTML bodies |
| Products | `products.list` | `products.info` | — |
| Custom Fields | `customFieldDefinitions.list` | `customFieldDefinitions.info` | Global definitions |
| Files | `files.list` | `files.download` | Temporary download URL |
| Calls | `calls.list` | `calls.info` | Activity log |

Use the `includes` parameter on `.info` endpoints to sideload related data and reduce round-trips. Not all entities support all sideloads — check the API blueprint for the specific `includes` values supported per endpoint. Common supported sideloads include `custom_fields`, `tags`, `responsible_user`, and linked entity references. Sideloading reduces total request count but increases individual response payload size; test with your account's data volume before committing to a sideload strategy.

### API Error Handling

A production-grade extraction script must handle the full range of API error responses, not just 429. The following table maps status codes to their causes and correct recovery actions:

| HTTP Status | Cause | Recovery Action |
|---|---|---|
| `400 Bad Request` | Malformed JSON body or invalid parameter | Log the request body, fix the parameter, do not retry automatically |
| `401 Unauthorized` | Expired or invalid access token | Refresh the access token using the refresh token, then retry the request |
| `403 Forbidden` | Missing OAuth scope or insufficient permissions | Re-authenticate with the correct scopes; check that the authorizing user has admin rights |
| `404 Not Found` | Record deleted between list and info call | Log the missing ID, continue extraction; treat as soft-deleted record |
| `429 Too Many Requests` | Rate limit exceeded | Read `X-RateLimit-Reset` header, sleep until reset time, then retry |
| `500 Internal Server Error` | Teamleader-side error | Wait 30–60 seconds, retry up to 3 times with exponential backoff; log if persistent |
| `503 Service Unavailable` | Planned or unplanned maintenance | Check Teamleader status page, retry after several minutes |

> [!CAUTION]
> **Handle 429 responses correctly.** Do not blindly retry — check `X-RateLimit-Reset` to calculate the actual wait time. Continuous polling while rate-limited can result in temporary API bans. For 401 responses, note that refresh tokens are single-use: store the new refresh token returned with each token refresh or you will be permanently locked out of that session.

### Extraction Script Pattern

```python
import requests
import time
from datetime import datetime

BASE_URL = "https://api.focus.teamleader.eu"

def refresh_access_token(client_id, client_secret, refresh_token):
    """Single-use refresh tokens: store the new refresh_token returned."""
    resp = requests.post(f"{BASE_URL}/oauth2/access_tokens", json={
        "client_id": client_id,
        "client_secret": client_secret,
        "refresh_token": refresh_token,
        "grant_type": "refresh_token"
    })
    resp.raise_for_status()
    data = resp.json()
    # CRITICAL: store data["refresh_token"] — it is now the only valid refresh token
    return data["access_token"], data["refresh_token"]

def extract_all(resource, access_token, filters=None, includes=None):
    all_records = []
    page = 1
    while True:
        body = {"page": {"size": 100, "number": page}}
        if filters:
            body["filter"] = filters
        if includes:
            body["includes"] = includes

        resp = requests.post(
            f"{BASE_URL}/{resource}.list",
            headers={
                "Authorization": f"Bearer {access_token}",
                "Content-Type": "application/json"
            },
            json=body
        )

        if resp.status_code == 429:
            reset = int(resp.headers.get("X-RateLimit-Reset", time.time() + 60))
            wait = max(reset - int(time.time()), 1)
            print(f"Rate limited. Waiting {wait}s.")
            time.sleep(wait)
            continue

        if resp.status_code == 401:
            # Token expired — caller must refresh and retry
            raise TokenExpiredError("Access token expired. Refresh and retry.")

        if resp.status_code == 403:
            raise PermissionError(
                f"Scope missing for {resource}. Re-authenticate with correct scopes."
            )

        if resp.status_code == 404:
            print(f"Record not found on page {page} of {resource}. Skipping.")
            break

        if resp.status_code >= 500:
            # Exponential backoff for server errors
            for attempt in range(3):
                time.sleep(30 * (2 ** attempt))
                retry = requests.post(...)
                if retry.status_code < 500:
                    resp = retry
                    break
            else:
                raise RuntimeError(f"Persistent 5xx error on {resource} page {page}")

        resp.raise_for_status()
        data = resp.json().get("data", [])
        all_records.extend(data)

        if len(data) < 100:
            break
        page += 1

    return all_records

class TokenExpiredError(Exception):
    pass
```

## Extracting Complex Data Types

Standard fields like `first_name` and `email` are straightforward. The complexity lies in custom fields, activities, files, tickets, and financial documents.

### Custom Fields

The API does not return custom fields as simple key-value pairs with human-readable names. Instead, it returns an array of objects containing the custom field UUID and the value:

```json
"custom_fields": [
  {
    "id": "e2314b11-4567-4c89-a234-9876543210ab",
    "value": "Tier 1 Enterprise"
  }
]
```

Your script must first call `customFieldDefinitions.list` to retrieve the schema mapping each UUID to its actual field name and type. Build a local dictionary to translate these IDs before writing to your target format.

**Custom field type handling during export:**

| Field Type | API Value Format | CSV Export Representation | Notes |
|---|---|---|---|
| String / Text | `"value": "Acme Corp"` | Plain string | No transformation needed |
| Boolean | `"value": true` | `true`/`false` or `1`/`0` depending on locale | Normalize to a consistent format before loading |
| Date | `"value": "2024-03-15"` | ISO 8601 in API; locale-formatted in CSV | Always use API over CSV for dates |
| Dropdown (single-select) | `"value": {"id": "...", "label": "Gold"}` | Label string | ID is the stable reference; label can change |
| Multi-select | `"value": [{"id": "...", "label": "A"}, ...]` | Semicolon-delimited labels in CSV | Reconstruct as array from API response |
| Numeric | `"value": 42.5` | Locale-formatted number (comma vs. period decimal) | Use API response, not CSV, for numerics |

Record exports capture values but not field context or option sets — export `customFieldDefinitions.list` separately and store it alongside your data.

### Files and Attachments

Files require a two-step process:

1. Fetch metadata using `files.list` (filtered by subject type and subject ID) to get file IDs.
2. Call `files.download` with the specific ID — this returns a temporary URL with an expiry timestamp. Execute a standard HTTP GET to that URL to download the binary file.

If you cannot use the API for files, Teamleader's **Dropbox integration** can bulk sync documents from companies and contacts. But it comes with a delay of up to one hour, a 50 MB per-file limit, and non-symmetric delete behavior. That makes it a workaround, not a clean archival strategy. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25692160411409-How-To-How-do-I-link-Dropbox-to-Teamleader-Focus))

### Ticket Conversations

The backup and UI exports only give you ticket properties (status, assignee, dates) — not the actual conversation content. (This is a nearly universal limitation across support platforms; you will encounter the exact same API requirement when [exporting data from Freshdesk](https://clonepartner.com/blog/blog/how-to-export-data-from-freshdesk-methods-api-limits-mapping) or [Zoho Desk](https://clonepartner.com/blog/blog/how-to-export-data-from-zoho-desk-methods-limits-api-guide)). The API fills this gap: `tickets.listMessages` returns message bodies as HTML and lets you filter by `customer`, `internal`, or `thirdParty` message type. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25690929430545-How-can-I-create-a-backup-of-my-data))

### Invoices and Financial Documents

Extracting JSON metadata (amounts, line items, tax rates) is not enough for tax compliance — you must also extract the generated PDFs. Query `invoices.list` to get Invoice IDs, then use the download endpoint to retrieve the actual PDF. Name files according to invoice number for auditability.

Invoice batch PDF exports via the UI are capped at **250 invoices per batch**. For larger invoice volumes, the API is the only option. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25695218290321-Export-outgoing-invoices-from-Teamleader-Focus))

> [!CAUTION]
> **Credit Notes and Invoices are separate endpoints.** Do not assume extracting invoices captures your refunds or adjustments. You must explicitly extract `creditNotes.list` to maintain an accurate financial ledger.

### Projects: Legacy vs. v2

This is the sharpest edge in Teamleader data extraction. Teamleader now has **legacy project endpoints** under `/projects` and a **newer project model** under `/projects-v2`. The new Projects module is incompatible with legacy project endpoints, so your first technical task is to confirm which project model the account uses before writing a single extractor. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25695894567953-New-Teamleader-Focus-API-Project-Endpoints))

To identify which model an account uses: attempt a call to `projects-v2.list`. If it returns data, the account is on the new model. If it returns an empty result or 404 while `projects.list` returns data, the account is on the legacy model. Do not skip this check — using the wrong endpoints produces silently empty results, not errors.

## What You Cannot Export from Teamleader

Some data categories have no export path — not via UI, not via backup, and not via the API:

- **Automation rules and workflow configurations** — no API endpoint, no export
- **Email templates** — no bulk export
- **Integration settings and connected app configurations** — not exportable
- **Targets** — no export of any kind
- **Incoming invoices and expenses** — not exportable via UI; check API availability for your use case

## Delta Exports and Migration Cutover

If you're migrating an active team, you cannot freeze operations while you extract and load data. You need a delta sync strategy.

Most `.list` endpoints support an `updated_since` filter. The practical approach:

1. **Day 1:** Run a full historical extraction up to `Timestamp A`.
2. **Days 2–5:** Load the historical data into your new system.
3. **Day 6 (Cutover):** Run a delta extraction querying `filter.updated_since = Timestamp A`. This fetches only records created or modified during your load window, allowing you to sync the final changes in minutes instead of hours.

Teamleader also supports **webhooks** for real-time event notifications (`contact.added`, `deal.updated`, `invoice.booked`, `project.closed`, etc.), registered via the `webhooks.register` API endpoint. Webhook payloads contain the event type, a timestamp, and the ID of the affected resource — they do not include the full record payload. Your handler must call the corresponding `.info` endpoint to fetch current state. Teamleader does not publish explicit delivery guarantees or retry counts for webhooks, so treat them as a supplement to polling, not a replacement, for migration cutover.

## Export Time Estimates by Account Size

These estimates are theoretical maximums derived from the 200 req/min rate limit and 100-record page size. Actual times vary based on network latency, token refresh overhead, error retries, and the number of modules being extracted.

| Account Size | Backup (ZIP) | API Full Extraction |
|---|---|---|
| Small (< 5,000 records) | 5–15 minutes | 1–2 hours |
| Medium (5,000–50,000 records) | 15–45 minutes | 4–8 hours |
| Large (50,000–200,000 records) | 45–120 minutes | 12–24+ hours |

API estimates assume extraction of contacts, companies, deals, invoices, projects, tasks, time tracking, and custom fields. Accounts extracting ticket conversations, files, and activity logs will push toward or beyond the upper bound.

## Data Portability and Contract End

Teamleader is a Belgian company (headquartered in Ghent, owned by Visma since 2022), built primarily for European SMBs. GDPR compliance is central to its design.

**Key portability facts:**

- **Export before the subscription ends.** Teamleader's Terms state that post-subscription exports may require Teamleader's assistance and can incur cost. ([teamleader.eu](https://www.teamleader.eu/legal/terms-of-service-teamleader-focus))
- The Focus DPA says data is first **soft-deleted for 30 calendar days** after subscription end, then **hard-deleted no earlier than 30 days and no later than three months** after the subscription ends.
- Once hard-deleted, Teamleader cannot restore your account or provide an export.

The backup and module export features satisfy GDPR's right to data portability for structured CRM data. But the gaps — no ticket content export, no file bulk download, activity history excluded from backup — mean you don't get everything in a portable format without the API.

## Common Extraction Pitfalls

**Custom fields silently truncated in backup.** The backup only exports the first 10 custom fields per module (alphabetically). If your contacts have 30 custom fields, 20 are silently missing. Always compare your custom field count (Settings > Custom Fields) against the backup output before concluding the backup is sufficient.

**Missing OAuth scopes cause silent data gaps.** If your OAuth application was authorized without the `tickets` scope, `tickets.list` returns empty results without any error indicating the cause. Audit required scopes for every data category before starting extraction.

**Token expiration mid-extraction.** OAuth access tokens expire, and refresh tokens are single-use. For large accounts where extraction takes hours, your script must handle token refresh automatically — and store the new refresh token returned with each access token. Losing the current refresh token requires re-authorizing the OAuth application from scratch.

**Locale-dependent column names and date formats.** Module exports translate column headers to the user's language and format dates according to the account locale (DD/MM/YYYY for Dutch/French, MM/DD/YYYY for English). Export from a Dutch-language account and your English-expecting target system will choke on every column mapping and silently misparse every date.

**Multi-select custom fields collapse in CSV.** Multi-select fields are exported as semicolon-delimited strings in CSV exports. If your target system expects an array or uses a different delimiter, this causes data loss or import errors. Use the API response to get structured arrays.

**No relational links in CSV.** Backup CSVs are flat. A contact CSV has no foreign key linking to the related company CSV. Relationships between entities only exist in API responses as UUID references. If entity relationships matter for your migration — and they almost always do — the API is the only viable extraction path.

**Project endpoint mismatch.** Using legacy project API endpoints against an account that has migrated to the new Projects module will return silently empty data. Confirm which model the account uses before writing extractors.

**Assuming export and import are symmetric.** They are not. Teamleader's own import matrix shows some modules are CSV-importable, some are support-assisted, and some are API-only. ([support.focus.teamleader.eu](https://support.focus.teamleader.eu/hc/en-150/articles/25692762276113-Getting-started-What-data-can-I-import))

## Choosing the Right Extraction Strategy

| Scenario | Recommended Method |
|---|---|
| Quick backup for safekeeping | Native backup ZIP |
| One-time export of a specific module | Module-level UI export with custom template |
| Full CRM migration with relationships | API v2 (scripted extraction) |
| Ongoing data sync to another platform | API v2 + Webhooks |
| Archival of invoice PDFs | Invoices > Batch export to PDF (250/batch) + API for larger volumes |
| File/document archival | API `files.download` or Dropbox sync |
| Non-technical user, new records only | Zapier or Make connector to Google Sheets |
| Complete audit before contract end | API v2 extraction + backup ZIP for cross-validation |

For migrations, run both the backup ZIP and an API extraction in parallel. The backup serves as a cross-validation baseline — if your API extraction of contacts returns 15,000 records but the backup CSV has 15,003 rows, you have a discrepancy to investigate before loading into the target system.

## When to Bring in Help

Teamleader Focus extraction is tractable for teams with API scripting experience. The data model is relatively flat compared to enterprise CRMs, and the API is well-documented for the endpoints that exist.

Certain scenarios add meaningful complexity:

- **Accounts near the 200K hard cap** where extraction takes 24+ hours and rate-limit management, token refresh orchestration, and checkpoint/resume logic become the primary engineering problems
- **Cross-module relational integrity** — rebuilding contact→company→deal→invoice chains from flat API responses requires careful orchestration and UUID mapping before any data loading begins
- **Mixed legacy and v2 project models** — accounts that partially migrated to the new Projects module may have data split across both endpoint families
- **Target-side constraints** — the export is only half the problem. Every target CRM (Nutshell, HubSpot, Salesforce, Pipedrive) has its own import limits, field-type mismatches, and data model differences that must be resolved before loading
- **Timeline pressure** — if your Teamleader contract is ending and data deletion is imminent, there is no margin for iterative debugging of a novel extraction script

At ClonePartner, we handle Teamleader Focus extractions as part of CRM migrations. We manage the OAuth lifecycle, scope configuration, rate-limit orchestration, relational integrity, error recovery, and target-side loading.

> Need to get your data out of Teamleader Focus — cleanly, completely, and on a deadline? Our engineers handle the full extraction and migration pipeline, from API scripting to target-side loading. Book a 30-minute call to scope your project.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I export all my Teamleader data in one click?

Not fully. Teamleader offers an account backup ZIP, but it excludes activity tracking, ticket content, generated documents, files, expenses, timesheets older than one year, and custom fields beyond the first 10 per module. A complete export requires combining the backup with module-level UI exports and API extraction.

### What is the Teamleader Focus API rate limit?

The Teamleader Focus API v2 allows 200 requests per sliding minute per integration (client ID) per account. Exceeding this returns HTTP 429. Use the X-RateLimit-Remaining and X-RateLimit-Reset response headers to manage pacing.

### How do I export ticket conversations from Teamleader Focus?

The native backup and module exports only export ticket properties (status, assignee, dates) — not the conversation body. Use the API v2 tickets.listMessages endpoint, which returns message bodies as HTML and supports filtering by customer, internal, or thirdParty message type.

### Why are custom fields missing from my Teamleader backup?

The backup exports only the first 10 custom fields per module, sorted alphabetically. If you have more than 10 on any entity, the rest are silently dropped. Use module-level exports with a custom template, or the API's customFieldDefinitions.list endpoint for the full schema.

### How long does a full Teamleader API extraction take?

For a small account (under 5,000 records), expect 1–2 hours. Medium accounts (5,000–50,000 records) take 4–8 hours. Large accounts approaching the 200,000 record cap can take 12–24+ hours, all constrained by the 200 requests per minute rate limit and 100 records per page maximum.
