---
title: "Ada to Freshservice Migration: The CTO's Technical Guide"
slug: ada-to-freshservice-migration-the-ctos-technical-guide
date: 2026-07-27
author: Wahab
categories: [Migration Guide, Freshservice]
excerpt: "Technical guide to migrating data from Ada's conversational AI platform to Freshservice. Covers API extraction, object mapping, rate limits, and validation."
tldr: "Ada to Freshservice migration requires transforming conversation-centric data into ITSM tickets via API. Plan for Ada's 12-month data window, rate limits on both sides, and variable-to-custom-field mapping."
canonical: https://clonepartner.com/blog/ada-to-freshservice-migration-the-ctos-technical-guide/
---

# Ada to Freshservice Migration: The CTO's Technical Guide


# Ada to Freshservice Migration: The CTO's Technical Guide

Migrating from Ada to Freshservice means moving data from an AI-powered conversational automation platform to a full IT service management (ITSM) tool. These are fundamentally different systems. Ada stores conversations, messages, end-user identities, and bot variables. Freshservice stores tickets, requesters, assets, problems, changes, and knowledge base articles. If you attempt a simple point-to-point data dump, you will end up with unreadable tickets, lost context, and broken reporting.

This guide covers the technical details: what data you can extract from Ada, how Freshservice's API accepts it, where the models diverge, and how to handle the gaps.

## Why Teams Migrate from Ada to Freshservice

Ada is an agentic customer experience (ACX) platform designed to automate customer conversations using AI agents across messaging, email, and voice channels. <cite index="19-7,19-8">Ada is the agentic customer experience platform that empowers enterprises to manage and continuously improve their AI agents, resolving 84% of customer inquiries autonomously across voice, messaging, and email channels.</cite>

Freshservice is Freshworks' ITSM platform, built for internal IT operations — incident management, change management, asset tracking, and service catalogs. <cite index="14-6,14-7">Freshservice was named a Strong Performer in The Forrester Wave for Enterprise Service Management Platforms, Q4 2025.</cite>

Common reasons teams make this move:

- **Consolidation:** Moving away from a standalone chatbot to a unified ITSM platform that includes native virtual agents (Freddy AI), eliminating duplicate tooling and reducing vendor count.
- **Handoff archive:** Ada was used as a front-line deflection layer with handoffs to a separate helpdesk. Now the team wants all conversation history preserved as Freshservice tickets for unified reporting.
- **Platform switch:** The team is replacing Ada with Freshservice's Freddy AI agent and needs historical data for analytics and compliance.
- **Compliance:** Regulated industries (healthcare, financial services, government) need permanent conversation records stored in their system of record, not scattered across a conversational AI layer with a 12-month retention ceiling.

The primary technical hurdle is transforming unstructured, multi-turn chat logs into structured ITSM tickets with proper requester linkage, status mapping, and chronologically accurate notes.

## Core Architecture Differences

Before mapping fields, understand the structural mismatch:

| Concept | Ada | Freshservice |
|---|---|---|
| Primary record | Conversation | Ticket |
| User identity | End User (chatter) | Requester |
| Interaction log | Messages (bot + human) | Ticket Conversations (replies, notes) |
| Contextual data | Variables (meta, global, local) | Custom Fields on tickets |
| Bot config | Answers, Processes, Instructions | No equivalent (rebuild in Freddy AI) |
| Knowledge content | Knowledge Hub (synced sources) | Solutions (Knowledge Base) |
| Agent handoff | Handoff event + transcript | Ticket assignment |
| Resolution tracking | Automated Resolution (AR) | Ticket status + resolution fields |

Ada's data model is conversation-centric and session-based. Freshservice's model is ticket-centric and entity-based. Every Ada conversation must be transformed into a Freshservice ticket, with messages becoming conversation entries and variables becoming custom field values.

## Data Model & Object Mapping

### Ada Conversations → Freshservice Tickets

Each Ada conversation maps to a single Freshservice ticket. <cite index="24-1">The Data Export API allows authenticated access to conversation and message data, enabling you to integrate this data into your own systems.</cite> The conversation object includes timestamps, channel, resolution status, and variable values.

A representative Ada conversation object (sanitized) looks like this:

```json
{
  "id": "conv_abc123def456",
  "created": "2024-09-15T14:32:08Z",
  "ended": "2024-09-15T14:38:22Z",
  "channel": "web",
  "end_user_id": "eu_789xyz",
  "chatter_id": "ch_456abc",
  "resolution": "resolved",
  "has_handoff": false,
  "variables": {
    "email": "jane.doe@example.com",
    "name": "Jane Doe",
    "department": "Engineering",
    "issue_type": "vpn_access",
    "priority_level": "high"
  },
  "tags": ["vpn", "access-request"],
  "message_count": 8
}
```

The field mapping:

| Ada Field | Freshservice Field | Notes |
|---|---|---|
| `conversation_id` | `custom_fields.ada_conversation_id` | Store as custom text field for traceability |
| `created` | `created_at` | ISO 8601 — Freshservice accepts this on ticket creation via API when using an admin API key. Standard agent keys may silently ignore this field — test in sandbox first. |
| `channel` | `custom_fields.source_channel` | Map to dropdown: web, email, SMS, social |
| `end_user_id` | `requester_id` | Must create requester first, then reference ID |
| `resolution` | `status` | Map Ada resolution states to Freshservice statuses (Open=2, Pending=3, Resolved=4, Closed=5) |
| `variables` (global/meta) | `custom_fields.*` | Create matching custom fields in Freshservice before import |
| Handoff occurred | `custom_fields.handoff` | Boolean flag |
| `tags` | `tags` | Direct transfer if using simple string tags |

### Ada Messages → Freshservice Ticket Conversations

<cite index="25-9">The API responds with either a Conversation or a Message object, depending on which endpoint you're querying.</cite> Each Ada message becomes either a **reply** or a **note** in Freshservice:

- **End-user messages** → public replies (attributed to requester)
- **Bot responses** → private notes (attributed to a system agent account, e.g., "Ada Bot")
- **Agent messages** (post-handoff) → public replies (attributed to the handling agent)

Alternatively, you can concatenate the entire message array into a single HTML block and inject it into the ticket `description` field. This is simpler but loses the threaded note structure. Choose based on whether your team needs to read individual exchanges or just needs a historical archive.

**Note on message types:** Ada exports richer message shapes than Freshservice note bodies can represent cleanly. Quick replies, carousel cards, tool calls, and structured events should be flattened into readable HTML. For example, a quick-reply message with three options becomes a note reading: `"Bot offered options: [Reset Password] [Unlock Account] [Contact IT]"`. Preserve files as ticket attachments where size limits allow (40 MB ceiling in Freshservice).

### Ada End Users → Freshservice Requesters

Ada tracks end users with `end_user_id` and `chatter_id`. <cite index="35-4,35-5">A persistent identifier represents a user across multiple conversations and channels, remaining stable if the user can be recognized based on persistence rules or channel metadata.</cite> These map to Freshservice **Requesters**. Create requesters first, then reference their IDs when creating tickets.

| Ada Field | Freshservice Field |
|---|---|
| Email (from metavariable) | `email` (primary key for dedup) |
| Name (from metavariable) | `first_name`, `last_name` |
| Phone (from metavariable) | `phone` |
| `end_user_id` | `custom_fields.ada_end_user_id` |

> [!WARNING]
> Freshservice requires a Requester email to create a ticket. If an Ada chat was anonymous (in observed migrations, 8–15% of conversations lack email metadata), you must create a generic "Guest User" requester (e.g., `anonymous@yourdomain.com`) and map all anonymous conversations to that ID to prevent API rejections.

### Ada Knowledge Hub → Freshservice Solutions

Ada's Answers (the KB content the bot uses) map to Freshservice Solution Articles within the **Categories → Folders → Articles** hierarchy. Ada's knowledge hub typically syncs from external sources, so you may already have this content in another system. If not, extract the answers and convert Markdown to HTML before POSTing to `/api/v2/solutions/articles`.

## Migration Approaches

### Native Export/Import (CSV-based)

**How it works:** Export Chatter and Conversation data from Ada via their Data Export tool. Format the CSVs and upload them via the Freshservice admin UI.

**When to use it:** Small datasets where you only need basic requester profiles and a summary of conversations — not full message threading.

**Trade-offs:** Free and requires no coding. But Freshservice's CSV importer has limited support for conversation threading — you get flat ticket records, not threaded replies. It fails completely at maintaining complex chat transcripts, inline images, or multi-threaded conversations.

**Scalability:** Low. Best for under 10,000 conversations where you only need the conversation summary.

### API-Based Custom Migration (Recommended)

**How it works:** Write ETL scripts that query the Ada Data Export API, transform the JSON payloads, and push data via the Freshservice REST API v2.

<cite index="24-8,24-9,24-10,24-11,24-12">The Data Export API adheres to global rate limits with specific limits of 10 requests per second per endpoint. Page size is limited to a maximum of 10,000 records per page. A query's end date cannot be more than 60 days after its start date, and data is available from the past 12 months.</cite>

On the Freshservice side, <cite index="11-1,11-2">rate limits vary by plan — Starter accounts get 100 requests per minute, Growth gets 200, Pro gets 400, and Enterprise gets 500.</cite> <cite index="13-1">For migration partners, the rate limit can be increased to 700 per minute for the required duration.</cite>

**When to use it:** Medium to large datasets, teams with engineering capacity, or when conversation data requires complex transformation (formatting transcripts as HTML, mapping variables to custom fields).

**Trade-offs:** Full control over transformation logic and preserves conversation threading. The downside is engineering cost — you must handle pagination, network timeouts, rate limits on both sides, and Ada's 12-month data window means older data may be inaccessible via API.

**Scalability:** High, assuming your scripts handle rate-limit backoffs and idempotency properly.

### Middleware / iPaaS Platforms (Zapier, Make)

**How it works:** Use platforms like Make, Zapier, or Workato to connect Ada's API to Freshservice's API through visual workflows.

**When to use it:** For ongoing, real-time sync between the two platforms during a phased rollout — not for historical backfills.

**Trade-offs:** Fast to set up and some platforms have pre-built Freshservice connectors. But Ada is not natively supported by most iPaaS tools, so you will use generic HTTP modules. Per-operation pricing gets expensive at scale. Pushing 100,000 historical chats through Zapier will exhaust your task quota and likely break on complex transformations like flattening nested variable objects.

**Scalability:** Low for historical data; Medium for ongoing sync.

### Custom ETL Pipeline

**How it works:** Build a full pipeline (using tools like Airflow or dbt) that extracts Ada data, normalizes it in a staging store, runs transformation rules, and loads it into Freshservice through public or bulk APIs.

**When to use it:** Enterprise environments where data is already centralized in a warehouse, or when you need repeatability, custom compliance controls, or ongoing coexistence between Ada and Freshservice.

**Scalability:** Enterprise-grade, but the build cost and ongoing maintenance are significant.

### Managed Migration Service

**How it works:** A migration specialist handles extraction, transformation, loading, validation, and rollback planning. Freshservice documents a migration-partner request flow and separate bulk migration endpoints that accept batched ticket and note creation.

**When to use it:** Large datasets (100K+ conversations), tight timelines, limited engineering bandwidth, or when conversation fidelity is critical for compliance.

**Trade-offs:** No internal engineering time consumed. Handles edge cases like duplicate users, missing fields, and variable mapping. External cost and requires coordination and access provisioning.

### Approach Comparison

| Factor | CSV Import | API Custom | iPaaS | Custom ETL | Managed Service |
|---|---|---|---|---|---|
| Engineering effort | Low | High | Medium | High | Low |
| Conversation threading | ❌ Flat only | ✅ Full | ⚠️ Partial | ✅ Full | ✅ Full |
| Custom field mapping | ⚠️ Limited | ✅ | ⚠️ | ✅ | ✅ |
| Scale (100K+ conversations) | ❌ | ✅ | ⚠️ Cost | ✅ | ✅ |
| Historical data support | Poor | Excellent | Very Poor | Excellent | Excellent |
| Ongoing sync capable | ❌ | ✅ | ✅ | ✅ | ✅ |
| Idempotency support | N/A | Must build | ⚠️ | Must build | ✅ |

## Pre-Migration Planning

A successful migration is won during the planning phase. Do not extract data until you have mapped your environment.

### Data Audit Checklist

Before extracting anything, inventory what you have in Ada:

- [ ] **Total conversation count** and date range
- [ ] **Active end users** vs. anonymous sessions (expect 8–15% anonymous in typical deployments)
- [ ] **Variables in use** — list all global and metavariables; these become custom fields in Freshservice
- [ ] **Channels active** — web chat, email, SMS, social; each may have different data structures
- [ ] **Handoff volume** — conversations that escalated to human agents contain richer data
- [ ] **Knowledge base content** — identify if it lives in Ada or is synced from an external source
- [ ] **Data older than 12 months** — <cite index="24-12">the Data Export API provides access to data from the past 12 months</cite>, so older records require a separate bulk export arranged with Ada support
- [ ] **Attachment volume** — count conversations with inline images or file uploads; these require a separate download-and-reupload workflow

### Define Migration Scope

Not everything needs to move. Decide:

- **Must migrate:** Conversations with handoffs (real support interactions), end-user records, compliance-sensitive transcripts
- **Nice to have:** Fully automated conversations that resolved without human intervention
- **Skip:** Bot training data, A/B test configurations, draft answers, abandoned chats under 10 seconds — these have no equivalent in Freshservice

Decide whether you are doing a **big-bang cutover** over a weekend or a **phased approach** by department.

### Prepare Freshservice

Build the scaffolding before importing any records:

1. **Create custom ticket fields** for every Ada variable you want to preserve. Freshservice custom field limits are plan-dependent: Starter plans support approximately 30 custom fields per ticket type, Growth supports around 60, Pro supports approximately 150, and Enterprise supports up to 300. Audit your Ada variable count against these limits before committing to a plan tier.

2. **Set up requester custom fields** for Ada-specific identifiers (`end_user_id`, `chatter_id`).

3. **Create a dedicated agent account** (e.g., "Ada Bot") to attribute bot messages. This consumes one agent seat in your Freshservice license — account for this in your seat count. Use a service account rather than a named user license to avoid audit complications.

4. **Configure ticket statuses** to accommodate Ada's resolution taxonomy.

5. **Set up groups and categories** if you want to segment imported conversations by channel or topic.

6. **Provision a sandbox environment.** Freshservice sandbox is available on Pro and Enterprise plans. It mirrors your production configuration but operates independently. API behavior in sandbox matches production, including rate limits, making it reliable for migration dry runs. Starter and Growth plans do not include sandbox — use a separate trial instance instead.

### Risk Mitigation

Take a full JSON export of your Ada environment as a cold backup before initiating any API writes to Freshservice. Ada's retention window means you cannot go back later. Store this export in your own object storage (S3, GCS, Azure Blob) with versioning enabled.

## Common Transformation Failures

Based on observed migration patterns, these are the most frequent failure modes and their fixes:

| Failure | Frequency | Cause | Fix |
|---|---|---|---|
| Missing requester email | 8–15% of conversations | Anonymous web chat sessions | Map to catch-all requester; flag with `anonymous=true` custom field |
| Duplicate requesters created | 5–10% of user records | Same person with multiple `end_user_id` values across channels | Deduplicate on email before requester creation; build email→requester_id lookup table |
| Markdown rendering as raw text | 100% if untransformed | Ada stores Markdown; Freshservice expects HTML | Run all message content through a Markdown→HTML converter (e.g., `markdown2`, `marked`) |
| Ticket created without notes | 2–5% of tickets | Race condition between ticket creation and note API calls | Add retry logic with exponential backoff; verify note count after batch |
| Custom field value rejected | Variable | Ada variable value exceeds Freshservice field length limit or type mismatch | Validate and truncate/cast values before POST; log rejections for manual review |
| Timestamp silently ignored | 100% if wrong API key type | `created_at` override requires admin-level API key | Use admin API key for ticket creation; verify in sandbox that timestamps persist |
| Attachment upload fails | Variable | File exceeds 40 MB or Ada URL expired | Pre-download attachments to staging storage; skip files over 40 MB with logged warning |

## Step-by-Step Migration Process

### Step 1: Extract from Ada

Use the Data Export API to pull conversations and messages. <cite index="25-11,25-12">Be aware of limitations with ingestion time and rate limits — it takes at least two hours to ingest conversation data into the Data API database.</cite>

```python
import requests
import time
import json
import os

ADA_API_KEY = "your-ada-api-key"
ADA_BASE_URL = "https://your-bot.ada.support/api/data-export/v2"
CROSSWALK_FILE = "crosswalk.json"

def load_crosswalk():
    """Load the crosswalk table mapping Ada IDs to Freshservice IDs."""
    if os.path.exists(CROSSWALK_FILE):
        with open(CROSSWALK_FILE, "r") as f:
            return json.load(f)
    return {"conversations": {}, "requesters": {}}

def save_crosswalk(crosswalk):
    with open(CROSSWALK_FILE, "w") as f:
        json.dump(crosswalk, f, indent=2)

def fetch_conversations(start_date, end_date):
    """Fetch conversations within a 60-day window.
    Ada limits queries to 60-day date ranges, so iterate
    in windows across your desired time span."""
    headers = {"Authorization": f"Bearer {ADA_API_KEY}"}
    params = {
        "start_date": start_date,
        "end_date": end_date,
        "page_size": 10000
    }
    conversations = []
    has_more = True
    while has_more:
        resp = requests.get(f"{ADA_BASE_URL}/conversations",
                           headers=headers, params=params)
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", 1))
            time.sleep(retry_after)
            continue
        if resp.status_code != 200:
            raise Exception(f"Ada API error: {resp.status_code} - {resp.text}")
        data = resp.json()
        conversations.extend(data.get("results", []))
        has_more = data.get("has_more", False)
        if has_more:
            params["cursor"] = data["next_cursor"]
    return conversations
```

> [!WARNING]
> Ada's Data Export API limits queries to 60-day date ranges. For a full extraction, iterate in 60-day windows across your desired time span. Data older than 12 months is not available through this API — contact Ada support for a bulk export if you need it. Allow at least 2 weeks lead time for Ada support to process bulk export requests.

### Step 2: Extract Messages per Conversation

For each conversation, fetch the associated messages. Batch these requests and respect the 10 req/s rate limit.

```python
def fetch_messages(conversation_id):
    """Fetch all messages for a single conversation."""
    headers = {"Authorization": f"Bearer {ADA_API_KEY}"}
    messages = []
    params = {"page_size": 10000}
    has_more = True
    while has_more:
        resp = requests.get(
            f"{ADA_BASE_URL}/conversations/{conversation_id}/messages",
            headers=headers, params=params
        )
        if resp.status_code == 429:
            time.sleep(float(resp.headers.get("Retry-After", 0.1)))
            continue
        if resp.status_code != 200:
            raise Exception(f"Message fetch failed for {conversation_id}: {resp.text}")
        data = resp.json()
        messages.extend(data.get("results", []))
        has_more = data.get("has_more", False)
        if has_more:
            params["cursor"] = data["next_cursor"]
    return messages
```

### Step 3: Transform Data

Map Ada's data structures to Freshservice's schema. Convert Ada's Markdown or plain text into Freshservice-compatible HTML. Map Ada variables (custom data collected in chat) into Freshservice custom fields.

```python
import markdown2

def transform_conversation_to_ticket(conversation, requester_map, crosswalk):
    """Transform an Ada conversation into a Freshservice ticket payload.
    Includes idempotency check against crosswalk table."""
    conv_id = conversation["id"]

    # Idempotency guard: skip if already migrated
    if conv_id in crosswalk.get("conversations", {}):
        return None  # Already created

    email = conversation.get("variables", {}).get("email", "")
    requester_id = requester_map.get(email)

    if not requester_id:
        # Fall back to anonymous requester
        requester_id = requester_map.get("anonymous@yourdomain.com")

    status_map = {
        "resolved": 4,    # Freshservice Resolved
        "unresolved": 2,  # Freshservice Open
        "handed_off": 3,  # Freshservice Pending
    }

    # Convert first message from Markdown to HTML
    first_message = conversation.get("first_message", "Imported from Ada")
    description_html = markdown2.markdown(first_message)

    # Truncate custom field values to Freshservice limits
    channel = conversation.get("channel", "web")[:255]

    return {
        "subject": f"Ada Conversation {conv_id}",
        "description": description_html,
        "status": status_map.get(conversation.get("resolution"), 2),
        "priority": 1,  # Low — adjust based on business rules
        "requester_id": requester_id,
        "created_at": conversation.get("created"),  # Requires admin API key
        "custom_fields": {
            "ada_conversation_id": conv_id,
            "source_channel": channel,
            "handoff": conversation.get("has_handoff", False),
        },
        "tags": ["ada-import"]
    }
```

### Step 4: Load into Freshservice

Create requesters first, then tickets, then add conversation entries as notes. Respect Freshservice's per-minute rate limits and read the `Retry-After` header on 429 responses.

```python
FS_API_KEY = "your-freshservice-admin-api-key"  # Must be admin for created_at override
FS_DOMAIN = "yourcompany.freshservice.com"

def create_ticket(ticket_payload, crosswalk):
    """Create a ticket in Freshservice with idempotency check."""
    conv_id = ticket_payload["custom_fields"]["ada_conversation_id"]

    # Idempotency: check crosswalk before creating
    if conv_id in crosswalk.get("conversations", {}):
        return crosswalk["conversations"][conv_id]

    url = f"https://{FS_DOMAIN}/api/v2/tickets"
    headers = {"Content-Type": "application/json"}

    resp = requests.post(url, auth=(FS_API_KEY, "X"),
                         headers=headers, data=json.dumps(ticket_payload))

    if resp.status_code == 201:
        fs_ticket_id = resp.json()["ticket"]["id"]
        # Record in crosswalk for idempotency
        crosswalk.setdefault("conversations", {})[conv_id] = fs_ticket_id
        save_crosswalk(crosswalk)
        return fs_ticket_id
    elif resp.status_code == 429:
        retry_after = int(resp.headers.get("Retry-After", 60))
        time.sleep(retry_after)
        return create_ticket(ticket_payload, crosswalk)  # Retry
    else:
        # Log failure with Ada conversation ID for investigation
        with open("migration_errors.log", "a") as f:
            f.write(f"FAIL|{conv_id}|{resp.status_code}|{resp.text}\n")
        return None

def add_note(ticket_id, message_body, is_private=True):
    """Add a note to an existing Freshservice ticket."""
    # Convert Markdown to HTML
    body_html = markdown2.markdown(message_body)
    resp = requests.post(
        f"https://{FS_DOMAIN}/api/v2/tickets/{ticket_id}/notes",
        json={"body": body_html, "private": is_private},
        auth=(FS_API_KEY, "X")
    )
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("Retry-After", 60))
        time.sleep(retry_after)
        return add_note(ticket_id, message_body, is_private)
    return resp.json()
```

> [!NOTE]
> <cite index="13-4">Freshservice public APIs are primarily intended for integration purposes, and not recommended for large-volume API transactions.</cite> For migrations exceeding a few thousand tickets, request elevated rate limits through Freshservice's data migration partner program, which provides separate bulk migration endpoints accepting batched ticket and note creation. Allow 3–5 business days for Freshservice to process rate limit increase requests.

### Step 5: Rebuild Knowledge Base

Extract Ada Answers and POST them to Freshservice `/api/v2/solutions/articles`. Map each answer to the correct Category and Folder IDs in Freshservice. Convert Markdown to HTML before posting.

```python
def create_solution_article(title, content_markdown, folder_id):
    """Create a knowledge base article in Freshservice."""
    content_html = markdown2.markdown(content_markdown)
    payload = {
        "title": title,
        "description": content_html,
        "article_type": 1,  # 1 = permanent
        "folder_id": folder_id,
        "status": 1,  # 1 = draft; set to 2 for published
        "tags": ["ada-import"]
    }
    resp = requests.post(
        f"https://{FS_DOMAIN}/api/v2/solutions/articles",
        json=payload,
        auth=(FS_API_KEY, "X")
    )
    return resp.json()
```

### Step 6: Validate

After loading, run validation checks:

- **Record count:** Total conversations extracted from Ada = total tickets created in Freshservice (query crosswalk table for exact counts)
- **Message count:** Sum of messages per conversation = sum of notes/replies per ticket
- **Field spot-check:** Sample 50 tickets and verify custom field values match Ada variables
- **Requester linkage:** Confirm every ticket has a valid requester, not a fallback default (query for tickets where requester matches anonymous catch-all — if >15%, investigate)
- **Timestamp verification:** Compare `created_at` on 20 migrated tickets against Ada source data to confirm timestamps were preserved
- **Attachment audit:** Verify that files attached in Ada appear as Freshservice attachments; check file sizes and accessibility
- **UI validation (UAT):** Have agents log into Freshservice and read 20–30 migrated chat transcripts — check line breaks, images, HTML rendering, and chronological ordering of notes

## Edge Cases & Challenges

- **HTML/Markdown rendering:** Ada supports Markdown in its chat UI. Freshservice tickets use a rich text editor (HTML). If you do not convert Markdown to HTML during the transformation phase, your tickets will display raw asterisks and hashes. Use `markdown2` (Python) or `marked` (Node.js) for conversion.

- **Inline attachments:** Users often upload screenshots in Ada chat. You must download these files from Ada's URLs and upload them to Freshservice as multipart form data. This requires a two-step API process: create the ticket, then attach the file. Freshservice enforces a 40 MB attachment ceiling per file. Ada attachment URLs may expire — download to staging storage before beginning the Freshservice load phase.

- **Anonymous users:** Ada conversations without email or identifying metadata cannot be linked to a meaningful Freshservice requester. Create a catch-all requester (e.g., "Anonymous Web Visitor") or skip these conversations. Tag anonymous-sourced tickets for easy filtering.

- **Variable sprawl:** Ada allows unlimited variables per conversation. Freshservice custom fields are finite and plan-dependent (Starter: ~30, Growth: ~60, Pro: ~150, Enterprise: ~300 per ticket type). Audit which variables carry business value before creating fields — prioritize variables used in >5% of conversations.

- **12-month data cliff:** <cite index="24-11,24-12">The Data Export API has date range limitations — a query's end date cannot be more than 60 days after its start date, and data is available only from the past 12 months.</cite> If you need older data, contact Ada support for a bulk export. Allow 2+ weeks lead time.

- **Duplicate end users:** Ada may create multiple `end_user_id` values for the same person across channels. <cite index="35-12,35-13">After timeout, the next message creates a new conversation_id, and Ada also generates a new chatter_id/end_user_id pair for the session.</cite> Deduplicate on email before creating Freshservice requesters. Build an email → `requester_id` lookup table and resolve conflicts before the load phase.

- **Attachment staging:** If you use Freshservice's partner bulk migration APIs, attachments must be provided as publicly accessible URLs. If Ada files are private, you need a temporary object store (S3 with pre-signed URLs, expiry set to 72 hours) and a cleanup plan after migration completes.

- **Timestamp preservation:** Freshservice allows setting `created_at` on ticket creation via API, but only when using an **admin-level API key**. Standard agent API keys silently ignore the `created_at` parameter, resulting in all tickets showing the import date. Conversation notes do not support `created_at` override — notes are timestamped at creation time. To preserve chronological order, insert notes sequentially with brief delays between them. Test this behavior in sandbox before production migration.

- **Freshservice workflows during migration:** Ticket creation triggers automation rules, SLA timers, email notifications, and Slack/Teams integrations. Before bulk loading, either disable these automations temporarily or create a migration-specific group/category that bypasses workflow rules. Re-enable after migration and validation are complete.

## Limitations & Constraints

Be clear about what cannot be migrated:

| Constraint | Detail |
|---|---|
| Ada data retention | 12 months via API; 6 months visible in dashboard |
| Ada rate limit | 10 req/s per endpoint |
| Freshservice rate limit | 100–500 req/min by plan; up to 700 for migration partners |
| Freshservice pagination | Max 100 records per page (30 default) |
| Custom field limits | Starter: ~30, Growth: ~60, Pro: ~150, Enterprise: ~300 per ticket type |
| Attachment size | 40 MB per file |
| Bot logic migration | Not possible — Ada's conversation flows, decision trees, and NLP models have no equivalent in Freshservice; rebuild in Freddy AI |
| Live chat state | Cannot migrate active chats; all migrated conversations enter as historical closed tickets |
| Conversation note timestamps | Notes API does not support `created_at` override — notes are timestamped at insertion time |
| Timestamp override | Requires admin API key; standard keys silently ignore `created_at` |
| Workflows during migration | Not automatically muted during bulk migration — disable or bypass manually |
| Sandbox availability | Pro and Enterprise plans only; Starter/Growth must use trial instances |

### Freddy AI vs. Ada: Capability Gap Analysis

If you are replacing Ada with Freshservice's Freddy AI, understand what translates and what does not:

| Ada Capability | Freddy AI Equivalent | Gap |
|---|---|---|
| Multi-turn conversation flows | Freddy AI Agent (virtual agent) | Freddy supports decision trees but lacks Ada's generative multi-turn context persistence across sessions |
| Custom NLP training on intents | Freddy uses pre-trained models + knowledge base | No custom intent training — Freddy relies on Solution Articles and ticket history |
| Omnichannel (web, email, SMS, voice, social) | Web widget + email; limited social | No native SMS or voice support in Freddy AI; requires third-party integration |
| Proactive messaging | Freddy does not initiate conversations | No equivalent — must use Freshservice workflow triggers for proactive outreach |
| Custom variables per conversation | Ticket custom fields | Indirect mapping; no session-scoped variables |
| A/B testing on bot responses | Not available | No equivalent in Freshservice |
| Handoff with full transcript | Ticket creation on escalation | Freddy creates a ticket on handoff; transcript formatting is less rich than Ada |

Plan to rebuild approximately 60–70% of Ada's conversational logic in Freddy AI using a different paradigm. Budget 2–4 weeks of bot-building effort depending on complexity, separate from the data migration timeline.

## Validation & Testing

Never assume a 200 OK means the data looks right. Run a **pilot migration** with a 7-day window of Ada data before committing to the full dataset:

1. Extract one week of conversations (100–500 records)
2. Transform and load into a **Freshservice sandbox environment** (Pro/Enterprise) or trial instance (Starter/Growth)
3. Verify record counts, field mapping, and conversation threading
4. Have an agent review 20 tickets for data fidelity — specifically check:
   - HTML rendering of chat transcripts
   - Correct requester attribution
   - Custom field values populated correctly
   - Note chronological ordering
   - Attachment accessibility
5. Document any transformation failures or unmapped fields
6. Fix issues and re-run before scaling to the full dataset

Sample at least one clean record, one large record (20+ messages), one attachment-heavy record, one handoff record, and one record that used custom metadata.

Build a **rollback plan**: tag all imported tickets with `ada-import` so they can be bulk-deleted if the migration needs to be re-run. Deleting thousands of tickets via API is time-consuming due to rate limits (at 500 req/min on Enterprise, deleting 50,000 tickets takes ~100 minutes of sustained API calls), so getting the sandbox run right is worth the time.

Because Freshservice search and analytics can lag during migration, validate against API responses and the crosswalk table before trusting dashboard reports.

## Post-Migration Tasks

Once the data is moved, the operational transition begins.

- **Re-enable automations:** If you disabled workflow rules, SLA policies, or notification integrations before migration, re-enable them after validation is complete.
- **Rebuild automations:** Ada's conversation flows, processes, and custom instructions have no equivalent in Freshservice. Recreate routing rules, SLA policies, and any handoff logic using Freshservice's Workflow Automator.
- **Configure Freddy AI:** If replacing Ada's AI with Freshservice's Freddy AI, train it on imported Solution Articles and configure agent assist features. Expect to rebuild conversational logic rather than import it (see capability gap table above).
- **Agent onboarding:** Train your team on the new ticket interface, emphasizing how historical chats are formatted in the description field or as notes. Agents familiar with Ada's conversational dashboard need onboarding to Freshservice's ticket-centric workflow.
- **Monitor for gaps:** Run weekly reports for the first month comparing Ada's historical metrics against Freshservice data to catch missing records, orphaned requesters, or automation side effects.
- **Clean up staging resources:** Delete temporary object storage (pre-signed URLs for attachments), revoke Ada API keys if decommissioning, and archive the crosswalk table for audit purposes.

## Best Practices

- **Back up everything first.** Export Ada data to your own storage (S3, GCS, or Azure Blob) before beginning any migration work. Ada's 12-month retention window means you cannot go back later.
- **Build a crosswalk table.** Maintain a persistent mapping of Ada conversation IDs → Freshservice ticket IDs and Ada end_user_ids → Freshservice requester IDs. This enables idempotent reruns and post-migration auditing.
- **Run a delta migration.** Migrate 90% of your historical data a week before go-live. On the weekend of the cutover, run a delta script that only migrates conversations created since the last extraction. This ensures near-zero downtime and minimal data loss.
- **Tag imported records.** Apply a consistent tag (`ada-import`) to every ticket for filtering, reporting, and potential rollback.
- **Log everything.** Your migration script should write every success and failure to a structured log file (CSV or JSONL). Each log entry should include the Ada Conversation ID, Freshservice Ticket ID (if created), HTTP status code, and error message. If a ticket fails due to a missing Requester ID, your log tells you exactly which conversation to investigate.
- **Validate incrementally.** Do not wait until the end. Validate after each batch of 1,000 tickets: check record counts, spot-check 5 tickets per batch, verify no duplicate tickets were created.
- **Document your mapping.** Keep a living spreadsheet of Ada fields → Freshservice fields, including transformation rules, truncation limits, and type conversions. This is your audit trail.
- **Run test migrations.** Never go straight to production. Use a Freshservice sandbox environment for every dry run. Expect 2–3 sandbox iterations before your transformation logic is stable.
- **Disable automations during load.** Freshservice workflow rules, SLA timers, and notification integrations will fire on every imported ticket unless explicitly disabled or bypassed.

## Sample Data Mapping Table

| Ada Object / Field | Freshservice Object / Field | Data Type | Notes |
|---|---|---|---|
| Chatter `id` / `end_user_id` | Requester `id` | String / Integer | Maintain crosswalk table; deduplicate on email before creation |
| Chatter `email` | Requester `primary_email` | String | Required for Freshservice ticket creation; primary dedup key |
| Conversation `id` | Ticket custom field `ada_conversation_id` | String | Freshservice auto-generates IDs; store Ada ID in custom field |
| Conversation `created_at` | Ticket `created_at` | ISO 8601 | Requires admin API key; standard keys silently ignore this field |
| Message Array | Ticket `description` or Notes | HTML String | Parse JSON array into HTML; convert Markdown before POST |
| Answer `title` | Solution Article `title` | String | Direct mapping |
| Answer `content` | Solution Article `description` | HTML | Convert Markdown to HTML before POSTing |
| `variables.channel` | Custom field `source_channel` | Dropdown | Map web, email, SMS, social |
| `variables.email` | Requester `primary_email` | String | Used for requester dedup and creation |
| `variables.name` | Requester `first_name` + `last_name` | String | Split on space; handle single-name cases |
| Handoff metadata | Custom field `handoff` or private note | Boolean / Text | Useful for audit context and reporting |
| Message `sender_type` | Note `private` flag | Boolean | Bot messages → private notes; user messages → public replies |

This table is a starting point. The right version depends on whether you need analytics parity, live sync, or only historical lookup.

## When to Use a Managed Migration Service

Build in-house when you have dedicated engineering capacity, a small dataset (under 5,000 conversations), straightforward variable-to-field mapping, and engineers experienced with both APIs.

Use a managed service when:

- Your Ada instance has **100,000+ conversations** across multiple channels
- You need data **older than 12 months** that requires coordination with Ada's support team for bulk export
- Variable mapping is complex — dozens of custom variables with interdependencies or type mismatches
- **Compliance requirements** demand a validated, auditable migration with rollback capability and chain-of-custody documentation
- Your engineering team is already committed to other projects and cannot dedicate 2–4 weeks to migration engineering

**Estimating DIY engineering effort:** For a dataset of 50,000 conversations with 10 custom variables, moderate attachment volume, and requester deduplication, expect approximately 2–4 weeks of a senior engineer's time. This breaks down to: ~3 days for extraction scripting and pagination handling, ~5 days for transformation logic (Markdown conversion, variable mapping, dedup), ~3 days for Freshservice load scripting with idempotency, ~3 days for sandbox testing and iteration, and ~2 days for production migration and validation. Add 50% buffer for edge cases. For a migration you will run once, weigh this against the cost of a managed service.

A good Ada-to-Freshservice cutover leaves you with three things: every live user matched to the right requester, every historical Ada conversation accessible as a Freshservice ticket with readable notes and files, and a clear rule for what still flows after go-live. If you cannot describe those three outcomes before you start loading data, the project is not ready.

## Frequently asked questions

### Can I migrate Ada conversation history into Freshservice tickets?

Yes. Ada's Data Export API lets you extract conversations and messages as JSON. Each conversation maps to a Freshservice ticket, with individual messages becoming ticket notes or replies. You need to create requesters first, then tickets, then add conversation entries via the Freshservice API. Freshservice has no native 'chat' object, so transcripts must be parsed and formatted as HTML.

### What are the API rate limits for both platforms during migration?

Ada's Data Export API allows 10 requests per second per endpoint, with a max page size of 10,000 records and 60-day query windows. Freshservice rate limits vary by plan: Starter gets 100 req/min, Growth 200, Pro 400, Enterprise 500. Migration partners can request up to 700/min. Both sides require exponential backoff on 429 responses.

### How do Ada variables map to Freshservice?

Ada's global and metavariables (email, name, custom data) map to Freshservice custom ticket fields. You must create these custom fields in Freshservice before importing data. Not all variables carry business value — audit them first to avoid creating unnecessary fields.

### Can I migrate Ada bot logic to Freshservice?

No. Ada's conversation flows, processes, decision trees, and AI agent instructions have no direct equivalent in Freshservice. If you're replacing Ada's AI layer, you need to rebuild automation logic using Freshservice's Workflow Automator and configure Freddy AI separately.

### How do I handle anonymous Ada chatters in Freshservice?

Freshservice requires a Requester email for ticket creation. Assign a dummy email (e.g., anonymous@yourdomain.com) or map anonymous conversations to a generic 'Guest' Requester profile to prevent API rejections.
