Freshdesk to Pylon Migration: The CTO's Technical Guide (2026)
A CTO-level guide to migrating from Freshdesk to Pylon — covering API rate limits, object mapping, attachment handling, and zero-downtime cutover approaches.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
Migrating from Freshdesk to Pylon is not a settings import. You are moving from a traditional, email-first ticketing system to an AI-native, Slack-first B2B support platform. The data models are fundamentally different, the API rate limits are asymmetric, and there is no native import path between them.
This guide covers the exact object mapping, rate-limit math, extraction ceilings, attachment handling, and migration approaches you need before writing a single line of code. If you need a source-side deep dive first, see How to Export Data from Freshdesk: Methods, API Limits & Mapping.
Why B2B Teams Move from Freshdesk to Pylon
The shift is architectural, not cosmetic. Freshdesk is a mature, highly configurable helpdesk built around email-centric ticket queues with a flat contact-and-company structure. Pylon is purpose-built for B2B support where the primary conversation happens in shared Slack channels and Microsoft Teams — not email portals. (developers.freshdesk.com)
Teams migrate for three reasons:
- Channel alignment. B2B customers live in Slack Connect and Teams. Pylon integrates natively with these channels, plus Discord, WhatsApp, and in-app chat, so customers never leave their workspace. Freshdesk treats chat as a bolt-on to an email-first architecture.
- Account-centric support model. Pylon ties every Issue to an Account, giving agents full customer context — health scores, CRM data, renewal risk — directly in the support view. Freshdesk can achieve this with custom apps and marketplace add-ons, but the experience requires significant configuration.
- AI-native workflows. Pylon's AI handles triage, routing, knowledge gap detection, and draft replies out of the box. Freshdesk's Freddy AI capabilities are available as paid add-ons (Copilot, AI Agent sessions), with sessions that expire monthly.
If you are comparing Pylon to other migration targets, our Zendesk to Pylon Migration: The CTO's Technical Guide covers the Zendesk-specific data model differences.
Core Architecture Differences
| Dimension | Freshdesk | Pylon |
|---|---|---|
| Primary object | Ticket | Issue |
| Contact model | Contacts + Agents (separate APIs) | Contacts + Users (unified) |
| Company model | Companies | Accounts (with subaccounts, partner accounts) |
| Conversation model | Conversations (replies, notes) | Messages |
| Custom fields | On tickets, contacts, companies | On issues, contacts, accounts |
| Statuses | Open, Pending, Resolved, Closed (+ custom) | new, waiting_on_you, waiting_on_customer, on_hold, closed (+ custom) |
| Priority | Low, Medium, High, Urgent (integers 1–4) | low, medium, high, urgent (strings) |
| Attachments | content_url in ticket/conversation JSON (temporary S3 URLs) |
attachment_urls array on issues |
| Auth model | Basic Auth (API key) | Bearer token |
| Rate limits | 200–700 req/min account-wide (by plan) | 10–60 req/min (by endpoint) |
| Pagination | Page-based (100/page, 300-page ceiling) | Cursor-based |
This asymmetry is the source of most migration failures. You can extract data from Freshdesk roughly 3–10x faster than you can write it to Pylon, so any pipeline needs a buffer or write-side throttle.
Migration Approaches: CSV vs. API vs. Managed Service
There are five ways to move your data. Choosing the wrong one leads to broken threads, orphaned attachments, or weeks of wasted engineering time.
1. Native CSV Export → Manual Import
How it works: Export tickets, contacts, and companies from Freshdesk's admin UI as CSV files. Restructure the data, then use Pylon's API to bulk-import. Freshdesk also offers a full Account Export (Admin → Account → Export) that generates an XML dump including archived tickets. (support.freshdesk.com)
When to use it: Fewer than 1,000 tickets, minimal custom fields, no attachments to preserve.
Limitations: Freshdesk's CSV export does not include full conversation histories, attachments, or archived tickets. You get a flat snapshot — ticket metadata without thread context. Pylon has no CSV import UI, so you still need the API to load data.
Complexity: Low to extract. Medium to load.
2. API-Based Custom ETL Pipeline
How it works: Write a script (Python, Node.js) that extracts data via Freshdesk's v2 REST API, transforms it to match Pylon's schema, and loads it via Pylon's REST API. For enterprise programs or regulated environments, land the data into a staging database first for better observability, dead-letter handling, audit logs, and rerun safety. (developers.freshdesk.com)
When to use it: You have engineering bandwidth, need full control over transformation logic, and want repeatable delta syncs.
Pros:
- Full control over field mapping and transformation
- Can handle custom fields, attachments, and conversation threading
- Repeatable for delta syncs
- Staging-database approach gives the strongest audit trail and rollback posture
Cons:
- Freshdesk's List All Tickets API is capped at 30,000 tickets (300 pages × 100 records). Larger accounts require date-range segmentation or the Account Export feature.
- Pylon's issue creation endpoint is rate-limited to 10 requests per minute. A 10,000-ticket migration with multiple API calls per ticket takes significant write time.
- Attachments require separate HTTP GET requests to Freshdesk's temporary S3 URLs, then re-upload before referencing in Pylon.
Complexity: High.
3. Third-Party Automated Migration Tools
How it works: Tools like Help Desk Migration or Import2 offer pre-built connectors for Freshdesk. Import2's workflow runs sample-to-full: connect source and destination, run a sample migration, inspect results, edit mappings, then execute. Their docs note that system configuration (users, reports, templates, automations, workflows) is not migrated, and validation errors on a parent record can block linked records. (help.import2.com)
When to use it: Only if a connector for Pylon exists and your data structure is straightforward. As of mid-2026, dedicated Freshdesk-to-Pylon connectors are extremely limited.
Limitations: Standard migration tools often fail on knowledge base articles, complex custom fields, multi-level account hierarchies, inline images, conversation threading, and CSAT data. They typically do not offer delta syncs, forcing a hard weekend cutover.
For a comparison of automated tools vs. managed services, see ClonePartner vs Help Desk Migration: 2026 Price & Feature Comparison.
Complexity: Medium (when connectors exist).
4. Middleware Platforms (Zapier, Make)
How it works: Use prebuilt connectors to create or update Pylon objects from events. Zapier exposes Pylon actions such as Create Account and Create Account Activity. Make currently lists Pylon as a community connector that talks to official API endpoints. (zapier.com)
When to use it: Ongoing sync after the bulk backfill is complete. Not for replaying years of ticket history.
Limitations: Connector throttling, task costs, and community-adapter stability make these unviable for historical bulk loads. Migrating 100,000+ tickets through Zapier is financially and technically unworkable.
Complexity: Low for simple syncs. Unviable for bulk migration.
5. Managed Migration Service
How it works: A specialized team handles the full ETL pipeline — extraction, transformation, loading, validation, and delta sync — on your behalf.
When to use it: More than 10,000 tickets, complex custom fields, attachments that must be preserved, or zero tolerance for downtime.
Pros:
- No engineering distraction from product work
- Handles rate-limit orchestration, attachment re-hosting, and relationship integrity
- Supports parallel operation and delta sync for zero-downtime cutover
Complexity: Low (for your team). Scalability: High.
Approach Comparison
| Criteria | CSV Export | Custom ETL | Auto Tool | Middleware | Managed Service |
|---|---|---|---|---|---|
| Engineering effort | Low | High | Low-Medium | Low | None |
| Conversation history | ❌ | ✅ | Partial | ❌ | ✅ |
| Attachments | ❌ | ✅ (complex) | Partial | ❌ | ✅ |
| Custom fields | Partial | ✅ | Partial | Partial | ✅ |
| Scale (50k+ tickets) | ❌ | Risky | Depends | ❌ | ✅ |
| Delta sync | ❌ | DIY | ❌ | Partial | ✅ |
| Zero downtime | ❌ | DIY | ❌ | ❌ | ✅ |
Scenario recommendations:
- Small business, <1,000 tickets, low engineering bandwidth: Use a self-service tool if official Freshdesk-to-Pylon support exists. Otherwise, go straight to a managed service.
- Enterprise, attachments, deep history, or account hierarchy: API-based ETL or managed service. CSV and iPaaS are false economies here.
- Bulk migration + ongoing sync: Run the bulk move via API or managed service, then keep deltas alive with a small worker or iPaaS flow.
- Zero-downtime requirement: Stage historical imports, validate, then cut channels over after delta sync reaches zero.
Pre-Migration Planning & Data Audit
Before touching any API, audit your Freshdesk instance. Freshdesk archives closed inactive tickets after 120 days, and the standard UI ticket export excludes archived history and full conversations. Decide up front what is in scope. (support.freshdesk.com)
Data inventory checklist:
- Total ticket count (check if >30,000 — you will hit the pagination ceiling)
- Total contacts and companies
- Custom ticket fields (names, types, picklist values)
- Custom contact/company fields
- Ticket tags and categories
- Conversation count per ticket (affects API call volume)
- Attachment volume (count and total size in GB)
- Knowledge base articles and folder structure
- Automations, SLAs, and business hours (must be rebuilt manually)
- Canned responses / saved replies
What to leave behind:
- Spam and deleted tickets
- Duplicate contacts (merge before migration)
- Test tickets from implementation
- Time-tracking entries (Pylon has no equivalent)
- Historical SLA metrics (not transferable as native SLA objects)
- Leads and Opportunities from Freshsales or your CRM — these are a separate migration; sync only the context Pylon needs through its CRM layer
Migration strategy:
- Big bang: Migrate everything in one pass over a weekend. Works for fewer than 10,000 tickets.
- Phased by channel: Migrate email tickets first, then move Slack/Teams channels to Pylon. Lets you validate before going all-in.
- Parallel operation with delta sync: Run both systems simultaneously. New tickets go to Pylon; historical data migrates in the background. A final delta pass catches anything created during migration. This is the zero-downtime approach and what we recommend for most B2B teams.
Disable all Freshdesk automations and notification rules before starting any data export. Bulk API reads can trigger webhooks, which consume your rate limit and can send unintended emails to customers.
Data Model & Object Mapping: Tickets to Issues
This is where migrations break. Freshdesk and Pylon use fundamentally different schemas, and raw field copy is the wrong strategy. Map the semantic next-action state, not just labels.
Object-Level Mapping
| Freshdesk Object | Pylon Object | Notes |
|---|---|---|
| Company | Account | Pylon supports subaccounts and partner accounts. Preserve source company ID as external_id. |
| Contact | Contact | Link to Accounts via account_id. Match by email. |
| Agent | User | Pylon uses roles (Admin, Member, etc.). Manual rebuild. |
| Ticket | Issue | Core mapping — see field table below. |
| Conversation (reply/note) | Message | Each reply becomes a Message on the Issue. Preserve internal vs. public visibility strictly. |
| Ticket attachment | Attachment URL | Must be re-hosted; Freshdesk S3 URLs are temporary. |
| Knowledge Base article | Knowledge Base article | Separate API; requires collection structure. Migrate as a separate workstream. |
| Tags | Tags | Direct mapping; create tags in Pylon first. Normalize casing before load. |
| Custom fields | Custom fields | Must pre-create in Pylon with matching slugs. |
| Groups | Teams | Manual rebuild. |
| Canned responses | Macros | Manual rebuild. |
| SLAs | SLAs | Manual rebuild. |
| Automations | Triggers | Manual rebuild. |
| Parent/child companies | Subaccounts | Use when roll-up reporting matters. |
| Agencies / consultants | Partner accounts | Avoid duplicating cross-customer contacts. |
Field-Level Mapping: Tickets → Issues
| Freshdesk Field | Pylon Field | Transformation |
|---|---|---|
id |
external_id |
Store for cross-reference and idempotent reruns |
subject |
title |
Direct |
description / description_text |
body_html |
Freshdesk provides HTML; Pylon accepts HTML. Parse for inline images. |
status (2=Open, 3=Pending, 4=Resolved, 5=Closed) |
state (new, waiting_on_you, waiting_on_customer, on_hold, closed) |
Integer → string mapping (see below) |
priority (1=Low, 2=Med, 3=High, 4=Urgent) |
priority (low, medium, high, urgent) |
Integer → string |
requester_id |
contact_id |
Lookup by email from pre-migrated contacts |
responder_id |
assignee_id |
Lookup by email from pre-created users |
company_id |
account_id |
Lookup from pre-migrated accounts |
tags |
tags |
Array → array |
created_at |
created_at |
RFC3339 format |
custom_fields.cf_* |
custom_fields [{slug, value}] |
Map cf_ prefix fields to Pylon slugs |
attachments [].attachment_url |
attachment_urls |
Download from Freshdesk S3, re-host, pass URLs |
Pylon's issue creation API accepts a created_at timestamp (RFC3339), which lets you preserve original ticket creation dates. If omitted, the current time is used — destroying your historical timeline.
Status Mapping
Freshdesk uses integer status codes. Pylon uses string states:
| Freshdesk Status | Code | Pylon State |
|---|---|---|
| Open | 2 | new |
| Pending | 3 | waiting_on_customer |
| Resolved | 4 | closed |
| Closed | 5 | closed |
| Custom statuses | 6+ | Map to Pylon custom statuses (create before import) |
Pylon's issue workflow supports five core status categories. Custom statuses are supported but must be created before import. Closed issues can reopen on new activity — keep that in mind when mapping Resolved vs. Closed.
Freshdesk allows unlimited custom statuses (codes 6+). Pylon supports custom statuses within its status categories. That is usually enough for support operations, but it is not a 1:1 substitute for every bespoke Freshdesk taxonomy.
Custom objects and schema constraints: Pylon has a streamlined schema. If you rely heavily on Freshdesk Custom Objects or integrations that simulate custom objects (e.g., asset tracking), those patterns don't transfer directly. Flatten this data into standard Issue attributes or Account-level fields. Push CRM data back to the CRM rather than forcing it into the support platform.
Multi-company contacts: Freshdesk can associate contacts with multiple companies. Pylon's contact creation centers on one account reference at a time. Decide early whether to collapse those relationships, duplicate contacts selectively, or model the intermediary as a partner account. (support.freshdesk.com)
Custom Field Type Mapping
Freshdesk contact and company fields expose types like custom_text, custom_paragraph, custom_checkbox, custom_number, custom_dropdown, custom_phone_number, custom_url, and custom_date. Pylon custom fields support Text, Number, Decimal, Select, Multi-select, Boolean, Date, User, URL, Datetime, and Formula. Build from the overlap, and normalize picklist values before load so Select fields do not fail on unmapped values. (developers.freshdesk.com)
Freshdesk API Constraints & Attachment Handling
This is where DIY scripts fail. If your custom script breaks here, see Helpdesk Migration Failed? The Engineer's Rescue Guide.
Rate Limits by Plan
Freshdesk enforces account-wide rate limits plus endpoint-specific sublimits. The account-wide limit is shared with every other integration, app, and webhook running on that Freshdesk instance. Even failed requests (400, 404) count against the limit.
| Freshdesk Plan | Account-Wide Rate Limit | Ticket List Sublimit |
|---|---|---|
| Trial | 50/min | — |
| Growth | 200/min | ~20 list calls/min |
| Pro | 400/min | ~40 list calls/min |
| Enterprise | 700/min | ~70 list calls/min |
Monitor usage via response headers: X-Ratelimit-Total, X-Ratelimit-Remaining, X-Ratelimit-Used-CurrentRequest. A 429 Too Many Requests response includes a Retry-After header — queue your calls and obey it. (developers.freshdesk.com)
The 30,000-Ticket Ceiling
Freshdesk's List All Tickets endpoint (/api/v2/tickets) is capped at 300 pages × 100 records = 30,000 tickets maximum. The endpoint also returns only the last 30 days by default. If your account has more tickets, you must:
- Segment by date range using the
updated_sinceparameter to pull batches under the ceiling. - Use Freshdesk's Account Export (Admin → Account → Export), which generates a full XML dump of all data including archived tickets.
- Use the Search API (
/api/v2/search/tickets) for targeted extraction only — it is capped at 10 pages (300 results per query), uses URL-encoded query syntax with case-sensitive field names, and is unsuitable for bulk export.
Attachment Extraction
Freshdesk attachments are not embedded in the ticket JSON. The API response includes an attachments array where each file has a content_url pointing to a temporary S3 URL. To migrate attachments:
- Fetch the ticket with
include=conversationsto get conversation attachments. Note: this returns only up to 10 conversations per ticket and consumes extra API cost. For tickets with more replies, use the dedicated/api/v2/tickets/{id}/conversationsendpoint with pagination. - Download each file from the
content_urlvia authenticated GET request. - Re-host the file (S3 bucket, GCS, or any publicly accessible URL).
- Pass the hosted URL into Pylon's
attachment_urlsarray when creating the issue.
Freshdesk's S3 URLs are temporary. If you extract ticket metadata today but download attachments next week, the URLs may have expired. Treat every attachment as a separate download-and-reupload problem, and do it during extraction, not later.
Inline images will break. Freshdesk inline images in HTML ticket descriptions use Freshdesk-hosted URLs (*.freshdesk.com/...). After you cancel Freshdesk, those URLs die. Every inline image in every ticket description must be parsed, downloaded, re-hosted, and URL-rewritten before loading into Pylon.
Pylon-Side Rate Limits
Pylon enforces per-endpoint rate limits that are significantly lower than Freshdesk's extraction limits. Your pipeline needs a write-side throttle. (docs.usepylon.com)
| Pylon Endpoint | Rate Limit |
|---|---|
| Account/Contact create & update | 60 req/min |
| Issue creation | 10 req/min |
| Issue search | 20 req/min |
| Attachment upload | 10 req/min |
At 10 issues per minute, a 10,000-ticket migration takes approximately 1,000 minutes (~16.7 hours) of write time for issue creation alone — before adding messages, attachments, and custom field updates. Plan accordingly.
Only Admin users can create Pylon API tokens. And one safety feature matters during migration: issue creation supports destination_metadata with an "internal" destination, so imported history does not trigger notifications to customers.
Step-by-Step API Migration Process
The migration follows a strict dependency order. Violating it breaks relationships.
Phase 1: Extract from Freshdesk
Extraction order:
1. Companies → GET /api/v2/companies?per_page=100
2. Contacts → GET /api/v2/contacts?per_page=100
3. Agents → GET /api/v2/agents?per_page=100
4. Tickets → GET /api/v2/tickets?per_page=100&updated_since=...&include=description
5. Conversations → GET /api/v2/tickets/{id}/conversations
6. Attachments → GET each content_url
7. KB Articles → GET /api/v2/solutions/categories → folders → articles
Store everything locally (JSON files or staging database) before attempting any writes. This decouples extraction from loading and lets you retry the load phase without re-extracting.
curl -u "$FD_API_KEY:X" \
"https://yourdomain.freshdesk.com/api/v2/tickets?updated_since=2026-01-01T00:00:00Z&include=description&page=1"Phase 2: Transform
- Map Freshdesk company IDs → Pylon account names/domains
- Map Freshdesk contact emails → Pylon contact objects
- Map Freshdesk agent emails → Pylon user IDs
- Convert status integers to Pylon state strings
- Convert priority integers to Pylon priority strings
- Map
cf_*custom fields to Pylon custom field slugs - Parse HTML descriptions for inline images; download, re-host, and rewrite URLs
- Upload attachments to your hosting; generate public URLs
- Build ID lookup tables (Freshdesk ID → staged ID) for accounts, contacts, and users
Phase 3: Load into Pylon
Load order (strict):
1. Create Accounts → POST /accounts
2. Create Contacts → POST /contacts (with account_id)
3. Create Issues → POST /issues (with account_id, contact_id, assignee_id)
4. Add Messages → POST /issues/{id}/messages
5. Create KB Collections → POST /knowledge-bases/{id}/collections
6. Create KB Articles → POST /knowledge-bases/{id}/articles
When creating issues via API, set destination_metadata.destination to "internal" to prevent Pylon from sending email or Slack notifications to the customer for every historical ticket you import.
Example: Creating an Issue in Pylon
import requests
import time
PYLON_BASE = "https://api.usepylon.com"
PYLON_TOKEN = "your_bearer_token"
def create_pylon_issue(ticket, account_map, contact_map, user_map):
payload = {
"title": ticket["subject"],
"body_html": ticket["description"],
"account_id": account_map.get(ticket["company_id"]),
"contact_id": contact_map.get(ticket["requester_id"]),
"assignee_id": user_map.get(ticket["responder_id"]),
"priority": {1: "low", 2: "medium", 3: "high", 4: "urgent"}.get(
ticket["priority"], "medium"
),
"state": {2: "new", 3: "waiting_on_customer", 4: "closed", 5: "closed"}.get(
ticket["status"], "new"
),
"created_at": ticket["created_at"],
"tags": ticket.get("tags", []),
"attachment_urls": ticket.get("rehosted_attachment_urls", []),
"destination_metadata": {"destination": "internal"},
"custom_fields": [
{"slug": slug, "value": val}
for slug, val in transform_custom_fields(ticket).items()
],
}
resp = requests.post(
f"{PYLON_BASE}/issues",
json=payload,
headers={"Authorization": f"Bearer {PYLON_TOKEN}"},
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
time.sleep(retry_after)
return create_pylon_issue(ticket, account_map, contact_map, user_map)
resp.raise_for_status()
return resp.json()["data"]["id"]Error Handling & Logging
Every API call should log:
- Freshdesk source ID
- Pylon target ID (on success)
- HTTP status code
- Error body and Pylon
request_id(on failure) - Timestamp
Build an ID mapping table (Freshdesk ID → Pylon ID) and persist it. You need it for rebuilding relationships, debugging, running validation, and future integrations. Pylon explicitly supports external identifiers on accounts and contacts, which makes deterministic upserts easier on reruns. (docs.usepylon.com)
Make loaders idempotent using external IDs and mapping tables. Send failed records to a dead-letter queue, not back into the hot path.
Edge Cases & What Breaks
Duplicate Contacts
Freshdesk allows multiple contacts with the same email across different companies. Pylon's /import/contacts endpoint supports this — the same email can exist across different accounts — but standard POST /contacts may enforce uniqueness. Deduplicate before loading, or use the import endpoint.
Missing Relationships
If a ticket's requester_id references a contact that was not migrated (deleted user, spam contact), the issue creation will fail or create an orphaned record. Pre-validate all foreign keys before loading.
Inline Images
Freshdesk users often paste images directly into replies. These are hosted on Freshdesk's CDN. If you do not download and re-host these images during migration, they will render as broken links in Pylon once the Freshdesk account is closed.
Time Tracking and CSAT
Freshdesk time entries have no Pylon counterpart — archive them to a data warehouse. Historical CSAT scores can be stored as custom field values but will not feed into Pylon's native CSAT analytics.
Historical SLA Metrics
Pylon calculates SLAs based on its own internal logic. Historical SLA compliance metrics from Freshdesk cannot be imported as native SLA objects. Store them as custom text fields or export to a data warehouse.
Conversation Replay
Before committing to a DIY build, confirm the exact historical message replay behavior you need. Pylon's API supports creating messages on issues, but verify whether reply ordering, internal note attribution, and full thread reconstruction match your requirements.
Validation & Testing
Do not trust the migration until you have validated it.
Record count comparison:
- Compare total accounts, contacts, and issues between Freshdesk and Pylon
- Account for intentionally excluded records (spam, duplicates, test data)
Field-level validation (golden sample):
- Pull 50–100 tickets at random from Freshdesk
- Compare every field against the corresponding Pylon issue
- Verify: title, body HTML, status, priority, requester, assignee, account, tags, custom fields,
created_at, attachment count
Relationship integrity:
- Every issue should link to a valid account and contact
- Every contact should link to a valid account
- No orphaned issues (requester = null)
Conversation threading:
- Verify that reply order is preserved
- Verify internal notes vs. customer-facing messages are correctly attributed
Attachment QA:
- Open files from 20–50 sampled issues across file types
- Check for broken inline images in HTML descriptions
Rollback plan:
- If you have not cut over DNS or email routing, rollback is simple: keep using Freshdesk
- Maintain the staging database until UAT is signed off — if the Pylon import fails, you can wipe and restart without re-querying Freshdesk
- Pylon supports bulk delete via API for cleanup
Best Practices
- Back up everything. Request a full Account Export from Freshdesk before touching anything.
- Run a test migration first. Migrate 100 tickets to a Pylon sandbox environment. Validate every field.
- Migrate in dependency order. Accounts → Contacts → Issues → Messages. Never reverse this.
- Use
destination: internalon all issue creation calls to prevent customer notifications. - Preserve
created_attimestamps. Without this, your historical reporting in Pylon is useless. - Build an ID mapping table. Freshdesk ID → Pylon ID for every object. You need it for debugging, validation, and any future integrations.
- Disable Freshdesk automations before export to prevent webhooks from consuming your rate limit.
- Schedule bulk extraction during off-peak hours to avoid competing with live agent API usage.
- Pre-create all custom fields, tags, and teams in Pylon before loading any records. Select fields will reject unmapped values.
Post-Migration Tasks
Data migration is half the job. The other half is rebuilding operational configuration that no migration tool transfers.
Rebuild in Pylon (manual):
- SLA policies and support hours
- Triggers and workflow automations (Freshdesk Dispatch'r / Observer rules → Pylon triggers)
- Macros (Freshdesk canned responses → Pylon macros)
- CSAT surveys
- Views and assignment rules
- Team structure and roles
- Email routing and forwarding
- Chat widget configuration
- Integrations (Salesforce, HubSpot, Linear, Jira)
Monitoring (first 30 days):
- Watch for orphaned issues (no account or contact)
- Monitor Pylon's analytics for data gaps
- Check knowledge base articles for broken links and missing images
- Verify CSAT surveys are firing on new issues
- Keep Freshdesk in read-only mode for at least 7 days while monitoring Pylon for inconsistencies
When to Use a Managed Migration Service
Build in-house when:
- Fewer than 5,000 tickets
- No attachments or inline images to preserve
- Simple custom field structure
- An engineer with a week to dedicate
Use a managed service when:
- More than 10,000 tickets with attachments
- Complex custom field and status mapping
- You need zero downtime (parallel operation + delta sync)
- Your engineering team cannot afford the distraction
- Compliance requirements (HIPAA, SOC 2) demand auditable data handling
- Multi-level account hierarchies, agency structures, or partner accounts
Building an in-house ETL pipeline for a one-time migration is rarely the best use of engineering resources. The Freshdesk API constraints covered in this guide — 429 throttling, the 30,000-ticket ceiling, temporary S3 attachment URLs, inline image rewriting — require dedicated backend expertise for a task your team will never repeat.
ClonePartner handles the rate-limit orchestration, attachment re-hosting, relationship integrity, and delta sync so your engineering team stays focused on product work. We run your Freshdesk and Pylon instances in parallel with continuous delta syncs, enabling a zero-downtime cutover. Most Freshdesk-to-Pylon migrations complete in days, not weeks.
Frequently Asked Questions
- Can I migrate Freshdesk tickets to Pylon using CSV export?
- Only partially. Freshdesk CSV exports include ticket metadata but not full conversation histories, attachments, or archived tickets. Pylon has no CSV import UI, so you still need the API to load data. For anything beyond a basic snapshot of fewer than 1,000 tickets, API-based migration is required.
- What are the Freshdesk API rate limits for data migration?
- Freshdesk enforces account-wide rate limits by plan: Growth gets 200 calls/min, Pro gets 400/min, and Enterprise gets 700/min. Individual endpoints have sublimits — ticket listing is roughly 20 calls/min on Growth. The limit is shared with all integrations, and even failed requests count. The List All Tickets endpoint is capped at 300 pages (30,000 tickets max).
- What is Pylon's API rate limit for importing data?
- Pylon enforces per-endpoint rate limits: 60 req/min for account and contact operations, 10 req/min for issue creation, 20 req/min for issue search, and 10 req/min for attachment uploads. This is significantly lower than Freshdesk's extraction limits, so your pipeline needs a write-side throttle.
- How do I migrate Freshdesk ticket attachments to Pylon?
- Freshdesk provides temporary S3 URLs for attachments in the API response. Download each file via authenticated GET requests during extraction (URLs expire), re-host them on your own storage (S3, GCS), then pass the new public URLs into Pylon's attachment_urls array when creating issues. Inline images in HTML descriptions also need to be parsed, downloaded, re-hosted, and URL-rewritten.
- How do I prevent customer notifications during a Freshdesk to Pylon import?
- Create historical Pylon issues with destination_metadata.destination set to 'internal'. This prevents Pylon from sending email or Slack notifications to customers for every imported ticket. Patch workflow data like status and tags after validation.