Skip to content

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.

Wahab Wahab · · 20 min read
How to Export Data from Kayako: API Limits, Methods & Portability
TALK TO AN ENGINEER

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
Danger

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=100

For 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:

  1. Offset-based (default): ?limit=100&offset=0, then increment offset by 100.
  2. Cursor-based: Use before_id and after_id parameters, relative to the current page.
  3. Date-based: Use since and until parameters 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=1

By 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.

Info

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=100

This 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=100

By 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:

  1. Get all section IDs: GET /api/v1/sections.json
  2. 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:

  1. Parse the JSON response for every message.
  2. Identify the attachments array.
  3. Extract the download URL for each file.
  4. Execute an authenticated GET request to that URL to download the binary.
  5. 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:

  1. Go to Insights > Reports in Kayako.
  2. Create a new report with filter conditions (status, date range, assignee, tags).
  3. Save and run the report.
  4. 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=1 concept — 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.
Warning

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.sql

You 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.php for the TABLE_PREFIX constant.

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-After header 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:

  1. Archived ticket omission. Default /api/v1/cases calls skip cases closed 30+ days ago. Always append &archived=1. This is not documented prominently in the Kayako UI.

  2. 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.

  3. total_count drift. During pagination, the total count shifts if records are created or deleted mid-export. Do not rely on total_count as a stop condition. Stop when the API returns an empty data array.

  4. 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.

  5. 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.

  6. 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).

  7. 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.

  8. 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.

  9. Knowledge Base inline images. KB article bodies are HTML. Inline images referenced in src attributes require parsing and separate authenticated download. They will not appear in the API response as structured attachment objects.

  10. ISO 8601 timestamp format required. The since and until pagination 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.

  11. Classic attachment chunk reassembly. In Kayako Classic, binary attachments stored in swattachmentchunks must be queried by attachmentid, sorted by attachmentchunkid ascending, 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:

  1. 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.
  2. Convert the JSON output to CSV or another portable format.
  3. 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:

  1. Historical Extraction: Pull all tickets closed older than 30 days using archived=1. For large instances (50K+ tickets), use MySQL dump for this phase.
  2. Active Extraction: Pull all currently open, pending, and recently closed tickets via API. Use date-based pagination (since/until in ISO 8601) to bound the window.
  3. 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.")
Tip

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=1 is 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.

More from our Blog