LiveAgent to Freshservice Migration: A Technical Guide
Technical guide to migrating from LiveAgent to Freshservice. Covers API constraints, ITSM object mapping, rate limits, code examples, and validation.
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
Migrating from LiveAgent to Freshservice is a data-model conversion, not a data copy. LiveAgent is an omnichannel customer support platform designed for external engagement — email, live chat, call center, social media, and knowledge base. Freshservice is Freshworks' ITIL-aligned IT Service Management (ITSM) platform built for internal IT operations — incidents, problems, changes, assets, CMDB, and service catalog.
There is no one-to-one mapping between these systems. LiveAgent stores Contacts (external customers) and Companies (customer organizations). Freshservice stores Requesters (internal employees) and Departments. LiveAgent ticket history is stored as messages with types, not as Freshservice-style conversations split into replies and notes. Attempting a direct export-import will break threaded conversation history, orphan attachments, and strand non-ITSM data.
This guide covers every viable migration method, the exact object mapping between platforms, API constraints on both sides, and the edge cases that break most attempts.
Why Teams Move from LiveAgent to Freshservice
LiveAgent is a multi-channel help desk with ticketing, live chat, call center, and a built-in CRM. It targets customer-facing support teams handling external queries. Freshservice is designed for internal IT operations, built around ITIL processes — incident management, problem management, change management, asset tracking, and a service catalog for employee self-service.
The reasons teams make this move are operating-model reasons, not feature comparisons:
- Internal IT consolidation: The organization is retiring a customer-support tool repurposed for internal IT ticketing and wants a purpose-built ITSM platform with proper change management and asset tracking.
- ITIL compliance: LiveAgent has no native support for problems, changes, releases, or a CMDB. Freshservice provides these out of the box.
- Enterprise service management (ESM): Freshservice extends service management to HR, Facilities, Legal, and Finance departments — use cases LiveAgent was never designed for.
- Tool consolidation: Merging disparate customer support and IT teams into a single Freshworks ecosystem.
- AI-powered triage: Freshservice's Freddy AI provides ticket triage, summarization, and a virtual agent in Slack and Microsoft Teams. LiveAgent's automation is rule-based with no native AI.
Architecture mismatch alert: LiveAgent's Contacts are external customers; Freshservice's Requesters are internal employees. LiveAgent's Departments route tickets to agent teams; Freshservice's Departments represent organizational structure. Every record must be conceptually reframed before import.
Migration Approaches: All Viable Methods Compared
1. Native CSV Export + Manual Import
How it works: Export CSV files from LiveAgent (tickets, contacts, companies) and upload them via Freshservice's native CSV importer for requesters, departments/companies, assets, or custom object records.
When to use it: Fewer than 1,000 tickets with simple metadata, or moving static lists of contacts and companies. Historical conversations can stay in an archive.
Pros: No code required. Quick for small datasets and master-data seeding.
Cons: LiveAgent's panel CSV export provides only a ticket snapshot — it drops the full conversation thread, custom fields, and only includes a preview. No attachment export. No relationship preservation (contact-to-ticket linkage is lost). Freshservice's documented CSV imports target specific entities and do not provide an end-to-end ticket-history import path. (faq.liveagent.com)
Scalability: Small only. Breaks above a few thousand records.
Complexity: Low (but data quality is also low).
2. API-Based Migration (LiveAgent API v3 → Freshservice API v2)
How it works: Write extraction scripts against LiveAgent's API v3 to pull tickets, messages, contacts, companies, tags, and knowledge base articles. Transform the data to match Freshservice's ITSM schema, then load it through Freshservice's API v2.
When to use it: Any migration where you need full conversation history, attachment preservation, or more than a few thousand records.
Pros: Full control over data transformation. Preserves complete conversation threads. Handles attachments, tags, and custom fields. Can be run incrementally for delta syncs.
Cons: Requires development effort (Python, Node.js, or similar). Must handle rate limits on both sides. Error handling and retry logic must be built manually. LiveAgent API v3 has limited filtering options for tickets — only filters available in the panel are supported. LiveAgent ticket retrieval is capped at 10,000 records per filtered request. (support.liveagent.com)
Scalability: Small through enterprise. Throttle-aware batching required for large volumes.
Complexity: High.
3. Third-Party Migration Tools
Services like Help Desk Migration offer automated transfer between help desk platforms. These tools provide a visual mapping interface and handle the API integration on both sides. Help Desk Migration's pricing is public and starts at $39 for small migrations, scaling per record — approximately $15–40 per 1,000 records depending on volume tier, with custom quotes for datasets above 20,000 records.
When to use it: Mid-size migrations (5,000–50,000 tickets) where your team lacks API development capacity but needs more fidelity than CSV.
Pros: No code required. Pre-built connectors for both platforms. Support for test migrations before full run. Delta migration capability for records created during the migration window.
Cons: Per-record pricing becomes significant at scale — a 50,000-ticket migration can cost $2,000–$5,000+ depending on data types. Limited control over transformation logic. May not handle every custom field or edge case. You depend on the tool's mapping capabilities between a help desk and an ITSM platform — those are architecturally different systems. You still need to freeze or exclude automations and verify edge cases. (help-desk-migration.com)
Scalability: Medium. Cost-prohibitive for very large datasets.
Complexity: Low to Medium.
4. Custom ETL Pipeline
How it works: Build a dedicated extract-transform-load pipeline with staging databases, transformation layers, crosswalk tables, retry queues, and QA reports. If the API is not sufficient, LiveAgent also offers database dumps to paying customers leaving the service, though working with the API is recommended because the dump is raw. (support.liveagent.com)
When to use it: Enterprise migrations with 100K+ tickets, complex field mappings, strict compliance requirements that prohibit third-party data access, or unusual attachments and notes handling.
Pros: Maximum control over every transformation. Audit trail and logging at every stage. Reusable for future migrations. Can integrate validation and rollback logic.
Cons: Highest development effort. Requires deep knowledge of both APIs. Typically 2–4 weeks of engineering time. Easy to underestimate QA and cutover work.
Scalability: Enterprise-grade.
Complexity: High.
5. Middleware / Integration Platforms (Zapier, Make)
How it works: Configure triggers and actions in Zapier or Make to move data between LiveAgent and Freshservice. Both platforms have Zapier and Make connectivity. (liveagent.com)
When to use it: Ongoing sync of new tickets only (not historical migration). Useful for a transition period where both systems run in parallel.
Pros: No code required. Quick setup for simple workflows. Good for real-time sync of new records during a transition window.
Cons: Cannot migrate historical data in bulk. Their published modules center on watch/create/update patterns rather than controlled high-volume backfills. Per-task pricing adds up fast. No batch operations — processes one record at a time. Cannot rebuild message threads or handle attachments reliably. (apps.make.com)
Scalability: Not viable for bulk migration. Only for small ongoing sync.
Complexity: Low.
Migration Approach Comparison
| Method | Complexity | Data Fidelity | Scalability | Cost | Best For |
|---|---|---|---|---|---|
| CSV Export/Import | Low | Low | Small only | Free | Quick archival snapshots |
| API-Based (Custom) | High | High | Enterprise | Dev time | Full-fidelity migrations |
| Third-Party Tools | Low–Med | Medium–High | Medium | ~$15–40/1K records | Mid-size, no-code teams |
| Custom ETL Pipeline | High | Highest | Enterprise | Dev time | Regulated/complex orgs |
| Middleware (Zapier/Make) | Low | Low | Small | Per-task fee | Transition-period sync |
Recommendations by Scenario
- Small business (<5K tickets), no dev team: Third-party migration tool or managed service. Accept some manual cleanup.
- Mid-market (5K–50K tickets), limited engineering: Third-party tool or managed migration service.
- Enterprise (50K+ tickets), dedicated dev team: API-based custom pipeline or managed migration service.
- Ongoing sync during transition: Middleware for new records; API-based for historical backfill.
- Dedicated dev team with unusual schema: Custom ETL.
Common Pitfalls in DIY Migrations
Building a custom migration between a help desk and an ITSM platform is harder than it looks. The surface-level API work is straightforward — the complexity hides in the data model translation:
- LiveAgent's Contacts (external customers) must become Freshservice Requesters (internal employees) — the semantic meaning is different
- LiveAgent's Departments (routing groups) map to Freshservice Groups, not Departments — Freshservice Departments represent organizational structure
- LiveAgent's Companies have no direct Freshservice equivalent; they may map to Departments or be discarded (see the decision framework below)
- Message threading, internal notes, and agent attribution must be reconstructed in Freshservice's conversation model
DIY migrations frequently result in:
- Broken relationships: Tickets imported without valid requester IDs fail or get assigned to a default user
- Lost conversation history: If you don't fetch and replay every message in order, the thread is incomplete
- Attachment orphaning: Attachments referenced by URL in LiveAgent may expire or require separate download and re-upload
- Rate limit cascades: Both platforms throttle aggressively — LiveAgent at 180 requests/minute, Freshservice at 100–500/minute depending on plan
- Silent data loss: Fields that don't map cleanly get dropped without errors
- Mass notification storms: If you do not explicitly suppress automations during import (via the
bypass_mandatoryparameter and manual deactivation of workflows), Freshservice will fire Slack notifications, Teams messages, email alerts, and business rules on every ticket created — potentially sending thousands of false notifications to employees
Freshservice itself draws a line between ordinary integrations and migration work: its documentation states public APIs are primarily for integrations, not large-volume migrations, and it exposes separate migration-request and bulk-migration processes for partners. (support.freshservice.com)
The hidden costs in DIY are not the first happy-path script. They are the crosswalk tables, reruns, duplicate handling, attachment retries, validation reports, and surprise side effects from workflows. Freshservice's own bulk-migration documentation warns that Slack, Teams, business rules, and workflows are not muted automatically during migration. (support.freshservice.com)
Where a Managed Migration Service Fits
ClonePartner specializes in complex helpdesk and ITSM migrations. A managed service is a stronger fit than a generic wizard or DIY script when the project has relationship-heavy data, nonstandard field mapping, large ticket volumes, or a cutover that cannot interrupt support. For migrations under 2,000 simple tickets with no custom fields, a third-party tool or DIY script typically works fine.
Pre-Migration Planning
Data Audit Checklist
Before extracting anything, inventory what you have in LiveAgent:
| Object | What to Count | What to Check |
|---|---|---|
| Tickets | Total count, by status, by department | Are there tickets with 0 messages? Spam? |
| Contacts | Total, duplicates, contacts without email | Missing fields that Freshservice requires |
| Companies | Total, orphaned (no linked contacts) | Will these map to Departments or be dropped? (See decision framework below) |
| Agents | Active vs. deactivated | License implications in Freshservice |
| Tags | Total unique tags | Naming conflicts with Freshservice tags |
| Custom Fields | Ticket fields, contact fields | Type compatibility with Freshservice |
| Knowledge Base | Article count, categories, folders | Structure mapping to Freshservice KB |
| Attachments | Total size, file types | Freshservice attachment size limits (15 MB per attachment) |
| Canned Messages / Predefined Answers | Count | Cannot be migrated programmatically — must be rebuilt. Budget 1–2 hours per 50 canned responses for manual recreation. |
Define Migration Scope
Not everything should move. Identify and exclude:
- Spam tickets and test data
- Contacts with no associated tickets
- Duplicate contacts (merge before migration)
- Tickets older than your retention policy requires
- Automated system alerts and inactive customers older than three years
- Chat transcripts with no ongoing business value
For a practical framework on what to move and what to leave behind, see the Help Desk Data Migration Playbook.
Migration Strategy
- Big bang: Extract everything, transform, load in a single cutover window. Standard for helpdesks and best for small datasets.
- Phased: Migrate by department or date range. Reduces risk but extends the timeline. Only necessary if different departments are migrating months apart or you are dealing with multiple Freshservice workspaces.
- Incremental: Run initial migration, then delta syncs until cutover. Best for zero-downtime requirements.
Risk mitigation: Export full JSON backups of LiveAgent before beginning. LiveAgent also offers database dumps to leaving customers, though the dump is raw. (support.liveagent.com)
Data Model & Object Mapping
This is where the migration gets technically interesting. LiveAgent and Freshservice serve fundamentally different purposes, and their data models reflect that.
Core Object Mapping
| LiveAgent Object | Freshservice Object | Notes |
|---|---|---|
| Ticket | Ticket (Incident/Service Request) | Status values differ. LiveAgent uses letter codes (N/T/A/P/R/X). Freshservice uses numeric codes (2=Open, 3=Pending, 4=Resolved, 5=Closed). See LiveAgent API v3 ticket status reference. |
| Contact | Requester | External customers → internal employees. Email is the join key. |
| Company | Department (or discard) | No direct equivalent. See decision framework below. |
| Department | Group | LiveAgent departments route tickets to agent teams. Freshservice Groups serve the same function. |
| Agent | Agent | Map by email. Agent roles may differ between platforms. |
| Tag | Tag | Direct mapping, but verify no naming conflicts. |
| Custom Ticket Field | Custom Ticket Field | Type-by-type mapping required. Not all LiveAgent field types exist in Freshservice. |
| Knowledge Base Article | Solution Article | Category/folder structure must be recreated first. |
| Canned Message / Predefined Answer | Canned Response | No API import path. Must be rebuilt manually. |
| Chat Transcript | Ticket Note (or discard) | Freshservice has no native chat object. Chat history can be appended as ticket notes. |
| Call Recording | Attachment on Ticket | No native call object in Freshservice. Attach as file. |
| SLA Rules | SLA Policy | Cannot be migrated. Rebuild in Freshservice. |
| Automation Rules | Workflow Automator | Cannot be migrated. Rebuild in Freshservice. |
Classifying Legacy Tickets: Incident vs. Service Request
Freshservice distinguishes between incidents (break/fix issues) and service requests (employee asks for something). LiveAgent has no such distinction — all tickets are just tickets. You must classify legacy tickets before loading. Use this decision framework:
| Criteria | Classification | Freshservice type value |
|---|---|---|
| Ticket originated from an employee reporting something broken (e.g., "my laptop won't boot," "VPN is down") | Incident | Incident |
| Ticket originated from an employee requesting something (e.g., "I need a new monitor," "Set up a new hire account") | Service Request | Service Request |
| Ticket originated from an external customer (LiveAgent's core use case) | Archive as Incident with a legacy_external tag, or exclude from migration |
Incident (default) |
| Cannot determine intent from ticket subject/body | Default to Incident | Incident |
For bulk classification, build a keyword-based classifier against ticket subjects. Terms like "request," "need," "order," "new," "setup," and "access" correlate with Service Requests. Terms like "broken," "error," "down," "not working," "failed," and "outage" correlate with Incidents. Apply the classifier, then manually review edge cases. For most migrations of repurposed customer support data, defaulting all tickets to Incident is the safest approach — you can reclassify in Freshservice post-migration.
Companies-to-Departments Decision Framework
LiveAgent Companies represent external customer organizations. Freshservice Departments represent internal organizational units. Here is how to decide which mapping to use:
| Condition | Action |
|---|---|
| Your LiveAgent Companies represent internal divisions (IT, HR, Finance) that were tracked as "companies" | Map to Freshservice Departments |
| Your LiveAgent Companies represent external customers and you need to preserve the association | Store the company name as a custom text field on the Requester (e.g., cf_legacy_company) |
| Fewer than 5% of tickets reference Company data, and the data has no ongoing operational value | Discard — log the dropped records for audit |
| You are on Freshservice Enterprise and need structured external-entity data | Map to a Custom Object (subject to 10,000-record and 20-field limits) |
Field-Level Mapping: Tickets
| LiveAgent Field | Freshservice Field | Transformation |
|---|---|---|
subject |
subject |
Direct map |
status (N/T/A/P/R/X) |
status (2/3/4/5) |
N→2 (Open), T→2, A→3 (Pending), P→3, R→4 (Resolved), X→5 (Closed). See LiveAgent API v3 docs. |
departmentid |
group_id |
Lookup against pre-created Groups |
agentid |
responder_id |
Lookup against pre-created Agents by email |
date_created |
created_at |
Freshservice accepts ISO 8601. Timezone conversion required (see code below). |
date_resolved |
resolved_at |
Same timezone handling |
tags |
tags |
Array of strings |
priority |
priority (1=Low, 2=Medium, 3=High, 4=Urgent) |
Map to numeric values |
| Custom fields | custom_fields |
Key-value pairs. Freshservice custom field API names are prefixed with cf_. |
Timezone gotcha: LiveAgent's API v3 returns dates in your account's configured timezone, not UTC. You must either add a Timezone-Offset header to API calls or convert timestamps in your transformation layer before loading into Freshservice, which expects ISO 8601 format. See the normalize_timestamp() function in the code samples below.
Handling LiveAgent Objects with No Freshservice Equivalent
Live Chat Sessions: Freshservice has no native live chat module (Freshchat is a separate product). Chat transcripts can be converted to tickets with the full conversation appended as notes, or archived externally.
Contact Groups: Freshservice supports requester groups, but the structure differs. Manual reconstruction is usually required.
Handling CRM-Style Data
LiveAgent is sometimes used as a lightweight CRM. Freshservice is strictly an ITSM. If your team used LiveAgent to track leads, sales pipelines, or accounts, you must decide how to handle that data:
- Leads → Requesters (with custom fields): Freshservice has no "Lead" object. Map leads to Requesters and flag them with a custom boolean field (e.g.,
is_lead = true). - Opportunities → Tickets (Service Requests): Freshservice lacks a sales pipeline. You can map opportunities to Tickets categorized as a specific Service Request type with custom statuses to mimic pipeline stages. But be cautious — forcing sales data into tickets will damage reporting, ownership, and search. Keep those objects in a CRM or model them as lightweight reference data, not operational tickets.
- Activities/Events → Notes: Map LiveAgent calls, meetings, or task logs to Freshservice Notes appended to the relevant Ticket or Requester.
Freshservice Enterprise supports Custom Objects. If you have complex LiveAgent data (like external warranties or custom contracts), map these to Freshservice Custom Objects rather than cluttering the Ticket schema. Custom Objects work best as reference tables and form lookups — not as a full CRM pipeline replacement. Only a text field can act as the identity field, and the limit is 10,000 records per entity with a maximum of 20 fields per entity. (support.freshservice.com)
Migration Architecture
Data Flow: Extract → Transform → Load
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ LiveAgent │────▶│ Transform │────▶│ Freshservice │
│ API v3 │ │ (Staging) │ │ API v2 │
└─────────────┘ └──────────────┘ └───────────────┘
│ │ │
Extract: Transform: Load:
- Tickets - Status mapping - Create Requesters
- Messages - Field conversion - Create Tickets
- Contacts - Dedup contacts - Add Conversations
- Companies - Timezone fix - Upload Attachments
- Tags - Relationship IDs - Link Tags
- KB Articles - Data validation - Create KB Articles
- Attachments - Ticket classification
(incident vs. SR)
LiveAgent API v3 Constraints
- Rate limit: 180 requests per minute per API key (cloud accounts)
- Pagination: Uses
_perPageand_pagefor most endpoints. Some endpoints (tickets/history, calls) use_cursorinstead — the cursor value comes from thenext_page_cursorresponse header - Record cap: Ticket retrieval is capped at 10,000 records per filtered request. Use date-range windows to work around this. (support.liveagent.com)
- Filtering: Limited. Date range filters work:
_filters=[["date_created","D>","2024-01-01 00:00:00"]]. LiveAgent narrowed supported/ticketsfilters to those available in the panel, so test filters before trusting them. - Note type changes: From LiveAgent version 5.62 onward, note-related message types and IDs changed. Old and new note records can coexist in the same ticket response — newer notes may use UUIDs instead of numeric IDs. Treat message-group IDs as strings. (support.liveagent.com)
- API v1 deprecated: v1 is no longer receiving new endpoints. Use v3 exclusively.
Freshservice API v2 Constraints
- Rate limits by plan: Starter: 100/min, Growth: 200/min, Pro: 400/min, Enterprise: 500/min
- Account-wide: Rate limits are shared across all users, apps, and integrations on the same instance
- Pagination: Page-number-based (
pageandper_pageparams). Max 100 per page. Default is 30. - Filter cap: Filter endpoints return a maximum of 300 results (30 per page × 10 pages max). If you need more, use the list endpoint with date-range sorting instead.
- Authentication: Basic Auth with API key as username, any string (conventionally "X") as password. No OAuth for server-to-server calls.
- Workspace context: In multi-workspace Freshservice accounts, many create/list operations require a
workspace_id. If you omit it, records fall into the primary workspace. (support.freshservice.com) - Timestamp overrides: Freshservice allows overriding the
created_attimestamp during import, but you cannot override theupdated_attimestamp. It will always reflect the date of the migration. - Embedding: Use
?include=requesterto embed related data. Each embedded resource on a single-object call costs an additional API credit.include=conversationsreturns a maximum of 10 conversations — use the dedicated conversations endpoint for full thread retrieval.
Bulk migration APIs: For partner-grade migrations, Freshservice exposes bulk APIs that support tickets and notes only. These require a migration token, allow 50 tickets or notes per request, run at 10 requests per minute, and require attachments to be publicly accessible by URL (capped at 40 MB total per entity). All other entities use standard public APIs. (support.freshservice.com)
Handling Large Datasets
For migrations over 50,000 tickets:
- Batch by date range: Extract LiveAgent tickets in daily or weekly windows to stay within the 10,000-record pagination limit
- Implement exponential backoff: When you hit 429 (rate limit) on either side, wait the
Retry-Aftervalue before retrying. MonitorX-RateLimit-Remainingheaders on Freshservice responses. - Use a job queue: Process records through a queue (Redis, RabbitMQ, or a simple database queue) to handle failures gracefully
- Log every operation: Store the LiveAgent source ID and corresponding Freshservice ID for every record created. This crosswalk table is your reconciliation map, your retry mechanism, and your rollback index.
Step-by-Step Migration Process
Step 0: Utility Functions
These helper functions are used throughout the migration code:
from datetime import datetime
import pytz
# Configure your LiveAgent account timezone here
LIVEAGENT_TIMEZONE = "Europe/Bratislava" # Check your LiveAgent account settings
def normalize_timestamp(la_timestamp, source_tz=LIVEAGENT_TIMEZONE):
"""
Convert a LiveAgent timestamp (in account timezone) to ISO 8601 UTC.
LiveAgent returns dates like '2024-03-15 14:30:00' in the account's
configured timezone. Freshservice expects ISO 8601 format.
"""
if not la_timestamp:
return None
local_tz = pytz.timezone(source_tz)
naive_dt = datetime.strptime(la_timestamp, "%Y-%m-%d %H:%M:%S")
local_dt = local_tz.localize(naive_dt)
utc_dt = local_dt.astimezone(pytz.utc)
return utc_dt.strftime("%Y-%m-%dT%H:%M:%SZ")
def transform_custom_fields(la_ticket):
"""
Map LiveAgent custom fields to Freshservice format.
Freshservice custom field API names must be prefixed with 'cf_'.
"""
custom = {}
for key, value in la_ticket.get("custom_fields", {}).items():
fs_key = f"cf_{key}" if not key.startswith("cf_") else key
custom[fs_key] = value
return customStep 1: Extract Data from LiveAgent
import requests
import time
import json
LIVEAGENT_DOMAIN = "https://youraccount.ladesk.com"
API_KEY = "your_api_key_here"
def get_tickets(date_from, date_to, page=1, per_page=50):
"""Fetch tickets from LiveAgent API v3 with date filtering."""
filters = json.dumps([
["date_created", "D>", date_from],
["date_created", "D<", date_to]
])
url = f"{LIVEAGENT_DOMAIN}/api/v3/tickets"
params = {
"_perPage": per_page,
"_page": page,
"_filters": filters,
"_sortField": "date_created"
}
headers = {"apikey": API_KEY}
response = requests.get(url, params=params, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
return get_tickets(date_from, date_to, page, per_page)
response.raise_for_status()
return response.json()
def get_ticket_messages(ticket_id):
"""
Fetch all messages for a specific ticket.
Note: From LiveAgent v5.62+, message IDs may be UUIDs or numeric.
Treat all IDs as strings.
"""
url = f"{LIVEAGENT_DOMAIN}/api/v3/tickets/{ticket_id}/messages"
headers = {"apikey": API_KEY}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
def get_contacts(page=1, per_page=50):
"""Fetch contacts from LiveAgent."""
url = f"{LIVEAGENT_DOMAIN}/api/v3/contacts"
params = {"_perPage": per_page, "_page": page}
headers = {"apikey": API_KEY}
response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
return response.json()
def get_kb_articles(page=1, per_page=50):
"""Fetch knowledge base articles from LiveAgent."""
url = f"{LIVEAGENT_DOMAIN}/api/v3/knowledgebase/articles"
params = {"_perPage": per_page, "_page": page}
headers = {"apikey": API_KEY}
response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
return response.json()Step 2: Transform Data
def transform_ticket(la_ticket, group_map, agent_map):
"""Transform a LiveAgent ticket into Freshservice format."""
STATUS_MAP = {
"N": 2, # New -> Open
"T": 2, # Open -> Open
"A": 3, # Answered -> Pending
"P": 3, # Postponed -> Pending
"R": 4, # Resolved -> Resolved
"X": 5, # Deleted -> Closed (or skip)
}
PRIORITY_MAP = {
"low": 1,
"normal": 2,
"high": 3,
"urgent": 4,
}
return {
"subject": la_ticket.get("subject", "(No Subject)") or "(No Subject)",
"description": la_ticket.get("preview", ""),
"email": la_ticket.get("customerEmail", "unknown@example.com"),
"status": STATUS_MAP.get(la_ticket.get("status"), 2),
"priority": PRIORITY_MAP.get(
la_ticket.get("priority", "normal"), 2
),
"group_id": group_map.get(la_ticket.get("departmentid")),
"responder_id": agent_map.get(la_ticket.get("agentid")),
"tags": la_ticket.get("tags", []),
"custom_fields": {
**transform_custom_fields(la_ticket),
"cf_legacy_liveagent_id": str(la_ticket.get("id", "")),
},
"created_at": normalize_timestamp(la_ticket.get("date_created")),
}
def transform_kb_article(la_article, category_map):
"""Transform a LiveAgent KB article into Freshservice solution article."""
return {
"title": la_article.get("title", "(No Title)"),
"description": la_article.get("content", ""),
"folder_id": category_map.get(la_article.get("category_id")),
"status": 2, # 1=Draft, 2=Published
}Step 3: Load into Freshservice
import base64
FS_DOMAIN = "https://yourdomain.freshservice.com"
FS_API_KEY = "your_freshservice_api_key"
# Set to your workspace ID for multi-workspace accounts; None for single workspace
FS_WORKSPACE_ID = None
def _fs_headers():
auth = base64.b64encode(f"{FS_API_KEY}:X".encode()).decode()
return {
"Authorization": f"Basic {auth}",
"Content-Type": "application/json",
}
def _fs_request(method, url, json_data=None, max_retries=3):
"""Make a Freshservice API request with retry logic."""
for attempt in range(max_retries):
response = method(url, json=json_data, headers=_fs_headers())
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
continue
if response.status_code in (500, 502, 503, 504):
time.sleep(2 ** attempt)
continue
response.raise_for_status()
return response.json()
raise Exception(f"Failed after {max_retries} retries: {url}")
def create_fs_ticket(ticket_data):
"""
Create a ticket in Freshservice.
Uses bypass_mandatory=True to skip mandatory field validation
and prevent automation triggers during migration.
"""
url = f"{FS_DOMAIN}/api/v2/tickets"
if FS_WORKSPACE_ID:
url += f"?workspace_id={FS_WORKSPACE_ID}"
# bypass_mandatory skips mandatory field checks and
# prevents observer/supervisor rules from firing
ticket_data["bypass_mandatory"] = True
return _fs_request(requests.post, url, ticket_data)
def add_conversation_note(ticket_id, body, is_private=True):
"""Add a note to reconstruct conversation history."""
url = f"{FS_DOMAIN}/api/v2/tickets/{ticket_id}/notes"
payload = {
"body": body,
"private": is_private,
}
return _fs_request(requests.post, url, payload)
def create_fs_requester(requester_data):
"""Create a requester in Freshservice."""
url = f"{FS_DOMAIN}/api/v2/requesters"
return _fs_request(requests.post, url, requester_data)
def create_fs_solution_article(article_data, folder_id):
"""Create a knowledge base article in Freshservice."""
url = f"{FS_DOMAIN}/api/v2/solutions/folders/{folder_id}/articles"
return _fs_request(requests.post, url, article_data)
def create_fs_solution_folder(folder_data, category_id):
"""Create a KB folder in Freshservice under a category."""
url = f"{FS_DOMAIN}/api/v2/solutions/categories/{category_id}/folders"
return _fs_request(requests.post, url, folder_data)Critical: The bypass_mandatory parameter. Setting bypass_mandatory: true on ticket creation prevents Freshservice from enforcing mandatory field validation and suppresses observer/supervisor rule execution. Without this, every migrated ticket will trigger email notifications, Slack messages, Teams alerts, and workflow automations — potentially sending thousands of false alerts to employees. You must also manually deactivate Workflow Automator rules, SLA policies, and notification templates in the Freshservice admin panel before starting the migration, then re-enable them after validation.
Step 4: Rebuild Relationships
The load sequence matters. Create records in this order:
- Departments (in Freshservice) — map from LiveAgent Companies if applicable
- Groups — map from LiveAgent Departments
- Agents — match by email address
- Requesters — create from LiveAgent Contacts. Store new Freshservice IDs in a crosswalk database.
- Tickets — reference requester email, group_id, responder_id. Replace LiveAgent Contact IDs with corresponding Freshservice Requester IDs using your crosswalk.
- Conversation notes — add messages to tickets in chronological order. Map public messages to replies, internal notes to private notes.
- Attachments — download from LiveAgent URLs, store temporarily, re-upload to specific Freshservice tickets as multipart/form-data.
- Knowledge Base — create categories first, then folders, then articles.
Out-of-order execution will result in orphaned tickets or API rejections.
Step 5: Orchestrate the Full Flow
Here is a high-level orchestration pattern that ties the extraction, transformation, and loading together:
import sqlite3
# Initialize crosswalk database
def init_crosswalk(db_path="crosswalk.db"):
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS crosswalk (
entity_type TEXT,
source_id TEXT,
target_id TEXT,
PRIMARY KEY (entity_type, source_id)
)
""")
conn.commit()
return conn
def log_crosswalk(conn, entity_type, source_id, target_id):
conn.execute(
"INSERT OR REPLACE INTO crosswalk VALUES (?, ?, ?)",
(entity_type, str(source_id), str(target_id))
)
conn.commit()
def already_migrated(conn, entity_type, source_id):
row = conn.execute(
"SELECT target_id FROM crosswalk WHERE entity_type=? AND source_id=?",
(entity_type, str(source_id))
).fetchone()
return row[0] if row else None
def upsert_requester(conn, contact):
"""Create or find requester, return Freshservice requester ID."""
existing = already_migrated(conn, "requester", contact.get("id"))
if existing:
return int(existing)
email = (contact.get("email") or "").strip().lower()
if not email:
return None # Log: contact has no email, cannot create requester
requester_data = {
"first_name": contact.get("firstname", "Unknown"),
"last_name": contact.get("lastname", ""),
"primary_email": email,
}
result = create_fs_requester(requester_data)
fs_id = result["requester"]["id"]
log_crosswalk(conn, "requester", contact["id"], fs_id)
return fs_id
def migrate_window(start_ts, end_ts, group_map, agent_map, conn):
"""Migrate all tickets in a date window. Idempotent via crosswalk."""
tickets = get_tickets(start_ts, end_ts)
for t in tickets:
# Skip if already migrated (idempotent reruns)
if already_migrated(conn, "ticket", t["id"]):
continue
# Create or find requester
contact = {"id": t.get("customerid"), "email": t.get("customerEmail"),
"firstname": t.get("customerFirstname", ""),
"lastname": t.get("customerLastname", "")}
requester_id = upsert_requester(conn, contact)
# Transform and create ticket
fs_ticket_data = transform_ticket(t, group_map, agent_map)
fs_ticket = create_fs_ticket(fs_ticket_data)
fs_ticket_id = fs_ticket["ticket"]["id"]
# Replay conversation messages in chronological order
messages = get_ticket_messages(t["id"])
messages.sort(key=lambda m: m.get("date_created", ""))
for msg in messages:
is_private = msg.get("type") in ("internal", "note")
add_conversation_note(fs_ticket_id, msg.get("body", ""), is_private)
log_crosswalk(conn, "ticket", t["id"], fs_ticket_id)
# Usage:
conn = init_crosswalk()
# Pre-build group_map and agent_map from your setup
migrate_window("2023-01-01 00:00:00", "2023-12-31 23:59:59",
group_map, agent_map, conn)Step 6: Migrate Knowledge Base Articles
Knowledge base migration requires creating the folder hierarchy first, then inserting articles:
def migrate_kb(conn, category_map):
"""
Migrate LiveAgent KB articles to Freshservice Solution Articles.
category_map: {liveagent_category_id: freshservice_folder_id}
"""
page = 1
while True:
articles = get_kb_articles(page=page, per_page=50)
if not articles:
break
for article in articles:
if already_migrated(conn, "kb_article", article["id"]):
continue
folder_id = category_map.get(article.get("category_id"))
if not folder_id:
# Log: article has no mapped folder, skip or use default
continue
article_data = transform_kb_article(article, category_map)
result = create_fs_solution_article(article_data, folder_id)
fs_article_id = result["article"]["id"]
log_crosswalk(conn, "kb_article", article["id"], fs_article_id)
page += 1Note on KB structure: LiveAgent organizes articles into categories. Freshservice uses a three-level hierarchy: Category → Folder → Article. You must create Freshservice categories and folders before migrating articles. Map each LiveAgent category to a Freshservice folder within a single "Migrated KB" category, or restructure the hierarchy to match your new ITSM taxonomy.
Edge Cases & Challenges
Duplicate Contacts
LiveAgent may have multiple contact records for the same person (created from different channels). Freshservice will reject duplicate requester emails. Deduplicate contacts before migration — merge by email address, keeping the most complete record.
Missing or Inconsistent Data
- Tickets without a valid contact email: Freshservice requires a requester. Assign to a placeholder "System User" requester or skip.
- Contacts without email addresses: Cannot be created as Freshservice requesters. Log and report.
- Tickets with empty subjects: Freshservice requires a subject. Generate one from the first message body or use a default like
(No Subject).
Multi-Channel Conversation Threading
A single LiveAgent ticket can contain email replies, chat messages, call notes, and internal notes — all in one thread. When replaying into Freshservice:
- Preserve the chronological order of all message types
- Mark internal notes as private in Freshservice
- Convert chat messages to note format (Freshservice has no chat object)
- Attribute messages to the correct agent or requester
Inline Images
LiveAgent stores inline images as base64 strings or hosted URLs. Freshservice may block external image rendering. You must parse the HTML body, download each image, upload it to Freshservice, and rewrite the <img> tag src attribute with the new URL.
Attachments
LiveAgent attachments are downloaded via API as binary files. Freshservice accepts file uploads via multipart form data on the ticket or note creation endpoint. The pipeline must:
- Download the attachment from LiveAgent
- Store temporarily
- Re-upload to the correct Freshservice ticket
- Verify the upload succeeded
Freshservice has a 15 MB per attachment limit. Files exceeding this must be handled separately (e.g., linked from cloud storage). For bulk migration APIs, attachments must be publicly accessible by URL and the total is capped at 40 MB per entity.
Bypassing Automations During Migration
Freshservice triggers automations on ticket creation by default. It does not automatically mute Slack, Microsoft Teams, workflows, or business rules during migration. You must:
- Set
bypass_mandatory: truein the API payload (suppresses observer/supervisor rules) - Manually deactivate Workflow Automator rules in Admin → Workflow Automator
- Disable or redirect email notification templates
- Pause SLA policies to prevent false SLA breach alerts on historical tickets
Re-enable all automations only after migration validation is complete. (support.freshservice.com)
Limitations & Constraints
Freshservice Capabilities vs. LiveAgent
| Capability | LiveAgent | Freshservice |
|---|---|---|
| External customer management | Native (Contacts, Companies) | Not designed for this (Requesters = employees) |
| Live chat | Built-in, multi-channel | No native live chat (use Freshchat separately) |
| Custom objects | N/A | Enterprise plans only, max 10,000 records per entity, max 20 fields |
| Call center | Built-in with call recording | No native call center (use Freshcaller separately) |
| Social media ticketing | Facebook, Twitter, Instagram, WhatsApp | Not natively available (ITSM-focused) |
API Rate Limits
| Platform | Rate Limit | Scope |
|---|---|---|
| LiveAgent (Cloud) | 180 requests/minute per API key | Per API key |
| Freshservice Starter | 100 requests/minute | Account-wide |
| Freshservice Growth | 200 requests/minute | Account-wide |
| Freshservice Pro | 400 requests/minute | Account-wide |
| Freshservice Enterprise | 500 requests/minute | Account-wide |
Data Structure Compromises
- LiveAgent's flat ticket model vs. Freshservice's ITIL categorization (incident vs. service request vs. problem) — you must classify legacy tickets before loading (see the classification framework above)
- LiveAgent's company-contact hierarchy has no clean Freshservice equivalent (see the Companies-to-Departments decision framework above)
- Canned messages, SLA policies, automation rules, and business hours cannot be migrated programmatically — they must be recreated manually. Budget 1–2 hours per 50 canned responses, 2–4 hours per SLA policy, and 1–3 hours per automation rule.
- Freshservice requires specific fields for Service Requests (like Category and Subcategory) that LiveAgent does not enforce. You must set default values for null fields during transformation.
- Schema rigidity: Freshservice custom field limits vary by type (e.g., max 50 text/dropdown, 25 checkboxes, 10 number fields per service item)
Validation & Testing
Do not trust a 200 OK response blindly. Validate the data at multiple levels.
Freshservice Sandbox Setup
Before migrating into production, run at least two full test cycles on a Freshservice sandbox:
- Create a sandbox: In Freshservice Admin → Sandbox, provision a sandbox instance. Freshservice Pro and Enterprise plans include sandbox access.
- Replicate configuration: Copy your production custom fields, groups, departments, and categories to the sandbox. Freshservice sandboxes can sync configuration from production.
- Run the full migration against the sandbox using the same scripts and mappings.
- Validate (see below), then destroy the sandbox data and repeat.
- Only after two successful sandbox runs should you execute against production.
If you are on a Freshservice plan without sandbox access, use a separate Freshservice trial instance (21-day free trial) for testing.
Record Count Comparison
Build a reconciliation report:
| Metric | LiveAgent Count | Freshservice Count | Delta |
|---|---|---|---|
| Tickets | X | Y | Should be 0 (or match excluded records) |
| Requesters | X | Y | May differ due to deduplication |
| KB Articles | X | Y | Should be 0 |
| Attachments | X | Y | Verify total, spot-check file sizes |
Field-Level Validation
For a random sample of 5–10% of tickets:
- Compare subject, status, priority, and assigned agent
- Verify conversation note count matches message count
- Check that custom field values transferred correctly
- Confirm attachment count and file names
- Verify
created_attimestamps are correct (not the migration date) - Confirm
cf_legacy_liveagent_idis populated and matches the source
UAT Process
- Agent test: Have 2–3 agents review their assigned tickets in Freshservice for completeness. Verify the conversation thread reads chronologically.
- Search test: Search for known tickets by keyword and verify they appear.
- Workflow test: Trigger an automation rule on a migrated ticket to confirm it fires correctly.
- Reporting test: Run a basic ticket volume report and compare against LiveAgent data.
Rollback Planning
Freshservice does not have a bulk delete API that makes rollback trivial. Plan for rollback by:
- Keeping LiveAgent active (read-only) until validation is complete
- Tagging all migrated tickets with a
migrated_from_liveagenttag for easy identification - Testing on a Freshservice sandbox or trial instance before touching production
- Retaining your crosswalk table to issue targeted DELETE requests if UAT fails
Post-Migration Tasks
Rebuild Automations and Workflows
Nothing from LiveAgent's rule engine transfers to Freshservice's Workflow Automator. Rebuild:
- Ticket assignment rules (round-robin, load-balanced, skill-based)
- SLA policies with proper business hours
- Escalation rules
- Email notification templates
- Approval workflows (if using change management)
Budget time for this — it is often 20–30% of the total migration effort. For a deeper look at handling automation migration, see the guide on migrating automations, macros, and workflows.
User Training and Onboarding
Freshservice's interface and mental model are different from LiveAgent's:
- Agents now work within an ITIL framework (incidents vs. service requests)
- The service catalog replaces LiveAgent's contact forms for request intake
- Asset linking and CMDB are new concepts for teams coming from a help desk
- Train agents on the differences between replies, public notes, and private notes in Freshservice (support.freshservice.com)
- Reporting is structured differently — Freshservice uses analytics dashboards, not LiveAgent-style report filters
Monitor for Data Inconsistencies
For the first 2 weeks post-migration:
- Check for tickets with missing requesters
- Monitor for broken attachment links
- Verify that KB articles render correctly (HTML/formatting issues)
- Watch for custom field values that show as blank or incorrect
- In multi-workspace deployments, verify that each workspace has the right fields, groups, tags, and notification behavior
Best Practices
- Back up everything first. Export a full data dump from LiveAgent before starting. Request a database dump from LiveAgent support if you need a complete backup.
- Run test migrations. Use a Freshservice sandbox or trial instance. Run at least two test cycles before the production migration. Never migrate directly into production.
- Validate incrementally. Don't wait until 100,000 tickets are migrated to check for errors. Validate after every batch — large reruns are where timelines go to die.
- Migrate in the right order. Departments → Groups → Agents → Requesters → Tickets → Notes → Attachments → KB. Breaking this order breaks relationships.
- Handle timezones explicitly. LiveAgent API returns dates in account timezone. Freshservice expects ISO 8601 formats. Use the
normalize_timestamp()function to convert everything to UTC in your staging layer. - Tag migrated records. Add a tag like
migrated_from_liveagentto every ticket. This makes post-migration auditing and rollback identification easier. - Keep legacy IDs in Freshservice. Store the original LiveAgent ticket ID in a custom field (e.g.,
cf_legacy_liveagent_id). Without that, support and finance teams lose the ability to reconcile old references. - Suppress automations during import. Set
bypass_mandatory: trueon every ticket creation call, and manually deactivate Workflow Automator rules, notification templates, and SLA policies before the migration run. - Spend 80% of your time on the mapping spreadsheet. The code is 20% of the work. The mapping — status values, field types, priority levels, custom field alignment, incident vs. service request classification — is where migrations succeed or fail.
- Plan for manual work. Canned responses, SLA policies, automations, and business hours must be rebuilt by hand. Budget 1–2 hours per 50 canned responses, 2–4 hours per SLA policy, and 1–3 hours per automation rule.
For a comprehensive pre-migration checklist, see the Help Desk Data Migration Checklist.
Sample Data Mapping Table
| LiveAgent Field | API Path | Freshservice Field | API Path | Notes |
|---|---|---|---|---|
| Ticket ID | ticket.id |
Legacy ID (custom) | ticket.custom_fields.cf_legacy_liveagent_id |
Crosswalk, reruns, audit |
| Ticket Code | ticket.code |
Subject prefix | ticket.subject |
Prepend [LA-{code}] for searchability |
| Subject | ticket.subject |
Subject | ticket.subject |
Trim and normalize whitespace. Default to (No Subject) if empty. |
| Status | ticket.status (N/T/A/P/R/X) |
Status | ticket.status (2/3/4/5) |
Value mapping required. See LiveAgent API v3 docs. |
| Priority | ticket.priority |
Priority | ticket.priority (1–4) |
Map to numeric values |
| Department | ticket.departmentid |
Group | ticket.group_id |
Lookup by pre-created Group ID |
| Agent | ticket.agentid |
Responder | ticket.responder_id |
Match by email with fallback queue |
| Contact Email | contact.email |
Requester Email | requester.primary_email |
Lowercase + dedupe. Must be unique. |
| Contact Name | contact.firstname + lastname |
Requester Name | requester.first_name + last_name |
Split fields |
| Company | company.name |
Department / custom field | department.name or requester.custom_fields.cf_legacy_company |
See Companies-to-Departments decision framework |
| Tags | ticket.tags |
Tags | ticket.tags |
Array of strings, verify naming conflicts |
| Created Date | ticket.date_created |
Created At | ticket.created_at |
Timezone conversion required via normalize_timestamp() |
| First message body | Message API | Description | ticket.description |
Preserve HTML safely |
| Later public messages | Message API | Conversation reply/note | Conversations API | Keep order by created time |
| Internal notes | Message API | Private note | Conversations API | Mark non-public to prevent leakage |
| Attachments | Attachment API | Ticket/note attachments | Multipart upload | Stage to public URL if using bulk API. 15 MB per file limit. |
| Custom Field | ticket.custom_fields [key] |
Custom Field | ticket.custom_fields.cf_key |
Freshservice prefixes with cf_ |
Making the Right Call
Migrating from LiveAgent to Freshservice is a platform-class change, not a data copy. You are moving from a customer-facing help desk to an internal ITSM tool — and the data model, user model, and workflow paradigm are all different.
The API-based approach is the only method that preserves full conversation history and relationships. CSV exports lose too much. Middleware tools cannot handle the structural transformation. Third-party tools work for standard mid-size migrations but break on edge cases and complex field mappings.
The decision on approach depends on your specific constraints: ticket volume, custom field complexity, compliance requirements, and available engineering capacity. For datasets under 5,000 simple tickets, a third-party tool is usually sufficient. For anything involving complex field mappings, multi-workspace routing, full conversation threads with attachments, or regulatory audit requirements, an API-based pipeline — whether built in-house or through a managed service — is the only reliable path.
Frequently Asked Questions
- Can I migrate LiveAgent data to Freshservice using CSV export?
- Only partially. LiveAgent's panel CSV export provides a ticket snapshot but drops full conversation threads, attachments, custom fields, and relational data. Freshservice's CSV imports target specific entities (requesters, departments, assets) and do not provide an end-to-end ticket-history import path. For anything requiring full history, use the API-based approach.
- What are the API rate limits for LiveAgent and Freshservice during migration?
- LiveAgent's cloud API v3 allows 180 requests per minute per API key. Freshservice's API v2 limits vary by plan: Starter 100/min, Growth 200/min, Pro 400/min, Enterprise 500/min. Freshservice limits are account-wide (shared across all users, apps, and integrations). Both platforms return 429 errors when limits are exceeded.
- How do LiveAgent Contacts and Companies map to Freshservice?
- LiveAgent Contacts are external customers; Freshservice Requesters are internal employees. They map by email address, but the semantic meaning differs. LiveAgent Companies have no direct Freshservice equivalent — they may map to Departments (if they represent internal orgs), be stored as custom fields on requesters, or be discarded. LiveAgent Departments (routing groups) map to Freshservice Groups, not Freshservice Departments.
- Can I migrate LiveAgent automations and SLA policies to Freshservice?
- No. LiveAgent's automation rules, SLA policies, canned messages, and predefined answers cannot be migrated programmatically. They must be manually rebuilt in Freshservice's Workflow Automator and SLA configuration. This manual work is often 20–30% of the total migration effort.
- What breaks most often in a LiveAgent to Freshservice migration?
- The most common failures are: wrong message classification (public vs. private notes), duplicate requesters rejected by Freshservice, broken parent-child relationships from out-of-order record creation, attachment handling errors (size limits, URL expiration), and Freshservice automations firing during import and sending thousands of false notifications.

