LiveChat to Tidio Migration: The Technical Guide
Step-by-step technical guide to migrating from LiveChat to Tidio. Covers API mapping, data extraction, rate limits, attachment handling, and what you'll lose.
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
LiveChat to Tidio Migration: The Technical Guide
Moving from LiveChat to Tidio means trading a mature, integration-heavy live chat platform for an all-in-one customer service tool with built-in AI (Lyro) and multichannel support at a lower price point. The migration is straightforward in concept — extract chats and customers from LiveChat's API, transform them to Tidio's data model, and load via Tidio's OpenAPI — but the details make it demanding: differing object models, tight rate limits, several data types that can't be migrated programmatically, and no rollback tooling if something goes wrong mid-run.
Chat data is particularly hard to move. Unlike email-based ticketing systems where messages have clear headers and threading, chat platforms treat conversations as continuous event streams. LiveChat relies on a highly structured, event-driven model where a chat contains multiple threads, each containing an array of events — messages, system notes, file uploads, rich interactive cards, form fills. Tidio operates on a more streamlined model centered on the Contact object, with conversations tied to contacts through a merged live-chat-plus-ticketing interface (Tidio has two distinct modules: a live chat inbox for real-time conversations and a Help Desk ticketing system — imported historical conversations land in Help Desk, not the live chat inbox). Bridging these two models requires strict payload transformation and hard decisions about what to preserve.
This guide covers the full technical methodology based on the LiveChat Agent Chat API v3.5 and the Tidio OpenAPI.
API version note: This guide targets LiveChat Agent Chat API v3.5 and the Tidio OpenAPI (version 1). Verified against both APIs as of July 2025. Both APIs evolve — verify endpoints against current documentation before running any migration scripts.
Migration Object Mapping: LiveChat → Tidio
Before writing any code, understand how LiveChat's data model translates to Tidio's:
| LiveChat Object | Tidio Object | Migration Method | Notes |
|---|---|---|---|
| Customers | Contacts | POST /contacts/batch |
Match on email; batch max 100; all-or-nothing |
| Agents | Operators | Manual | Create in Tidio admin panel; no create API |
| Groups | Departments | Manual | Create before ticket import |
| Chat Archives | Tickets (Help Desk) | POST /tickets/as-contact |
One ticket per archived chat; no bulk endpoint |
Chat Events — message type |
Ticket Replies | POST /tickets/{id}/reply |
Sequenced by original timestamp; no author_id support |
Chat Events — file type |
Re-hosted URLs in replies | Re-host to S3/GCS, inject as plaintext URL | LiveChat download URLs expire |
Chat Events — rich_message type |
Plaintext degradation in replies | Flatten to readable text | Buttons, images, carousels not renderable |
Chat Events — form type |
Contact Properties or reply text | Map fields to Contact Properties schema | Define schema in Tidio first |
Chat Events — system_message type |
Omit or include as bracketed note | Optional | e.g., "Agent joined", "Chat transferred" |
| Tags | Tags | Manual + API | Create in Tidio first; attach via tag_ids |
| Pre/Post-Chat Form Data | Contact Custom Properties | Manual schema + API data | Silently dropped if property not pre-defined |
| Attachments | Hosted Links | Re-host externally | Download from LiveChat, upload to S3/GCS |
| Canned Responses | Canned Responses | Manual | No export API on LiveChat side |
| Knowledge Base | Knowledge Base | Manual | No bulk import API on Tidio side |
| Chat Ratings | — | Not migrated | No target field in Tidio tickets |
| Chat Properties (custom) | — | Not migrated | Per-integration scoping in LiveChat |
| Automation rules/triggers | — | Manual rebuild | Platform-specific logic; no export format |
| Reporting/analytics history | — | Not migrated | No import mechanism |
LiveChat Event Type Taxonomy
LiveChat events arrays contain several distinct types. Each requires a different handling strategy during transformation:
| Event Type | Description | Migration Strategy |
|---|---|---|
message |
Standard text message from agent or customer | Preserve as ticket reply with sender prefix |
file |
File attachment with download URL | Download, re-host, inject new URL as plaintext |
rich_message |
Interactive card with buttons, images, carousels | Flatten to plain text; extract title, URL, description |
form |
Pre-chat or post-chat survey submission | Map fields to Tidio Contact Properties |
system_message |
Platform events: agent joined, chat transferred, tagged | Omit or include as bracketed annotation |
filled_form |
Completed survey data (distinct from form trigger event) | Same as form — map to Contact Properties |
custom |
Integration-specific payloads | Evaluate per integration; usually omit |
Example flattening for rich_message:
[System - Product Card] "Blue Widget Pro" — https://shop.example.com/blue-widget — $49.99Example flattening for system_message:
[System - 2024-03-15 14:38 UTC] Chat transferred to Billing teamPre-Migration Checklist
Walk through this list before touching any code:
- Audit LiveChat data volume: Count archived chats, customers, and agents. LiveChat's in-app export is limited to 100,000 records — larger datasets require the API.
- Confirm Tidio plan level: Tidio's OpenAPI requires a Plus plan or above. Free through Growth tiers only get access to the Products endpoint. Rate limits differ by plan (Plus: 60 req/min; Premium: 120 req/min).
- Generate LiveChat credentials: Create a Personal Access Token (PAT) in the LiveChat Developer Console under Tools → Personal Access Tokens. For
list_archivesyou need thechats--all:roscope. For Configuration API (agents, groups) you needagents--all:roandgroups--all:roscopes. PATs work for both the Agent Chat API and Configuration API but require different scope sets. - Generate Tidio API credentials: Get your
X-Tidio-Openapi-Client-IdandX-Tidio-Openapi-Client-Secretfrom Tidio's admin panel under Settings → OpenAPI. - Create Operators in Tidio: Agents cannot be created via the Tidio API. Manually create each operator account before importing tickets. Ensure email addresses match the ones used in LiveChat — this is critical for attribution.
- Create Departments in Tidio: If you use LiveChat Groups to route chats, recreate those as Departments in Tidio first. Record the resulting UUIDs.
- Define Custom Properties in Tidio: Go to Settings → Contact Properties and create any fields you want to preserve from LiveChat's pre-chat survey or custom variables. If you attempt to pass a custom property via the API that doesn't exist in the schema, Tidio will silently drop the data with no error returned.
- Map Tags: Inventory your LiveChat tags and recreate them in Tidio's Help Desk settings. Use
GET /tickets/tagsto retrieve IDs, then includetag_idsin ticket creation payloads. - Retrieve custom field IDs: Use
GET /tickets/custom-fieldsto retrieve IDs for any ticket-level custom fields you plan to populate. - Plan rollback procedure: Decide how you will undo a partial import before you start. See the Rollback Strategy section.
- Check GDPR/DPA coverage: If you handle EU customer data, verify that your Data Processing Agreements cover the transfer between platforms.
Step 1: Extract Data from LiveChat
LiveChat separates its APIs into the Configuration API (for agents, groups, and settings) and the Agent Chat API (for retrieving conversation archives). Both use the same PAT for authentication but require different permission scopes.
Extracting Chat Archives
The primary extraction endpoint is list_archives on the Agent Chat API v3.5:
curl -X POST 'https://api.livechatinc.com/v3.5/agent/action/list_archives' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <YOUR_PAT_TOKEN>' \
-d '{
"filters": {
"from": "2020-01-01T00:00:00Z",
"to": "2026-08-01T00:00:00Z"
},
"limit": 100,
"sort_order": "asc"
}'Key details:
- Pagination: Results use cursor-based pagination via
page_id. Each response includesnext_page_id— pass it in subsequent requests. - Limit: Between 1 and 100 chats per page.
- Sort order: Use
asc(oldest first) for chronological import consistency. - Data returned: Each chat includes
users(customer + agents),threadscontainingevents(messages, filled forms, system messages),properties,tags, andaccess(group IDs).
Because chat histories can be massive, write raw JSON payloads to local disk or a staging database before attempting any transformation:
import requests
import json
import time
LIVECHAT_PAT = "your_personal_access_token"
HEADERS = {
"Authorization": f"Bearer {LIVECHAT_PAT}",
"Content-Type": "application/json"
}
def fetch_livechat_archives():
url = "https://api.livechatinc.com/v3.5/agent/action/list_archives"
payload = {
"filters": {
"from": "2023-01-01T00:00:00Z",
"to": "2024-01-01T00:00:00Z"
},
"sort_order": "asc",
"limit": 100
}
page = 0
has_more = True
while has_more:
response = requests.post(url, headers=HEADERS, json=payload)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
time.sleep(retry_after)
continue
data = response.json()
with open(f"archive_page_{page}.json", "w") as f:
json.dump(data.get('chats', []), f)
if data.get('next_page_id'):
payload['page_id'] = data['next_page_id']
page += 1
else:
has_more = FalseRate limit: LiveChat enforces approximately 1,000 requests per 10 minutes per license, shared across all integrations hitting the same license. If other integrations (CRMs, monitoring tools) are actively calling the same license, they consume headroom from the same bucket. Monitor X-RateLimit-Remaining headers and implement exponential backoff on 429 responses.
Extracting Agents and Groups
Use the Configuration API with agent and group scopes:
# List agents (requires agents--all:ro scope)
curl -X POST 'https://api.livechatinc.com/v3.5/configuration/action/list_agents' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <YOUR_PAT_TOKEN>' \
-d '{}'
# List groups (requires groups--all:ro scope)
curl -X POST 'https://api.livechatinc.com/v3.5/configuration/action/list_groups' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <YOUR_PAT_TOKEN>' \
-d '{}'These give you agent IDs → names/emails and group IDs → department names. Build lookup tables from these before processing archive data.
Extracting Customer Data
Customer details are embedded in each chat's users array. There is no standalone "list all customers" endpoint on the Agent Chat API v3.5. Deduplicate customers from your chat export by email address.
What You Can't Export via API
- Canned responses: No export endpoint exists. Plan to manually recreate them in Tidio.
- Automation rules: No export format. Rebuild in Tidio's workflow editor.
- Real-time/in-progress chats: The
list_archivesendpoint only covers completed chats. For a live cutover with minimal data gap, consider LiveChat's RTM (Real-Time Messaging) API or webhook-based event capture to catch any chats that close during the migration window.
Step 2: Prepare Your Tidio Environment
Before loading any data, Tidio needs manual configuration. The API is read-only for Operators (GET /operators) and Departments (GET /departments) — you must create these through the admin panel.
Operators: Create an account for every agent. Make sure email addresses match the ones used in LiveChat — this is critical for data attribution in Tidio's ticketing system.
Departments: Recreate your LiveChat Groups as Tidio Departments. Record the resulting UUIDs; you'll need assigned_department_id when creating tickets.
Contact Properties: LiveChat captures custom visitor data through pre-chat surveys, post-chat surveys, and custom variables. In Tidio, the equivalent is Contact Properties, defined under Settings → Contact Properties. Once the schema exists, you can populate values via the properties object in the contact creation payload. Properties not pre-defined in the schema are silently dropped — no 400 error, no warning.
Tags and Custom Fields: Create tags in Help Desk settings. Use GET /tickets/tags to retrieve IDs, then include tag_ids in ticket creation payloads. Ticket custom fields work the same way — define them in the admin panel, retrieve field IDs via GET /tickets/custom-fields, and include them in the custom_fields array.
Step 3: Transform and Load Contacts
Deduplicate the customer records extracted from your chat archives by email, then use idempotent check-before-create logic to avoid duplicates:
import requests
TIDIO_CLIENT_ID = "your_client_id"
TIDIO_CLIENT_SECRET = "your_client_secret"
TIDIO_HEADERS = {
"X-Tidio-Openapi-Client-Id": TIDIO_CLIENT_ID,
"X-Tidio-Openapi-Client-Secret": TIDIO_CLIENT_SECRET,
"Content-Type": "application/json"
}
def get_existing_contact(email: str) -> dict | None:
"""Check if a contact already exists in Tidio by email."""
response = requests.get(
"https://api.tidio.com/contacts",
headers=TIDIO_HEADERS,
params={"email": email}
)
if response.status_code == 200:
data = response.json()
contacts = data.get("contacts", [])
return contacts[0] if contacts else None
return None
def create_or_get_contact(contact_data: dict) -> str:
"""Return existing Tidio contact_id or create new. Returns contact_id."""
email = contact_data.get("email")
if email:
existing = get_existing_contact(email)
if existing:
return existing["id"]
response = requests.post(
"https://api.tidio.com/contacts",
headers=TIDIO_HEADERS,
json=contact_data
)
response.raise_for_status()
return response.json()["id"]For bulk creation, use the batch endpoint:
curl -X POST 'https://api.tidio.com/contacts/batch' \
-H 'Content-Type: application/json' \
-H 'X-Tidio-Openapi-Client-Id: <CLIENT_ID>' \
-H 'X-Tidio-Openapi-Client-Secret: <CLIENT_SECRET>' \
-d '{
"contacts": [
{
"email": "customer@example.com",
"first_name": "Jane",
"last_name": "Doe",
"properties": {
"company": "Acme Corp",
"livechat_customer_id": "b7eff798-f8df-4364-8059-649c35c9ed0c"
}
}
]
}'Key constraints:
- Batch limit: Maximum 100 contacts per request.
- All-or-nothing: If one contact in the batch is invalid, the entire batch fails. Validate every record before sending — don't mix clean and suspect records in the same batch.
- No deduplication on create: The
POST /contactsendpoint always creates a new contact. It will not merge or overwrite even if the email already exists. Theget_existing_contactcheck above is your guard. - Required fields: At least one of
email,first_name,last_name, orphonemust be present.
Store the original LiveChat customer_id as a custom property in Tidio (e.g., livechat_customer_id). This gives you a lookup key during ticket import and a permanent cross-reference for post-migration audits. Also maintain an in-memory or SQLite mapping table of livechat_customer_id → tidio_contact_id to avoid re-querying Tidio on every ticket.
CSV Import Alternative
If your team prefers a no-code path for contacts, Tidio supports importing contacts from CSV and TXT files via the admin panel (Contacts → Import). Files must be UTF-8 encoded. This works for smaller datasets but lacks the idempotency controls and automation of the API approach.
Step 4: Import Tickets and Conversation History
This is the most demanding phase. Each LiveChat archived chat maps to a single Tidio ticket. All imported tickets land in Tidio's Help Desk module, not the live chat inbox.
Creating Tickets
Use the POST /tickets/as-contact endpoint:
curl -X POST 'https://api.tidio.com/tickets/as-contact' \
-H 'Content-Type: application/json' \
-H 'X-Tidio-Openapi-Client-Id: <CLIENT_ID>' \
-H 'X-Tidio-Openapi-Client-Secret: <CLIENT_SECRET>' \
-d '{
"contact_email": "customer@example.com",
"subject": "[2024-03-15] Chat with Jane Doe",
"message_content": "[Customer - 2024-03-15 14:32 UTC] Hello, I need help with my order #12345",
"assigned_department_id": "<DEPARTMENT_UUID>",
"tag_ids": [1, 5],
"custom_fields": [
{ "id": 42, "value": "livechat-imported" }
]
}'Critical constraints:
- No bulk ticket creation: There is no batch endpoint for tickets. Each ticket is created individually, gating high-volume migrations entirely by rate limits.
- One message at creation: The
POST /tickets/as-contactcall creates the ticket with a singlemessage_content. All subsequent messages must be added viaPOST /tickets/{ticketId}/reply. - No historical timestamps: Tidio's API does not accept custom
created_attimestamps. Tickets and replies are timestamped at the moment of API call execution. Embed the original date in the ticket subject or a custom field as a workaround. - No author attribution on replies: The reply API doesn't support
author_id. Prefix each message with the original sender name and timestamp to preserve readability and auditability.
Timestamp loss is permanent. Every imported ticket will show the import date, not the original conversation date. If preserving original timestamps is a hard requirement for SLA reporting, compliance, or legal hold purposes, evaluate whether Tidio is the right target platform before committing to the migration.
Adding Replies to Tickets
After creating a ticket, append each message from the original LiveChat conversation as a reply:
def import_chat_as_ticket(chat: dict, contact_email: str, department_id: str) -> str:
"""
Create a Tidio ticket from a LiveChat archived chat.
Returns the created ticket ID.
Checks for existing ticket by subject to support idempotent re-runs.
"""
# Flatten all events to ordered reply strings
events = []
for thread in chat.get("threads", []):
events.extend(thread.get("events", []))
events.sort(key=lambda e: e.get("created_at", ""))
if not events:
return None
def format_event(event: dict) -> str | None:
etype = event.get("type")
author = event.get("author", {})
author_name = author.get("name", "Unknown")
author_type = author.get("type", "agent").capitalize()
ts = event.get("created_at", "")[:16].replace("T", " ")
if etype == "message":
text = event.get("text", "")
return f"[{author_type} - {author_name} - {ts} UTC] {text}"
elif etype == "file":
url = event.get("url", "")
name = event.get("name", "attachment")
return f"[{author_type} - {author_name} - {ts} UTC] Sent file: {name} — {url}"
elif etype == "rich_message":
elements = event.get("elements", [{}])
title = elements[0].get("title", "Rich message")
url = elements[0].get("url", "")
return f"[System - {ts} UTC] Sent card: {title} — {url}"
elif etype == "system_message":
text = event.get("text", "")
return f"[System - {ts} UTC] {text}"
elif etype in ("form", "filled_form"):
fields = event.get("fields", [])
summary = "; ".join(f"{f.get('label')}: {f.get('answer', {}).get('value', '')}" for f in fields)
return f"[Form submission - {ts} UTC] {summary}"
return None
formatted = [format_event(e) for e in events if format_event(e)]
if not formatted:
return None
chat_id = chat.get("id", "unknown")
chat_date = events[0].get("created_at", "")[:10]
subject = f"[{chat_date}] LiveChat import — {chat_id}"
# Idempotency: check if ticket already exists by searching subject
existing = search_ticket_by_subject(subject)
if existing:
return existing["id"]
# Create ticket with first message
ticket_payload = {
"contact_email": contact_email,
"subject": subject,
"message_content": formatted[0],
"assigned_department_id": department_id,
"custom_fields": [{"id": 42, "value": "livechat-imported"}]
}
resp = requests.post(
"https://api.tidio.com/tickets/as-contact",
headers=TIDIO_HEADERS,
json=ticket_payload
)
resp.raise_for_status()
ticket_id = resp.json()["id"]
# Append remaining messages as replies
for message in formatted[1:]:
reply_resp = requests.post(
f"https://api.tidio.com/tickets/{ticket_id}/reply",
headers=TIDIO_HEADERS,
json={"message_content": message}
)
reply_resp.raise_for_status()
time.sleep(0.5) # conservative throttle at 120 req/min
return ticket_idYour prefix format should be consistent across all tickets for readability:
[Customer - Jane Doe - 2024-03-15 14:32 UTC] Hi, I need help with my invoice.
[Agent - John Smith - 2024-03-15 14:33 UTC] I can help with that. Can you provide the invoice number?
[Customer - Jane Doe - 2024-03-15 14:34 UTC] It is INV-1002.
[System - 2024-03-15 14:38 UTC] Chat transferred to Billing team
[Agent - Sarah Lee - 2024-03-15 14:40 UTC] Here is the corrected PDF: https://your-s3-bucket.com/inv-1002-fixed.pdfAlternative: Contact Notes for Historical Transcripts
If you don't need conversations in Tidio's ticketing system, you can flatten entire chat threads into formatted text blocks and append them as Contact Notes via POST /contacts/{contact_id}/notes. This is simpler and faster — one API call per conversation instead of N+1 — but the data won't be searchable or reportable through Tidio's ticket analytics.
When using this approach, maintain an in-memory mapping table (local SQLite or Redis) keyed by livechat_customer_id → tidio_contact_id. You'll need it for every note attachment call.
Handling Attachments and Rich Media
Attachment URLs exported from LiveChat are often authenticated or have expiration policies. If you simply reference the LiveChat URL, the link will eventually break, resulting in permanent data loss.
To preserve attachments:
- Parse the LiveChat JSON for
event.type == "file". - Download the file using your LiveChat PAT (include the
Authorizationheader — these URLs are not publicly accessible). - Upload to secure, long-term storage (AWS S3, Google Cloud Storage).
- Generate a signed URL or public URL depending on your security posture.
- Inject the new URL into the ticket reply or contact note as plaintext.
The Tidio ticket reply API does not support inline file attachments via REST — you can only include URLs as plaintext in the message body.
For many teams, historical chat attachments are low-value artifacts. Decide early if they're worth the re-hosting engineering effort.
Rate Limits and Throughput Planning
Both APIs enforce rate limits that gate migration speed:
| Platform | Rate Limit | Scope | Backoff Signal |
|---|---|---|---|
| LiveChat | ~1,000 req / 10 min | Per license (shared across integrations) | X-RateLimit-Remaining, Retry-After on 429 |
| Tidio (Plus) | 60 req / min | Per project | x-ratelimit-remaining header |
| Tidio (Premium) | 120 req / min | Per project | x-ratelimit-remaining header |
Tidio 429 responses include an x-ratelimit-remaining: 0 header and a Retry-After header indicating seconds to wait. Always check Retry-After before falling back to exponential backoff.
For a typical migration of 5,000 chats with an average of 8 messages each:
- 5,000 ticket creation calls + 35,000 reply calls = ~40,000 Tidio API requests
- At 60 req/min (Plus): ~11 hours of continuous import
- At 120 req/min (Premium): ~5.5 hours of continuous import
- Add ~10–15% overhead for contact creation calls, tag lookups, and idempotency checks
Build your migration script with:
- Exponential backoff on 429 responses. Check
Retry-Afterfirst; if missing, start at 5 seconds and double on subsequent failures (cap at 60 seconds). - Checkpointing — log the last successfully imported chat ID to a local file or database so you can resume after failures without re-processing completed records.
- Dry-run mode — validate payloads against Tidio's schema before sending by logging the payload structure without making the API call.
- Serial execution only — do not run concurrent threads against Tidio's API unless you have explicitly negotiated a rate limit increase with their support team. Concurrent threads share the same rate limit bucket and will cause cascading 429s.
Common API Errors and Fixes
| HTTP Status | Endpoint | Likely Cause | Fix |
|---|---|---|---|
400 Bad Request |
POST /contacts/batch |
Missing required field; all contacts in batch rejected | Validate that each contact has at least one of: email, first_name, last_name, phone |
400 Bad Request |
POST /tickets/as-contact |
contact_email not found in Tidio |
Create the contact first; as-contact requires a pre-existing contact record |
400 Bad Request |
POST /tickets/as-contact |
assigned_department_id UUID doesn't exist |
Verify department was created in Tidio admin; re-fetch IDs |
422 Unprocessable Entity |
POST /contacts/batch |
Invalid tag_ids or custom_field IDs |
Ensure tags and custom fields were pre-created; re-fetch IDs before batch |
422 Unprocessable Entity |
POST /tickets/{id}/reply |
Empty message_content |
Check for events where text is null or empty string; skip or substitute placeholder |
401 Unauthorized |
Any endpoint | Expired or invalid credentials | Regenerate X-Tidio-Openapi-Client-Secret in Tidio admin |
429 Too Many Requests |
Any endpoint | Rate limit exceeded | Read Retry-After header; pause and retry |
| Silent data drop | POST /contacts |
Custom property key not pre-defined | Define property schema in Settings → Contact Properties before import |
Rollback Strategy
Before starting any import run, define your rollback procedure. There is no bulk undo in Tidio.
Contacts: Tidio provides DELETE /contacts/{contactId} to remove individual contacts. There is no batch delete endpoint. For a rollback, you must iterate over the contact IDs you created (log them during import) and issue individual delete calls, subject to rate limits.
Tickets: Similarly, DELETE /tickets/{ticketId} removes individual tickets. Log every created ticket ID during import.
Practical approach for large datasets:
- Run a pilot import of 50–100 records into a staging environment (if Tidio allows sandbox access on your plan) or a dedicated test project.
- Log all created entity IDs to a
created_ids.jsonfile during every run. - Write a rollback script that reads
created_ids.jsonand issues DELETE calls with rate-limit handling before starting a full re-run. - Never run the full migration without a complete
created_ids.jsoncheckpoint you can restore from.
If you discover a data quality issue after a full import (e.g., all contacts missing a required property), a full rollback at 60 req/min against 5,000 contacts + 5,000 tickets = ~167 minutes of delete calls before you can re-run cleanly.
What You'll Lose in the Migration
Be honest with stakeholders about what doesn't survive the move:
| Data | Status | Why |
|---|---|---|
| Original timestamps | Lost | Tidio API doesn't accept custom created_at |
| Chat ratings/satisfaction | Lost | No equivalent field in Tidio tickets |
| Agent-specific message attribution | Degraded | Reply API doesn't support author_id; name embedded in text |
| File attachments | Degraded | No attachment upload via ticket reply API; re-hosting required |
| Canned responses | Manual rebuild | No LiveChat export API |
| Chat properties (custom) | Lost | Per-integration scoping in LiveChat; no mapping target |
| Automation rules/triggers | Manual rebuild | Platform-specific logic |
| Reporting/analytics history | Lost | No import mechanism |
| In-progress chats at cutover | Gap risk | list_archives covers completed chats only |
Validation and Post-Migration Audit
After the import completes:
- Count check: Compare contacts created in Tidio against your deduplicated customer list from LiveChat. Paginate
GET /contactsto get a total count. - Ticket count: Compare tickets in Tidio against your archived chat count. Paginate
GET /ticketsand count. - Message fidelity: Pick 10–20 tickets at random and compare message content and ordering against the original LiveChat archive JSON. Look for encoding issues with emoji, special characters, and non-Latin scripts.
- Custom property validation: Fetch a sample of contacts via
GET /contacts/{contactId}and verify properties populated correctly. Any silent drops will show as missing fields here. - Department assignment: Confirm tickets landed in the correct departments by filtering
GET /ticketsby department ID. - Attachment links: For any re-hosted attachments, verify the S3/GCS URLs resolve successfully. Test both authenticated and public access depending on your configuration.
- Event type coverage: Verify that
system_messageandrich_messageevents appear correctly formatted in a sample of tickets — these are the most error-prone transformations.
Run a pilot migration of 50–100 chats first. Review the output in Tidio's UI with your support team before committing to the full run. This catches formatting issues, missing fields, encoding problems, and workflow gaps early — and gives you a clean rollback test before scale.
When the Engineering Cost Doesn't Make Sense
This migration is achievable for teams with API experience, but the combination of single-ticket creation (no bulk endpoint), no timestamp preservation, tight rate limits, manual pre-configuration requirements, and no native rollback tooling makes it labor-intensive at scale.
For reference: a 10,000-chat dataset with 8 messages average generates ~90,000 API calls. At Premium tier rate limits, that's approximately 12.5 hours of execution time, not counting error recovery, rollback testing, or validation. Datasets above 10,000 conversations typically require a dedicated migration engineer for 2–5 days of build, test, and run time.
At ClonePartner, we've handled LiveChat-to-Tidio migrations with datasets ranging from a few hundred to hundreds of thousands of conversations. Our scripts handle rate-limit orchestration, timestamp preservation in metadata fields, attachment re-hosting, idempotent re-runs, and end-to-end validation.
For related migrations, see our guides on Zendesk to Tidio, Zendesk to LiveChat, or Tidio to Zendesk.
Frequently Asked Questions
- Can I migrate LiveChat conversations to Tidio with original timestamps?
- No. Tidio's API does not accept custom created_at timestamps. All imported tickets and replies will show the import date. You can preserve original dates by embedding them in the ticket subject line, message prefixes, or a custom field as a workaround.
- Does Tidio have a bulk ticket import API?
- No. Tidio's ticket creation endpoint (POST /tickets/as-contact) handles one ticket per request. There is no batch endpoint, so large migrations are throttled by the 60 req/min (Plus) or 120 req/min (Premium) rate limits.
- How do I handle LiveChat attachments during the migration?
- LiveChat attachment URLs expire or require authentication. Download the files via the LiveChat API, host them in a secure external storage bucket (like AWS S3 or GCS), and include the new URLs as plaintext links in your Tidio ticket replies or contact notes.
- What Tidio plan do I need to use the API for migration?
- You need at least the Tidio Plus plan for full OpenAPI access. Free through Growth tiers only get the Products endpoint. Plus offers 60 requests/min; Premium offers 120 requests/min.
- Can I export canned responses from LiveChat?
- There is no API endpoint or in-app method to export canned responses from LiveChat. You'll need to manually recreate them in Tidio's Canned Responses settings.


