How to Export Data from Kayako: API Limits, Methods & Portability
Kayako has no bulk export button. Learn every extraction method — REST API, custom reports, MySQL dump — plus rate limits, archived ticket traps, and code.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
How to Export Data from Kayako: API Limits, Methods & Portability
Kayako has no one-click bulk export for most data types. There is no "Export All" button in the admin panel. Your realistic options are: extract via the REST API (JSON for Cloud, XML for Classic), generate custom reports as CSV, or work with a MySQL data dump. Each method has hard limits on what it returns, and the biggest silent risk is that the default API endpoint omits archived tickets entirely.
This guide covers every extraction method, their specific limitations, the API constraints you will hit, and how to plan a complete data pull — whether you are migrating to another helpdesk, archiving for compliance, or building a data warehouse.
Last verified: June 2025. Kayako Classic is in maintenance mode at version 4.98.9; Kayako Cloud is actively updated. Verify endpoint behavior against current Kayako documentation for post-2025 changes.
Which Kayako Version Are You Running?
Before you export anything, identify your version. The export paths differ significantly between Kayako Cloud and Kayako Classic (On-Premise).
To identify which version you're using, head to the Admin Control Panel and check under API > Settings. If the URL contains api/index.php, you're likely using Kayako Classic.
| Kayako Cloud | Kayako Classic (On-Premise) | |
|---|---|---|
| API style | Resource-based REST (/api/v1/cases) |
Controller-based (/api/index.php?e=/Tickets/Ticket) |
| Response format | JSON | XML |
| Auth method | Basic Auth (email:password) or OAuth | HMAC-SHA256 (API key + secret key) |
| Direct DB access | No — must request dump | Yes — mysqldump directly |
| Rate limits | Per-minute (undisclosed threshold; ~100–150 req/min observed in practice) | No rate limits |
| Latest version | Actively updated | 4.98.9 (maintenance mode) |
| OAuth token expiry | Access tokens expire; refresh token flow required | N/A |
Kayako Classic uses XML and the REST API, while the Cloud version uses JSON. Everything below applies to both unless stated otherwise.
What Data Can You Export from Kayako?
Kayako's data model centers on Cases (tickets/conversations), Users, Organizations, Messages (posts within cases), Notes, Attachments, Help Center Articles, Custom Fields, Tags, Teams, and SLAs.
| Object | Custom Reports (CSV) | REST API | MySQL Dump | Key Limitation |
|---|---|---|---|---|
| Cases / Conversations | Yes (filtered) | GET /api/v1/cases.json |
Yes | Default call excludes archived cases (closed 30+ days) |
| Messages / Posts | Partial (preview only) | GET /api/v1/cases/:id/messages.json |
Yes | Must fetch per-case; no bulk messages endpoint |
| Notes | No | GET /api/v1/cases/:id/notes.json |
Yes | Per-case only; no bulk export |
| Users | No | GET /api/v1/users.json |
Yes | Default returns 10 per page |
| Organizations | No | GET /api/v1/organizations.json |
Yes | Default returns 10 per page |
| Attachments | No | Via message objects + file download | Yes | Must specify "database with attachments" for Cloud dumps; no max size documented |
| Help Center Articles | No | GET /api/v1/sections/:id/articles.json |
Yes | No direct bulk endpoint; must iterate by section |
| Custom Fields | No | GET /api/v1/cases/fields.json |
Yes | Need ?include=* for dropdown options |
| SLAs | No | GET /api/v1/slas.json |
Yes | Read-only; config cannot be migrated programmatically |
| Tags | No | Included in case objects | Yes | No standalone tags endpoint in Cloud |
| Macros / Automations | No | No | Partial (raw DB tables) | Must be rebuilt manually in any target system |
The archived ticket trap: The default API will not include archived cases (cases that were closed 30 days ago or longer). To include archived cases, add &archived=1 at the end of the request. If you skip this parameter, you silently lose your entire closed-ticket history. This is the single most common cause of incomplete Kayako migrations.
Method 1: Kayako REST API Extraction
The API is the most flexible extraction method. It gives you per-object control over what you pull and when. This is an engineering task requiring script orchestration, pagination handling, and rate-limit pacing.
Authentication
Kayako Cloud uses Basic Auth. Pass your agent or admin credentials encoded as Base64 in the Authorization header:
curl -u 'admin@example.com:yourpassword' \
https://DOMAIN.kayako.com/api/v1/cases.json?limit=100For OAuth on Kayako Cloud: access tokens are short-lived and require a refresh token flow. Kayako supports the following scopes: users, conversations, insights, search, and configuration. Each scope can be specified with an access level of read or write. For a full export, request read access on all five scopes. Token expiry behavior is not publicly documented; implement token refresh logic defensively.
Kayako Classic uses HMAC-SHA256 signature authentication. You need your API key and secret key from the Admin Panel:
$apiKey = "your-api-key";
$secretKey = "your-secret-key";
$salt = mt_rand();
$signature = base64_encode(hash_hmac('sha256', $salt, $secretKey, true));
// Append ?apikey={$apiKey}&salt={$salt}&signature={$signature}Pagination Defaults and Limits
This is where most DIY exports break. By default the API returns the first 10 items of a collection. This can be changed with the limit argument. You can set limit=100 to fetch up to 100 records per page.
Kayako Cloud supports three pagination styles:
- Offset-based (default):
?limit=100&offset=0, then incrementoffsetby 100. - Cursor-based: Use
before_idandafter_idparameters, relative to the current page. - Date-based: Use
sinceanduntilparameters for time-windowed iteration. Both parameters accept ISO 8601 format (e.g.,since=2024-01-01T00:00:00Z). Unix epoch timestamps are not accepted.
Be aware that total_count can change between pages if records are created or deleted during iteration — treat the export as a point-in-time snapshot.
Offset pagination degradation: In practice, offset-based queries against large Kayako Cloud instances begin returning degraded response times at approximately offset=10,000–20,000, and 502/504 Gateway Timeout errors become common at offset=50,000+. For instances with more than 50,000 cases, switch to date-based pagination (since/until) to chunk the dataset into smaller time windows rather than relying on deep offsets. Cursor-based pagination (after_id) is more stable than offset-based at scale.
Extracting Cases (Tickets)
The core endpoint for ticket export:
# Active cases only (default) — INCOMPLETE
GET /api/v1/cases.json?limit=100&offset=0
# Include archived cases — REQUIRED for complete export
GET /api/v1/cases.json?limit=100&offset=0&archived=1By default, API calls will return a total of 10 results per page. You can add the limit argument to increase the number of results — for example, retrieve up to 100 conversations by setting a limit of 100.
Each case object returns metadata (subject, status, priority, assignee, timestamps, custom fields, tags) but not the full message bodies. Messages must be fetched separately per case.
You can retrieve all cases using GET /api/v1/cases.json, but you can also use GET /api/v1/conversations.json. The difference is in permissions — the /conversations endpoint is also accessible by Customer accounts, while /cases requires Collaborator, Agent, or Admin roles.
Extracting Messages and Notes Per Case
Messages (the conversation thread) and notes (internal) are fetched per-case:
# Messages for a specific case
GET /api/v1/cases/{case_id}/messages.json?limit=100
# Notes for a specific case
GET /api/v1/cases/{case_id}/notes.json?limit=100This means a full export of 50,000 cases requires at minimum 50,000 additional API calls for messages — plus another 50,000 for notes. For a help desk with 100,000 tickets, you are looking at over 200,000 API calls just for the conversation content. At a conservative rate of 1 request/second with rate-limit pauses, this is approximately 55–60 hours of sequential API execution. Parallelizing to 10 concurrent workers reduces this to 6–8 hours, but increases 429 exposure.
Extracting Users and Organizations
There's no built-in feature in Kayako that would allow you to export users and organizations directly into a document file. However, you can use the API to retrieve the information in JSON format.
# Users with all related fields
GET /api/v1/users.json?include=*&fields=full_name,role,emails,organization&limit=100
# Organizations
GET /api/v1/organizations.json?limit=100By default, these calls will return a total of 10 results per page. Always override with limit=100 and paginate using offset. Passwords are hashed and salted — you cannot export plain-text passwords. Users will need to reset credentials on your new platform.
Extracting Custom Fields
Custom field definitions are available at:
GET /api/v1/cases/fields.json?include=*When you run an API call, you receive only the field ID without the required options. The options for custom fields can be viewed with the include=* parameter.
The API field key for any custom field is auto-generated by the system and cannot be edited. Once the custom field is created, this key is generated. Even if you change the custom field name, the API field key remains the same. This matters for mapping during migration — always reference the immutable key value (e.g., cf_0001), not the display title, when building your field mapping schema.
Extracting Help Center (KB) Articles
There's no built-in feature in Kayako that would allow you to export Help Center articles directly into a document file. However, you can use the API to retrieve the information in JSON format.
The extraction requires a two-step approach:
- Get all section IDs:
GET /api/v1/sections.json - Get articles per section:
GET /api/v1/sections/{section_id}/articles.json
There is no flat "get all articles" endpoint. You must iterate through every section. Article bodies are returned as HTML; inline images referenced in src attributes are not returned as structured attachment objects — you must parse the HTML, extract each image URL, and download them as separate authenticated requests.
Extracting Attachments via API
Attachments present a distinct challenge. The API does not serve binary files directly in the JSON response. The messages endpoint returns metadata about attachments, including an access URL.
To extract attachments:
- Parse the JSON response for every message.
- Identify the
attachmentsarray. - Extract the download URL for each file.
- Execute an authenticated
GETrequest to that URL to download the binary. - Map the downloaded file back to the specific case and message IDs so it can be re-associated during import to your target system.
There is no documented maximum attachment size limit for API downloads. In practice, very large attachments (500MB+) may time out on slow connections. For instances with large attachment libraries, the MySQL dump method (requesting "database with attachments") is more reliable than per-file API download.
If you are migrating to a platform like Zendesk, you will need to upload these binaries to the target system first, generate a target token, and pass that token into the ticket creation payload. See our guide on Kayako to Zendesk migration for specific mapping details.
Method 2: Custom Reports (CSV Export)
Custom reports give you direct access to all of your conversation and customer data, and make it available for quick export into a CSV spreadsheet.
This is the only method that gives you a downloadable file directly from the Kayako UI without any engineering work.
How it works:
- Go to Insights > Reports in Kayako.
- Create a new report with filter conditions (status, date range, assignee, tags).
- Save and run the report.
- When the report is ready, you'll get an email, and the label will change to show it's ready. Hover over the report and click the Download link to save a copy of the CSV file.
Limitations:
- Reports export conversation-level metadata (subject, status, assignee, timestamps) — not full message bodies or attachments.
- CSV exports include only the columns you selected in the report builder.
- No option to include attachment binaries.
- Large reports can take significant time to generate and may time out.
- No relational integrity — you get a flat file. Rebuilding the relationship between a user, their organization, and their tickets in another system using this CSV is error-prone.
- Does not respect the
archived=1concept — archived ticket inclusion depends entirely on the filter conditions you set in the report builder.
Custom reports are useful for quick audits or filtered snapshots. They are not sufficient for a full migration.
Method 3: MySQL Database Dump
The most complete extraction method. It gives you everything — every table, every record, every attachment (if requested). It is also the only method that provides full message bodies, internal notes, and attachments in a single operation.
Kayako Cloud: Request from Infrastructure Team
If you no longer want to use Kayako or need a full backup, the request is processed by the Infrastructure team. They can only generate the backup file in a MySQL dump file format. Requests to get the backup in CSV format are not possible.
What you need to provide:
- The name of the instance you are requesting backup for (e.g., sniper.kayako.com).
- Whether you want database only, attachments only, or database with attachments.
- If the requester is not a recognized billing contact, provide the billing email address and mailing address registered in the account.
Hard constraints on Cloud data dumps:
- You cannot request a backup from a specific date in the past. Only current data.
- Download links expire within 1–7 days.
- MySQL format only — no CSV, no JSON.
- Kayako does not assist in converting the file from MySQL to CSV.
Kayako Classic: Direct mysqldump
Self-hosted installations allow direct access to the MySQL database. You can run mysqldump yourself:
mysqldump -u [username] -p[password] [database_name] > kayako_backup.sqlYou can find the necessary credentials in the config/config.php file.
Kayako Classic Schema: Key Tables
Kayako Classic's database schema is complex. The following are the primary tables for a complete help desk export:
| Table | Contents | Primary Key | Key Foreign Keys |
|---|---|---|---|
swtickets |
Ticket metadata (subject, status, priority, timestamps) | ticketid |
userid, departmentid |
swticketposts |
Individual replies/messages within a ticket | ticketpostid |
ticketid, userid |
swticketposttexts |
Full message body text (separate from post metadata) | ticketpostid |
ticketpostid |
swattachments |
Attachment metadata (filename, size, MIME type) | attachmentid |
ticketpostid |
swattachmentchunks |
Actual binary data, stored in chunks | attachmentchunkid |
attachmentid |
swusers |
Customer/end-user records | userid |
organizationid |
swuserorganizations |
Organization records | organizationid |
— |
swnotes |
Internal notes on tickets | noteid |
ticketid |
swcustomfieldvalues |
Custom field values per ticket | customfieldvalueid |
ticketid, customfieldid |
swcustomfields |
Custom field definitions | customfieldid |
customfieldgroupid |
swkbarticles |
Knowledge base article content | kbarticleid |
kbcategoryid |
swkbcategories |
KB category structure | kbcategoryid |
parentkbcategoryid |
swtags |
Tag definitions | tagid |
— |
swtaglinks |
Tag-to-ticket associations | taglinkid |
tagid, ticketid |
Note: Table name prefixes may vary by installation if a custom prefix was set during installation. Check
config/config.phpfor theTABLE_PREFIXconstant.
In Kayako Classic, attachments are stored directly in the database as BLOBs chunked across swattachmentchunks. Extracting these requires stitching chunks together in sequence order:
-- Retrieve all chunks for a specific attachment, in order
SELECT
ac.attachmentchunkid,
ac.contents,
ac.notesid
FROM swattachmentchunks ac
WHERE ac.attachmentid = [your_attachment_id]
ORDER BY ac.attachmentchunkid ASC;
-- Example query to identify attachments linked to specific tickets
SELECT
a.attachmentid,
a.filename,
a.filesize,
a.filetype,
t.ticketid,
t.displayid
FROM swattachments a
JOIN swticketposts p ON a.ticketpostid = p.ticketpostid
JOIN swtickets t ON p.ticketid = t.ticketid
WHERE t.ticketid = [your_ticket_id];Parsing the raw MySQL schema requires database expertise. Once you have the dump, you will need to write a translation layer — usually in Python or Node.js — to map the relational SQL data into the specific JSON payloads required by your new help desk's API.
If you are moving to Freshdesk, see our Kayako to Freshdesk Migration Engineering Guide for specific mapping strategies.
When to Use API vs. MySQL Dump
| Scenario | Recommended Method |
|---|---|
| < 10K cases, no attachments | API extraction |
| Incremental sync / delta before cutover | API extraction |
| > 50K cases with attachments | MySQL dump |
| Complete archive for compliance | MySQL dump (database + attachments) |
| Selective export (date range, status) | Custom reports (CSV) or API with filters |
| Full migration to another helpdesk | MySQL dump + API for delta sync |
| Need message bodies without engineering | Not possible — no UI export for message content |
Use the API when you need controlled extraction, interval syncs, or a final delta before cutover. Use a MySQL dump when completeness and volume matter more than convenience — especially for archived or attachment-heavy instances.
API Rate Limits and Throttling
Kayako Cloud
API rate limits are defined per minute. Kayako does not publicly disclose the exact threshold per endpoint. Based on observed behavior across multiple large-scale extractions, throttling typically begins around 100–150 requests per minute. Treat this as an empirical guideline, not a guaranteed limit.
If you exceed the rate limit, you get HTTP 429 Too Many Requests with a Retry-After header holding the number of seconds before you can send your request again.
Common Kayako API error responses:
| HTTP Status | Meaning | Recommended Handling |
|---|---|---|
429 Too Many Requests |
Rate limit exceeded | Read Retry-After header, sleep, retry |
401 Unauthorized |
Invalid credentials or expired token | Re-authenticate; refresh OAuth token |
403 Forbidden |
Insufficient scope for the resource | Verify OAuth scopes include required permission |
404 Not Found |
Resource does not exist or was deleted | Skip and log; do not retry |
502 Bad Gateway |
Server-side timeout (common at deep offsets) | Retry with exponential backoff (max 3 attempts); switch to date-based pagination if persistent |
504 Gateway Timeout |
Deep offset pagination overload | Switch pagination strategy; reduce offset window |
In practice:
- Implement exponential backoff on 429 responses
- Respect the
Retry-Afterheader value - Start at 1 request/second and increase only after confirming no throttling
- You can ask the Kayako support team to increase the rate limit if you believe it's too narrow.
Kayako Classic
There's no API rate limit implemented in Kayako Classic. You won't see any API request failing due to the rate limit. That said, hammering a self-hosted instance too aggressively will degrade performance for live agents. In practice, pacing to 5–10 concurrent requests is safe for most Classic installations without impacting live traffic meaningfully.
Kayako Export Edge Cases and Gotchas
These are the traps that cause data loss or wasted engineering time:
-
Archived ticket omission. Default
/api/v1/casescalls skip cases closed 30+ days ago. Always append&archived=1. This is not documented prominently in the Kayako UI. -
No PATCH endpoint. Kayako does not have a dedicated PATCH endpoint; use PUT and include all required fields to avoid unintended data loss. This is relevant if you are updating or cleaning records during export.
-
total_countdrift. During pagination, the total count shifts if records are created or deleted mid-export. Do not rely ontotal_countas a stop condition. Stop when the API returns an emptydataarray. -
Messages are per-case only. No global "get all messages" endpoint exists. At 100,000 tickets with an average of 5 messages each, you are making 500,000 API calls just to fetch message content.
-
Attachment downloads are separate binary operations. Attachment metadata appears in message objects, but binary files require separate authenticated download calls. Budget extra time and bandwidth proportional to total attachment storage size.
-
Help Center articles lack a flat endpoint. Discover section IDs first, then fetch articles per section. Articles with no folder (in Classic) can cause hierarchy import failures on platforms with strict category requirements (e.g., Zendesk requires at least one category).
-
Custom field keys are immutable. The API field key for any custom field is auto-generated by the system and cannot be edited. Map these keys — not display names — when building your migration schema. Display names can change; keys cannot.
-
No outbound webhooks for change detection. The lack of outbound webhooks means user lifecycle events cannot be pushed to downstream systems — polling GET requests on a schedule is the only detection mechanism.
-
Knowledge Base inline images. KB article bodies are HTML. Inline images referenced in
srcattributes require parsing and separate authenticated download. They will not appear in the API response as structured attachment objects. -
ISO 8601 timestamp format required. The
sinceanduntilpagination parameters require ISO 8601 format (e.g.,2024-01-01T00:00:00Z). Passing Unix epoch integers or other formats will return unexpected results or errors. -
Classic attachment chunk reassembly. In Kayako Classic, binary attachments stored in
swattachmentchunksmust be queried byattachmentid, sorted byattachmentchunkidascending, and concatenated before writing to disk. Missing a chunk or writing them out of order produces a corrupt file.
Data Portability Gaps: What You Cannot Export
Regardless of whether you use the API or a database dump, certain configurations are locked inside Kayako and cannot be programmatically exported or migrated:
- Macros and Canned Responses: Text content can be extracted, but trigger logic (e.g., "If ticket status is X, apply tag Y and send this text") cannot be automatically translated to another system's automation format.
- SLAs and Automations: Time-based rules and event triggers must be rebuilt manually in the target platform. There is no standard interchange format for helpdesk automation logic.
- Custom Views: Agent workspaces and filtered views do not export via any method.
- Passwords: User and agent passwords are hashed and salted. No plain-text export is possible or intended. All users must reset credentials post-migration.
- Reporting Dashboards: Insights reports and dashboard configurations are not exportable. Raw data is accessible via API or dump, but visualization configurations are lost.
GDPR and Data Portability Requests
If you handle EU customer data, GDPR Article 20 gives data subjects the right to receive their personal data in a "structured, commonly used and machine-readable format."
Kayako does not offer a self-service "Download My Data" button for end-users. To fulfill a data portability request, you need to:
- Use the API to extract the specific user's data:
GET /api/v1/users/{id}plus their associated cases (GET /api/v1/cases.json?requester_id={user_id}&archived=1) and messages. - Convert the JSON output to CSV or another portable format.
- Deliver it within the GDPR-mandated timeframe (one calendar month, extendable by two additional months for complex requests per Article 12(3)).
Important: Kayako's MySQL dump is a full-instance dump containing every user's data. It cannot be scoped to a single data subject. You must use the API for per-user GDPR extractions. Do not deliver a full instance dump in response to a GDPR Article 20 request.
Kayako does not document a data retention policy for archived tickets. There is no confirmed expiry date after which archived tickets are purged. For compliance-critical use cases, treat archived data as potentially at risk and export it proactively with &archived=1.
Planning Your Extraction Architecture
If you are exporting data for a permanent migration, a one-time extraction is insufficient. Live support teams cannot freeze operations for days while scripts run.
A production-grade extraction requires a three-phase delta sync architecture:
- Historical Extraction: Pull all tickets closed older than 30 days using
archived=1. For large instances (50K+ tickets), use MySQL dump for this phase. - Active Extraction: Pull all currently open, pending, and recently closed tickets via API. Use date-based pagination (
since/untilin ISO 8601) to bound the window. - Delta Extraction: A script running on a schedule (every 15–60 minutes) querying Kayako for records updated since the timestamp of the last successful extraction (
since={last_run_timestamp}). Continue until the moment of cutover.
The delta phase captures all replies made while your bulk migration is running, ensuring zero conversation data loss at cutover. The since parameter is your primary tool here.
Approximate time estimates for API extraction (sequential, 1 req/sec):
| Instance Size | Cases | Estimated API Calls | Estimated Duration (Sequential) | With 10 Workers |
|---|---|---|---|---|
| Small | 5,000 | ~15,000 | ~4 hours | ~30 min |
| Medium | 25,000 | ~75,000 | ~21 hours | ~2.5 hours |
| Large | 100,000 | ~300,000 | ~83 hours | ~9 hours |
| Very Large | 500,000 | ~1,500,000 | ~17 days | ~40 hours |
For instances above 50,000 cases, the MySQL dump + delta API approach is operationally superior to full API extraction.
Python Script: Full Kayako Cloud Export
Here is a practical extraction script that handles pagination, the archived ticket parameter, rate limiting, date-based pagination for large datasets, and per-case message and note fetching:
import requests
import json
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
BASE_URL = "https://DOMAIN.kayako.com/api/v1"
AUTH = ("admin@example.com", "yourpassword")
LIMIT = 100
MAX_WORKERS = 5 # Adjust based on rate limit tolerance
def fetch_paginated(endpoint, params=None):
"""Fetch all pages from a Kayako API endpoint using offset pagination.
For large datasets (>10K records), prefer date-based pagination
using since/until parameters to avoid deep-offset timeouts.
Stops when the API returns an empty data array (not on total_count).
"""
all_data = []
offset = 0
if params is None:
params = {}
params["limit"] = LIMIT
while True:
params["offset"] = offset
resp = requests.get(f"{BASE_URL}/{endpoint}", auth=AUTH, params=params)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
if resp.status_code in (502, 504):
# Deep offset timeout — reduce window or switch pagination strategy
print(f"Gateway timeout at offset {offset}. Retrying with backoff...")
time.sleep(30)
continue
resp.raise_for_status()
body = resp.json()
data = body.get("data", [])
if not data: # Stop on empty array, not on total_count
break
all_data.extend(data)
offset += LIMIT
time.sleep(0.5) # Conservative pacing (~2 req/sec)
return all_data
def fetch_case_details(case):
"""Fetch messages and notes for a single case."""
case_id = case["id"]
messages = fetch_paginated(f"cases/{case_id}/messages.json")
case["_messages"] = messages
notes = fetch_paginated(f"cases/{case_id}/notes.json")
case["_notes"] = notes
return case
# 1. Export all cases INCLUDING archived — critical parameter
print("Fetching cases (including archived)...")
cases = fetch_paginated("cases.json", {"archived": 1})
print(f"Exported {len(cases)} cases")
# 2. Export messages and notes per case (parallelized)
print(f"Fetching messages and notes for {len(cases)} cases...")
enriched_cases = []
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = {executor.submit(fetch_case_details, case): case for case in cases}
for i, future in enumerate(as_completed(futures)):
enriched_cases.append(future.result())
if i % 100 == 0:
print(f" Processed {i}/{len(cases)} cases...")
# 3. Export users
print("Fetching users...")
users = fetch_paginated("users.json", {"include": "*"})
print(f"Exported {len(users)} users")
# 4. Export organizations
print("Fetching organizations...")
orgs = fetch_paginated("organizations.json")
print(f"Exported {len(orgs)} organizations")
# 5. Export custom field definitions
print("Fetching custom fields...")
fields = fetch_paginated("cases/fields.json", {"include": "*"})
# 6. Write to file
output = {
"cases": enriched_cases,
"users": users,
"organizations": orgs,
"custom_fields": fields,
"export_timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
}
with open("kayako_export.json", "w") as f:
json.dump(output, f, indent=2)
print(f"Export complete. {len(enriched_cases)} cases with messages and notes.")For instances with 50K+ cases, the per-case message fetching is the bottleneck. With MAX_WORKERS=10, expect 6–9 hours for 100K cases. Monitor for sustained 429 responses — if they occur more than once per 100 requests, reduce MAX_WORKERS to 3. The script stops on empty data arrays rather than total_count to handle records created or deleted mid-export.
Decision Framework: How to Plan Your Kayako Export
If you have < 5K cases and no attachments: API-only export is fine. The script above will finish in under 2 hours.
If you have 5K–50K cases with attachments: Request the MySQL dump (database + attachments) for your base dataset, then use the API for a final delta sync before cutover. Delta window should cover at minimum the time it takes to load the MySQL data into your target system.
If you have 50K+ cases: API-only route is impractical — sequential execution will take 3+ days. MySQL dump for the bulk dataset, API for the delta sync window only.
If you need per-user GDPR compliance: Do not use the MySQL dump for individual data subject requests. Use the API with requester_id filtering and archived=1.
If you are migrating to a specific platform (Zendesk, Freshdesk, etc.), the export is only half the problem. The harder part is mapping Kayako's data model to your target's schema — especially custom fields (immutable keys vs. display names), ticket statuses (Kayako's model differs from Zendesk's and Freshdesk's), and user roles. Check our guides on Kayako to Zendesk migration and Kayako to Freshdesk migration for platform-specific field mapping details.
What Makes This Hard
Kayako's data portability story is weaker than most modern helpdesks. There is no self-service full export, no native CSV dump of message bodies, and the API requires significant engineering effort to extract everything completely. Specific design decisions that create friction:
- The archived ticket default behavior silently drops all historical closed tickets unless
&archived=1is explicitly set. - Messages are only accessible per-case, forcing an O(n) API call pattern where n is your total ticket count.
- Kayako Classic stores attachments as database BLOBs in chunks requiring reassembly, not as files on disk.
- There is no documented data retention policy for archived tickets, creating compliance uncertainty.
- The MySQL dump for Cloud is a full-instance dump — there is no scoped export by date range, department, or user segment.
For teams without dedicated engineering bandwidth: writing a Python script is a reasonable project for an internal developer if you have under 10,000 tickets and no attachments. At 100,000+ tickets, gigabytes of attachments, or a legacy Classic schema, the edge cases — 502 timeouts on deep offsets, BLOB chunk reassembly, HTML sanitization of KB articles, delta sync timing — consume weeks of engineering time.
Frequently Asked Questions
- How do I export all tickets from Kayako?
- Use the API endpoint GET /api/v1/cases.json?limit=100&archived=1 and paginate with offset. The &archived=1 parameter is required — without it, cases closed more than 30 days ago are silently excluded. For a complete dump, request a MySQL backup from Kayako's Infrastructure team.
- Does Kayako have a CSV export feature?
- Only through Custom Reports (Insights > Reports), which export filtered conversation metadata as CSV. There is no native full CSV export for all data types. MySQL dumps are available but only in .sql format — Kayako does not convert to CSV.
- What are Kayako's API rate limits?
- Kayako Cloud enforces per-minute rate limits (exact threshold is not publicly documented). Exceeding them returns HTTP 429 with a Retry-After header. Kayako Classic has no API rate limits.
- Can I get a full database backup from Kayako Cloud?
- Yes. Contact Kayako support with your instance name and billing details. You'll receive a MySQL dump file (not CSV). You can request database only, attachments only, or both. Download links expire within 1–7 days.
- How do I export Kayako Help Center articles?
- There is no built-in export for KB articles. Use the API: first retrieve section IDs via GET /api/v1/sections.json, then fetch articles per section via GET /api/v1/sections/{id}/articles.json. Output is JSON — you'll need to convert to your target format.

