---
title: "How to Migrate Gladly to Freshdesk: Data Mapping & API Limits"
slug: how-to-migrate-gladly-to-freshdesk-data-mapping-api-limits
date: 2026-07-14
author: Rishabh
categories: [Migration Guide, Gladly, Freshdesk]
excerpt: "How to migrate from Gladly to Freshdesk: object mapping, sessionization logic, API rate limits (10/sec vs 200–700/min), and the created_at timestamp trap."
tldr: "Migrating Gladly to Freshdesk requires sessionizing continuous conversations into discrete tickets. Biggest risks: Freshdesk API won't set historical timestamps, and off-the-shelf tools can't handle the split logic."
canonical: https://clonepartner.com/blog/how-to-migrate-gladly-to-freshdesk-data-mapping-api-limits/
---

# How to Migrate Gladly to Freshdesk: Data Mapping & API Limits


# How to Migrate Gladly to Freshdesk: Data Mapping & API Limits

> [!NOTE]
> **TL;DR — Gladly to Freshdesk Migration**
>
> Migrating from Gladly to Freshdesk is a high-complexity project because the platforms use fundamentally incompatible data models. Gladly uses a person-centric, ticketless model — one continuous conversation per customer — while Freshdesk uses discrete tickets. You must "sessionize" Gladly's lifelong conversation threads into individual Freshdesk tickets, which no off-the-shelf tool handles well. A typical migration takes 2–3 weeks including mapping, sessionization logic, testing, and cutover. The single biggest risk: Freshdesk's standard public API does not let you set historical `created_at` timestamps on tickets, so without workarounds through Freshdesk support, all migrated tickets show today's date. Gladly Rules, SLA policies, People Match configurations, and Sidekick Guides cannot be migrated and must be rebuilt manually. Teams with fewer than 5,000 conversation items and simple schemas can attempt a scripted DIY migration. For anything larger or with complex sessionization requirements, a managed migration service is the safer path.
>
> *Targets Gladly REST API v1 and Freshdesk API v2.*

## What Is a Gladly to Freshdesk Migration?

A Gladly to Freshdesk migration is the process of extracting Customers, Conversations, Conversation Items, Agents, Topics, Tasks, and Answers from Gladly's person-centric data model and loading them into Freshdesk's ticket-centric model as Contacts, Companies, Tickets, Conversations (replies/notes), Agents, Groups, Tags, and Solutions.

This is not a lift-and-shift. It is a **data model translation** — and the gap between the two architectures is the largest you will encounter in any helpdesk-to-helpdesk migration. ([developer.gladly.com](https://developer.gladly.com/rest/))

### Why Teams Move from Gladly to Freshdesk

Teams typically leave Gladly for three platform-specific reasons:

1. **Cost structure.** Gladly does not publish pricing and requires a sales engagement for any budget estimate. Freshdesk's Growth plan starts at $15/agent/month, with transparent, published pricing. Freshdesk also offers a free tier (up to 2 agents) that Gladly has no equivalent to.
2. **Ecosystem breadth.** Freshdesk's marketplace lists 1,000+ pre-built integrations across categories including CRM, e-commerce, analytics, and DevOps. Gladly's integration model centers on its Lookup Adaptor pattern and a smaller set of native connectors (Shopify, Magento, Salesforce, Klaviyo), which can limit teams that need to connect a wider range of tools.
3. **B2B and multi-vertical fit.** Gladly's architecture is optimized for retail, DTC, and hospitality — the "people, not tickets" model is purpose-built for consumer brand loyalty. Teams expanding into B2B, SaaS, or ITSM use cases often find Freshdesk's ticket-centric model a better structural fit, especially with Freshdesk's multi-product portal support for managing multiple brands or product lines from a single account.

The trade-off is real: you gain a cleaner ticket workflow, but you give up Gladly's native cross-channel customer timeline.

### The Architectural Mismatch: Conversations vs. Tickets

This is the single most important thing to understand before planning this migration.

**Gladly organizes all interactions around a persistent Customer Profile.** Every email, chat, SMS, voice call, and social message from a single customer lives in one continuous Conversation Timeline — for the life of the relationship. There are no ticket numbers, no discrete case records. If a customer emails you in 2023 about a billing issue and chats with you in 2025 about a technical bug, Gladly treats this as a single continuous timeline. ([help.gladly.com](https://help.gladly.com/docs/what-is-a-conversation))

**Freshdesk organizes all interactions around discrete Tickets.** Each support request is a numbered record with a defined lifecycle: created → open → pending → resolved → closed. Those same two interactions would be two separate tickets with distinct IDs, subjects, and statuses.

A 1:1 mapping from Gladly Conversations to Freshdesk Tickets is structurally impossible. You must apply **sessionization logic** — breaking each Gladly Conversation into logical groups of Conversation Items that become individual Freshdesk tickets. Without this, you either end up with one massive, unreadable ticket per customer, or thousands of fragmented single-message tickets.

## Gladly to Freshdesk Object Mapping Matrix

Before writing any extraction code, establish how Gladly's objects translate to Freshdesk's schema. This mapping dictates your entire pipeline architecture. [For a deeper dive into field-level mapping methodology, see our data mapping guide](https://clonepartner.com/blog/blog/your-ultimate-guide-to-data-mapping-for-a-flawless-help-desk-data-migration-with-csv-templates/).

| Gladly Object | Freshdesk Object | Notes / Caveats |
|---|---|---|
| Customer | Contact | Map name, emails, phones, address. Gladly stores multiple emails/phones per Customer natively; Freshdesk Contact supports multiple emails but only one primary. |
| Customer customAttributes | Contact custom fields (`cf_*`) | Gladly custom attributes come from Lookup Adaptors (Shopify, custom API). Must be manually mapped to Freshdesk custom fields with `cf_` prefix. |
| Customer business/account data | Company | Freshdesk companies support `domains`; use them for domain-based association. |
| Conversation | Ticket(s) | **One Gladly Conversation → multiple Freshdesk Tickets.** Requires sessionization logic. |
| Conversation Item (EMAIL) | Ticket Reply or Note | Map `initiator.type` = CUSTOMER to public note with `incoming: true`, AGENT to agent note or reply. |
| Conversation Item (CHAT_MESSAGE) | Ticket Note (private) | No native chat-to-ticket replay; store as private notes with channel metadata. |
| Conversation Item (VOICE) | Ticket Note (private) | Voice transcripts (if available) become notes. Call recordings require separate attachment handling. |
| Conversation Item (SMS) | Ticket Note (private) | SMS messages stored as notes with original phone number metadata. |
| Topic | Tag | Gladly Topics are labels applied to Conversations for categorization. Map directly to Freshdesk Tags. |
| Freeform Topic attributes | Ticket custom fields | Conversation-level custom attributes in Gladly (e.g., Order Number) map to Freshdesk ticket custom fields. |
| Agent | Agent | Map by email address. Verify Freshdesk agent seat limits before import. |
| Team / Inbox | Group | Gladly Inboxes route by channel + destination; Freshdesk Groups route by team function. Not always 1:1. |
| Task | Ticket (type = task) or Note | Gladly Tasks are standalone work items. Map open tasks to Freshdesk tickets, closed tasks to private notes. |
| Answer | Solution Article | Gladly Answers (knowledge base) map to Freshdesk Solutions. Freshdesk Solutions are organized in a three-tier hierarchy: **Category → Folder → Article**. Create categories and folders via `POST /api/v2/solutions/categories` and `POST /api/v2/solutions/folders/{category_id}/folders` before importing articles. Multi-language content requires per-language article creation using the `{language}` variant endpoints. Inline formatting often needs HTML cleanup. |
| Rules / People Match | Automations / Dispatch'r | Cannot be migrated. Must be rebuilt manually in Freshdesk. |
| Sidekick Guides | — | No Freshdesk equivalent. Must be recreated using Freshdesk's Freddy AI or bot builder. |

> [!WARNING]
> **What has no clean equivalent on Freshdesk:** Gladly's People Match (AI-powered customer identification), Sidekick Guides (conversational AI workflows), Lookup Adaptor integrations, and the continuous Conversation Timeline structure have no direct Freshdesk equivalents. These must be rebuilt using Freshdesk's native automation tools, Freddy AI, or custom integrations.

### Field-Level Mapping Details

**Customer → Contact field mapping:**

| Gladly Field | Freshdesk Field | Type | Transformation |
|---|---|---|---|
| `name` | `name` | String | Direct map |
| `emails [0].original` | `email` | String | Use primary; additional emails via `other_emails` |
| `phones [0].original` | `phone` | String | Use primary. Gladly stores in E.164 format; Freshdesk is more permissive but will reject invalid lengths. Extras to custom field. |
| `address` | `address` | String | Direct map |
| `customAttributes.*` | `custom_fields.cf_*` | Varies | Manual mapping per attribute |
| `externalCustomerId` | `custom_fields.cf_gladly_id` | String | Preserve for cross-reference |

**Conversation Item → Ticket/Reply field mapping:**

| Gladly Field | Freshdesk Field | Transformation |
|---|---|---|
| `conversationId` | Ticket (grouped) | Sessionization determines grouping |
| `timestamp` | Note body prefix or custom field | Freshdesk API does not allow setting `created_at` via public endpoint |
| `initiator.type` | Reply direction | CUSTOMER → public note with `incoming: true`; AGENT → private note |
| `content.type` | `source` field | EMAIL=2, PORTAL=2. Freshdesk's `source` enum (1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback Widget, 10=Outbound Email) does not cover every Gladly channel — preserve the original channel in a custom field or tag. |
| `content.body` / `content.text` | `description` or note `body` | HTML for email, plain text for chat/SMS |
| Topic IDs | `tags []` | Resolve Topic ID → name, then apply as tag |

Gladly custom attributes are stored as key-value pairs inside the Customer object. Freshdesk requires you to define these as custom fields on the Contact or Ticket object **before** import. If a Gladly attribute does not exist in the Freshdesk schema, the API will reject the entire payload with a `400 Bad Request`. Freshdesk custom date fields accept `YYYY-MM-DD`, so full Gladly ISO 8601 timestamps (e.g., `2024-03-15T14:22:00Z`) need truncation or a separate text field for audit fidelity. ([developer.gladly.com](https://developer.gladly.com/rest/))

**Creating custom fields programmatically:**

Custom ticket fields can be created via Freshdesk's admin UI or the API. To create them programmatically:

```python
# Create a custom ticket field in Freshdesk
field_payload = {
    "label": "Original Created Date",
    "label_for_customers": "Original Date",
    "type": "custom_date",           # Options: custom_text, custom_number, custom_date, custom_dropdown, etc.
    "customers_can_edit": False,
    "required_for_closure": False,
    "required_for_agents": False
}

resp = requests.post(
    f"https://{FD_DOMAIN}.freshdesk.com/api/v2/ticket_fields",
    auth=(FD_API_KEY, "X"),
    headers={"Content-Type": "application/json"},
    data=json.dumps(field_payload)
)
# Returns the field with its API name (e.g., "cf_original_created_date")
```

Create all custom fields before importing any tickets or contacts. Use `GET /api/v2/ticket_fields` and `GET /api/v2/contact_fields` to verify the `name` property (the API key you'll use in payloads).

## How to Export Data from Gladly: APIs and Rate Limits

Gladly provides two primary extraction paths: the **Export API** (bulk, file-based) and the **REST API** (record-by-record). ([developer.gladly.com](https://developer.gladly.com/rest/))

### Export API (Recommended for Migration)

The Gladly Export API is a file-based bulk export that produces JSONL files. It is the recommended path for migration because it bypasses per-record API rate limits entirely.

- **Output format:** `.jsonl` — each line is a JSON object
- **File types:** `conversation_items.jsonl`, `customers.jsonl`, `agents.jsonl`, `topics.jsonl`
- **Scheduling:** Daily by default. Hourly schedules available with approximately 2 hours of lag. Contact Gladly Support to change frequency.
- **Availability:** Export files are available for **14 days**, then auto-deleted. Archive them locally before they expire.
- **Date range:** Export all communications delivered within a custom date range
- **Cost:** One-time exports covering a period greater than 6 months may incur a service fee
- **Lag:** Data is approximately 24 hours behind real-time on daily schedules

> [!WARNING]
> **Gladly data retention post-cancellation:** Gladly does not publicly document how long your data remains accessible after you cancel your subscription. Before initiating the migration, confirm with your Gladly account manager: (1) how long after cancellation you can still access the Export API, (2) whether export files generated before cancellation remain available for the full 14-day window, and (3) whether REST API access is revoked immediately on cancellation or after a grace period. Extract and archive all data locally before canceling your Gladly contract.

```python
# Example: Download Gladly export files
import requests

GLADLY_DOMAIN = "yourorg.gladly.com"
API_TOKEN = "your-api-token"
AGENT_EMAIL = "api-user@yourorg.com"

# List completed export jobs
response = requests.get(
    f"https://{GLADLY_DOMAIN}/api/v1/export/jobs?status=COMPLETED",
    auth=(AGENT_EMAIL, API_TOKEN)
)
jobs = response.json()

# Download conversation_items.jsonl from the latest job
job_id = jobs[0]["id"]
for file_info in jobs[0]["files"]:
    if "conversation_items" in file_info:
        file_response = requests.get(
            f"https://{GLADLY_DOMAIN}/api/v1/export/jobs/{job_id}/files/{file_info}",
            auth=(AGENT_EMAIL, API_TOKEN)
        )
        with open("conversation_items.jsonl", "wb") as f:
            f.write(file_response.content)
```

### REST API (Supplementary)

Use the REST API to enrich export data with details the bulk export does not include (e.g., Inbox assignments, full Customer profiles, attachment URLs).

**Key endpoints and limits:**

| Endpoint | Returns | Pagination | Notes |
|---|---|---|---|
| `GET /api/v1/customer-profiles` | Customer search results | Max 50 profiles | Sorted by `updatedAt` descending |
| `GET /api/v1/customers/{id}` | Single Customer | N/A | Returns merged-customer errors if applicable |
| `GET /api/v1/customers/{id}/conversations` | Conversation list | Not paginated, max 100 | `Gladly-Limited-Data` header indicates if more exist |
| `GET /api/v1/conversation-items/{conversationId}` | Conversation Items | Not paginated, max 1,000 | Use for item-level detail; export files are safer for full-fidelity backfills |
| `GET /api/v1/customers/{id}/tasks` | Task list | Not paginated, max 2,000 | `Gladly-Limited-Data` header if more exist |

> [!CAUTION]
> **Gladly API Rate Limits:** Gladly enforces a default rate limit of **10 requests per second** across all HTTP methods (GET, POST, PUT, PATCH, DELETE). Exceeding this returns HTTP 429. Monitor usage via response headers: `Ratelimit-Limit-Second` and `Ratelimit-Remaining-Second`. The Reports API is slower at 10 requests per minute with a concurrency limit of two. At 10 req/sec you can make 36,000 API calls per hour — but the Export API's bulk JSONL files are far more efficient for large datasets. ([developer.gladly.com](https://developer.gladly.com/rest/))

## Loading Data into Freshdesk: Rate Limits and Batching

Freshdesk's API v2 is the only path for importing tickets, replies, and notes. Freshdesk native CSV imports only support Contacts and Companies — not Tickets. Freshdesk does not offer a dedicated bulk import endpoint for ticket creation (unlike Zendesk's `/api/v2/imports/tickets`). The `PUT /api/v2/tickets/bulk_update` endpoint exists for updating existing tickets in batches, but there is no `bulk_create` analog — each ticket must be created individually via `POST /api/v2/tickets`. ([developers.freshdesk.com](https://developers.freshdesk.com/api/))

### Freshdesk API Rate Limits by Plan

| Plan | Monthly Cost | Rate Limit | Tickets List Sub-limit | Notes |
|---|---|---|---|---|
| Free | $0 (up to 2 agents) | 50 calls/min | — | Not viable for migration |
| Growth | $15/agent/mo | 200 calls/min | 20/min | Minimum viable plan for small migrations |
| Pro | $49/agent/mo | 400 calls/min | 40/min | Recommended for migrations >10K items |
| Enterprise | $79/agent/mo | 700 calls/min | 70/min | Best throughput for large migrations |

> [!WARNING]
> **Trial account trap:** Freshdesk trial accounts are throttled to 50 API calls per minute. If you run test migrations against a trial instance, your scripts will hit 429 errors within seconds. Budget for a paid plan before serious testing, or request a temporary limit increase from Freshdesk support for your sandbox environment. **Provisioning a test environment:** Create a separate Freshdesk account on the plan tier you intend to use in production (e.g., Pro at 400 calls/min). This ensures your migration scripts are tested against realistic rate limits. Freshdesk offers 14-day free trials on all paid plans — use this for your test migration, then convert to paid for the production run. ([support.freshdesk.com](https://support.freshdesk.com/support/solutions/articles/225439-what-are-the-rate-limits-for-the-api-calls-to-freshdesk-))

**Key Freshdesk API behaviors for migration:**

- **Auth:** HTTP Basic Auth using API key as username, any string (e.g., `"X"`) as password. No OAuth required.
- **Ticket creation:** `POST /api/v2/tickets` — creates tickets with the **current** timestamp by default.
- **Notes:** `POST /api/v2/tickets/{id}/notes` — private or public notes. Supports `incoming: true` for notes that should represent inbound customer messages. This is the safer primitive for imported customer history, since `Create Reply` is outbound-oriented.
- **Replies:** `POST /api/v2/tickets/{id}/reply` — public replies. **This triggers email notifications to the customer.** Never use this for historical data.
- **Custom fields:** Pass inside a `custom_fields` object with keys prefixed `cf_`. Use `GET /api/v2/ticket_fields` to discover API names.
- **Attachments:** Freshdesk supports up to 20MB per attachment on paid plans. Use `multipart/form-data` for uploads.
- **Pagination:** Max 100 records per page. Filter endpoint (`GET /api/v2/search/tickets`) capped at 10 pages (300 results total).

### The `created_at` Timestamp Problem

This is the most common failure point in Freshdesk migrations.

Unlike Zendesk (which offers a dedicated `/api/v2/imports/tickets` endpoint that accepts `created_at` as a writable field), **Freshdesk's public API does not expose `created_at` as a writable field.** All tickets created via the standard `POST /api/v2/tickets` endpoint carry today's date as their creation timestamp. ([developers.freshdesk.com](https://developers.freshdesk.com/api/))

**Workarounds:**

1. **Contact Freshdesk support** (`support@freshdesk.com`) before migration. Their internal tools can sometimes handle bulk imports with historical dates. This is not publicly documented and may involve a service fee. Request this early — response times can be 3–5 business days, and the process may require a Freshdesk Professional Services engagement.
2. **Store the original timestamp** in a custom date field (e.g., `cf_original_created_date` of type `custom_date`, formatted as `YYYY-MM-DD`) and prepend the full ISO 8601 timestamp to the ticket description body.
3. **Use private notes** to embed the original date as the first line of each historical reply: `[Original: 2024-03-15T14:22:00Z]`.

This also means **SLA metrics for migrated tickets will be inaccurate.** Tag all migrated tickets with `gladly-migration` and exclude them from historical SLA reporting using Freshdesk's reporting filters. SLA tracking will only be accurate for new tickets created after cutover.

### Throughput Math

On a Freshdesk **Pro plan** (400 calls/min):

- Creating one ticket requires 1 API call
- Adding each note/reply requires 1 API call per note
- A typical sessionized Gladly conversation with 1 ticket + 5 reply/note items = **6 API calls**
- Effective throughput: ~66 tickets per minute, or **~4,000 tickets per hour**
- A 50,000-ticket migration at this rate takes **~12.5 hours** of continuous API calls (before retries and 429 backoff)

On **Growth** (200 calls/min), that same migration takes roughly 25 hours. On **Enterprise** (700 calls/min), approximately 7 hours. Real throughput is 20–40% lower once you factor in contact lookups, attachment uploads (which consume additional API calls and bandwidth), validation, and retry cycles with exponential backoff.

## Sessionization: How to Split Gladly Conversations into Freshdesk Tickets

Sessionization is the transformation step unique to Gladly migrations. A single Gladly Customer may have one Conversation with hundreds of Conversation Items spanning years. You need to split that into logical Freshdesk tickets that agents can actually work with.

### Sessionization Strategies

| Strategy | Logic | Best For | Typical Result |
|---|---|---|---|
| **Time-gap based** | New ticket when gap between Conversation Items exceeds N hours (24–72h typical) | High-volume support with clear session patterns | Most balanced ticket density for e-commerce/DTC |
| **Topic-based** | New ticket when Gladly Topic changes | Teams that consistently tag Topic per interaction | Clean subject lines but depends on tagging discipline |
| **Session ID** | Split on Gladly's `sessionId` field (available in exports) | Chat-heavy histories where Gladly already groups items by session | Tight sessions but may miss email threads |
| **Channel-based** | New ticket per channel switch (email → chat → voice) | Multi-channel teams where channel = context boundary | Can over-fragment if customers switch channels within one issue |
| **Hybrid** | Time gap AND topic change | Most accurate, highest implementation cost | Best fidelity; recommended for most migrations |

> [!TIP]
> **Choosing the right session gap:** Start with 48 hours and validate against 50–100 sample customers. If your support team handles fast-turnaround e-commerce issues (average resolution under 4 hours), 24 hours may produce more natural ticket boundaries. If you handle longer B2B cases (average resolution 3–7 days), 72 hours or topic-based splitting may be better. If old history is mostly reference material, consider migrating only the active window (e.g., last 12–24 months) and archiving the rest as a static JSONL backup — that is usually better than forcing years of passive context into live tickets.

Also split before **large voice artifacts** if call recordings or attachments could push the Freshdesk ticket over the 20MB attachment cap. And split by **operational rule** when one Gladly conversation crosses brands, queues, or SLA ownership.

**Multi-brand Freshdesk consideration:** If your Freshdesk instance uses multi-product portals (available on Pro and Enterprise plans), sessionization logic must also route tickets to the correct product. Add a product-mapping step that inspects the Gladly Inbox or Topic to determine which Freshdesk product each session belongs to. Pass the `product_id` field in the `POST /api/v2/tickets` payload.

**Example: Hybrid sessionization (Python)**

```python
from datetime import datetime, timedelta

SESSION_GAP_HOURS = 48

def sessionize(conversation_items: list) -> list:
    """Split a flat list of Gladly conversation items into ticket sessions."""
    sessions = []
    current_session = []

    # Sort by timestamp ascending
    items = sorted(conversation_items, key=lambda x: x["timestamp"])

    for item in items:
        ts = datetime.fromisoformat(item["timestamp"].replace("Z", "+00:00"))

        if current_session:
            last_ts = datetime.fromisoformat(
                current_session[-1]["timestamp"].replace("Z", "+00:00")
            )
            last_topic = current_session[-1].get("topicIds", [])
            current_topic = item.get("topicIds", [])

            time_gap = (ts - last_ts) > timedelta(hours=SESSION_GAP_HOURS)
            topic_changed = current_topic != last_topic and current_topic

            if time_gap or topic_changed:
                sessions.append(current_session)
                current_session = []

        current_session.append(item)

    if current_session:
        sessions.append(current_session)

    return sessions  # Each session becomes one Freshdesk ticket
```

## Migration Approaches Compared

| Approach | How It Works | Complexity | Best For | Key Risk |
|---|---|---|---|---|
| **Native CSV export/import** | Export Gladly via Export API → transform to CSV → import Contacts/Companies into Freshdesk via CSV upload | Low | Contacts/Companies only | Cannot import tickets via CSV. Conversations must still use the API. |
| **DIY API script** | Custom Python/Node script using Gladly Export API + REST API → sessionize → POST to Freshdesk API v2 | High | Engineering teams with bandwidth, <5K conversation items | Timestamp loss, notification triggers, rate limit management, broken relationships |
| **Help Desk Migration (HDM)** | Point-and-click automated tool that maps standard objects between helpdesks | Medium | Standard migrations with ticket-based source platforms | Cannot execute programmatic sessionization. HDM attempts a 1:1 object map, which fails for Gladly's ticketless model. Verify before committing. ([help-desk-migration.com](https://help-desk-migration.com/gladly/)) |
| **Managed migration service** | Expert team builds custom ETL pipeline with sessionization logic, rate limit orchestration, and validation | Medium (for you) | >10K items, complex schemas, zero-downtime requirement | Cost (~$3K–$15K+ depending on volume and complexity) |

**Managed migration cost factors:** The $3K–$15K+ range depends on: total conversation item count (the primary volume driver), number of custom attributes requiring mapping, sessionization complexity (simple time-gap vs. hybrid logic), attachment volume and total storage size, whether historical timestamps must be preserved via Freshdesk support coordination, and zero-downtime cutover requirements. Migrations under 10K items with simple schemas typically fall in the $3K–$5K range; migrations exceeding 100K items with complex sessionization, multi-brand routing, and attachment rewriting can exceed $15K.

Standard migration tools fail on Gladly-to-Freshdesk projects because they attempt a 1:1 object map. They cannot execute the programmatic sessionization required to break a single Gladly customer timeline into multiple Freshdesk tickets.

## Step-by-Step Migration Process

The order of operations matters. Import in the wrong sequence and you will break relationships.

### Step 1: Export from Gladly

1. Trigger a full Export API job covering your desired date range
2. Download `customers.jsonl`, `conversation_items.jsonl`, `agents.jsonl`, `topics.jsonl`
3. Supplement with REST API calls for Inbox assignments, Customer custom attributes, and attachment URLs
4. Store all raw data in a staging area (S3 bucket, local DB, or versioned file storage)
5. Count total customers, conversation items, and attachments — this forms your baseline reconciliation ledger

### Step 2: Clean and Deduplicate

- Deduplicate Customers by email (Gladly treats email as unique identifier)
- Remove test/spam profiles
- Identify merged customers (Gladly returns errors for merged profile IDs — follow redirect to canonical profile)
- Flatten Gladly's nested customer arrays (like emails and phones) into primary fields
- [Decide what data to migrate and what to archive](https://clonepartner.com/blog/blog/help-desk-data-migration-playbook/)

### Step 3: Create Freshdesk Structure

**This must happen before ticket import:**

1. Create **Groups** (from Gladly Inboxes/Teams) via `POST /api/v2/groups`
2. Create **Agents** (verify seat limits — Freshdesk returns HTTP 403 if the agent limit for your plan is exceeded) via `POST /api/v2/agents`
3. Create **custom ticket fields** and **contact fields** (via Freshdesk admin or `POST /api/v2/ticket_fields` / `POST /api/v2/contact_fields`)
4. Create **Solution Categories and Folders** if migrating Gladly Answers (via `POST /api/v2/solutions/categories` and `POST /api/v2/solutions/folders/{category_id}/folders`)
5. Create **Tags** (from Gladly Topics — tags auto-create on ticket creation in Freshdesk, so explicit pre-creation is optional)
6. **Disable email notifications** and automations (Dispatch'r, Supervisor, Email Notifications) to prevent imported tickets from triggering customer-facing emails. In Freshdesk Admin: go to **Admin → Automations → Ticket Creation** and disable all rules; go to **Admin → Email Notifications** and disable agent and requester notifications.

### Step 4: Import Contacts and Companies

- `POST /api/v2/contacts` for each Gladly Customer
- Map primary email, name, phone, address, and custom fields
- Store the Freshdesk `contact_id` → Gladly `customerId` mapping for ticket association
- **Watch for HTTP 409** (duplicate email) — deduplicate before posting, or implement upsert logic: catch the 409, then `GET /api/v2/contacts?email={email}` to retrieve the existing contact ID
- If migrating company data: `POST /api/v2/companies` and associate contacts via the `company_id` field

### Step 5: Migrate Gladly Answers to Freshdesk Solutions

If your Gladly instance uses Answers (knowledge base articles), migrate them before cutover so agents have reference material available from day one.

1. Extract Answers from Gladly (via Export API or REST API)
2. Create Solution Categories in Freshdesk: `POST /api/v2/solutions/categories` with `{ "name": "Category Name" }`
3. Create Folders within categories: `POST /api/v2/solutions/folders/{category_id}/folders` with `{ "name": "Folder Name", "visibility": 2 }` (1=all users, 2=logged-in users, 3=agents only)
4. Create Articles: `POST /api/v2/solutions/folders/{folder_id}/articles` with `{ "title": "...", "description": "<html>...", "status": 2 }` (1=draft, 2=published)
5. For multi-language articles: use `POST /api/v2/solutions/articles/{article_id}/{language_code}` to create translated versions
6. Clean HTML formatting: Gladly Answers may use formatting that doesn't render cleanly in Freshdesk's article editor. Strip or normalize non-standard HTML before import.

### Step 6: Sessionize and Import Tickets

1. Group Conversation Items by `customerId`
2. Apply sessionization logic to split into ticket sessions
3. For each session:
   - Create ticket via `POST /api/v2/tickets` with first customer-visible item as description
   - Add subsequent items as notes via `POST /api/v2/tickets/{id}/notes`
   - Use `private: true` for agent items; for customer items, use `private: false` with `incoming: true`
   - Embed original timestamp in note body: `[Original: 2024-03-15T14:22:00Z]`
   - Store original Gladly timestamp in `cf_original_created_date`
   - Apply tags (from Topics), custom fields, and status
   - If using multi-product Freshdesk, include `product_id` in the ticket payload
   - Tag all imported tickets with `gladly-migration`

```python
import requests
import json
import time

FD_DOMAIN = "yourcompany"
FD_API_KEY = "your-freshdesk-api-key"

def create_ticket_from_session(session_items, contact_email, tags):
    first_item = session_items[0]

    ticket_payload = {
        "email": contact_email,
        "subject": f"Migrated: {tags[0] if tags else 'Gladly conversation'}",
        "description": (
            f"[Original date: {first_item['timestamp']}]<br>"
            f"{first_item['content'].get('body', first_item['content'].get('text', ''))}"
        ),
        "status": 5,  # Closed
        "priority": 1,  # Low
        "tags": tags + ["gladly-migration"],
        "custom_fields": {
            "cf_original_created_date": first_item["timestamp"][:10]
        }
    }

    resp = requests.post(
        f"https://{FD_DOMAIN}.freshdesk.com/api/v2/tickets",
        auth=(FD_API_KEY, "X"),
        headers={"Content-Type": "application/json"},
        data=json.dumps(ticket_payload)
    )

    if resp.status_code == 429:
        retry_after = int(resp.headers.get("Retry-After", 60))
        time.sleep(retry_after)
        return create_ticket_from_session(session_items, contact_email, tags)

    if resp.status_code not in (200, 201):
        raise Exception(f"Ticket creation failed: {resp.status_code} {resp.text}")

    ticket_id = resp.json()["id"]

    # Add remaining items as notes
    for item in session_items[1:]:
        is_agent = item["initiator"]["type"] == "AGENT"
        note_payload = {
            "body": (
                f"[Original: {item['timestamp']}] [{item['content']['type']}]<br>"
                f"{item['content'].get('body', item['content'].get('text', ''))}"
            ),
            "private": is_agent,
            "incoming": not is_agent
        }
        note_resp = requests.post(
            f"https://{FD_DOMAIN}.freshdesk.com/api/v2/tickets/{ticket_id}/notes",
            auth=(FD_API_KEY, "X"),
            headers={"Content-Type": "application/json"},
            data=json.dumps(note_payload)
        )
        if note_resp.status_code == 429:
            retry_after = int(note_resp.headers.get("Retry-After", 60))
            time.sleep(retry_after)
        else:
            time.sleep(0.15)  # Stay under rate limits

    return ticket_id
```

### Step 7: Handle Attachments and Inline Images

Download Gladly attachments to a local buffer, then upload them to Freshdesk using `multipart/form-data` alongside the specific ticket note.

**Inline images require special handling.** Gladly allows customers to paste images directly into the chat/email body. These are stored as hosted URLs in the Gladly payload. If Gladly's hosting expires post-migration, those images break. Your migration script must:

1. Parse the HTML body for `img src` URLs pointing to Gladly-hosted domains
2. Download each image to local storage
3. Upload it to Freshdesk as a ticket attachment
4. Rewrite the HTML body with the new Freshdesk-hosted URLs

Voice recordings stored in external telephony providers (e.g., Twilio, Amazon Connect) do not migrate — only text transcripts can be carried over as ticket notes. Freshdesk caps total attachment size at 20MB per ticket; large call recordings need an external archive (e.g., S3 bucket with pre-signed URLs) or a link-back strategy where you store the recording URL in a custom field or note body.

### Step 8: Validate and Cutover

- Reconcile record counts: Gladly Customers = Freshdesk Contacts
- Verify sessionized ticket count matches expected split
- Spot-check 50–100 tickets across edge cases (see Validation section below)
- Run a final export from Gladly covering items created since the initial export
- Import the delta into Freshdesk
- Cut DNS/channel routing to Freshdesk
- [Follow a zero-downtime cutover process](https://clonepartner.com/blog/blog/zero-downtime-help-desk-data-migration/)
- Keep Gladly active (read-only) for 30 days post-cutover as a fallback

## Timeline, Phases, and Resourcing

| Phase | Duration | Dependencies |
|---|---|---|
| Discovery and mapping | 2–3 days | Access to both platforms, stakeholder alignment |
| Sessionization logic design | 2–4 days | Sample data analysis, session gap decision |
| Script development | 3–5 days | Gladly API credentials, Freshdesk paid plan |
| Knowledge base migration | 1–2 days | Gladly Answers export, Freshdesk Solutions category structure |
| Test migration (golden sample) | 2–3 days | 50–100 representative customers |
| Full migration run | 1–3 days | Depends on volume and plan-level rate limits |
| Validation and UAT | 2–3 days | CS team availability for spot-checks |
| Delta sync and cutover | 1 day | Low-traffic window, DNS control |
| **Total** | **2–4 weeks** | |

A phased or incremental cutover is usually safer than a big-bang approach. Gladly can produce scheduled exports, and Freshdesk can absorb history while the source system stays live, which makes a pilot → backfill → delta → cutover sequence more predictable.

### Risk Register

| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Timestamps lost on tickets | High | Medium | Store in custom field + description; contact Freshdesk support for internal import tool |
| Reply import triggers customer emails | High | Critical | Use notes (not replies) for all historical data; disable email notifications before migration |
| Automation rules fire on imported tickets | High | Critical | Disable Dispatch'r, Supervisor, and Email Notifications; tag imported tickets with `gladly-migration` so exclusion rules can detect them |
| Sessionization produces incorrect ticket boundaries | Medium | High | Validate with CS team on 50+ samples before full run |
| Rate limit throttling extends timeline | Medium | Low | Implement exponential backoff; upgrade Freshdesk plan |
| Freshdesk trial API limits block testing | High | Medium | Use paid sandbox matching production plan tier |
| Merged Gladly customers cause extraction errors | Low | Medium | Handle `merged into` API errors; follow redirect to canonical profile |
| Gladly data access revoked before migration completes | Medium | Critical | Archive all export files locally before canceling Gladly; confirm data retention timeline with Gladly account manager |
| Attachments exceed Freshdesk 20MB per-ticket limit | Low | Medium | Split sessions before large attachments; use external archive with link-back for oversized files |

## Customer and Operational Impact

Your customers should experience zero disruption during this migration if done correctly. Both platforms remain live during the bulk import.

After cutover, customers see Freshdesk's portal UI instead of Gladly's. Ticket numbers replace the continuous conversation view. Historical conversation context is preserved as notes within tickets, but the visual presentation changes significantly.

**The agent paradigm shift:** Because Gladly displays a single continuous thread, agents are used to scrolling up to see a customer's entire history. In Freshdesk, that history is distributed across multiple discrete tickets. Train your team to use Freshdesk's **"Recent Tickets from this Contact"** widget on the right-hand sidebar to reconstruct the contextual view they relied on in Gladly.

**Change management checklist:**

- Brief agents on the new ticket-based workflow 1 week before cutover
- Provide a reference card mapping Gladly concepts → Freshdesk equivalents (e.g., "Conversation = multiple Tickets," "Topic = Tag," "Inbox = Group")
- Tag all imported tickets with `gladly-migration` so agents can identify historical data
- Add the original Gladly conversation ID, topic, channel, and timestamp window to the opening note on each imported ticket
- Plan for 2 weeks of elevated support escalations as agents adjust
- Schedule a 30-minute live walkthrough showing agents how to navigate migrated ticket history using the Contact sidebar

## Edge Cases and Known Limitations

1. **Gladly conversation list cap:** The `GET /api/v1/customers/{id}/conversations` endpoint returns a maximum of 100 conversations per customer, unpaginated. The conversation items endpoint caps at 1,000 items per conversation, and tasks at 2,000 per customer. For power-users with large histories, rely on the Export API's JSONL files instead of per-record REST calls. ([developer.gladly.com](https://developer.gladly.com/rest/))

2. **Conversation items with invalid attributes:** Gladly's documentation notes that items with invalid attributes (e.g., undelivered emails, auto-reply SMS) may be excluded from the export. Your reconciliation counts may not match perfectly — document the delta and flag excluded items in your reconciliation ledger.

3. **Inline images:** Gladly stores inline images as hosted URLs. If Gladly's hosting expires post-migration, those images break. Parse the HTML body, download images, re-upload to Freshdesk, and rewrite the `img src` URLs. (See Step 7 for implementation details.)

4. **Voice recordings:** Actual call recordings are typically stored in the telephony provider (e.g., Twilio, Amazon Connect), not Gladly. Only transcripts can be carried over as ticket notes. Freshdesk caps total ticket attachments at 20MB.

5. **Custom attributes from Lookup Adaptors:** Gladly custom attributes sourced from external Lookup Adaptors (e.g., Shopify LTV, loyalty tier) are dynamic, not stored natively — they are fetched in real-time from the external system. Snapshot these values at export time and store them as static Freshdesk custom fields. Document that these values represent a point-in-time snapshot, not live data.

6. **Duplicate contacts:** Freshdesk returns HTTP 409 if you attempt to create a contact with an email that already exists. Deduplicate by email before import, or implement upsert logic: catch the 409, extract the existing contact ID from `GET /api/v2/contacts?email={email}`, and use it for ticket association.

7. **Agent authorship on notes:** Freshdesk notes created via API are attributed to the API key holder's identity by default. To attribute notes to the correct agent, you need each agent's individual API key — or accept that historical notes will show the migration user as author. A practical compromise: include the original agent name in the note body (e.g., `[Agent: Jane Smith]`).

8. **Freshdesk ticket source enum:** Freshdesk's `source` field accepts integer values: 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback Widget, 10=Outbound Email. This does not cover every Gladly channel (e.g., SMS, Instagram, Facebook Messenger, Twitter DM). Preserve the original Gladly channel in a custom field (e.g., `cf_original_channel`) or tag for reporting accuracy. ([developers.freshdesk.com](https://developers.freshdesk.com/api/))

9. **Automation triggers on API-created tickets:** Freshdesk automation rules (Dispatch'r, Supervisor, Observer) run on ticket creation, including API-created tickets. If you fail to disable automations before migration, Freshdesk will trigger "New Ticket Created" rules and potentially email your entire customer base with their own years-old support requests. Always disable Dispatch'r, Supervisor, and Email Notifications before running any migration load. ([support.freshdesk.com](https://support.freshdesk.com/support/solutions/articles/37614-setting-up-automation-rules-to-run-on-ticket-creation))

10. **Freshdesk ticket list API cap:** The `GET /api/v2/tickets` endpoint returns a maximum of 30,000 tickets total (300 pages × 100 per page). For post-migration validation of larger datasets, use `GET /api/v2/search/tickets` with date filters or export data via Freshdesk's built-in data export feature (Admin → Account → Export Data).

## Validation and Testing

Do not rely on spot-checking alone. Execute programmatic record-count reconciliation.

**Record-count reconciliation:**

- Count Gladly Customers → compare to Freshdesk Contacts created
- Count Gladly Conversation Items → compare to total Freshdesk ticket replies + notes
- Count unique Topics → compare to Freshdesk Tags
- Count Gladly Answers → compare to Freshdesk Solution Articles

**Field-level checks (sample 50–100 tickets):**

- Verify customer email matches between source and target
- Verify tag/topic mapping accuracy
- Confirm custom field values populated correctly (`cf_original_created_date`, `cf_gladly_id`, `cf_original_channel`)
- Check that inline HTML renders properly in ticket descriptions
- Verify attachment links are accessible (not expired Gladly URLs)
- Confirm private vs. public note visibility is correct

**UAT with customer success team:**

- Pick 10 high-value customers and review their full migrated history
- Confirm sessionization produced natural ticket boundaries
- Check that the `cf_original_created_date` custom field contains the correct source timestamp
- Verify that the "Recent Tickets from this Contact" sidebar provides adequate cross-ticket context

Build a sampling strategy that selects 100 highly complex Gladly profiles (those with 50+ messages and multiple attachments) and compare them side-by-side in both UIs.

> [!WARNING]
> **Freshdesk validation gotcha:** Freshdesk's list tickets endpoint defaults to the last 30 days and maxes out at 30,000 tickets. Validation scripts need the `updated_since` parameter or Freshdesk data exports (Admin → Account → Export Data) for older or larger datasets. Do not rely on `include=conversations` on the tickets endpoint, which only returns up to ten entries per ticket and consumes extra API credits against your rate limit. ([developers.freshdesk.com](https://developers.freshdesk.com/api/))

**Rollback plan:** Keep Gladly active (read-only) for 30 days post-cutover. If critical data is missing, you can re-extract and supplement.

**Rollback procedure if cutover fails:**

1. Re-route DNS and channel configurations (email forwarding, chat widget, social integrations) back to Gladly
2. Re-enable Gladly agent access and restore any suspended inboxes
3. For tickets created in Freshdesk during the migration window, export them via `GET /api/v2/tickets?updated_since={cutover_timestamp}` and either manually re-create them in Gladly or archive them
4. Communicate the rollback to agents with clear instructions on which system is active
5. Investigate the failure, fix the migration pipeline, and schedule a new cutover window

Gladly export files are only available for 14 days, so archive all export files locally and verify their integrity before they expire.

## Build In-House vs. Use a Managed Service

**When in-house is the right call:**

- Fewer than 5,000 conversation items
- Simple schema (no custom attributes, no Lookup Adaptor data)
- Dedicated engineer available for 2–3 weeks (100–160 hours)
- Comfortable with timestamp limitations
- No hard deadline pressure
- No multi-brand or multi-product Freshdesk requirements

**When in-house is not the right call:**

- More than 10,000 conversation items
- Sessionization logic is non-trivial (multiple topics, channel switches, multi-brand routing)
- Historical timestamps must be preserved accurately (requires Freshdesk support coordination)
- Zero-downtime cutover is required
- Engineering team is already committed to product work
- Knowledge base migration involves multi-language content

The hidden cost of DIY is not the initial build — it is the re-migration. The hidden work is not fetching records; it is defining split rules, precreating fields, controlling rate limits, repairing failed attachments, and proving that private versus public history stayed correct after load. Broken contact associations, notification emails sent to customers from historical replies, and incorrect sessionization that produces tickets with mixed topics all force partial re-runs that quickly outpace the cost of doing it right the first time.

> Planning a Gladly to Freshdesk migration? Our engineers will review your Gladly data model, sessionization requirements, and Freshdesk plan limits — and map out a zero-downtime migration plan. No obligation.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### How long does a Gladly to Freshdesk migration take?

A typical Gladly to Freshdesk migration takes 2–3 weeks end-to-end, including discovery, sessionization design, scripting, testing, and cutover. The actual data transfer for 50,000 tickets on a Freshdesk Pro plan runs approximately 12–15 hours of continuous API calls.

### What data cannot be migrated from Gladly to Freshdesk?

Gladly Rules, SLA policies, People Match configurations, Sidekick Guides, and Lookup Adaptor integrations cannot be migrated — they must be rebuilt manually. Voice call recordings stored in external telephony providers do not transfer; only text transcripts can be carried over. The continuous conversation structure requires sessionization into discrete tickets.

### Can I use CSV imports for a Gladly to Freshdesk migration?

No. Freshdesk's native CSV import tool only supports Contacts and Companies. To migrate Gladly conversations, messages, notes, and attachments, you must use the Freshdesk REST API (v2).

### Will Freshdesk automations fire on imported tickets?

Yes. Freshdesk automation rules run on ticket creation, including API-created tickets. Disable Dispatch'r, Supervisor, and Email Notifications before any migration load, and tag imported tickets with a migration identifier so exclusion rules can detect them.

### How much does a Gladly to Freshdesk migration cost?

DIY costs are primarily engineering time: 100–300 hours depending on complexity, plus a Freshdesk paid plan ($15–$79/agent/month). A managed migration service typically ranges from $3,000–$15,000+ for 10,000–200,000 conversation items, depending on sessionization requirements and attachment handling.
