Skip to content

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.

Raaj Raaj · · 29 min read
LiveAgent to Freshservice Migration: A Technical Guide
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

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

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_mandatory parameter 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_.
Info

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 _perPage and _page for most endpoints. Some endpoints (tickets/history, calls) use _cursor instead — the cursor value comes from the next_page_cursor response 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 /tickets filters 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 (page and per_page params). 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_at timestamp during import, but you cannot override the updated_at timestamp. It will always reflect the date of the migration.
  • Embedding: Use ?include=requester to embed related data. Each embedded resource on a single-object call costs an additional API credit. include=conversations returns a maximum of 10 conversations — use the dedicated conversations endpoint for full thread retrieval.
Warning

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:

  1. Batch by date range: Extract LiveAgent tickets in daily or weekly windows to stay within the 10,000-record pagination limit
  2. Implement exponential backoff: When you hit 429 (rate limit) on either side, wait the Retry-After value before retrying. Monitor X-RateLimit-Remaining headers on Freshservice responses.
  3. Use a job queue: Process records through a queue (Redis, RabbitMQ, or a simple database queue) to handle failures gracefully
  4. 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 custom

Step 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)
Warning

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:

  1. Departments (in Freshservice) — map from LiveAgent Companies if applicable
  2. Groups — map from LiveAgent Departments
  3. Agents — match by email address
  4. Requesters — create from LiveAgent Contacts. Store new Freshservice IDs in a crosswalk database.
  5. Tickets — reference requester email, group_id, responder_id. Replace LiveAgent Contact IDs with corresponding Freshservice Requester IDs using your crosswalk.
  6. Conversation notes — add messages to tickets in chronological order. Map public messages to replies, internal notes to private notes.
  7. Attachments — download from LiveAgent URLs, store temporarily, re-upload to specific Freshservice tickets as multipart/form-data.
  8. 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 += 1

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

  1. Download the attachment from LiveAgent
  2. Store temporarily
  3. Re-upload to the correct Freshservice ticket
  4. 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:

  1. Set bypass_mandatory: true in the API payload (suppresses observer/supervisor rules)
  2. Manually deactivate Workflow Automator rules in Admin → Workflow Automator
  3. Disable or redirect email notification templates
  4. 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:

  1. Create a sandbox: In Freshservice Admin → Sandbox, provision a sandbox instance. Freshservice Pro and Enterprise plans include sandbox access.
  2. Replicate configuration: Copy your production custom fields, groups, departments, and categories to the sandbox. Freshservice sandboxes can sync configuration from production.
  3. Run the full migration against the sandbox using the same scripts and mappings.
  4. Validate (see below), then destroy the sandbox data and repeat.
  5. 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_at timestamps are correct (not the migration date)
  • Confirm cf_legacy_liveagent_id is populated and matches the source

UAT Process

  1. Agent test: Have 2–3 agents review their assigned tickets in Freshservice for completeness. Verify the conversation thread reads chronologically.
  2. Search test: Search for known tickets by keyword and verify they appear.
  3. Workflow test: Trigger an automation rule on a migrated ticket to confirm it fires correctly.
  4. 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_liveagent tag 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

  1. 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.
  2. 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.
  3. 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.
  4. Migrate in the right order. Departments → Groups → Agents → Requesters → Tickets → Notes → Attachments → KB. Breaking this order breaks relationships.
  5. 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.
  6. Tag migrated records. Add a tag like migrated_from_liveagent to every ticket. This makes post-migration auditing and rollback identification easier.
  7. 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.
  8. Suppress automations during import. Set bypass_mandatory: true on every ticket creation call, and manually deactivate Workflow Automator rules, notification templates, and SLA policies before the migration run.
  9. 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.
  10. 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.

More from our Blog

Help Desk Data Migration Playbook: What Data to Move and What to Leave Behind
Help Desk

Help Desk Data Migration Playbook: What Data to Move and What to Leave Behind

This definitive playbook answers the single most critical question: "What data do we actually need to move?". This strategic guide helps you declutter and decide what's precious and what's junk. We provide a clear breakdown of the non-negotiable Tier 1 data, like tickets , knowledge bases , and user profiles, versus the Tier 2 data that provides rich context, like automations and organizations. Use this as your strategic checklist to avoid common mistakes and ensure a flawless, functional new help desk.

Raaj Raaj · · 7 min read
Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration
Help Desk

Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration

Ensure your help desk migration is a success with this comprehensive 20-point post-migration QA checklist. This expert guide details the 20 essential tests needed to validate your data integrity, system functionality, user-friendliness, and performance . Learn exactly how to check everything from ticket data, attachments, and knowledge base articles to critical workflows, automations, and integrations before you go live. This process is your final line of defense against lost tickets, broken workflows, and unhappy customers.

Raaj Raaj · · 8 min read