Skip to content

The Complete Guide to Migrating from Freshdesk to Help Scout

Freshdesk to Help Scout migration made simple. Learn how to move tickets, customers, companies, KB content, tags, and rebuild automations correctly.

Tejas Mondeeri Tejas Mondeeri · · 11 min read
The Complete Guide to Migrating from Freshdesk to Help Scout
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

Moving your customer support data from Freshdesk to Help Scout is a significant step toward streamlining your support operations. While both platforms manage customer interactions, they structure data differently.

This guide breaks down exactly how to map your Freshdesk data to Help Scout's structure using the API, covering field mapping, rate limits, known data loss, and the dependency order your scripts must follow.

What You Will Lose Permanently

Before you write a single line of Freshdesk to Help Scout migration code, understand what cannot be recovered afterwards. These gaps are structural — they are not solvable with workarounds.

  • Satisfaction Ratings in analytics: Help Scout has no API endpoint to create historical rating records. You can preserve the fact that a rating existed (as a Tag or private Note), but it will never appear in Help Scout's native reporting graphs.
  • Automation and SLA history: The rules themselves must be manually rebuilt. The audit trail of which automation fired on which ticket does not transfer.
  • Merged ticket lineage: If a Freshdesk ticket was merged into another, the merged child ticket's history may appear as a standalone conversation in Help Scout with no pointer to its parent. Naive migration scripts break on this edge case.
  • Products as a native object: Help Scout has no equivalent. You must decide whether to flatten Product names into a Custom Field dropdown or split them into separate Docs Sites. Either way, the native Product reporting from Freshdesk is gone.
  • Ticket status granularity: Freshdesk statuses (Open, Pending, Resolved, Closed, plus custom statuses) do not map cleanly to Help Scout's conversation states (Active, Pending, Closed). Custom statuses are lost entirely.
  • Agent group–level permissions: Freshdesk supports group-based ticket visibility restrictions. Help Scout's Mailbox-level permissions model is different. Permission boundaries will need to be manually re-evaluated post-migration.

Pre-Migration Checklist

Complete these steps before running any API calls.

  1. Export a full ticket count from Freshdesk (Admin → Reports or via GET /api/v2/tickets?per_page=1) and record the number. You will use this to validate completeness after migration.
  2. Document every custom Ticket Field name, type (text, dropdown, checkbox, date), and its allowed values.
  3. Screenshot or export all Automation rules and SLA Policies. These must be manually rebuilt in Help Scout.
  4. List every Mailbox (Email Config) in Freshdesk and decide which Help Scout Inbox each maps to. Record this mapping in a spreadsheet before creating anything.
  5. Identify all Products in use and decide the handling strategy: Custom Field dropdown or separate Docs Site.
  6. Confirm API credentials for both platforms (see Authentication section below).
  7. Run a sample export of 50–100 tickets and inspect them for merged tickets, unusual statuses, or missing requester IDs. These are the records most likely to fail.

Authentication

Freshdesk: Uses HTTP Basic Auth. Pass your API key as the username with any string as the password, or use Authorization: Basic base64(apikey:X).

curl -u your_api_key:X \
  https://yourdomain.freshdesk.com/api/v2/tickets

Help Scout: Uses OAuth 2.0 client credentials flow. Exchange your App ID and App Secret for a bearer token at POST https://api.helpscout.net/v2/oauth2/token, then pass Authorization: Bearer <token> on all subsequent requests. Tokens expire after 2 hours and must be refreshed.

Define Your Migration Scope

Not everything can travel through the API automatically. Categorize your data before starting.

  • Migrate via API: Agents, Groups, Companies, Contacts, Tickets, Ticket Conversations, Solutions, Canned Responses, and Tags.
  • Configure Manually: Mailboxes (Inboxes) and Ticket Fields (Custom Fields) have no POST create endpoint in the Help Scout API and must be built by hand before import begins. Automations and SLA Policies must also be rebuilt manually.
  • Workarounds and Archives: Freshdesk Products and Satisfaction Ratings require workarounds described in the Insider Secrets section below.

API Rate Limits and Pagination

This is where migrations fail at scale. Do not skip this section.

Freshdesk rate limits: Vary by plan, but most plans allow around 1,000 API calls per hour per account. The response headers include X-RateLimit-Remaining and X-RateLimit-Reset. When X-RateLimit-Remaining reaches zero, pause until the reset timestamp and retry.

Help Scout rate limits: Help Scout enforces a limit of 200 requests per minute on its v2 API. Exceeding this returns a 429 Too Many Requests response. Implement exponential backoff: wait 1 second after the first 429, 2 seconds after the second, 4 after the third, and so on.

Freshdesk pagination: Ticket exports are paginated at 30 records per page by default, with a maximum of 100 per page (?per_page=100&page=1). The API returns an empty array when you have exhausted all pages — there is no total_count field in the response body on most endpoints, so you must loop until you receive an empty result.

# Example: paginate all Freshdesk tickets
page = 1
all_tickets = []
while True:
    response = requests.get(
        f"https://yourdomain.freshdesk.com/api/v2/tickets",
        auth=(API_KEY, "X"),
        params={"per_page": 100, "page": page}
    )
    batch = response.json()
    if not batch:
        break
    all_tickets.extend(batch)
    page += 1

Data volume thresholds: For accounts under ~5,000 tickets, a single-threaded script with rate limit handling is practical. Above 10,000 tickets, plan for parallel workers per Mailbox and expect multiple days of continuous runtime at Freshdesk's rate limits. Above 50,000 tickets, bulk export via CSV for read-side and API only for write-side is more reliable.

Prepare Help Scout for Data Import

You cannot dump data into an empty Help Scout account. Construct the framework first.

  • Set Up Inboxes: Freshdesk organizes tickets into Mailboxes or Email Configs. Help Scout calls these Inboxes. The Help Scout API exposes GET /v2/mailboxes to list Inboxes, but POST /v2/mailboxes does not exist — Inboxes can only be created through the Help Scout UI. Log in, create one Inbox per Freshdesk Mailbox, then record each Inbox ID from the API response (GET /v2/mailboxes) for your mapping table.
  • Recreate Custom Fields: Freshdesk Ticket Fields hold vital data. Help Scout supports Custom Fields, but schema creation is not available via the API. Manually recreate every text box, dropdown, and checkbox in Help Scout settings. Once created, retrieve the generated Field IDs via GET /v2/custom-fields/conversations and record them. You will need these IDs to populate field values during ticket import.

Field Mapping Reference

The table below maps Freshdesk field names to their Help Scout equivalents for Tickets and Conversations — the most commonly searched artifact for this migration.

Freshdesk Field Freshdesk Type Help Scout Equivalent Help Scout Type Notes
subject string subject string Direct map
description string (HTML) First thread body string (text/HTML) Becomes the first Thread on the Conversation
status integer (2=Open, 3=Pending, 4=Resolved, 5=Closed) status string (active, pending, closed) Custom statuses are lost; map 4+5 → closed
priority integer (1–4) No native field Custom Field Migrate as a Custom Field dropdown
requester_id integer customer.id string Must resolve to a Help Scout Customer ID
responder_id integer assignTo.id string Must resolve to a Help Scout User ID
group_id integer assignTo.teamId string Must resolve to a Help Scout Team ID
tags array of strings tags array of strings Direct map
created_at ISO 8601 createdAt ISO 8601 Direct map
due_by ISO 8601 No native field Custom Field Migrate as a date Custom Field if needed
product_id integer No native object Custom Field or Docs Site See Insider Secrets section
fr_due_by ISO 8601 No native field Dropped First response SLA deadline — not recoverable
custom_fields.* varies fields [].id + fields [].value varies Must map each field ID manually using your recorded IDs

Create a Conversation: API Example

The following example shows the core API call — reading a ticket from Freshdesk and writing it as a Conversation to Help Scout.

import requests
 
FD_DOMAIN = "yourdomain.freshdesk.com"
FD_KEY = "your_freshdesk_api_key"
HS_TOKEN = "your_helpscout_bearer_token"
 
# Step 1: Read a Freshdesk ticket
fd_ticket = requests.get(
    f"https://{FD_DOMAIN}/api/v2/tickets/12345",
    auth=(FD_KEY, "X")
).json()
 
# Step 2: Build the Help Scout Conversation payload
# hs_customer_id and hs_mailbox_id come from your mapping tables
hs_payload = {
    "subject": fd_ticket["subject"],
    "mailboxId": hs_mailbox_id,  # from your Mailbox→Inbox mapping table
    "customer": {"id": hs_customer_id},  # from your Contact→Customer mapping table
    "assignTo": hs_user_id,  # from your Agent→User mapping table
    "status": "closed" if fd_ticket["status"] in [4, 5] else "active",
    "createdAt": fd_ticket["created_at"],
    "tags": fd_ticket.get("tags", []),
    "fields": [
        # custom fields — map each Freshdesk key to Help Scout field ID
        {"id": hs_field_id_map["priority"], "value": str(fd_ticket["priority"])}
    ]
}
 
# Step 3: POST to Help Scout
response = requests.post(
    "https://api.helpscout.net/v2/conversations",
    headers={"Authorization": f"Bearer {HS_TOKEN}"},
    json=hs_payload
)
# Help Scout returns 201 Created with a Location header containing the new Conversation ID
new_conversation_id = response.headers.get("Location").split("/")[-1]

Error Handling and Deduplication

Two failure modes will hit you repeatedly at scale.

Contact already exists: Help Scout deduplicates Customers by email address. If you attempt to create a Customer with an email that already exists, Help Scout returns the existing Customer record rather than an error. Always check the response body for the returned Customer ID and use it — do not assume creation succeeded by HTTP status alone.

Conversation already created (re-run safety): Your script will crash and restart. To avoid duplicate Conversations, store a local mapping of freshdesk_ticket_id → helpscout_conversation_id in a database or CSV after each successful creation. On restart, skip any ticket ID that already has an entry.

Missing requester: Some Freshdesk tickets have a null requester_id (spam catches, system-generated tickets). Decide in advance whether to assign these to a placeholder Customer or skip them.

Migrate Objects

Migrate in this exact sequence. Each step depends on the IDs produced by the previous one.

  1. Agents to Users: Retrieve agents via GET /api/v2/agents (Freshdesk) and create corresponding Users in Help Scout. Record the freshdesk_agent_id → helpscout_user_id mapping.
  2. Groups to Teams: List groups via GET /api/v2/groups (Freshdesk) and create Teams in Help Scout via POST /v2/teams. Assign Users using your agent mapping.
  3. Companies to Organizations: GET /api/v2/companiesPOST /v2/companies in Help Scout. Direct transfer.
  4. Contacts to Customers: GET /api/v2/contactsPOST /v2/customers. Import names, emails, phone numbers, and social profiles. Link each Customer to the Organization created in the previous step.
  5. Solutions to Docs: Freshdesk Solutions map to Help Scout Docs. Migrate the hierarchy: Freshdesk Categories → Help Scout Collections; Freshdesk Folders → Help Scout Categories; Freshdesk Articles → Help Scout Articles.
  6. Tickets to Conversations: GET /api/v2/ticketsPOST /v2/conversations. Apply the field mapping table above. Populate Custom Fields using the IDs recorded during setup. Use pagination as described in the rate limits section.
  7. Ticket Conversations to Threads: For each migrated Conversation, retrieve its replies and notes via GET /api/v2/tickets/{id}/conversations and post them as Threads (POST /v2/conversations/{id}/reply for public replies, POST /v2/conversations/{id}/notes for private notes). Preserve chronological order.
  8. Canned Responses to Saved Replies: GET /api/v2/canned_responsesPOST /v2/conversations/replies as Saved Replies. Migrate these last.

Post Migration Configuration

Once the data transfer is complete, you have some housekeeping to do to make the system functional.

The logic that governed your old help desk does not transfer. Freshdesk Automations and SLA Policies must be manually rebuilt as Workflows in Help Scout.

The API allows you to list workflows but not create them, so you will need to set up your rules for routing, assignment, and escalation within the Help Scout settings.

Validation

Do not declare the migration complete without running these checks.

  1. Record count: Compare your pre-migration Freshdesk ticket count against the total Conversations in Help Scout (GET /v2/conversations with no filters, check the page.totalElements field in the response).
  2. Spot checks: Pick 10–20 random ticket IDs from Freshdesk, look them up in Help Scout, and verify subject, customer, agent assignment, tags, custom field values, and thread count.
  3. Attachment audit: Confirm that file attachments on a sample of tickets are accessible in Help Scout. Attachments require separate handling (downloading from Freshdesk's CDN and re-uploading to Help Scout) and are the most commonly dropped data type in migrations.
  4. Custom field values: Pull a sample of Conversations via the API and confirm the fields array contains expected values — not null.
  5. Agent assignment: Confirm that assigned conversations show the correct agent name, not a fallback or blank.

Insider Secrets

There are nuances to this migration that can trip you up if you are not prepared. Here is how to handle the tricky data that does not fit perfectly.

Handling Products: Freshdesk has a native object called "Products" which Help Scout lacks. You have two options here. If you used Products primarily for filtering tickets, you should migrate the Product name into a Help Scout Custom Field (specifically a dropdown) on the Conversation. However, if you used Products to segregate different Knowledge Bases, you should map each Product to a separate Help Scout "Docs Site".

Preserving Satisfaction Ratings: You cannot migrate historical survey results into Help Scout's native reporting graphs because the API does not have a "Create" endpoint for Ratings. If you want to keep the record that a customer was happy or unhappy on a specific ticket, you must migrate the Freshdesk Rating as a private Note (Thread) or a Tag on the Conversation. This keeps the context accessible even if it does not appear in the dashboard analytics.

Mapping Inboxes is Critical: Because you created Inboxes manually, their IDs in Help Scout will be completely different from the Mailbox IDs in Freshdesk. You must maintain a strict mapping table. When your script reads a ticket from Freshdesk Mailbox A, it must know exactly which Help Scout Inbox ID to assign it to, or your tickets will end up in a disorganized pile.

Handling attachments: File attachments on Freshdesk tickets are stored on Freshdesk's CDN and referenced by URL in the API response. These URLs are not permanent. You must download each attachment during migration and re-upload it to Help Scout when creating the corresponding Thread. Skipping this step results in broken links in your conversation history.

Merged tickets: Freshdesk tickets that were merged into a parent ticket will appear in the API as closed tickets with a merged status or with a reference to the parent. A naive migration script will create these as standalone Conversations in Help Scout with no link to their parent. Identify merged tickets before migration and decide whether to skip them, annotate them with a Tag, or add a private Note pointing to the parent Conversation ID.

Summary

Migrating from Freshdesk to Help Scout requires a mix of automated scripts and manual configuration.

By following the strict order of operations, you ensure that every conversation lands in the right place and is attached to the right person.

While objects like Products and Ratings require creative workarounds, the core of your support history can be preserved if you map the fields correctly, handle rate limits and pagination, and prepare your destination environment first.


At ClonePartner, we've completed numerous Freshdesk to Help Scout migrations. Each project is handled by a dedicated engineer who customizes the data mapping to match your setup. If you want to avoid the technical complexity, ClonePartner can manage the entire migration for you, from planning and field mapping to validation and go-live.

Further reading:

More from our Blog