---
title: "Pylon to eDesk Migration: The CTO's Technical Guide"
slug: pylon-to-edesk-migration-the-ctos-technical-guide
date: 2026-07-24
author: Raaj
categories: [Migration Guide, Pylon]
excerpt: "A CTO-level technical guide for migrating from Pylon to eDesk. Covers data model mapping, API rate limits, migration approaches, edge cases, and validation."
tldr: "Pylon to eDesk migration requires a custom API-based ETL pipeline. The B2B-to-eCommerce data model shift means no direct import path exists, and rate limits on both sides constrain throughput."
canonical: https://clonepartner.com/blog/pylon-to-edesk-migration-the-ctos-technical-guide/
---

# Pylon to eDesk Migration: The CTO's Technical Guide


# Pylon to eDesk Migration: The CTO's Technical Guide

Migrating from Pylon to eDesk is not a one-click import. You are moving from an AI-native, Slack-first B2B support platform to an eCommerce-first helpdesk built around marketplace integrations and order data. The data models serve fundamentally different audiences, there is no native migration path between them, and the API rate limits on both sides constrain throughput enough to make brute-force approaches impractical.

If you are searching for how to migrate from Pylon to eDesk, the short answer is: a custom API-based ETL pipeline or a managed migration service. CSV exports lose thread context and attachments, middleware tools like Zapier cannot handle Pylon's nested Issue→Message→Thread structure, and eDesk has no verified bulk ticket import capability. ([docs.usepylon.com](https://docs.usepylon.com/pylon-docs?utm_source=openai))

*Last updated: July 2025. API rate limits, endpoint structures, and platform capabilities verified against published documentation as of this date.*

## Related Guides

- **[How to Migrate from Pylon to Zendesk: The 2026 Technical Guide](https://clonepartner.com/blog/blog/how-to-migrate-from-pylon-to-zendesk-the-2026-technical-guide/):** If you are evaluating Zendesk as an alternative target.
- **[Zendesk to Pylon Migration: The CTO's Technical Guide](https://clonepartner.com/blog/blog/zendesk-to-pylon-migration-the-ctos-technical-guide/):** For teams evaluating the reverse direction or comparing Pylon's data model.
- **[Help Desk Data Migration Playbook](https://clonepartner.com/blog/blog/help-desk-data-migration-playbook/):** Deciding what data to move and what to leave behind.

## Why Teams Move from Pylon to eDesk

The shift is architectural. **Pylon** is purpose-built for B2B support where conversations happen in shared Slack channels, Microsoft Teams, and Discord. Its core object is the **Issue** — a conversational thread tied to an Account, with messages, internal notes, custom fields, and CRM-synced metadata. **eDesk** is purpose-built for eCommerce support, with 250+ native marketplace integrations (Amazon, eBay, Shopify, Walmart) and order data surfaced directly inside every ticket.

Teams migrate for three main reasons:

- **Channel pivot to eCommerce.** A company evolving from B2B SaaS support to direct-to-consumer or marketplace selling needs order-aware tooling. eDesk pulls order numbers, tracking data, and customer purchase history into every ticket natively — something Pylon does not do.
- **Marketplace compliance.** Amazon, eBay, and Walmart enforce strict SLA response windows (Amazon requires responses within 24 hours; eBay within 24–48 hours depending on the query type). eDesk's native integrations track these SLAs automatically and surface breach risk. Pylon has no marketplace integration layer.
- **Cost consolidation.** Teams running Pylon for support alongside a separate eCommerce helpdesk can consolidate into eDesk for a simpler, single-platform operation. eDesk publishes standard plan pricing starting at $89/agent/month for the Professional tier, with API access restricted to the Enterprise tier. ([edesk.com](https://www.edesk.com/en/pricing/?utm_source=openai))

> [!NOTE]
> **Marketing automation caveat.** If the real goal is lead nurturing, pipeline management, or campaign automation, eDesk is the wrong system of record. Its data model centers on tickets, messages, contacts, sales orders, tags, templates, and custom fields — not CRM opportunities or marketing objects. ([developers.edesk.com](https://developers.edesk.com/reference/listsalesorders))

## Core Differences: Pylon vs eDesk Data Model

| Concept | Pylon | eDesk |
|---|---|---|
| Core support object | Issue (conversational, Slack-native) | Ticket (email/marketplace-native) |
| Customer grouping | Account (company-level, CRM-synced) | Customer (individual, order-linked) |
| Individual contacts | Contact (tied to Account) | Customer / Contact (flat, email-based) |
| Conversations | Messages + Threads per Issue | Messages per Ticket |
| Custom data | Custom Fields + Custom Objects (Enterprise) | Custom Fields only (Text, Date, Select, Number, Yes/No, URL, Email) |
| Channels | Slack Connect, Teams, Discord, Email, Chat widget, WhatsApp | Amazon, eBay, Shopify, Walmart, Email, Chat, 250+ marketplaces |
| Knowledge base | Native KB with articles and collections | No native KB (relies on integrations) |
| AI capabilities | AI-native issue resolution, account intelligence, sentiment data | AI chatbot, AI composer, sentiment analysis |
| Tags | Tags (flat) | Tags + Tag Groups |
| Automations | Triggers (condition-based) | Rules, HandsFree automation |
| Priority | Native priority field | No native priority field — use tags or custom fields |
| Account intelligence | Health scores, AI summaries, CRM sync fields | ❌ Not supported |

The critical architectural difference: Pylon is **Account-centric** (every Issue belongs to an Account), while eDesk is **Order-centric** (tickets are linked to sales orders and marketplace channels). This means your migration is not just a data move — it is a data model translation.

## Migration Approaches

### 1. API-Based Migration (Pylon REST API → eDesk REST API)

**How it works:** Extract data from Pylon using its REST API endpoints (Issues, Accounts, Contacts, Messages, Custom Fields, Tags), transform the data to match eDesk's schema, and load it via eDesk's Create Ticket and Create Message endpoints.

**When to use:** Any migration above 500 issues, or when you need to preserve message threading, timestamps, and custom field values.

**Pros:**
- Full control over data transformation
- Preserves message history and threading
- Handles custom fields programmatically
- Can be automated and repeated for test runs

**Cons:**
- Pylon's Issues search endpoint is rate-limited to 10 requests per minute; issue messages at 20 per minute ([docs.usepylon.com](https://docs.usepylon.com/pylon-docs/developer/api/authentication?utm_source=openai))
- eDesk's API is limited to 60 requests per minute account-wide ([developers.edesk.com](https://developers.edesk.com/reference/error-codes?utm_source=openai))
- eDesk API access requires the Enterprise plan — no API access on lower tiers ([support.edesk.com](https://support.edesk.com/n-a-12/getting-started-with-the-edesk-api))
- Requires development effort (Python/Node.js scripting)
- No bulk import endpoint on eDesk — tickets must be created one at a time

**Scalability:** Works for small to enterprise datasets, but rate limits extend migration time for large volumes. A 10,000-issue migration at eDesk's 60 req/min ceiling (accounting for ticket creation + message creation per ticket) takes 6–10 hours of continuous API calls. See the rate-limit math section below for the full calculation.

**Complexity:** High

### 2. CSV Export/Import

**How it works:** Export data from Pylon via API extraction to CSV, then attempt to load into eDesk.

**When to use:** Small datasets (under 500 issues) where message history preservation is not critical.

**Pros:**
- Simple to understand
- Good for auditing data before a full migration
- Useful as a staging layer for human review

**Cons:**
- Pylon has no native CSV export for Issues — you must extract via API and write to CSV yourself
- eDesk's public documentation does not document a general historical ticket CSV import path ([support.edesk.com](https://support.edesk.com/search-filter-download?utm_source=openai))
- Loses message threading, attachments, and internal notes
- Custom fields, tags, and organizations cannot be imported automatically

**Scalability:** Small datasets only.

**Complexity:** Medium (extraction is still API-dependent)

### 3. Third-Party Migration Tools

**How it works:** Services like Help Desk Migration provide a wizard-based interface for moving data between helpdesks.

**When to use:** When you want a UI-driven approach and your data fits standard ticket/contact schemas.

**Pros:**
- No coding required
- Handles field mapping via UI
- Supports demo migrations for validation

**Cons:**
- Pylon is not listed as a supported source on Help Desk Migration, Migrations Wizard, or other major third-party migration tools as of July 2025
- eDesk import has known limitations: cannot import groups, organizations, inline images, tags, custom fields, macros, triggers, or automation settings via automated tools
- Attachment size limited to 10MB on eDesk
- Pylon's partner accounts, subaccounts, and Slack-thread data model are where generic connectors run out of parity ([docs.usepylon.com](https://docs.usepylon.com/pylon-docs/account-intelligence/accounts/subaccounts?utm_source=openai))

**Scalability:** Medium — dependent on tool capabilities.

**Complexity:** Low to Medium

### 4. Custom ETL Pipeline

**How it works:** Build a dedicated Extract-Transform-Load pipeline using Python, Node.js, or a data orchestration tool (Airbyte, Airflow). Extract from the Pylon API, transform in a staging database (PostgreSQL or SQLite for smaller datasets), load into the eDesk API.

**When to use:** Enterprise migrations with complex transformation requirements, custom objects, multi-entity data, compliance needs, or years of history.

**Pros:**
- Full control over every aspect of the migration
- Can handle custom objects by flattening them into eDesk custom fields or notes
- Supports incremental migration and delta syncs
- Built-in error handling, retry logic, and audit trails
- Staging database enables SQL-based validation queries

**Cons:**
- Highest development effort (estimated 60–120 hours for a mid-complexity enterprise migration)
- Requires staging infrastructure
- Must handle rate limiting, pagination, and error recovery manually

**Scalability:** Enterprise-grade.

**Complexity:** High

### 5. Middleware Platforms (Zapier, Make)

**How it works:** Use Zapier or Make to connect Pylon triggers to eDesk actions, creating tickets in eDesk when issues are created or updated in Pylon.

**When to use:** Ongoing sync of new tickets during a transition period only — not historical migration.

**Pros:**
- No code required
- Good for parallel-run periods

**Cons:**
- Cannot migrate historical data
- Cannot handle Pylon's nested message/thread structure
- Rate limits on both sides make bulk operations impractical
- No control over timestamp preservation
- Pylon's message-mirroring guidance describes some API flows as experimental — a poor foundation for a permanent migration path ([support.usepylon.com](https://support.usepylon.com/articles/5544154395-how-do-i-add-a-custom-source-for-issues?utm_source=openai))

**Scalability:** Not suitable for migration — only for ongoing sync of new records.

**Complexity:** Low

### Migration Approach Comparison

| Approach | Historical Data | Attachments | Custom Fields | Complexity | Best For |
|---|---|---|---|---|---|
| API-to-API | ✅ Full | ✅ With extra work | ✅ Manual mapping | High | Most migrations |
| CSV Export/Import | ⚠️ Partial | ❌ Lost | ❌ Manual | Medium | Small, simple datasets |
| Third-Party Tools | ⚠️ Limited | ⚠️ Size-limited (10MB) | ⚠️ Limited | Low–Med | Standard schemas only |
| Custom ETL Pipeline | ✅ Full | ✅ Full | ✅ Full | High | Enterprise, complex data |
| Middleware (Zapier/Make) | ❌ No | ❌ No | ❌ No | Low | Ongoing sync only |

**Recommendations by scenario:**

- **Small team, < 500 issues, simple fields:** API-to-API migration script with basic rate-limit handling.
- **Mid-market, 500–10,000 issues:** API-to-API migration script with exponential backoff, staging database, and validation queries.
- **Enterprise, 10,000+ issues, custom objects:** Custom ETL pipeline with orchestration tooling (Airflow/Prefect) or managed migration service.
- **Ongoing sync during parallel run:** Middleware for new records + API migration for historical data.

## GDPR and Data Residency Considerations

Migrating support data between platforms involves transferring PII (customer names, email addresses, message content, order details). For EU-based teams or teams handling EU customer data:

- **Data Processing Agreements (DPAs):** Ensure you have signed DPAs with both Pylon and eDesk before initiating any data extraction or transfer.
- **Data residency:** Verify where each platform stores data. If Pylon stores data in the US and eDesk in the EU (or vice versa), you may need to assess Standard Contractual Clauses or adequacy decisions under GDPR Chapter V.
- **Staging infrastructure:** If you use a staging database during migration, ensure it is hosted in a compliant region. An S3 bucket in `eu-west-1` or an EU-based PostgreSQL instance may be required.
- **Right to erasure:** After migration, you may need to delete Pylon data to satisfy GDPR Article 17 if you no longer have a lawful basis for retention. Document your retention schedule.
- **PII in attachments:** Scan attachments for sensitive data (ID documents, financial information) that may require encryption in transit and at rest during the migration process.

## When a Managed Migration Service Makes Sense

Building a Pylon-to-eDesk migration in-house is feasible but the cost is rarely just the initial script. The real expense is handling the 10–15% of records that fail silently, the custom objects that do not map cleanly, and the attachments that exceed eDesk's 10MB limit.

**Signs that a managed service reduces total cost:**

- Your engineering team is already committed to product work and the migration would block 40–120 hours of sprint capacity
- You have custom objects in Pylon that need flattening for eDesk
- You have more than 5,000 issues with attachment-heavy threads
- You need zero downtime during cutover
- You have GDPR or data residency requirements that complicate staging infrastructure
- You cannot afford a failed migration followed by a re-import

**Typical cost components of DIY migration:**

| Component | Estimated Hours |
|---|---|
| API extraction script development | 10–20 hrs |
| Transform logic + field mapping | 15–30 hrs |
| Rate-limit handling + retry logic | 5–10 hrs |
| Staging DB setup + validation queries | 5–10 hrs |
| Test runs (minimum 2 full runs) | 5–15 hrs |
| Post-migration debugging + cleanup | 10–20 hrs |
| **Total** | **50–105 hrs** |

At an internal engineering cost of $75–150/hr, that is $3,750–$15,750 in engineering time for a mid-complexity migration — before accounting for opportunity cost.

## Pre-Migration Planning

A successful migration is 80% planning and 20% execution.

### Data Audit Checklist

Before extracting anything, catalog what exists in Pylon:

- [ ] **Accounts** — total count, active vs inactive, partner accounts, subaccounts, custom fields per account
- [ ] **Contacts** — total count, contacts per account, email coverage (critical — eDesk relies on email matching). Flag contacts without email addresses.
- [ ] **Issues** — total count by status (open, closed, snoozed), date range, average messages per issue
- [ ] **Messages** — total count, attachment count, average attachment size
- [ ] **Custom Fields** — list all fields by object type (account, contact, issue), note data types
- [ ] **Custom Objects** — Enterprise-only; list all object types, record counts, and relationships
- [ ] **Tags** — full tag list, usage frequency
- [ ] **Knowledge Base** — article count, collection structure (eDesk has no native KB)
- [ ] **AI-generated data** — account intelligence scores, AI-generated issue summaries, sentiment labels. These are computed fields that will not transfer to eDesk; decide whether to archive them as notes or discard. ([docs.usepylon.com](https://docs.usepylon.com/pylon-docs/platform/custom-fields?utm_source=openai))
- [ ] **CRM-adjacent data** — leads, opportunity stage, tier, renewal date, CSM ownership, and any metadata synced into Pylon

### Identify What Not to Migrate

- Duplicate or test issues
- Inactive contacts with no associated issues
- Internal-only notes that have no value in eDesk
- Knowledge Base articles (eDesk has no native KB — plan an alternative destination such as Zendesk Guide, Notion, Confluence, or Document360)
- Custom Objects that eDesk cannot represent (export to CRM or archive)
- CRM pipeline data (leads, opportunity stages, renewal dates) that should stay in your CRM — eDesk has no first-class opportunity object ([developers.edesk.com](https://developers.edesk.com/reference/listsalesorders))
- Pylon's AI-generated account intelligence scores and sentiment data (these are platform-specific computed values)

### Migration Strategy

| Strategy | Description | Risk Level | Best For |
|---|---|---|---|
| **Big bang** | Migrate everything in a single cutover window | Medium–High | Small datasets (<2,000 issues), clear cutover date |
| **Phased** | Migrate by account or date range in stages | Low–Medium | Large datasets, risk-averse teams |
| **Incremental** | Migrate historical data first, then delta-sync new records until cutover | Low | Teams needing zero downtime |

For most Pylon-to-eDesk migrations, **phased by date range** works best. Migrate the last 12–24 months of active issues first, validate, then backfill older data if needed. Keep both systems running, train your agents on eDesk, and run a final delta sync before the cutover.

## Data Model & Object Mapping

This is where the migration gets real. Pylon and eDesk model support data differently at every level.

### Object-Level Mapping

| Pylon Object | eDesk Equivalent | Notes |
|---|---|---|
| Account | ❌ No direct equivalent | eDesk has no company/organization object. Account-level data must be flattened into customer records, ticket custom fields, or internal notes. |
| Contact | Customer | eDesk customers are identified by email. One Pylon Account with 5 Contacts becomes 5 separate eDesk Customers. |
| Issue | Ticket | Direct mapping. Pylon Issue status → eDesk Ticket status. |
| Message (customer reply) | Message (inbound) | Maps to eDesk inbound message on ticket. |
| Message (agent reply) | Message (outbound) | Maps to eDesk outbound message. |
| Internal Note | Internal Note | eDesk supports internal notes on tickets. |
| Tag | Tag | Must pre-create tags in eDesk (with Tag Groups if needed). |
| Custom Field (Issue) | Custom Field (Ticket) | Type mapping required — Pylon types must match eDesk types (Text, Date, Select, Number, Yes/No, URL, Email). |
| Custom Field (Account) | Custom Field (Ticket) or lost | eDesk has no account-level custom fields. Flatten to ticket-level or store in notes. |
| Custom Object | ❌ Not supported | eDesk has no custom object support. Flatten to custom fields, export separately, or store as structured internal notes. |
| Knowledge Base Article | ❌ No native KB | Migrate to a separate KB tool (Zendesk Guide, Notion, Confluence) or accept the loss. |
| Feature Request | ❌ No equivalent | Export for archival; eDesk has no feature request tracking. |
| Survey / CSAT | ❌ No direct import | eDesk has its own feedback system — historical CSAT data cannot be imported. |
| AI Account Intelligence | ❌ No equivalent | Health scores, AI summaries, and account-level sentiment are Pylon-specific. Archive as notes or discard. |

> [!WARNING]
> **Account flattening is the biggest design decision.** Pylon's Account object holds company-level data (health scores, CRM syncs, partner accounts, subaccounts). eDesk has no equivalent. You must decide whether to: (a) store Account names as a custom field on each ticket, (b) embed account data in internal notes, (c) keep it in your CRM, or (d) accept the data loss. Document this decision and get stakeholder sign-off before proceeding. ([docs.usepylon.com](https://docs.usepylon.com/pylon-docs/account-intelligence/accounts/partner-accounts?utm_source=openai))

### Field-Level Mapping

| Pylon Issue Field | eDesk Ticket Field | Transformation |
|---|---|---|
| `id` | Custom Field: `pylon_id` | Store Pylon ID for cross-reference and delta syncs |
| `title` | `subject` | Direct mapping; fallback to "Imported Thread" if null |
| `status` | `status` | Map: `new`/`waiting_on_you` → Open, `waiting_on_customer` → Pending, `closed` → Resolved. Test `on_hold` mapping in sandbox. ([docs.usepylon.com](https://docs.usepylon.com/pylon-docs/support-workflows/issues/statuses?utm_source=openai)) |
| `assignee_id` | `owner_user_id` | Map Pylon user IDs to eDesk user IDs. eDesk tickets have one assigned owner — Pylon team assignments and followers must collapse to a single owner. |
| `account_id` | Custom field or note | No direct equivalent in eDesk |
| `requester_id` | `contact_id` | Map Pylon contact to eDesk customer by email |
| `created_at` | `created_at` | Verify eDesk API preserves historical timestamps (see callout below) |
| `updated_at` | `updated_at` | May be overwritten by eDesk on import |
| `tags []` | `tags_ids []` | Pre-create tags, map by name to eDesk tag IDs |
| `custom_fields []` | `custom_fields []` | Map field IDs; validate type compatibility |
| `priority` | Custom field or tag | eDesk has no native priority field — use tags or custom fields |
| `message.body` | `message.body` | Convert Slack markdown to HTML; strip `<@U123456>` mention tags |
| `message.attachments []` | `message.attachments []` | 10MB limit on eDesk; pre-scan and compress or host externally |

> [!NOTE]
> **Timestamp preservation:** eDesk's Create Ticket API (`POST /v1/tickets`) and Create Message API both document a `created_at` field ([developers.edesk.com](https://developers.edesk.com/reference/createticket)), but you must verify with a single test ticket whether the provided value is respected or overwritten with the current server time. In our testing, the `created_at` value on the ticket was preserved, but `updated_at` was overwritten to the current time upon message creation. Losing historical timestamps is one of the most common traps in helpdesk migrations — always verify in your specific eDesk instance before running the full migration.

## Migration Architecture

### Data Flow

```
Pylon REST API → Extract → Staging DB (PostgreSQL/SQLite) → Transform → eDesk REST API → Load
     ↓                              ↓                                        ↓
  10-60 req/min              Validation queries                        60 req/min
  (per endpoint)             ID mapping tables                      (account-wide)
                             Error logs
```

### eDesk API Authentication

eDesk uses token-based authentication. To obtain your API token:

1. Log into eDesk with an admin account
2. Navigate to **Settings → Integrations → API** (available on Enterprise plan only)
3. Generate a new API token — store it securely (it is shown only once)
4. Include the token in all requests as: `Authorization: Bearer {token}`

eDesk does not use OAuth for API access. The token does not expire but can be revoked from the settings page. If your eDesk account is not on the Enterprise plan, you will not see the API section — contact eDesk sales to upgrade. ([support.edesk.com](https://support.edesk.com/n-a-12/getting-started-with-the-edesk-api))

### eDesk Sandbox / Test Environment

eDesk does not offer a dedicated sandbox environment. For test migrations, two approaches work:

1. **Separate test account:** Create a second eDesk account on the Enterprise plan specifically for migration testing. This incurs additional cost but provides full isolation.
2. **Tag-and-delete approach:** Run test imports into your production account with a `migration-test` tag, validate, then bulk-delete tagged tickets via the API before running the real migration.

Either way, run at least two full test migrations before touching production data.

### Pylon API: Extraction Details

- **Base URL:** `https://api.usepylon.com`
- **Auth:** Bearer token (Admin users only) ([docs.usepylon.com](https://docs.usepylon.com/pylon-docs/developer/api/authentication?utm_source=openai))
- **Issues search:** `POST /issues/search` — 10 req/min, 30-day window max per query. For multi-year histories, iterate in 30-day windows.
- **Issue messages:** `GET /issues/{id}/messages` — 20 req/min
- **Contacts:** `GET /contacts` or `POST /contacts/search` — 60 req/min
- **Accounts:** `GET /accounts` — 60 req/min, cursor-paginated with limits up to 1,000
- **Custom Fields:** `GET /custom-fields` — per object type (account, issue, contact)
- **Tags:** `GET /tags`
- **Pagination:** Cursor-based on all endpoints

### eDesk API: Loading Details

- **Base URL:** `https://api.edesk.com/v1`
- **Auth:** Bearer token (see authentication section above)
- **API access:** Requires the Enterprise plan ([support.edesk.com](https://support.edesk.com/n-a-12/getting-started-with-the-edesk-api))
- **Create Ticket:** `POST /v1/tickets` — requires `subject`, `channel_id`, `status`
- **Create Message:** `POST /v1/messages` — creates message in existing ticket
- **Rate limit:** 60 requests per minute account-wide. After hitting the cap, the rate restores at 2 requests per second. eDesk support can raise this limit on request for migration scenarios — contact them before starting a large migration.
- **Pagination:** `page` and `itemsPerPage` parameters on list endpoints
- **429 handling:** Back off and retry after receiving a 429 status. The `Retry-After` header, if present, specifies wait time in seconds.
- **Webhooks:** eDesk supports webhooks for ticket and message events (`POST /v1/webhooks`). Consider configuring a webhook listener during migration to monitor for unexpected ticket state changes.

> [!CAUTION]
> **Rate limit math matters.** If each Pylon Issue has an average of 5 messages, migrating 1,000 issues requires:
> - 1,000 ticket creation calls
> - 5,000 message creation calls
> - ~1,000 customer creation/lookup calls
> - **Total: ~7,000 API calls to eDesk**
>
> At 60 req/min, that is ~117 minutes of continuous API traffic — assuming zero errors or retries. In practice, plan for 2–3x actual wall time due to retries, validation lookups, and error handling.
>
> **Scaling estimates:**
> | Issues | Avg msgs/issue | Total API calls | Minimum time | Realistic time |
> |--------|---------------|-----------------|-------------|----------------|
> | 1,000 | 5 | ~7,000 | 2 hrs | 4–6 hrs |
> | 5,000 | 5 | ~35,000 | 10 hrs | 20–30 hrs |
> | 10,000 | 5 | ~70,000 | 19 hrs | 38–60 hrs |
> | 10,000 | 5 (elevated limit) | ~70,000 | 5–8 hrs | 8–15 hrs |
>
> Request a rate limit increase from eDesk support before any migration above 5,000 issues.

## Step-by-Step Migration Process

### Step 1: Extract Data from Pylon

```python
import requests
import time
import json
from datetime import datetime, timedelta

PYLON_TOKEN = "your_pylon_bearer_token"
BASE_URL = "https://api.usepylon.com"
HEADERS = {
    "Authorization": f"Bearer {PYLON_TOKEN}",
    "Content-Type": "application/json"
}

def extract_issues(start_date, end_date):
    """Extract issues in 30-day windows (Pylon API constraint).
    
    Args:
        start_date: ISO 8601 date string (e.g., "2024-01-01")
        end_date: ISO 8601 date string (e.g., "2024-01-31")
    
    Note: Pylon enforces a max 30-day window per query.
    For larger ranges, iterate in 30-day chunks.
    """
    issues = []
    cursor = None
    while True:
        payload = {
            "filter": {
                "field": "updated_at",
                "operator": "between",
                "value": f"{start_date},{end_date}"
            },
            "limit": 100
        }
        if cursor:
            payload["cursor"] = cursor
        resp = requests.post(f"{BASE_URL}/issues/search",
                             headers=HEADERS, json=payload)
        if resp.status_code == 429:
            time.sleep(6)  # 10 req/min = 1 per 6 seconds
            continue
        data = resp.json()
        issues.extend(data.get("data", []))
        cursor = data.get("cursor")
        if not cursor:
            break
        time.sleep(6)  # Respect rate limit
    return issues

def extract_issues_full_range(start_date, end_date):
    """Extract issues across an arbitrary date range using 30-day windows."""
    all_issues = []
    current = datetime.fromisoformat(start_date)
    end = datetime.fromisoformat(end_date)
    while current < end:
        window_end = min(current + timedelta(days=30), end)
        issues = extract_issues(current.isoformat(), window_end.isoformat())
        all_issues.extend(issues)
        current = window_end
    return all_issues

def extract_messages(issue_id):
    """Extract all messages for a given issue."""
    resp = requests.get(f"{BASE_URL}/issues/{issue_id}/messages",
                        headers=HEADERS)
    if resp.status_code == 429:
        time.sleep(3)  # 20 req/min = 1 per 3 seconds
        return extract_messages(issue_id)  # Retry
    return resp.json().get("data", [])
```

### Step 2: Download Attachments

For every message containing a file, download it to temporary storage (AWS S3 bucket or local volume). Pylon attachment URLs may require authentication — ensure your extraction script passes the correct bearer token when fetching files.

```python
import os

def download_attachment(url, dest_path):
    """Download a Pylon attachment with auth.
    Pre-scan: skip files > 10MB (eDesk limit).
    """
    # HEAD request to check size before downloading
    head = requests.head(url, headers=HEADERS)
    content_length = int(head.headers.get('content-length', 0))
    if content_length > 10 * 1024 * 1024:  # 10MB
        log_oversized_attachment(url, content_length)
        return None  # Skip or compress
    
    resp = requests.get(url, headers=HEADERS, stream=True)
    if resp.status_code == 200:
        os.makedirs(os.path.dirname(dest_path), exist_ok=True)
        with open(dest_path, 'wb') as f:
            for chunk in resp.iter_content(chunk_size=8192):
                f.write(chunk)
    return resp.status_code
```

### Step 3: Transform Data

```python
def transform_issue_to_edesk_ticket(pylon_issue, field_map, tag_map,
                                     user_map, account_lookup):
    """Transform a Pylon Issue into an eDesk Ticket payload."""
    status_map = {
        "new": "open",
        "waiting_on_you": "open",
        "waiting_on_customer": "pending",
        "on_hold": "pending",  # Verify in sandbox — eDesk may not have a hold state
        "closed": "resolved"
    }
    ticket = {
        "subject": pylon_issue.get("title") or "Imported Thread",
        "channel_id": EDESK_CHANNEL_ID,  # Pre-configured email channel
        "status": status_map.get(pylon_issue.get("status"), "open"),
        "owner_user_id": user_map.get(pylon_issue.get("assignee_id")),
        "created_at": pylon_issue.get("created_at"),
        "custom_fields": [
            {"id": field_map["pylon_source_id"],
             "value": pylon_issue["id"]}  # Cross-reference for delta syncs
        ],
        "tags_ids": [tag_map[t] for t in pylon_issue.get("tags", [])
                     if t in tag_map]
    }
    # Flatten Account name into custom field
    if pylon_issue.get("account_id"):
        account_name = account_lookup.get(pylon_issue["account_id"], "")
        ticket["custom_fields"].append({
            "id": field_map["account_name"],
            "value": account_name
        })
    # Map priority to custom field (eDesk has no native priority)
    if pylon_issue.get("priority"):
        ticket["custom_fields"].append({
            "id": field_map["priority"],
            "value": pylon_issue["priority"]
        })
    return ticket

def convert_slack_markdown_to_html(text):
    """Convert Slack-specific markdown to standard HTML.
    
    Handles: bold (*text*), italic (_text_), code (`text`),
    user mentions (<@U123456>), channel refs (<#C123456|channel>).
    """
    import re
    # Strip user mentions — replace with readable placeholder
    text = re.sub(r'<@(U[A-Z0-9]+)>', r'[user:\1]', text)
    # Strip channel references
    text = re.sub(r'<#(C[A-Z0-9]+)\|([^>]+)>', r'#\2', text)
    # Bold
    text = re.sub(r'\*([^*]+)\*', r'<strong>\1</strong>', text)
    # Italic
    text = re.sub(r'_([^_]+)_', r'<em>\1</em>', text)
    # Code
    text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
    # Line breaks
    text = text.replace('\n', '<br/>')
    return text
```

Key transformation tasks:

- **Convert Slack markdown to standard HTML** — eDesk's UI renders HTML. Use the conversion function above as a starting point.
- **Strip Slack-specific mention tags** (e.g., `<@U123456>`) — otherwise eDesk tickets will be littered with unreadable user IDs. Replace with `[user:U123456]` or resolve to display names via Slack's API.
- **Map Pylon User IDs to eDesk Agent IDs** using a pre-built lookup dictionary (match by email address).
- **Flatten Account-level data** into ticket custom fields.
- **Validate attachment sizes** against eDesk's 10MB limit before loading.
- **Handle missing email addresses** for Slack-only contacts (see Edge Cases section).

### Step 4: Load into eDesk

```python
EDESK_TOKEN = "your_edesk_bearer_token"
EDESK_URL = "https://api.edesk.com/v1"
EDESK_HEADERS = {
    "Authorization": f"Bearer {EDESK_TOKEN}",
    "Content-Type": "application/json"
}

def create_edesk_ticket(ticket_payload):
    """Create a ticket in eDesk with rate-limit handling."""
    resp = requests.post(f"{EDESK_URL}/tickets",
                         headers=EDESK_HEADERS, json=ticket_payload)
    if resp.status_code == 429:
        retry_after = int(resp.headers.get('Retry-After', 1))
        time.sleep(retry_after)
        return create_edesk_ticket(ticket_payload)
    if resp.status_code not in (200, 201):
        log_error(ticket_payload, resp.status_code, resp.text)
        return None
    return resp.json()

def create_edesk_message(ticket_id, message_payload):
    """Add a message to an existing eDesk ticket."""
    payload = {
        "ticket_id": ticket_id,
        "body": message_payload["body"],
        "type": "Note" if message_payload.get("is_internal") else "Message",
        "created_at": message_payload.get("created_at")
    }
    if not message_payload.get("is_internal"):
        payload["direction"] = ("Incoming" if message_payload.get("from_customer")
                                else "Outgoing")
    resp = requests.post(f"{EDESK_URL}/messages",
                         headers=EDESK_HEADERS, json=payload)
    if resp.status_code == 429:
        retry_after = int(resp.headers.get('Retry-After', 1))
        time.sleep(retry_after)
        return create_edesk_message(ticket_id, message_payload)
    return resp.json()
```

**Load order matters.** Create records in this sequence:

1. **Contacts / Customers** — match by email address. Use eDesk's customer search endpoint to check for existing records before creating duplicates.
2. **Tags** — pre-create all tags and tag groups. Store the name→ID mapping.
3. **Custom Fields** — create all custom field definitions in eDesk before importing tickets.
4. **Sales Orders** — if marketplace or order context is needed.
5. **Tickets** — with `created_at` preserved and `pylon_id` cross-reference stored.
6. **Messages and Notes** — replaying the thread chronologically (sort by `created_at` ascending).
7. **Attachments** — upload to each message after creation.

Store every source-to-target ID mapping in your staging database. You will need it for retries, attachment backfills, UAT fixes, and rollback.

### Step 5: Handle Errors Like an Integration

Build exponential backoff into your pipeline — do not rely on simple fixed-sleep timers:

```python
def api_call_with_retry(func, max_retries=5, description=""):
    """Execute an API call with exponential backoff and jitter."""
    import random
    for attempt in range(max_retries):
        try:
            result = func()
            if result.status_code == 429:
                wait = (2 ** attempt) + random.uniform(0, 1)
                log_retry(description, attempt, wait)
                time.sleep(wait)
                continue
            if result.status_code >= 500:
                # Server error — retry with backoff
                wait = (2 ** attempt) + random.uniform(0, 1)
                time.sleep(wait)
                continue
            return result
        except requests.exceptions.ConnectionError:
            time.sleep(2 ** attempt)
    raise Exception(f"Max retries exceeded for: {description}")
```

Send validation errors (eDesk codes like `4001`, `4002`, `4014 Mismatch between sales order and channel`, `4018`, `4019`) to a **dead-letter queue** rather than failing the entire batch. Log both platforms' IDs plus Pylon's `request_id` for support escalation. ([developers.edesk.com](https://developers.edesk.com/reference/error-codes?utm_source=openai))

Common eDesk error codes during migration:

| Error Code | Meaning | Resolution |
|---|---|---|
| `4001` | Validation error | Check required fields (subject, channel_id, status) |
| `4002` | Invalid field value | Verify custom field type compatibility |
| `4014` | Sales order/channel mismatch | Ensure channel_id matches the order's marketplace |
| `4018` | Duplicate record | Check if ticket already exists (use pylon_id lookup) |
| `4019` | Rate limit exceeded | Implement backoff; request limit increase |

### Step 6: Validate

Run validation queries against both systems (see Validation section below).

## Edge Cases & Challenges

### Duplicate Customer Records

Pylon Contacts can share email addresses across Accounts (e.g., a consultant working with multiple clients). eDesk deduplicates customers by email. This means two separate Pylon Contacts become one eDesk Customer — and their ticket histories merge. Decide in advance whether this is acceptable and document the expected contact count delta.

eDesk's merge capability has its own limits: merge is not available for Amazon/eBay tickets, tickets with orders attached, tickets with more than 10 messages, or tickets that were previously merged. ([support.edesk.com](https://support.edesk.com/n-a-16/what-is-a-ticket?utm_source=openai))

### Slack Thread Context and Formatting

Pylon Issues often contain rich Slack thread context — reactions, thread replies, `<@U123456>` user mentions, emoji reactions. None of this maps to eDesk's message model. Your transformation layer must:

- Strip or convert `<@U123456>` mention tags to readable text
- Convert Slack's `*bold*` and `_italic_` to HTML `<strong>` and `<em>`
- Remove Slack-specific emoji reactions (`:thumbsup:`) or convert to text equivalents
- Flatten threaded replies into chronological order (eDesk displays messages linearly)

### Missing Email Addresses

Pylon Contacts created purely via Slack might lack standard email addresses. eDesk requires an email address for Customer records. Options:

1. **Source from CRM:** Query your CRM (HubSpot, Salesforce) by Slack user ID or name to retrieve the associated email.
2. **Source from Slack API:** Use `users.lookupByEmail` or `users.info` to retrieve email addresses from Slack directly.
3. **Generate placeholder emails:** Create addresses like `slack-user-{slack_id}@yourdomain.com` — but flag these for manual cleanup post-migration.

### Custom Objects (Pylon Enterprise)

Pylon's Custom Objects (subscriptions, assets, implementation projects) have no equivalent in eDesk. Options ranked by data preservation:

1. **Flatten into ticket-level custom fields** — works for simple key-value data but loses relational structure.
2. **Store as structured JSON in internal notes** — preserves data but is not queryable in eDesk.
3. **Export to a separate system** (CRM, spreadsheet, data warehouse) — best for relational data you need to query.

### Attachments

eDesk limits ticket attachments to **10MB per file**. Pylon Issues with large file attachments (design files, logs, screenshots) may exceed this. Pre-scan attachments during extraction:

- Files under 10MB: upload directly
- Files 10–25MB: attempt compression (images, PDFs)
- Files over 25MB: host externally (S3, Google Drive) and link in the message body
- Document all excluded attachments for stakeholder review

### Single-Owner Model

eDesk tickets have one assigned owner at a time. Pylon's team assignment, followers, and shared Slack collaboration must collapse into one owner. Strategy:

- Map the Pylon `assignee_id` to the eDesk `owner_user_id`
- Store Pylon followers as a comma-separated list in a custom field or internal note
- If Pylon has a team assignment but no individual assignee, assign to a team lead or leave unassigned ([support.edesk.com](https://support.edesk.com/n-a-3/assigning-tickets-in-edesk?utm_source=openai))

### Knowledge Base

Pylon has a native Knowledge Base with articles and collections. eDesk has no built-in KB. Migration options:

| Target | Effort | Maintains structure |
|---|---|---|
| Zendesk Guide | Medium | ✅ Categories + articles |
| Notion | Low | ⚠️ Manual restructuring |
| Confluence | Medium | ✅ Spaces + pages |
| Document360 | Medium | ✅ Categories + articles |
| Static site (Docusaurus) | High | ✅ Full control |

### Branched Thread Display

eDesk displays messages linearly. Complex, branched Slack threads from Pylon will be flattened into a single chronological timeline. To preserve some context, prepend a `[Thread reply to: {parent_message_timestamp}]` prefix to messages that were originally thread replies.

## Limitations & Constraints

Be explicit with stakeholders about what eDesk cannot replicate from Pylon:

| Capability | Pylon | eDesk |
|---|---|---|
| Custom Objects | ✅ (Enterprise) | ❌ Not supported |
| Account hierarchy | ✅ Partner accounts, sub-accounts | ❌ Flat customer model |
| Knowledge Base | ✅ Native | ❌ No native KB |
| Slack-native support | ✅ Core architecture | ❌ Not supported |
| Feature request tracking | ✅ Native | ❌ Not supported |
| Native priority field | ✅ | ❌ Use tags or custom fields |
| AI account intelligence | ✅ Health scores, summaries | ❌ Not supported |
| Marketplace integrations | ❌ None | ✅ 250+ native |
| Order-linked tickets | ❌ No order model | ✅ Native sales order object |
| API rate limits | 10–60 req/min per endpoint | 60 req/min account-wide (increasable) |
| Bulk import API | ❌ None | ❌ None |

> [!WARNING]
> **Confirmed data loss scenarios.** The following Pylon data types will not survive the migration to eDesk in their original form. Document these in your migration plan and get stakeholder sign-off before proceeding:
>
> - Account-level metadata (health scores, CRM sync fields, partner/sub-account relationships)
> - Custom Objects (subscriptions, assets, implementation records)
> - KB articles and collections
> - Feature Requests
> - Survey/CSAT historical data
> - AI-generated account intelligence scores and summaries
> - Slack thread formatting, reactions, and branched conversation structure
> - Notebooks and highlights
> - Follower/watcher lists (collapsed to single owner)

## Validation & Testing

Do not skip User Acceptance Testing.

### Record Count Comparison

| Object | Pylon Count | eDesk Count | Expected Delta | Action |
|---|---|---|---|---|
| Issues → Tickets | X | Y | 0 (unless filtered) | Investigate any gap |
| Contacts → Customers | X | Y | Lower (dedup by email) | Document expected delta |
| Messages | X | Y | 0 (1:1 match) | Re-import missing |
| Tags | X | Y | 0 | Create missing |
| Custom Fields | X | Y | May differ (type mismatches) | Manual review |
| Attachments | X | Y | Lower (10MB limit exclusions) | Document excluded files |

### Field-Level Validation

Sample 50–100 tickets across different date ranges and statuses:

- Verify subject lines match original Pylon issue titles
- Verify message count per ticket matches Pylon issue message count
- Verify custom field values transferred correctly (especially Select/Enum fields)
- Verify tag assignments (compare by name)
- Verify assignee mapping (correct agent owns the ticket)
- Check timestamps — did `created_at` survive or get overwritten?
- Confirm attachments open correctly and are not corrupted
- Verify the original requester is correctly associated
- Check that Slack markdown was properly converted to HTML

### Automated Validation Script

```python
def validate_migration(staging_db, edesk_api):
    """Run automated validation checks post-migration."""
    errors = []
    
    # Check ticket count
    pylon_count = staging_db.query("SELECT COUNT(*) FROM pylon_issues")
    edesk_count = edesk_api.get_ticket_count()
    if pylon_count != edesk_count:
        errors.append(f"Ticket count mismatch: Pylon={pylon_count}, eDesk={edesk_count}")
    
    # Sample 100 tickets and verify field mapping
    sample = staging_db.query("SELECT * FROM pylon_issues ORDER BY RANDOM() LIMIT 100")
    for issue in sample:
        edesk_ticket = edesk_api.get_ticket_by_custom_field("pylon_id", issue["id"])
        if not edesk_ticket:
            errors.append(f"Missing ticket for Pylon issue {issue['id']}")
            continue
        if edesk_ticket["subject"] != (issue["title"] or "Imported Thread"):
            errors.append(f"Subject mismatch for {issue['id']}")
        # Verify message count
        pylon_msg_count = staging_db.query(
            "SELECT COUNT(*) FROM pylon_messages WHERE issue_id = ?", issue["id"])
        edesk_msg_count = len(edesk_api.get_messages(edesk_ticket["id"]))
        if pylon_msg_count != edesk_msg_count:
            errors.append(f"Message count mismatch for {issue['id']}: "
                         f"Pylon={pylon_msg_count}, eDesk={edesk_msg_count}")
    
    return errors
```

### UAT Process

1. Migrate a test batch of 100 tickets spanning different issue types and statuses
2. Have 2–3 agents review their assigned tickets in eDesk — confirm they can read message history
3. Confirm search and filter functionality works on migrated data (search by subject, customer email, tag)
4. Validate that eDesk rules and automations fire correctly on migrated tickets
5. Check Customer View — do merged contacts display correct history?
6. Verify that custom field filters work in eDesk's ticket list views

### Rollback Planning

- **Before migration:** Perform your initial migration into an eDesk test account or use the tag-and-delete approach
- **Tag migrated records:** Add a `pylon-migrated` tag to all imported tickets for easy identification and bulk operations
- **If mapping is flawed:** Delete all tagged tickets and adjust your transformation logic before retrying

**Rollback script:**

```python
def rollback_migration(edesk_api, migration_tag="pylon-migrated"):
    """Delete all tickets tagged with the migration marker."""
    page = 1
    deleted = 0
    while True:
        tickets = edesk_api.get_tickets(tag=migration_tag, page=page)
        if not tickets:
            break
        for ticket in tickets:
            resp = requests.delete(
                f"{EDESK_URL}/tickets/{ticket['id']}",
                headers=EDESK_HEADERS
            )
            if resp.status_code in (200, 204):
                deleted += 1
            else:
                log_error(f"Failed to delete ticket {ticket['id']}: {resp.text}")
            time.sleep(1)  # Respect rate limit
    return deleted
```

- **Post-rollback:** Verify ticket count is zero for the migration tag, then re-run the corrected pipeline

## Post-Migration Tasks

### Rebuild Automations

Pylon Triggers do not export to eDesk. Manually recreate:

- Auto-assignment rules and routing
- SLA policies (configure marketplace-specific SLAs: 24hr for Amazon, 48hr for eBay)
- Auto-response templates
- Escalation workflows
- Tag-based routing using eDesk's Message Rules engine
- HandsFree automation rules for common ticket categories

### Connect Marketplace Channels

Authenticate your Amazon, Shopify, eBay, or other marketplace channels directly in eDesk. Verify order and channel linkage — you can encounter `4014 Mismatch between sales order and channel` errors if these are not configured correctly. ([developers.edesk.com](https://developers.edesk.com/reference/error-codes?utm_source=openai))

### Configure Webhooks for Monitoring

Set up eDesk webhooks to monitor ticket events during the post-migration stabilization period:

```python
# Register a webhook for ticket updates
webhook_payload = {
    "url": "https://your-monitoring-endpoint.com/edesk-events",
    "events": ["ticket.created", "ticket.updated", "message.created"]
}
requests.post(f"{EDESK_URL}/webhooks", headers=EDESK_HEADERS, json=webhook_payload)
```

### Agent Training

eDesk's UI paradigm is fundamentally different from Pylon's. Key differences agents will notice:

- No Slack channel integration — all support happens in eDesk's mailbox interface
- Order data appears automatically for marketplace-linked tickets
- Tag Groups replace Pylon's flat tag model — train agents on the new taxonomy
- AI features (HandsFree, AI Composer) need configuration and testing
- Single-owner ticket assignment replaces Pylon's collaborative Slack model — establish escalation procedures
- Filters and views work differently — create saved views that replicate Pylon's workflow

### Monitoring

For the first 2 weeks post-migration:

- Monitor for duplicate customer records (check customer count daily)
- Check for tickets with missing messages (run the validation script daily)
- Watch for custom field values that did not map correctly
- Verify SLA timers are calculating correctly on historical tickets
- Monitor eDesk webhook events for unexpected ticket state changes
- Track agent-reported issues in a dedicated Slack channel or spreadsheet

## Best Practices

1. **Back up everything.** Run a full JSON export of your Pylon workspace to S3 or a local volume before running any import scripts. Verify the backup is complete and readable.
2. **Test incrementally.** Migrate 100 tickets, validate. Migrate 1,000, validate. Then run the full batch. Execute at least two full test runs against a test eDesk account before touching production.
3. **Request a rate limit increase.** Contact eDesk support before starting any migration above 5,000 issues. Explain the migration scenario and request a temporary increase from 60 req/min to 120–300 req/min.
4. **Freeze configurations.** Do not allow admins to add new custom fields, tags, or statuses in Pylon once the migration mapping is locked. Set a freeze date 48 hours before the first production run.
5. **Automate repetitive mapping.** Build lookup dictionaries for user IDs, tag IDs, and custom field IDs in your staging database. Do not map manually per record.
6. **Log everything.** Write every API request and response (success and failure) to structured logs (JSON format). Include timestamps, Pylon IDs, eDesk IDs, HTTP status codes, and response bodies. You will need this for debugging and post-migration audit.
7. **Communicate the cutover.** Agents should know exactly when Pylon goes read-only and eDesk becomes the primary system. Send a timeline 1 week before, 24 hours before, and at cutover. Keep Pylon accessible in read-only mode for 30 days post-migration.
8. **Handle PII carefully.** If you are subject to GDPR or CCPA, ensure your staging database and any intermediate files are encrypted at rest and deleted within 30 days of migration completion.

## Sample Data Mapping Table

| Pylon Field | Pylon Type | eDesk Field | eDesk Type | Notes |
|---|---|---|---|---|
| `issue.id` | String | Custom Field: `pylon_id` | Text | For cross-reference and delta syncs |
| `issue.title` | String | `ticket.subject` | String | Fallback to "Imported Thread" if null |
| `issue.status` | Enum | `ticket.status` | Enum | Map values (see field-level mapping above) |
| `issue.assignee_id` | String | `ticket.owner_user_id` | Integer | ID lookup required; match by email |
| `issue.requester_id` | String | `ticket.contact_id` | Integer | Match by email |
| `issue.created_at` | ISO 8601 | `ticket.created_at` | ISO 8601 | Verify preservation in test run |
| `issue.tags []` | Array<String> | `ticket.tags_ids []` | Array<Integer> | Name→ID lookup via pre-created tags |
| `issue.custom_fields []` | Mixed | `ticket.custom_fields []` | Mixed | Type-match required; verify enum values |
| `issue.account_id` | String | Custom Field: `company` | Text | Account name lookup from staging DB |
| `issue.priority` | Enum | Custom Field: `priority` | Select | No native priority in eDesk |
| `message.body` | Slack MD/Text | `message.body` | HTML | Strip Slack formatting, convert to HTML |
| `message.attachments []` | URLs | `message.attachments []` | URLs | 10MB limit on eDesk; pre-scan sizes |
| `message.is_internal` | Boolean | `message.type` | Enum | `true` → "Note", `false` → "Message" |
| `message.sender_type` | String | `message.direction` | Enum | Customer → "Incoming", Agent → "Outgoing" |

## What This Migration Actually Costs

A Pylon-to-eDesk migration is not just a data move — it is an architecture translation. You are shifting from a B2B, account-centric, Slack-native model to an eCommerce, order-centric, marketplace-native model. Some data will not survive this transition cleanly. The key is to be explicit about what transfers, what transforms, and what gets left behind.

**Cost summary by migration size:**

| Migration Size | DIY Engineering Hours | DIY Cost (at $100/hr) | Primary Risk |
|---|---|---|---|
| < 1,000 issues | 30–50 hrs | $3,000–$5,000 | Low — script + weekend |
| 1,000–5,000 issues | 50–80 hrs | $5,000–$8,000 | Medium — rate limits, edge cases |
| 5,000–10,000 issues | 80–120 hrs | $8,000–$12,000 | High — need rate limit increase |
| 10,000+ issues | 100–150+ hrs | $10,000–$15,000+ | High — orchestration tooling required |

These estimates include extraction scripting, transformation logic, rate-limit handling, staging infrastructure, two full test runs, validation, and post-migration debugging. They do not include agent training, automation rebuilding, or opportunity cost of diverted engineering capacity.

For teams with straightforward data (under 5,000 issues, few custom fields, no custom objects), a well-written script and a focused sprint can get this done. For anything more complex — custom objects requiring flattening, multi-year history, strict compliance requirements, or if your engineering bandwidth is committed elsewhere — a managed migration service typically reduces total cost and risk.

By treating this as a strict ETL project with a staging database, validation queries, and rollback scripts rather than a simple import, you protect your historical data, maintain attachment integrity, and ensure your support team has the context they need on day one.

## Frequently asked questions

### Can I migrate data from Pylon to eDesk directly?

No. There is no native import path between Pylon and eDesk. You need a custom API-based ETL pipeline, a third-party migration tool (if one supports Pylon as a source), or a managed migration service. Both platforms have REST APIs that can be used for extraction and loading, but no built-in migration bridge exists.

### What data is lost when migrating from Pylon to eDesk?

eDesk cannot replicate Pylon's Custom Objects, Knowledge Base articles, Feature Requests, Account hierarchy (partner/sub-accounts), Slack thread formatting, historical CSAT/survey data, notebooks, highlights, or account-level custom fields. These must be exported separately, flattened into ticket custom fields, or documented as accepted losses.

### How long does a Pylon to eDesk migration take?

A migration of 1,000 issues with an average of 5 messages each requires approximately 6,000 eDesk API calls. At eDesk's 60 requests per minute rate limit, that is about 100 minutes of continuous API traffic. In practice, expect 2–3x that due to retries, validation, and error handling. A full migration typically takes 5–10 business days including planning, test runs, and validation.

### What are the API rate limits for Pylon and eDesk?

Pylon enforces per-endpoint limits: Issues search at 10 requests per minute, issue messages at 20 per minute, and contacts/accounts at 60 per minute. eDesk allows 60 requests per minute account-wide, with a restoration rate of 2 requests per second after hitting the cap. Neither platform offers a bulk import API. eDesk API access requires the Enterprise plan.

### Does eDesk support custom objects or accounts like Pylon?

No. eDesk supports custom fields on tickets (Text, Date, Select, Number, Yes/No, URL, Email types) but has no custom object support and no company/organization object. Pylon's Custom Objects must be flattened into custom fields, stored as internal notes, or exported to a separate system. Account-level data must be handled similarly.
