Skip to content

Ada to Desk365 Migration: A Technical Guide

Technical guide for migrating from Ada to Desk365 — API extraction, data model mapping, conversation-to-ticket transformation, and step-by-step import process.

Abdul Abdul · · 25 min read
Ada to Desk365 Migration: A Technical Guide
TALK TO AN ENGINEER

Planning a migration?

Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.

Schedule a free call
  • 1,500+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing
  • Project success responsibility
  • Post-migration support included

Ada to Desk365 Migration: A Technical Guide

Info

TL;DR — Ada to Desk365 Migration

Migrating from Ada to Desk365 means moving data from an AI-powered conversational automation platform to a ticket-centric, Microsoft 365-native helpdesk. The two systems model support data differently: Ada stores conversations, messages, chatters, and bot variables; Desk365 stores tickets, contacts, groups, custom fields, and knowledge base articles. A typical migration for 20,000–100,000 conversations takes 1–3 weeks including extraction, transformation, test imports, and cutover. The hard parts: Ada's Data Export API only retains 12 months of history, queries are capped at 60-day date ranges per request, and conversation messages must be reconstructed into Desk365 ticket reply threads. Desk365's Import Tickets with Conversations API endpoint accepts up to 30 conversations per call — this is the primary load path. Ada variables and metavariables must be mapped to Desk365 custom fields with the cf_ prefix. Bot automations, playbooks, and coaching configurations cannot be migrated — they must be rebuilt as Desk365 automations manually. Teams with under 5,000 conversations and simple metadata can attempt a scripted DIY migration (40–80 engineer-hours). For larger volumes or complex variable mappings, a managed migration avoids weeks of rework.

Migrating from Ada to Desk365 is not a standard export-import. You are translating between two fundamentally different data models. Ada is an agentic customer experience (ACX) platform built around AI-driven conversations, intents, and continuous chat threads. Desk365 is an ITIL-aligned helpdesk deeply integrated with Microsoft 365, organized around discrete tickets, contacts, and categorical statuses.

If you attempt a basic CSV export and import, you will lose conversation context, break user references, and orphan metadata. This guide covers the exact API constraints, data mapping strategies, and edge cases required to move conversational data from Ada into Desk365 without losing historical context.

For source-side extraction strategies, see Ada to Freshservice Migration: The CTO's Technical Guide. For destination-side context on Desk365, see How to Export Data from Desk365: Methods, API Limits & Portability. For general migration planning, see Best Practices for a Successful Help Desk Data Migration.

Why Teams Move from Ada to Desk365

Ada is an agentic customer experience platform designed to automate customer conversations using AI agents. It resolves 84% of customer inquiries autonomously across voice, messaging, and email channels. Ada is not a helpdesk — it is a front-line deflection and automation layer that typically sits in front of a CRM or ticketing system.

Desk365 is a cloud-based helpdesk built for the Microsoft 365 ecosystem. Desk365 is a cloud-based, AI-enhanced helpdesk built for the Microsoft 365 ecosystem, enabling exceptional customer service through Microsoft Teams, Email, Web Forms/Widgets, and more.

Common reasons teams make this move:

  • Consolidation: Ada was the front-line chatbot, but the team now wants a single platform for both automated deflection and human-agent ticketing, with Desk365's native AI Agent handling what Ada used to do.
  • Handoff archive: Ada was used for automated conversations with handoffs to another tool. The team wants all conversation history preserved as Desk365 tickets for unified reporting and audit.
  • Platform shift: Moving from a standalone conversational AI tool to a full-featured helpdesk with SLA management, change/approval workflows, asset tracking, and deep Microsoft Teams integration.
  • Cost optimization: Ada's enterprise pricing for ACX capabilities may exceed what's needed when Desk365 covers both ticketing and AI-powered responses at a lower cost.

Ada vs. Desk365: Data Model Differences

This is where most migrations break. The two platforms don't share a data model, and a naive field-to-field copy will produce broken tickets.

Ada Concept Desk365 Equivalent Notes
Conversation Ticket One Ada conversation = one Desk365 ticket. The conversation's inquiry_summary maps to the ticket Subject.
Message Ticket Reply / Note Each message within a conversation becomes a reply or internal note on the corresponding ticket.
Chatter / End User Contact Ada's chatter_id and end_user_id map to a Desk365 Contact (identified by ContactEmail).
Variables / Metavariables Custom Fields (cf_ prefix) Flat key-value pairs. Multi-level variables need flattening.
Platform (chat, email, voice) Source channel Desk365 supports Email, Teams, Web Forms, Web Widgets, Support Portal. Ada's channel diversity won't map 1:1. API-imported records show as source 15 (API), so preserve the original Ada channel in a custom field like cf_ada_channel.
CSAT (score, feedback, NPS) Survey data / Custom Fields Desk365 supports CSAT, 5-star, and NPS surveys — but imported data won't populate native survey reports. Store in custom fields.
Generated Topic / Classifications Category / Subcategory Ada's AI-generated topic labels can seed Desk365 categories, but must be pre-created in Desk365 before import. Unstable AI-generated labels are better stored in custom fields than in live routing fields.
Playbooks / Coaching No equivalent Must be rebuilt as Desk365 Automation Rules manually.
Knowledge articles Knowledge Base articles Separate migration. Ada uses sources, articles, tags, and availability rules; Desk365 uses KB categories, folders, and visibility rules.
Agent ID / Agent Name Agent (AssignedTo) Only relevant for conversations escalated to human agents.
Warning

12-Month Data Retention Limit: The Data Export API provides access to data from the past 12 months. If you need conversation history older than 12 months, you must have it already exported to a data warehouse, or it is permanently inaccessible via the API. Validate retention before you scope the project.

Step 1: Extract Data from Ada

Ada's Data Export API is the only programmatic batch extraction path (note that while you can extract this data, Ada's API does not support importing historical conversations back into the platform—a constraint we detail in our Ada to Ada migration guide). The Data Export API allows authenticated access to conversation and message data, enabling you to integrate this data into your own systems.

Ada Data Export API: Key Constraints

  • Rate limit: 10 requests per second per endpoint.
  • Page size: A maximum of 10,000 records per page.
  • Date range: A query's end date cannot be more than 60 days after its start date.
  • Ingestion delay: It takes at least two hours to ingest conversation data into the Data API database. This means that queries won't return data from conversations created within the previous two hours.
  • Subscription gating: This feature may not be included with your organization's subscription package. Confirm Data Export API access before planning.
  • Rich content: Only text messages are supported at launch. Images and structured messages are not yet available. If your Ada conversations contain images or file uploads, those may not appear in the export. Verify against your Ada instance's current API version.

Throughput Calculation

At the documented constraints — 10 requests/second, 10,000 records/page, 60-day windows — practical throughput is approximately:

  • Conversations endpoint: 10 req/s × 10,000 records/req = 100,000 records/second theoretical maximum. In practice, network latency and pagination reduce this to 50,000–80,000 records per minute under sustained load.
  • 12 months of data: Requires a minimum of 7 sequential date-range queries (6 × 60-day windows + 1 partial window) per endpoint.
  • Messages endpoint: Scales with average conversation depth. A corpus of 100,000 conversations averaging 8 messages each = 800,000 message records. At 10,000 records/page, that is 80 paginated requests minimum.

Build your timeline estimate from these numbers before committing to a cutover date.

Webhook Alternative for Near-Real-Time Sync

Ada supports webhooks for conversation events, which is architecturally preferable to batch extraction if your use case is ongoing sync rather than one-time migration. Webhook events fire on conversation creation, update, and close. For a pure historical migration — moving a fixed corpus of past conversations — batch extraction via the Data Export API is the correct approach. For teams that need to keep Desk365 continuously updated with new Ada conversations during a parallel-run period before cutover, configure Ada webhooks to POST conversation payloads to a lightweight relay service that transforms and forwards to Desk365's import endpoint. This eliminates the need for repeated delta extractions near cutover.

Extraction Approach

You need to pull from two separate endpoints and join them:

  1. Conversations endpoint (GET /api/v2/export/conversations) — Returns conversation-level metadata: _id, chatter_id, end_user_id, date_created, date_updated, platform, is_escalated, csat, metavariables, variables, generated_topic_v2_title, inquiry_summary, agent_name, and more.
  2. Messages endpoint (GET /api/v2/export/messages) — Returns individual messages linked by conversation_id. A message object is created whenever either Ada (an AI Agent or scripted bot) or a chatter sends a message. All messages are associated with a conversation by a conversation_id.

Because of the 60-day date range limit, you must paginate through time windows. For 12 months of data, that's a minimum of 7 sequential date-range queries per endpoint.

import requests
import time
import logging
from datetime import datetime, timedelta
 
ADA_API_BASE = "https://YOUR-BOT.ada.support/api/v2/export"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
 
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
 
def extract_with_retry(url, params, max_retries=5, backoff_base=2):
    """Make API request with exponential backoff on rate limit or server errors."""
    for attempt in range(max_retries):
        try:
            resp = requests.get(url, headers=HEADERS, params=params, timeout=30)
            if resp.status_code == 429:
                wait = backoff_base ** attempt
                logger.warning(f"Rate limited. Waiting {wait}s before retry {attempt + 1}/{max_retries}")
                time.sleep(wait)
                continue
            if resp.status_code in (500, 502, 503, 504):
                wait = backoff_base ** attempt
                logger.warning(f"Server error {resp.status_code}. Waiting {wait}s before retry {attempt + 1}/{max_retries}")
                time.sleep(wait)
                continue
            resp.raise_for_status()
            return resp.json()
        except requests.exceptions.Timeout:
            logger.warning(f"Request timed out. Retry {attempt + 1}/{max_retries}")
            time.sleep(backoff_base ** attempt)
    raise RuntimeError(f"Max retries exceeded for {url}")
 
def extract_conversations(start_date, end_date, page_size=10000):
    """Extract conversations in 60-day windows with retry logic."""
    all_conversations = []
    window_start = start_date
    
    while window_start < end_date:
        window_end = min(window_start + timedelta(days=60), end_date)
        url = f"{ADA_API_BASE}/conversations"
        params = {
            "created_since": window_start.isoformat() + "Z",
            "created_to": window_end.isoformat() + "Z",
            "page_size": page_size
        }
        
        while url:
            data = extract_with_retry(url, params)
            batch = data.get("data", [])
            all_conversations.extend(batch)
            logger.info(f"Extracted {len(all_conversations)} conversations so far...")
            url = data.get("meta", {}).get("next_page_uri")
            params = {}  # next_page_uri includes params
        
        window_start = window_end
    
    return all_conversations
Tip

Join conversations and messages locally. Extract both datasets fully, store them in a local database or file system, then join on conversation_id. Do not try to fetch messages per-conversation in real time — that approach hits rate limits fast and is painfully slow at scale.

Ada's End Users API can supplement the identity side of the extraction. The schema includes first_name, last_name, display_name, email, language, and custom metadata. If you used external_id, note that Ada documents it as available for custom-channel integrations only.

Ada API Authentication

Ada's Data Export API uses Bearer token authentication. Your API key is generated from the Ada dashboard under Settings → Integrations → API. The token scope must include export:read — a token scoped only to bot:manage will return 403 on export endpoints. Confirm scope before starting extraction.

Step 2: Transform Ada Data to Desk365 Format

This is the most engineering-intensive phase. You are converting a flat conversation-plus-messages model into a ticket-with-replies model.

Mapping Conversation → Ticket

For each Ada conversation, create one Desk365 ticket:

Desk365 Field Source Logic
Subject inquiry_summary or first chatter message Use inquiry_summary if present (max 120 chars for Desk365). Fall back to truncated first message.
Description First chatter message body The opening message becomes the ticket description.
ContactEmail metavariables.email or End Users API lookup Required field. If Ada doesn't have an email for the chatter, you need a fallback (e.g., unknown+{chatter_id}@yourdomain.com).
CreatedBy metavariables.name or End Users API Maps to Contact name in Desk365.
Priority Custom logic Ada has no priority field. Default to Medium or derive from topic.
Status Conversation status mapping Ada conversations are active or ended. Map endedClosed; activeOpen.
createdOn date_created Convert UTC timestamp to Desk365's configured date/time format.
closedOn date_updated (if ended) Required if Status is Closed or Resolved.
CategoryName generated_topic_v2_title Pre-create matching categories in Desk365 before import.
Group Custom logic Ada has no group concept. Assign based on topic or default group.
cf_ada_conversation_id _id Store the original Ada conversation ID for traceability.
cf_ada_platform platform Preserve the original channel (chat, email, voice, etc.).
cf_csat_score csat.score Preserve CSAT data in a custom field since it won't populate Desk365's native survey module.

Mapping Messages → Ticket Conversations

Each Ada message becomes a conversation entry (reply or note) on the Desk365 ticket. Key decisions:

  • Bot messages: Import as internal notes or public replies depending on your use case. If you want agents to see the full conversation flow, import as public replies attributed to a generic "Ada Bot" agent.
  • Chatter messages: Import as customer replies (the contact's messages).
  • Agent messages (from escalated conversations): Import as agent replies, attributed to the correct agent if that agent exists in Desk365.

With the Import Tickets with Conversations endpoint, you can create new tickets and import their associated conversation history in a single API request. This is the ideal load path — it lets you create a ticket and attach its full conversation thread in one call.

Warning

30 conversations per API call. Desk365's import endpoint caps at 30 conversation entries per API call. For Ada conversations with 50+ messages, you need to batch the replies across multiple calls using the Import Conversations for Existing Tickets endpoint for overflow.

Formatting Transcripts as a Single Block (Fallback Approach)

If your Desk365 plan or workflow requires a single-field approach rather than individual reply entries, you can compile the entire message array into a formatted HTML block for the ticket description:

function formatTranscript(messages) {
  let htmlTranscript = '<div class="ada-transcript">';
  
  messages.forEach(msg => {
    const senderName = msg.sender_type === 'bot' ? 'Ada Bot' : 'Customer';
    const time = new Date(msg.created_at).toLocaleString();
    
    htmlTranscript += `
      <p style="margin-bottom: 10px;">
        <strong>${senderName}</strong> <span style="color: #888; font-size: 0.8em;">(${time})</span><br/>
        ${msg.text}
      </p>
    `;
  });
 
  htmlTranscript += '</div>';
  return htmlTranscript;
}

When enabled, the API offers two distinct fields for ticket descriptions: Description (HTML) – The description field contains the full content of the ticket's description in HTML format. Both HTML and plain text are accepted. If the compiled HTML transcript exceeds approximately 64KB, truncate it and add a [Transcript truncated — full content in cf_ada_transcript_json] marker. Store the complete JSON message array in a separate custom text field.

Handling Anonymous Chatters

Desk365 requires a valid ContactEmail to create a ticket. Ada often handles anonymous chats where the user never provides an email. If you push a ticket without a valid requester email, the API rejects the payload.

The fix: During transformation, if the chatter email is null, generate a synthetic email like unknown+{chatter_id}@yourdomain.com. Prepend the chatter's Ada ID or any available identifier to the ticket description for auditing.

Contact deduplication behavior in Desk365: When two Ada conversations share the same email address, Desk365 matches them to an existing contact rather than creating a duplicate — provided the email is identical. Desk365 does not perform fuzzy matching or partial-name deduplication. If the same person used two different emails across Ada conversations (one on chat, one on a different channel), Desk365 creates two separate contacts. Use Desk365's manual contact merge flow post-import to consolidate them. Desk365 supports up to 10 secondary email addresses on a contact, which helps consolidate known aliases before you merge.

Handling Ada Variables and Metavariables

Ada stores two types of key-value data per conversation:

  • Variables: Custom values set during the conversation (e.g., order_id, account_type).
  • Metavariables: System-captured metadata (e.g., browser, device, ip_address, language, user_agent).

When you retrieve tickets, custom fields now appear with a cf_ prefix (e.g., cf_department, cf_employee_id). When sending data, you'll also need to prefix custom field names with cf_ in your requests like v3/tickets/update and v3/tickets/create.

Before importing, create corresponding custom fields in Desk365 for each Ada variable you want to preserve. You'll find two types of these customizable fields: Single level field types and Multi level field types. Among the single-level fields are standard elements like Dropdown, Text Input, Checkbox, Date, Number, and so on.

Custom field API labels are permanent once created in Desk365 — the label used in API calls cannot be renamed after creation, only the display name can be changed. Name them carefully (e.g., cf_ada_order_id rather than cf_order_id) to avoid collision with fields you may create later for other purposes.

Don't map every metavariable. Browser version and user agent data is rarely useful in a helpdesk context. Focus on business-relevant variables like customer identifiers, account types, and order IDs.

Ada's Connected Systems Artifacts

If your Ada instance integrates with Salesforce, Zendesk, or another CRM, conversations may carry external record IDs in the metavariables payload (e.g., metavariables.sf_case_id, metavariables.zendesk_ticket_id). These cross-system identifiers are high-value for audit trails. Create dedicated cf_ fields for any external system IDs before importing, and populate them from the metavariables payload. Losing these IDs severs the link between Desk365 tickets and upstream CRM records.

Step 3: Prepare Desk365 for Import

Before loading any data, configure Desk365. Generating your Desk365 API key: navigate to Settings → API Management in the Desk365 admin portal. Keys are scoped at account level. The key must have write permissions on tickets, contacts, and custom fields. Rate limits vary by plan: Standard plan allows 100 API calls per hour; Plus and Premium plans allow 50 API calls per minute. At Plus/Premium rates, 50 calls/min × 30 conversations/call = 1,500 conversation records/minute = approximately 2.16 million conversation records per day theoretical maximum. Actual throughput is lower due to transformation overhead and network latency — plan for 500,000–800,000 records per day under sustained load.

  1. Create custom fields for all Ada variables you're preserving (e.g., cf_ada_conversation_id, cf_ada_platform, cf_csat_score, plus any business-specific variables). Note: custom contact and company fields are a Premium feature. API field labels are permanent once created.
  2. Create categories matching Ada's generated_topic_v2_title values. To ensure the import process runs smoothly, it is crucial that the Group, AssignedTo, and Category columns in the csv file match the group, agent names, and categories you have created in Desk365. This applies to the API as well — a ticket referencing a non-existent category will be rejected or silently assigned to no category.
  3. Create groups and agents that will receive imported tickets. If Ada conversations were escalated to specific agents, create those agents in Desk365 first.
  4. Use a Desk365 sandbox or staging instance for test imports before touching production. Desk365 does not offer a native sandbox on all plans — verify availability with your account. If a staging environment is unavailable, create a separate Desk365 subdomain for validation and use it exclusively for test batches before committing to production.
  5. Disable automations temporarily. Desk365 automation rules trigger on ticket creation. Importing thousands of historical tickets will fire every "On ticket creation" rule unless you disable them during import.
  6. Disable SLA policies during import. Historical tickets with past-due dates will immediately trigger SLA violations if policies are active.
  7. Disable email notifications for the import period.
Danger

Do not skip disabling automations and notifications. Importing 50,000 historical tickets with automations active will fire email notifications to every contact in the system. Those emails cannot be recalled.

Step 4: Load Data into Desk365

Desk365 offers two import paths. Choose based on your needs.

Path A: CSV Import (Simple, Limited)

Desk365's built-in CSV import is accessed via the Actions menu in the Agent Portal. To import the tickets in Desk365, go to the 'Actions' menu in the Tickets tab in Agent Portal, and select the 'Import' option.

Limitations:

  • CSV handles ticket metadata only — subject, description, contact, priority, status, category, dates.
  • It does not import conversation threads (replies and notes).
  • Best for small migrations under 1,000 tickets where conversation history is not needed.

Path B: API Import (Full Fidelity)

For a complete migration with conversation history, use the Desk365 API:

  • Import Tickets with Conversations endpoint: Desk365 introduced two new Ticket Import API endpoints. With the Import Tickets with Conversations endpoint, you can create new tickets and import their associated conversation history in a single API request.
  • Import Conversations for Existing Tickets endpoint: The Import Conversations for Existing Tickets endpoint allows you to import historical conversations and associate them with tickets that already exist.
  • Batch limit: 30 conversations per API call.
  • Rate limits: Standard: 100 API calls/hour; Plus and Premium: 50 API calls/minute.

Idempotency: Desk365 does not support native idempotency keys on ticket creation. If your script crashes and you restart, you will create duplicate tickets unless you build your own deduplication logic. Store a local mapping of ada_conversation_id to desk365_ticket_id in a SQLite database and check it before each API call.

import requests
import time
import sqlite3
import logging
 
DESK365_API_BASE = "https://YOUR-SUBDOMAIN.apps.desk365.io/apis/v3"
DESK365_HEADERS = {
    "Authorization": "Bearer YOUR_DESK365_API_KEY",
    "Content-Type": "application/json"
}
 
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
 
def init_state_db(db_path="migration_state.db"):
    """Initialize local deduplication store."""
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS imported_tickets (
            ada_conversation_id TEXT PRIMARY KEY,
            desk365_ticket_id TEXT,
            imported_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()
    return conn
 
def import_ticket_with_retry(ticket_data, conversations, state_db, max_retries=5):
    """Import a single ticket with deduplication check and exponential backoff."""
    ada_id = ticket_data.get("cf_ada_conversation_id")
    
    # Check deduplication store
    cursor = state_db.execute(
        "SELECT desk365_ticket_id FROM imported_tickets WHERE ada_conversation_id = ?",
        (ada_id,)
    )
    existing = cursor.fetchone()
    if existing:
        logger.info(f"Skipping already-imported conversation {ada_id} → Desk365 ticket {existing[0]}")
        return {"ticket_number": existing[0]}
    
    payload = {
        "ticket": ticket_data,
        "conversations": conversations[:30]
    }
    
    for attempt in range(max_retries):
        try:
            resp = requests.post(
                f"{DESK365_API_BASE}/tickets/import",
                headers=DESK365_HEADERS,
                json=payload,
                timeout=30
            )
            if resp.status_code == 429:
                wait = 2 ** attempt
                logger.warning(f"Rate limited on Desk365 import. Waiting {wait}s.")
                time.sleep(wait)
                continue
            if resp.status_code in (500, 502, 503, 504):
                wait = 2 ** attempt
                logger.warning(f"Desk365 server error {resp.status_code}. Waiting {wait}s.")
                time.sleep(wait)
                continue
            resp.raise_for_status()
            result = resp.json()
            ticket_id = result.get("ticket_number")
            
            # Persist to deduplication store
            state_db.execute(
                "INSERT INTO imported_tickets (ada_conversation_id, desk365_ticket_id) VALUES (?, ?)",
                (ada_id, ticket_id)
            )
            state_db.commit()
            
            # Handle overflow messages beyond 30-entry cap
            if len(conversations) > 30:
                for i in range(30, len(conversations), 30):
                    batch = conversations[i:i+30]
                    overflow_payload = {"ticket_number": ticket_id, "conversations": batch}
                    overflow_resp = requests.post(
                        f"{DESK365_API_BASE}/tickets/import-conversations",
                        headers=DESK365_HEADERS,
                        json=overflow_payload,
                        timeout=30
                    )
                    overflow_resp.raise_for_status()
                    time.sleep(0.5)
            
            return result
            
        except requests.exceptions.Timeout:
            logger.warning(f"Desk365 request timed out. Retry {attempt + 1}/{max_retries}")
            time.sleep(2 ** attempt)
    
    raise RuntimeError(f"Max retries exceeded for Ada conversation {ada_id}")

Step 5: Validate the Migration

Run these checks after importing:

  • Record count match: Total Ada conversations extracted = Total Desk365 tickets created.
  • Conversation thread integrity: Spot-check 20–50 tickets to verify all messages appear in the correct order with correct attribution (bot vs. chatter vs. agent). Specifically test tickets from escalated conversations — these have the highest risk of attribution errors.
  • Custom field population: Verify cf_ada_conversation_id and other custom fields are populated correctly. Spot-check at least one ticket from each Ada variable type.
  • Contact accuracy: Confirm contacts were created or matched correctly. Run a Desk365 contact export and check for duplicates where the same real user appears under two synthetic email addresses.
  • Category and group assignment: Verify categories map correctly from Ada topics.
  • Timestamp accuracy: Confirm createdOn and closedOn dates match Ada's date_created and date_updated.
  • CSAT data: If you mapped CSAT scores to custom fields, verify the values are correct. Check both CSAT v2 conversations and any v1 conversations — see the CSAT version note in the edge cases section below.
  • Connected system IDs: Verify that Salesforce, Zendesk, or other CRM identifiers stored in Ada metavariables landed in their designated cf_ fields.
Tip

Run a test migration first. Import 100–200 representative conversations — mix short/long, escalated/non-escalated, with/without CSAT, with/without connected system IDs, and with anonymous chatters — before committing to a full migration. Use threads with handoffs, duplicate identities, and custom fields. Clean demo threads prove nothing.

For a comprehensive testing framework, see How to Do a Help Desk Data Migration: The 7-Step Checklist.

What Cannot Be Migrated

Some Ada data has no Desk365 equivalent:

Ada Feature Why It Can't Migrate
Bot Playbooks Desk365 has no equivalent of Ada's Playbook execution engine. Rebuild using Desk365 Automation Rules.
Coaching configurations Training instructions for Ada's AI agent. No equivalent in Desk365.
Answer flows (scripted bot) Ada's decision-tree bot logic doesn't transfer. Desk365's AI Agent uses a different configuration model.
Knowledge availability rules Ada controls when the AI agent can use an article; Desk365 KB visibility is portal-, company-, and agent-group-based. Treat this as a redesign, not a copy.
OAuth/authentication state Per-conversation auth tokens and session state don't transfer.
Conversation webhooks Ada's webhook subscriptions are platform-specific. Rebuild in Desk365 or via Power Automate.
Real-time conversation state Active/in-progress conversations cannot be "resumed" in Desk365. Only completed conversations should be migrated.
CSAT v1 data Only CSAT v2 data is available via the Conversations endpoint. CSAT v1 data does not appear in API responses.

Edge Cases That Break Migrations

Timestamp Preservation

When you push a ticket into Desk365 via the standard POST /api/v3/tickets endpoint, the created_at timestamp defaults to the moment the API request was made. If you migrate three years of Ada chats on a Tuesday, all tickets will look like they were created on that Tuesday.

The Import Tickets with Conversations endpoint supports setting createdOn to override the system timestamp. If your Desk365 tier does not support timestamp override, store the original Ada timestamp in a custom field (e.g., cf_original_chat_date) and configure your reporting tools to query that field instead of the system creation date.

API Timeouts on Large Transcripts

Some Ada conversations span hundreds of messages — especially if the user looped through bot prompts repeatedly. If you are using the single-description HTML approach, transcripts exceeding approximately 64KB will cause Desk365's API to reject the payload. Truncate at that threshold, add a [Transcript Truncated — full content in cf_ada_transcript_json] marker, and store the complete JSON message array in a separate custom text field.

With the Import Tickets with Conversations endpoint this is less of an issue since messages are sent as separate entries, but you still need to respect the 30-entries-per-call cap.

Rich Content and Attachments

Ada's message schema includes types like picture, quick-reply, link, CSAT prompt, and CSAT result. Only text messages are supported at launch. Images and structured messages are not yet available. Verify what your Ada API version actually returns. For non-text message types, flatten them to readable text with explicit markers (e.g., [Image message — content not available in export]) so agents can still understand the thread context.

On the Desk365 side, the editor allows up to 10 attachments per ticket interaction with a combined attachment size limit of 20 MB per conversation. If you have media files from Ada through another channel or manual export, plan your upload strategy around these limits.

Identity Drift

Ada conversations may identify the same person differently across channels — one email on chat, another on voice, no email on an anonymous web session. Desk365 matches contacts by exact email only, not by name or phone. Use email as your primary key where it exists, keep the original end_user_id in a custom field, and plan for post-import merges using Desk365's contact merge flow for the inevitable collisions.

CSAT Version Detection

Only CSAT v2 data is available via the Conversations endpoint. To determine which CSAT version your Ada instance uses, check the csat field structure in a sample conversations API response. CSAT v2 returns an object with score, feedback, and type keys. CSAT v1 data is absent from the API response entirely — if your csat field is consistently null despite known survey responses, your instance uses CSAT v1 and that data is not recoverable via the export API. Contact Ada support to confirm before scoping your migration.

Ada API Gotchas

  1. created_since and updated_since are mutually exclusive. Note that updated_since and created_since are mutually exclusive, so you must only use one in a request. Use created_since for initial extraction; use updated_since only for delta syncs near cutover.
  2. Occasional ingestion delays. There are occasional delays when ingesting conversation data; if you did not receive the data you are expecting, please contact your Ada team. Build retry logic and do not assume real-time accuracy.
  3. Test user conversations. Ada's export includes test conversations. Filter out records where is_test_user is true before importing.

Desk365 Import Gotchas

  1. Pre-create all dropdown values. The Group, AssignedTo, and Category columns must match the group, agent names, and categories you have created in Desk365. A ticket referencing a non-existent category will fail or be silently assigned to no category.
  2. Status values must be exact strings. Desk365 accepts default statuses (Open, Pending, Resolved, Closed) and any custom statuses you have configured. Verify the exact string your Desk365 instance expects — custom status names are case-sensitive.
  3. The 30-conversation-per-call limit is hard. For long Ada conversations, you must split across multiple API calls. Preserve message ordering carefully, as Desk365 renders replies in the order they are received.

Knowledge Base Migration

If you are also migrating Ada's knowledge base to Desk365, treat it as a separate workstream.

Ada Knowledge API Constraints

Ada's Knowledge API has the following documented limits: 60,000 requests per day, a default maximum of 50,000 articles, a 100KB per-article size limit, and a 10MB request-size cap. Articles are organized under sources, with tags and availability rules controlling when the AI agent can use each article.

Desk365 KB Structure

Desk365 organizes knowledge base content into categories → folders → articles. Visibility is controlled at three levels: all visitors, signed-in users, specific companies, or restricted agent groups.

Ada → Desk365 KB Mapping

Ada KB Concept Desk365 Equivalent Migration Notes
Source KB Category One Ada source maps to one Desk365 KB category. Pre-create all categories before importing articles.
Article KB Article Direct mapping. Respect the 100KB per-article limit on export; recheck against Desk365's per-article size limits.
Tag Folder or article tag Desk365 supports folder-level organization and article tags. Map Ada tags to Desk365 folders where they represent topic clusters.
Availability rule Visibility setting Not a direct mapping — see below.

Availability Rules vs. Visibility Settings

Ada's availability rules determine when the AI agent can reference an article during a conversation (e.g., "only use this article for billing intents"). Desk365's visibility settings determine who can read an article in the support portal (all visitors, signed-in users, specific companies). These are different axes of control. There is no automated translation between them. Treat this as a redesign: for each Ada availability rule, decide whether the underlying intent maps to a portal visibility restriction, an agent-group restriction, or simply an absence of restriction in Desk365.

Web-Imported Content

If part of your Ada knowledge base was created through Ada's web import feature, note that Ada's scraper: imports only publicly accessible content, follows links up to five levels deep, truncates articles over 100KB, and sets scraped content language to English regardless of the original page language. Clean up these articles before importing them into Desk365 — multilingual content will need manual language tagging.

DIY vs. Managed Migration

Factor DIY Feasible Managed Recommended
Conversation volume Under 5,000 Over 5,000
Ada variables to preserve 0–3 simple variables 4+ variables, or nested metavariables
Conversation thread depth Average <10 messages Average >10 messages or mixed bot+agent
CSAT data migration Not needed Needed with reporting requirements
Custom field complexity Text fields only Dropdowns, multi-level, or date fields
Knowledge base migration Not needed or simple Complex with availability rules
Engineering availability Dedicated developer for 1–2 weeks No dedicated developer
Connected system IDs None Salesforce, Zendesk, or other CRM IDs present
Estimated DIY effort 40–80 engineer-hours 100+ engineer-hours (where errors compound)
Risk tolerance Comfortable with iterative debugging Needs first-time-right accuracy

The primary failure mode in DIY migrations at scale is not the initial extraction — it is data loss discovered weeks post-cutover when agents notice gaps in contact history or missing CSAT records. Building the deduplication store, retry logic, and post-import validation framework from scratch accounts for roughly 40% of the engineering hours.

Migration Timeline

Phase Duration Activities
Discovery & planning 2–3 days Audit Ada data volume, variable usage, conversation patterns. Configure Desk365 custom fields, categories, groups.
Extraction 1–3 days Pull all conversations and messages from Ada. Store locally. Handle 60-day windowing.
Transformation 3–5 days Build and test mapping logic. Handle edge cases (missing emails, long threads, variable flattening, connected system IDs).
Test migration 2–3 days Import 200–500 conversations. Validate. Fix bugs. Re-import.
Full migration 1–2 days Import all records. Monitor for errors.
Validation & cleanup 1–2 days Count checks, spot-check threads, re-enable automations and notifications.
Total 1–3 weeks Varies by volume and complexity.

Keeping Support Running During Migration

Ada and Desk365 can run in parallel during migration:

  1. Don't turn off Ada until Desk365 is fully configured and your team is trained.
  2. Migrate historical data first while Ada continues handling live conversations.
  3. Set a cutover date. On cutover day, disable Ada's live channels, do a final delta extraction of any conversations created since your last pull, import those into Desk365, and switch live traffic to Desk365.
  4. Use updated_since for the delta pull — this catches conversations that were updated (e.g., received CSAT responses) after your initial extraction. Note that updated_since and created_since are mutually exclusive per request.
  5. Stage Desk365 channel controls deliberately. Use Desk365's channel controls to enable Email, Microsoft Teams, and the Support Portal incrementally instead of opening every channel at once.

For zero-downtime strategies, see Zero-Downtime Help Desk Data Migration: How to Keep Support Running During the Move.

When This Migration Doesn't Make Sense

Be honest about whether you actually need to migrate Ada data into Desk365:

  • If Ada was purely a deflection bot with no human escalations, the conversation data may have limited long-term value in a helpdesk context. Consider archiving to a data warehouse instead.
  • If conversations are older than 12 months and you never exported them, they are gone from Ada's API. There is nothing to migrate.
  • If you don't need conversation threads — just metadata — a CSV export from Ada's dashboard (if available) plus Desk365's CSV import may be sufficient without any API work.
  • If your Ada instance used CSAT v1 exclusively and CSAT history is the primary reason for migration, verify that data is accessible before scoping the project.

After the Migration

Once historical data is in Desk365:

  • Rebuild automations: Convert Ada playbook and coaching logic into Desk365 Automation Rules (ticket creation triggers, time-based triggers, update triggers).
  • Set up SLA policies: Define response and resolution time targets — something Ada did not manage.
  • Configure Desk365's AI Agent: If you are replacing Ada's AI capabilities, configure Desk365's native AI features for ticket routing and response drafting.
  • Train your team: Desk365's Microsoft Teams integration means agents work tickets directly in Teams — a different workflow than monitoring Ada's dashboard.
  • Keep the raw Ada export. Store it alongside the cf_ada_conversation_id mapping so you have an audit trail and can reprocess if needed.
  • Verify connected system integrity. If your Ada instance integrated with Salesforce or Zendesk, confirm that the external record IDs stored in cf_ fields are queryable and that your CRM team knows which Desk365 custom field to reference for cross-system lookups.

Frequently Asked Questions

Can I migrate Ada conversations to Desk365?
Yes. Extract conversations and messages from Ada's Data Export API, transform them into Desk365's ticket format, and load them via Desk365's Import Tickets with Conversations API endpoint. Each API call supports up to 30 conversation entries per ticket.
Does Ada's API export all historical conversations?
No. Ada's Data Export API only provides access to data from the past 12 months. Each query is limited to a 60-day date range window, and there's a 2-hour ingestion delay for recent conversations.
What Ada data cannot be migrated to Desk365?
Bot playbooks, coaching configurations, answer flows, OAuth session state, active conversation state, and webhook subscriptions cannot be migrated. These must be rebuilt manually in Desk365 using its native automation rules and AI agent features.
What happens if Ada chatters didn't provide an email address?
Desk365 requires a valid ContactEmail to create a ticket. For anonymous Ada chatters, generate a synthetic email like unknown+{chatter_id}@yourdomain.com and store the chatter's Ada ID in the ticket body for auditing. You can merge contacts post-migration if real identities surface.
Can I move Ada knowledge articles into Desk365's knowledge base?
Yes, but the logic changes. Ada uses sources, articles, tags, and article availability rules; Desk365 uses KB categories, folders, articles, and visibility rules for portal users, companies, and agent groups. Treat it as a redesign, not a copy.

More from our Blog

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

Ada to Ada Migration: The CTO's Technical Guide

A technical guide to migrating between Ada instances — covering API constraints, knowledge transfer, conversation archival, end-user handling, and the edge cases that break DIY scripts.

Nachi Nachi · · 29 min read