---
title: SurveySparrow Ticket Management to Front Migration Guide
slug: surveysparrow-ticket-management-to-front-migration-guide
date: 2026-07-21
author: Nachi
categories: [Migration Guide, Front]
excerpt: "Technical guide to migrating from SurveySparrow Ticket Management to Front — covering API constraints, field mapping, migration methods, and edge cases."
tldr: "Front has no native SurveySparrow importer. Full-fidelity migrations require API-led thread reconstruction, field mapping, and public/private comment separation."
canonical: https://clonepartner.com/blog/surveysparrow-ticket-management-to-front-migration-guide/
---

# SurveySparrow Ticket Management to Front Migration Guide


Migrating from SurveySparrow Ticket Management to Front is a data-model translation problem with an added twist: SurveySparrow's ticketing system is feedback-first, not helpdesk-first. Tickets in SurveySparrow are typically generated from survey responses — NPS detractors, CSAT complaints, service requests triggered by form submissions. Front is a conversation-centric shared inbox where every interaction lives as a threaded message with collaboration happening via internal comments rather than isolated ticket notes.

There is no native migration path between these two systems. Front's documented history imports cover only Help Scout, Freshdesk, and Zendesk — not SurveySparrow. ([help.front.com](https://help.front.com/en/categories/332)) No third-party migration tool currently offers a pre-built SurveySparrow connector either. Every migration requires extracting data from SurveySparrow's REST API or export tools, transforming it to match Front's message-based schema, and importing it via Front's Core API.

This guide covers the API constraints on both platforms, complete field-mapping tables, every realistic migration method, and the edge cases that cause silent data loss — so your team can plan and execute this migration without losing ticket history or operational context.

> [!WARNING]
> **Front does not offer a native SurveySparrow importer.** Front's history-import documentation lists only Help Scout, Freshdesk, and Zendesk. Plan this as a Core API migration from day one. ([help.front.com](https://help.front.com/en/categories/332))

For a deeper look at Front's architecture, see [Mastering Front: A Technical Deep Dive](https://clonepartner.com/blog/blog/ultimate-guide-front-2026/). If you are migrating from other platforms alongside SurveySparrow, review our guides on [Zendesk to Front Migration](https://clonepartner.com/blog/blog/zendesk-to-front-migration-the-2026-technical-guide/) and [Help Scout to Front Migration](https://clonepartner.com/blog/blog/how-to-migrate-from-help-scout-to-front-complete-guide/).

> [!NOTE]
> **Scope check:** SurveySparrow Ticket Management is a feedback-loop ticketing system, not a full helpdesk. It does not have the same object depth as Zendesk or Freshdesk — there are no organizations/companies, no multi-level ticket hierarchies in the API, and custom objects are limited to custom ticket fields. Front is a conversation platform, not a CRM. CRM data (contacts, companies) can be surfaced in Front via Salesforce or HubSpot integrations, but those are not native Front objects.

## Why Teams Migrate from SurveySparrow Ticket Management to Front

Teams typically make this move for three reasons:

1. **Outgrowing feedback-loop ticketing.** SurveySparrow's ticket management was built to close the loop on survey responses — auto-creating tickets from NPS scores, CSAT dips, or form submissions. As support volume grows, teams need a platform designed for multi-channel, high-volume conversation management.

2. **Channel consolidation.** Front unifies email, SMS, WhatsApp, social media, and live chat into shared inboxes with per-teammate assignment, internal comments, and collaborative workflows. SurveySparrow's ticketing is primarily internal and email-based.

3. **Collaboration model.** Front's comment-on-conversation model lets teammates discuss a thread without internal notes cluttering the ticket view. SurveySparrow's collaboration is note-based and works differently at scale.

## Core Data Model Differences

Understanding the structural gap between these two platforms is the prerequisite to planning any migration.

| Concept | SurveySparrow | Front |
|---|---|---|
| Primary object | Ticket (structured record) | Conversation (threaded messages) |
| Ticket/conversation body | `description` / `description_html` field | First message in conversation |
| Customer-visible history | Ticket description + public comments | Messages |
| Internal collaboration | Ticket comments (`private` flag) | Comments on conversation thread |
| Requester | Contact (linked by `requester_id`) | Conversation sender / Front Contact |
| Agent assignment | `agent` object on ticket | Assignee on conversation |
| Team routing | `team` object on ticket | Inbox or tag-based routing |
| Status | Numeric status object | Open / Archived / Deleted |
| Priority | Numeric priority object | Tags or custom fields (no native priority) |
| Custom fields | `custom_fields` object on ticket | Conversation custom fields (limited types) |
| SLA tracking | `first_response_due`, `resolution_due` | Front SLA rules (rebuilt, not migrated) |
| Attachments | Via ticket description or comments | Message attachments |
| Parent-child tickets | Supported in UI | No native equivalent |

The fundamental challenge: a SurveySparrow ticket is a **single structured record** with metadata. A Front conversation is a **sequence of messages** with comments layered on top. Every ticket must be decomposed into at least one imported message, with internal notes becoming Front comments.

## Migration Approaches

No single method fits every team. The deciding factor is whether historical ticket history needs to be native and searchable inside Front.

### 1. Native Export + API Import (CSV/Excel/JSON)

**How it works:** Export ticket data from SurveySparrow via Settings → Ticket Management → Export Data in Excel (xlsx) or JSON format. Transform the exported file to match Front's expected structure. Import via Front's `POST /inboxes/:inbox_id/imported_messages` endpoint. ([support.surveysparrow.com](https://support.surveysparrow.com/hc/en/articles/20191180486045-Export-ticket-details/?utm_source=openai))

**When to use it:** Small datasets (under 500 tickets) where you need basic ticket history and don't have engineering resources for a full API migration.

**SurveySparrow export file structure:** The Excel export produces columns including `Ticket ID`, `Subject`, `Description`, `Status`, `Priority`, `Assignee`, `Requester Name`, `Requester Email`, `Created At`, `Updated At`, and any custom fields configured on your ticket templates. The JSON export mirrors the API response shape with nested objects for `requester`, `agent`, `status`, `priority`, and `custom_fields`. Crucially, neither format includes ticket comments or file attachments.

**Pros:**
- No API development required on the SurveySparrow side
- Full control over which fields to export
- JSON export preserves HTML formatting in `description_html`

**Cons:**
- Ticket comments are **not included** in the standard export — only ticket-level fields
- Attachments are not included in the export file
- Manual transformation step is error-prone at scale
- Front's native CSV import is for contacts and accounts, not conversations — contacts support up to 30,000 rows per upload without `accountName`, 3,000 with `accountName`

**Scalability:** Low. Works for hundreds of tickets, breaks down past 1,000.

**Complexity:** Low to Medium.

> [!WARNING]
> SurveySparrow's ticket export includes ticket fields, assignee details, and requester details — but **not** ticket comments or attachments. If you need comment history, you must use the Ticket Comments API (`GET /v3/tickets/:id/comments`) to extract them separately.

### 2. API-Based Migration (SurveySparrow REST API → Front Core API)

**How it works:** Build a custom script that extracts tickets, comments, contacts, and custom fields from SurveySparrow's v3 REST API, transforms the data, and writes it into Front using the imported_messages endpoint. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/get-v-3-tickets/?utm_source=openai))

**When to use it:** Any migration where you need full fidelity — comments, custom fields, attachments, and correct timestamps.

**SurveySparrow API authentication:** Create a Private App via Settings → Apps & Integrations. Required scopes: `Read Tickets`, `Read Contacts`, and `Read Ticket Fields` at minimum. The token is shown once at creation — store it in a secrets manager. Without the correct scopes, ticket and comment endpoints return `403 Forbidden`.

**Pros:**
- Full data extraction including comments and custom fields
- Preserves original timestamps via `created_at` parameter on imported messages
- Handles attachments via multipart/form-data
- Scriptable, repeatable, testable

**Cons:**
- Requires development effort (Python/Node.js)
- Must handle rate limits on both sides
- SurveySparrow rate limits vary by plan tier and are not publicly documented in detail
- Front's default rate limit is 50 requests per minute (varies by plan), enforced per-company

**Scalability:** High. Handles thousands of tickets with proper pagination and rate-limit handling.

**Complexity:** High.

### 3. Third-Party Migration Tools

As of this writing, no third-party migration tool offers a pre-built SurveySparrow Ticket Management connector. Tools like Help Desk Migration support Front as a destination but require the source platform to be one of their supported connectors (Zendesk, Freshdesk, Intercom, etc.). Do not assume off-the-shelf coverage — verify SurveySparrow support before you commit. ([help.front.com](https://help.front.com/en/articles/2513?utm_source=openai))

**When to use it:** Not currently viable for this specific migration path.

### 4. Middleware/Integration Platforms (Zapier, Make)

**How it works:** Use Zapier or Make triggers on SurveySparrow ticket events to create corresponding conversations in Front.

**When to use it:** Ongoing sync of **new** tickets only. Not suitable for historical migration.

**Pros:**
- No-code setup
- Works for ongoing sync after migration
- Both platforms have SurveySparrow and Front integrations

**Cons:**
- Cannot migrate historical data (only triggers on new events)
- Limited field mapping control
- Zapier task limits and pricing at scale
- No batch processing — one ticket = one Zap execution
- Comment and attachment migration not supported natively

**Scalability:** Low for migration. Medium for ongoing sync.

**Complexity:** Low.

### 5. Custom ETL Pipeline

**How it works:** Build a structured Extract-Transform-Load pipeline with dedicated stages for extraction, data cleaning, transformation, and loading. This is approach #2 with proper engineering discipline — separate concerns, logging, validation, and rollback capabilities.

**When to use it:** Enterprise migrations with 10K+ tickets, complex custom fields, or compliance requirements that demand audit trails.

**Pros:**
- Full control over every transformation
- Audit logging at every stage
- Dry-run and rollback capabilities
- Can be extended for delta sync

**Cons:**
- Highest development investment
- Requires deep understanding of both APIs
- Overkill for small datasets

**Scalability:** Enterprise-grade.

**Complexity:** High.

### Migration Methods Comparison

| Method | Data Fidelity | Comments | Attachments | Historical Timestamps | Scalability | Complexity |
|---|---|---|---|---|---|---|
| Export + API Import | Partial | ❌ | ❌ | ✅ | Low | Low |
| API-Based Script | Full | ✅ | ✅ | ✅ | High | High |
| Third-Party Tools | N/A | N/A | N/A | N/A | N/A | N/A |
| Zapier/Make | Partial | ❌ | ❌ | ❌ | Low | Low |
| Custom ETL | Full | ✅ | ✅ | ✅ | Enterprise | High |

### Which Approach Fits Your Scenario?

- **Small team, <500 tickets, no comments needed:** Export + API import
- **Mid-size team, 500–5,000 tickets, full history needed:** API-based migration script
- **Enterprise, 10K+ tickets, audit requirements:** Custom ETL pipeline
- **Ongoing sync after migration:** Zapier/Make for new tickets, API script for historical
- **Low engineering bandwidth, high data fidelity needs:** Managed migration service

### Volume-Based Time Estimates

The following estimates assume Front's default 50 requests/minute rate limit. Each ticket requires approximately 3 API calls: 1 imported message, 1 conversation lookup (to get the `conversation_id` for subsequent operations), and 1 comment/tag/assignment patch. Tickets with comments require additional calls.

| Ticket Count | Avg Comments/Ticket | Estimated API Calls | Estimated Time (50 req/min) | Estimated Time (200 req/min) |
|---|---|---|---|---|
| 1,000 | 1 | ~4,000 | ~1.3 hours | ~20 minutes |
| 5,000 | 2 | ~15,000 | ~5 hours | ~1.25 hours |
| 10,000 | 3 | ~40,000 | ~13 hours | ~3.3 hours |
| 50,000 | 3 | ~200,000 | ~67 hours | ~17 hours |

For datasets above 10,000 tickets, request a temporary rate limit increase from Front support before starting the migration.

## When to Use a Managed Migration Service

Build in-house when you have dedicated engineering capacity, the migration is straightforward (few custom fields, no attachments), and the timeline is flexible.

Use a managed service when:

- **Your team lacks SurveySparrow + Front API experience.** Both APIs have undocumented behaviors — SurveySparrow's rate limits aren't published in detail, and Front's imported_messages endpoint has threading quirks that only surface at scale.
- **You can't afford extended downtime.** A managed service runs the migration in the background with delta sync before cutover.
- **Custom fields and comments are operationally important.** DIY scripts frequently drop these because they require separate API calls per ticket.
- **You need the migration done in days, not weeks.** Internal projects competing for sprint cycles tend to stretch.

The failure mode of DIY migrations is rarely row loss. It is **semantic corruption**: public comments imported as internal notes, private notes exposed as messages, common-domain requesters assigned to the wrong company, or legacy statuses collapsed into unusable tags.

**Hidden costs of DIY migration:**
- Engineering time debugging rate limits and pagination edge cases
- QA cycles for field-level validation
- Risk of silent data loss on custom fields and HTML content
- No rollback path if the import script has a logic error mid-run

ClonePartner has completed complex migrations into Front from platforms with similarly non-standard data models — including [700K tickets from Zendesk](https://clonepartner.com/blog/blog/zendesk-to-front-iin/) and [HubSpot-to-Front migrations](https://clonepartner.com/blog/blog/hubspot-to-front-migration-api-limits-data-mapping-methods/) requiring full conversation reconstruction. Our tooling handles rate-limit orchestration across both source and target APIs, preserves custom fields, comments, and attachments, and runs without interrupting your live support operations. If you have fewer than 500 tickets and no custom fields, you probably don't need us. If you have more, [let's talk](https://cal.com/clonepartner/meet?duration=30).

## Pre-Migration Planning

### Data Audit Checklist

Before writing any migration code, inventory what you actually have in SurveySparrow:

- [ ] **Total ticket count** — Use `GET /v3/tickets` with pagination to get an accurate count
- [ ] **Ticket statuses in use** — Map each numeric status to its meaning
- [ ] **Priority levels** — Identify which priorities are active
- [ ] **Custom fields** — Use the Ticket Fields endpoint (`GET /v3/ticket-fields`) to list all custom field definitions, noting field type (`text`, `number`, `dropdown`, `date`, `checkbox`, `multi_select`)
- [ ] **Ticket templates** — Identify if different templates create different field sets
- [ ] **Comment volume** — Estimate average comments per ticket (sample 50–100 tickets)
- [ ] **Attachment volume** — Check if descriptions contain embedded files or images; note total attachment size
- [ ] **Contact count** — Total unique requesters
- [ ] **Team and agent mappings** — List all teams and agents for assignment mapping
- [ ] **Related (parent/child) tickets** — Document relationships for manual reconstruction
- [ ] **Trash/deleted tickets** — Decide whether to migrate deleted tickets (the API supports `trash=true` filter)
- [ ] **Character encoding** — Check for emoji, RTL text, or non-ASCII characters in ticket descriptions and comments that may need explicit UTF-8 handling

### Identify Data to Leave Behind

Not everything needs to migrate:

- **Survey response data** — This lives in SurveySparrow's survey/response system, not in tickets. If you need it, export separately.
- **Workflow/automation rules** — These must be rebuilt as Front rules. They don't transfer.
- **SLA policies** — Rebuild in Front's SLA engine.
- **Ticket templates** — Front doesn't have an equivalent. Document them for manual recreation as canned responses.
- **Trashed tickets older than N months** — Set a cutoff date.
- **Inactive agents** — Map to a generic "Former Employee" teammate in Front rather than creating dead accounts.

### Migration Strategy

| Strategy | Best For | Risk Level |
|---|---|---|
| **Big bang** | <5,000 tickets, weekend cutover window | Medium |
| **Phased** | Large datasets, multiple teams | Low |
| **Incremental** | Ongoing sync needed, gradual rollout | Low |

For most SurveySparrow-to-Front migrations, a **big-bang approach over a weekend** works because SurveySparrow ticket volumes tend to be moderate (feedback-driven, not high-volume support). Run the full migration, validate, and cut over.

## Data Mapping: SurveySparrow Tickets to Front Conversations

This is where the migration succeeds or fails. Every SurveySparrow ticket field must map to a Front conversation attribute or be explicitly dropped.

### The Public vs. Private Comment Distinction

The biggest mistake in this migration is treating every SurveySparrow comment the same. SurveySparrow ticket comments carry a `private` flag — the API response for each comment includes `"private": true` or `"private": false`. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/get-v-3-tickets-id-comments)) In Front, customer-visible content must become **messages**, while internal collaboration must become **comments**. If you flatten both into comments, you lose customer history. If you flatten both into messages, you leak internal notes. Split them in your transformation logic before loading.

A typical SurveySparrow comment object from `GET /v3/tickets/:id/comments` looks like:

```json
{
  "id": 12345,
  "body": "Followed up with the customer via phone.",
  "body_html": "<p>Followed up with the customer via phone.</p>",
  "private": true,
  "created_at": "2024-03-15T14:22:00.000Z",
  "author": {
    "id": 678,
    "email": "agent@company.com",
    "name": "Jane Smith"
  }
}
```

Public comments (`"private": false`) become additional imported messages threaded into the conversation. Private comments (`"private": true`) become Front comments on the conversation.

### Object-Level Mapping

| SurveySparrow Object | Front Equivalent | Notes |
|---|---|---|
| Ticket | Conversation (imported message) | 1 ticket = 1 conversation with 1+ messages |
| Ticket description / public comments | Imported messages | Prefer `description_html` when available |
| Private comments | Comments on conversation | Requires separate API call per comment |
| Contact (requester) | Conversation sender / Front Contact | Create or match contacts by email |
| Agent (assignee) | Conversation assignee (teammate) | Map by email; must exist in Front first |
| Team | Inbox or Tag | No direct equivalent; use inbox routing or tags |
| Custom Fields | Conversation custom fields | Limited types in Front; may require transformation |
| Priority | Tag or custom field | Front has no native priority field |
| Status | Conversation status | Map Closed/Resolved → Archived; Open → Open |
| SLA timestamps | Dropped | Rebuild SLA rules natively in Front |
| Parent-child tickets | Custom fields + tag | No like-for-like hierarchy in Front |
| Survey/source metadata (NPS, CSAT) | Custom fields or tags | Store survey ID, submission ID, score for traceability |

### Field-Level Mapping Table

| SurveySparrow Field | Type | Front Field | Transformation |
|---|---|---|---|
| `id` | number | `external_id` | Use as `ss-ticket-{id}` for idempotency |
| `subject` | string (≤200 chars) | `subject` on imported message | Direct map |
| `description_html` | string (HTML) | `body` on imported message | Pass through; Front renders HTML |
| `description` | string (plain text) | `body` (fallback) | Use if `description_html` is empty |
| `requester.email` | string | `sender.handle` | Must be a valid email |
| `requester.name` | string | `sender.name` | Direct map |
| `agent.email` | string | Assignee (via `teammate_id`) | Must pre-exist in Front |
| `team.name` | string | Inbox ID or tag | Map teams to Front inboxes |
| `status.name` | string | Conversation status | Closed/Resolved → Archived |
| `priority.name` | string | Tag: `priority:high`, etc. | Front has no native priority field |
| `created_at` | datetime (ISO 8601) | `created_at` (Unix timestamp) | Parse as UTC, convert to epoch seconds |
| `updated_at` | datetime (ISO 8601) | N/A (informational) | Log for validation |
| `custom_fields.*` | varies | Conversation custom fields | Create matching fields in Front first |
| `template_id` | number | Tag: `template:{name}` | Preserve as tag for context |
| `source` | object | Tag: `source:{type}` | Preserve survey origin context |
| `nps_score` / CSAT score | integer | Custom field | Requires custom field setup in Front |

> [!WARNING]
> **Front has no native priority field.** SurveySparrow priorities must be converted to tags (e.g., `priority:urgent`, `priority:high`) or stored in a custom dropdown field. Decide this mapping before migration, not during.

### Timezone Handling

SurveySparrow returns timestamps in ISO 8601 format (e.g., `2024-03-15T14:22:00.000Z`). Front's `imported_messages` endpoint expects `created_at` as a **Unix epoch timestamp in seconds**. Always parse SurveySparrow timestamps as UTC before converting:

```python
from datetime import datetime, timezone

def parse_datetime(iso_string):
    """Parse ISO 8601 timestamp and convert to Unix epoch seconds.
    SurveySparrow timestamps may or may not include timezone info.
    Treat naive timestamps as UTC."""
    if iso_string.endswith("Z"):
        iso_string = iso_string[:-1] + "+00:00"
    dt = datetime.fromisoformat(iso_string)
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    return int(dt.timestamp())
```

If SurveySparrow timestamps are timezone-naive (no `Z` suffix, no offset), confirm with SurveySparrow support whether they represent UTC or the account's configured timezone. Incorrect timezone handling silently corrupts chronological ordering of conversations and comments in Front.

### Handling Parent-Child Tickets

SurveySparrow supports parent-child ticket hierarchies in its UI. Front has no equivalent. ([support.surveysparrow.com](https://support.surveysparrow.com/hc/en/articles/24806817991709-Related-Tickets/?utm_source=openai)) Options:

- **Flatten:** Migrate each child ticket as an independent conversation. Add a tag like `parent:ss-ticket-{parent_id}` and a custom field linking back to the parent ticket ID.
- **Merge:** Combine child ticket descriptions into comments on the parent conversation. Works if child tickets are short follow-ups.

Neither option perfectly preserves the hierarchy. Document the trade-off and get stakeholder sign-off before migration.

## Migration Architecture

### Data Flow

```
SurveySparrow REST API v3
        │
        ▼
   ┌─────────┐
   │ Extract │ ── GET /v3/tickets (paginated, max 100/page)
   │         │ ── GET /v3/tickets/:id/comments
   │         │ ── GET /v3/contacts/:id
   │         │ ── GET /v3/ticket-fields
   └────┬────┘
        │
        ▼
   ┌───────────┐
   │ Transform │ ── Map fields (see tables above)
   │           │ ── Split public vs private comments
   │           │ ── Convert timestamps to epoch seconds (UTC)
   │           │ ── Resolve agent emails → Front teammate IDs
   │           │ ── Build imported_message payloads
   │           │ ── Download inline images from SurveySparrow CDN
   └────┬──────┘
        │
        ▼
   ┌──────┐
   │ Load │ ── POST /inboxes/:inbox_id/imported_messages
   │      │ ── POST /conversations/:id/comments
   │      │ ── PATCH /conversations/:id (assign, tag, custom fields)
   └──────┘
        │
        ▼
     Front
```

### Authentication

**SurveySparrow:** OAuth 2.0 bearer token. Generate via Settings → Apps & Integrations → Create Private App. Required scopes: `Read Tickets`, `Read Contacts`, `Read Ticket Fields`. The token is shown once — store it in a secrets manager. Use the correct regional API base URL for your data center (e.g., `https://api.surveysparrow.com` for US, check your account settings for EU or other regions). ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/Introduction/?utm_source=openai))

**Front:** API token with scopes for `shared:manage` (imported messages require write access to shared inboxes). Generate via Settings → Developers → API Tokens. The token must also have `conversation:write` and `comment:write` scopes for patching conversations and adding comments.

### Rate Limits

| Platform | Limit | Enforcement | Notes |
|---|---|---|---|
| SurveySparrow | Varies by plan tier | Per-account | Not publicly documented in detail; monitor `X-RateLimit-Remaining` response headers for remaining quota; back off on 429s |
| Front | 50/100/200 req/min (by plan: Starter/Growth-Scale/Premier) | Per-company (not per-token) | Tier 2 endpoints (e.g., resource-specific updates): 5 req/resource/sec; burst allowance = half per-min limit |

> [!TIP]
> Front returns `x-ratelimit-remaining` and `retry-after` headers on every response. Build your script to read these headers and back off on 429 responses. Creating multiple API tokens does **not** increase throughput — Front enforces limits per company, not per token. ([dev.frontapp.com](https://dev.frontapp.com/reference/introduction?utm_source=openai))

### Handling Large Datasets

- **SurveySparrow pagination:** Tickets support max 100 records per page; contacts top out at 50 per page. Use the `page` parameter to iterate and check `has_next_page` in responses.
- **Batching:** Group API calls with controlled concurrency (e.g., 3–5 parallel requests with rate-limit awareness).
- **Checkpointing:** Log the last successfully processed ticket ID to a file or database. If the script fails, resume from that point rather than reprocessing from the beginning.
- **Attachment handling:** Download attachments from SurveySparrow, then upload to Front as multipart/form-data. Front's import endpoint caps individual attachments at 25 MB.

## Step-by-Step Migration Process

### Step 1: Extract Data from SurveySparrow

Pull catalogs first (ticket fields, teams, users, contacts) before tickets so you can build lookup tables. Store raw JSON for audit purposes. Never pipe data directly from API to API without a staging layer — if the script crashes, you lose your place.

A typical SurveySparrow ticket response from `GET /v3/tickets` looks like:

```json
{
  "data": [
    {
      "id": 1001,
      "subject": "Product performance issue",
      "description": "The dashboard loads slowly after the last update.",
      "description_html": "<p>The dashboard loads slowly after the last update.</p>",
      "status": {"id": 1, "name": "Open"},
      "priority": {"id": 2, "name": "High"},
      "requester": {"id": 501, "email": "customer@example.com", "name": "Alex Chen"},
      "agent": {"id": 201, "email": "agent@company.com", "name": "Jane Smith"},
      "team": {"id": 10, "name": "Product Support"},
      "custom_fields": {"region": "APAC", "product_tier": "Enterprise"},
      "template_id": 3,
      "source": {"type": "nps_survey", "survey_id": 42},
      "created_at": "2024-03-15T09:30:00.000Z",
      "updated_at": "2024-03-16T11:45:00.000Z"
    }
  ],
  "has_next_page": true
}
```

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

SS_BASE = "https://api.surveysparrow.com/v3"
SS_TOKEN = "your-surveysparrow-token"
headers = {"Authorization": f"Bearer {SS_TOKEN}"}

CHECKPOINT_FILE = "checkpoint.json"

def load_checkpoint():
    if os.path.exists(CHECKPOINT_FILE):
        with open(CHECKPOINT_FILE) as f:
            return json.load(f)
    return {"last_page": 0, "last_ticket_id": None}

def save_checkpoint(page, ticket_id):
    with open(CHECKPOINT_FILE, "w") as f:
        json.dump({"last_page": page, "last_ticket_id": ticket_id}, f)

def extract_tickets():
    tickets = []
    checkpoint = load_checkpoint()
    page = checkpoint["last_page"] + 1 if checkpoint["last_page"] else 1
    while True:
        resp = requests.get(
            f"{SS_BASE}/tickets",
            headers=headers,
            params={"page": page, "limit": 100}
        )
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("retry-after", 60))
            time.sleep(retry_after)
            continue
        resp.raise_for_status()
        data = resp.json()
        batch = data["data"]
        tickets.extend(batch)
        if batch:
            save_checkpoint(page, batch[-1]["id"])
        if not data.get("has_next_page"):
            break
        page += 1
        time.sleep(0.5)  # respect rate limits
    return tickets
```

### Step 2: Extract Comments per Ticket

For each ticket, fetch comments and split them by the `private` flag into two lanes: public comments (which become Front messages) and private comments (which become Front comments).

```python
def extract_comments(ticket_id):
    resp = requests.get(
        f"{SS_BASE}/tickets/{ticket_id}/comments",
        headers=headers
    )
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("retry-after", 60))
        time.sleep(retry_after)
        return extract_comments(ticket_id)  # retry
    resp.raise_for_status()
    comments = resp.json().get("data", [])
    public_comments = [c for c in comments if not c.get("private", False)]
    private_comments = [c for c in comments if c.get("private", False)]
    return public_comments, private_comments
```

### Step 3: Create Custom Fields in Front

Before importing conversations, create matching custom fields in Front for every SurveySparrow custom field you plan to migrate. Front supports these custom field types: `text`, `number`, `dropdown`, `checkbox`, and `datetime`. Front caps custom fields at **50 per category** (e.g., 50 conversation custom fields).

```python
def create_front_custom_field(name, field_type="string", description=""):
    """Create a conversation custom field in Front.
    field_type: 'string', 'number', 'boolean', 'datetime', 'enum'"""
    resp = requests.post(
        f"{FRONT_BASE}/custom_fields",
        headers={
            "Authorization": f"Bearer {FRONT_TOKEN}",
            "Content-Type": "application/json"
        },
        json={
            "name": name,
            "type": field_type,
            "description": description or f"Migrated from SurveySparrow"
        }
    )
    resp.raise_for_status()
    return resp.json()  # Returns {"id": "cuf_xxxxx", ...}
```

SurveySparrow fields that don't map cleanly to Front's types:

| SurveySparrow Type | Front Type | Transformation |
|---|---|---|
| `text` | `string` | Direct map |
| `number` | `number` | Direct map |
| `dropdown` | `enum` | Create enum values in Front first |
| `date` | `datetime` | Convert to ISO 8601 |
| `checkbox` | `boolean` | Direct map |
| `multi_select` | `string` | Serialize as comma-separated values |
| `formula` | `string` | Compute and store as text |
| `conditional` | `string` | Flatten to resolved value |

### Step 4: Transform to Front Payload

```python
from datetime import datetime, timezone

def parse_datetime_to_epoch(iso_string):
    """Parse ISO 8601 timestamp to Unix epoch seconds."""
    if iso_string is None:
        return int(datetime.now(timezone.utc).timestamp())
    if iso_string.endswith("Z"):
        iso_string = iso_string[:-1] + "+00:00"
    dt = datetime.fromisoformat(iso_string)
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    return int(dt.timestamp())

def transform_ticket(ticket):
    requester_email = ticket.get("requester", {}).get("email")
    if not requester_email or "@" not in requester_email:
        requester_email = f"unknown-{ticket['id']}@placeholder.invalid"

    body = ticket.get("description_html") or ticket.get("description") or ""
    if not body.strip():
        body = f"[Auto-generated ticket from SurveySparrow. Ticket ID: {ticket['id']}]"

    return {
        "sender": {
            "handle": requester_email,
            "name": ticket.get("requester", {}).get("name", "Unknown")
        },
        "subject": ticket.get("subject", "(No Subject)"),
        "body": body,
        "external_id": f"ss-ticket-{ticket['id']}",
        "created_at": parse_datetime_to_epoch(ticket.get("created_at")),
        "metadata": {
            "is_inbound": True,
            "should_skip_rules": True  # Prevent Front rules from firing on imported data
        },
        "tags": build_tags(ticket)
    }

def build_tags(ticket):
    tags = []
    if ticket.get("priority"):
        tags.append(f"priority:{ticket['priority'].get('name', 'unknown').lower()}")
    if ticket.get("status"):
        tags.append(f"ss-status:{ticket['status'].get('name', 'unknown').lower()}")
    if ticket.get("source"):
        tags.append(f"source:{ticket['source'].get('type', 'unknown')}")
    tags.append("migrated-from-surveysparrow")
    return tags
```

### Step 5: Create Contacts and Teammates in Front

Create all teammates in Front first. Map SurveySparrow agent emails to Front teammate IDs. Do the same for contacts — create or match by email. This must happen before conversation import so that assignments and sender attribution work correctly.

**Contact deduplication:** SurveySparrow may have multiple contact records with the same email address (e.g., from different survey submissions). Front treats each email handle as unique — attempting to create a contact with an existing email will return an error or merge into the existing contact depending on your Front settings. Before importing, deduplicate contacts by email on the SurveySparrow side: group contacts by email, pick the most recent record as the canonical version, and log any discrepancies.

```python
def get_or_create_front_contact(email, name):
    """Look up a contact by email handle. Create if not found."""
    # Search for existing contact
    resp = requests.get(
        f"{FRONT_BASE}/contacts",
        headers={"Authorization": f"Bearer {FRONT_TOKEN}"},
        params={"q": email}
    )
    resp.raise_for_status()
    results = resp.json().get("_results", [])
    for contact in results:
        for handle in contact.get("handles", []):
            if handle.get("handle") == email:
                return contact["id"]

    # Create new contact
    resp = requests.post(
        f"{FRONT_BASE}/contacts",
        headers={
            "Authorization": f"Bearer {FRONT_TOKEN}",
            "Content-Type": "application/json"
        },
        json={
            "handles": [{"source": "email", "handle": email}],
            "name": name
        }
    )
    resp.raise_for_status()
    return resp.json()["id"]
```

### Step 6: Import Conversations into Front

```python
FRONT_BASE = "https://api2.frontapp.com"
FRONT_TOKEN = "your-front-api-token"
INBOX_ID = "inb_xxxxx"

# Mapping table for post-migration audit
migration_map = {}  # {ss_ticket_id: front_conversation_id}

def import_to_front(payload, ss_ticket_id):
    resp = requests.post(
        f"{FRONT_BASE}/inboxes/{INBOX_ID}/imported_messages",
        headers={
            "Authorization": f"Bearer {FRONT_TOKEN}",
            "Content-Type": "application/json"
        },
        json=payload
    )
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("retry-after", 60))
        time.sleep(retry_after)
        return import_to_front(payload, ss_ticket_id)  # retry
    if resp.status_code == 409:
        # Duplicate external_id — already imported
        print(f"Skipping duplicate: ss-ticket-{ss_ticket_id}")
        return None
    resp.raise_for_status()
    result = resp.json()
    # Front returns a message resource; extract conversation_id
    # The response includes a `_links` object with `related.conversation`
    # or you can look up via GET /messages/:message_id
    return result
```

Front's `POST /inboxes/:inbox_id/imported_messages` response returns a message resource. To get the `conversation_id` needed for subsequent comment and patch calls, use the message's `_links.related.conversation` URL or call `GET /messages/:message_id` which includes the `conversation_id` in the response body.

After importing the seed message, replay public comments as additional imported messages threaded into the same conversation. Use the `conversation_id` from the seed message response and set the same `conversation_id` in the subsequent import call. Alternatively, use a consistent `thread_ref` value across all messages belonging to the same conversation:

```python
def import_threaded_message(conversation_id, comment, inbox_id):
    """Import a public comment as a threaded message in an existing conversation."""
    payload = {
        "sender": {
            "handle": comment["author"].get("email", "unknown@example.com"),
            "name": comment["author"].get("name", "Unknown")
        },
        "subject": "",  # Thread inherits subject from seed message
        "body": comment.get("body_html") or comment.get("body", ""),
        "external_id": f"ss-comment-{comment['id']}",
        "created_at": parse_datetime_to_epoch(comment.get("created_at")),
        "metadata": {
            "is_inbound": comment["author"].get("email") not in agent_emails,
            "should_skip_rules": True,
            "thread_ref": f"ss-thread-{conversation_id}"
        }
    }
    resp = requests.post(
        f"{FRONT_BASE}/inboxes/{inbox_id}/imported_messages",
        headers={
            "Authorization": f"Bearer {FRONT_TOKEN}",
            "Content-Type": "application/json"
        },
        json=payload
    )
    handle_response(resp)
```

> [!NOTE]
> **Threading mechanics:** Front groups imported messages into the same conversation using `thread_ref` — a string you define. All messages sharing the same `thread_ref` value are threaded together. Set `thread_ref` to a consistent value like `ss-thread-{ticket_id}` for the seed message and all subsequent public comments. If you omit `thread_ref`, Front may create separate conversations for each imported message. The `thread_ref` does not need to match any existing identifier in Front; it is an arbitrary grouping key used only during import.

### Step 7: Add Comments to Conversations

Post each private SurveySparrow comment as a Front comment on the corresponding conversation:

```python
def add_comment(conversation_id, comment, agent_id_map):
    """Add a private comment to a Front conversation.
    Front does not accept created_at on comments — prepend original timestamp."""
    original_ts = comment.get("created_at", "unknown")
    author_email = comment.get("author", {}).get("email", "unknown")
    body_with_timestamp = (
        f"[Original: {original_ts} by {author_email}]\n\n"
        f"{comment.get('body_html') or comment.get('body', '')}"
    )
    payload = {"body": body_with_timestamp}
    author_id = agent_id_map.get(author_email)
    if author_id:
        payload["author_id"] = author_id
    resp = requests.post(
        f"{FRONT_BASE}/conversations/{conversation_id}/comments",
        headers={
            "Authorization": f"Bearer {FRONT_TOKEN}",
            "Content-Type": "application/json"
        },
        json=payload
    )
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("retry-after", 60))
        time.sleep(retry_after)
        return add_comment(conversation_id, comment, agent_id_map)
    resp.raise_for_status()
    return resp.json()
```

> [!WARNING]
> Front's comment endpoint (`POST /conversations/:id/comments`) does **not** accept a `created_at` parameter. All comments are timestamped at import time — historical internal note timestamps are lost. ([dev.frontapp.com](https://dev.frontapp.com/reference/import-inbox-message)) Workaround: prepend the original timestamp to the comment body:
>
> ```
> [Original: 2024-03-15T14:22:00.000Z by agent@company.com]
> Actual comment text here...
> ```

### Step 8: Patch Workflow State

After the conversation exists, apply assignees, tags, ticket status, and custom fields via `PATCH /conversations/:id`.

> [!WARNING]
> **Front API edge case:** When you update conversation custom fields, Front expects the full `custom_fields` object. Sending only the field you want to change will erase the others. Always read the current custom fields first, merge your updates, then send the complete payload. ([dev.frontapp.com](https://dev.frontapp.com/reference/import-inbox-message))

```python
def patch_conversation(conversation_id, ticket, agent_id_map, custom_field_map):
    """Apply assignee, tags, status, and custom fields to a Front conversation."""
    patch_payload = {}

    # Assignee
    agent_email = ticket.get("agent", {}).get("email")
    if agent_email and agent_email in agent_id_map:
        patch_payload["assignee_id"] = agent_id_map[agent_email]

    # Status: Closed/Resolved → archived
    status_name = ticket.get("status", {}).get("name", "").lower()
    if status_name in ("closed", "resolved"):
        patch_payload["status"] = "archived"

    # Custom fields — always send full object
    if ticket.get("custom_fields") and custom_field_map:
        front_custom_fields = {}
        for ss_field, value in ticket["custom_fields"].items():
            if ss_field in custom_field_map:
                front_custom_fields[custom_field_map[ss_field]] = value
        if front_custom_fields:
            patch_payload["custom_fields"] = front_custom_fields

    if patch_payload:
        resp = requests.patch(
            f"{FRONT_BASE}/conversations/{conversation_id}",
            headers={
                "Authorization": f"Bearer {FRONT_TOKEN}",
                "Content-Type": "application/json"
            },
            json=patch_payload
        )
        handle_response(resp)
```

### Step 9: Validate

After import, run validation checks:

- Compare ticket count in SurveySparrow vs. conversation count in Front
- Spot-check 5–10% of records for field accuracy
- Verify comments were attached to correct conversations and are in correct order
- Confirm attachments are downloadable
- Check that tags match expected values
- Verify that `external_id` values on Front conversations match the expected `ss-ticket-{id}` pattern
- Confirm no private comments were imported as public messages (spot-check by searching for comments that should be internal)

## Edge Cases and Challenges

### Duplicate Prevention

Use `external_id` (set to `ss-ticket-{ticket_id}`) on every imported message. If you re-run the script, Front will reject duplicates with the same `external_id` by returning a `409 Conflict` response. This is your idempotency guarantee. Handle 409s gracefully in your script by logging and skipping.

### Error Handling Beyond Rate Limits

Your script must handle these Front API error responses:

| Status Code | Meaning | Action |
|---|---|---|
| 400 | Validation error (malformed payload, missing required field) | Log the full request payload and error response; fix and retry manually |
| 401 | Invalid or expired API token | Halt migration; regenerate token |
| 404 | Inbox, conversation, or teammate not found | Verify the resource ID exists; likely a mapping error |
| 409 | Duplicate `external_id` | Log and skip — record already imported |
| 429 | Rate limit exceeded | Read `retry-after` header; queue and retry |
| 500/502/503 | Front server error | Retry with exponential backoff (1s, 2s, 4s, max 60s); halt after 5 consecutive failures |

```python
def handle_response(resp, max_retries=5, attempt=0):
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("retry-after", 60))
        time.sleep(retry_after)
        return "retry"
    elif resp.status_code == 409:
        return "skip"  # duplicate
    elif resp.status_code in (500, 502, 503):
        if attempt >= max_retries:
            raise Exception(f"Server error after {max_retries} retries: {resp.status_code}")
        backoff = min(2 ** attempt, 60)
        time.sleep(backoff)
        return "retry"
    elif resp.status_code == 400:
        print(f"Validation error: {resp.json()}")
        return "fail"
    resp.raise_for_status()
    return "success"
```

### Missing or Inconsistent Data

- **Tickets without a requester email:** SurveySparrow tickets created from anonymous surveys may lack requester contact info. Default to a placeholder email (`unknown-{ticket_id}@placeholder.invalid`) and tag these with `needs-review:missing-requester` for manual review.
- **Empty descriptions:** Some auto-generated tickets may have no body. Import with a placeholder message noting the ticket origin.
- **HTML vs. plain text:** Always prefer `description_html` over `description`. If both are null, log and skip.
- **Character encoding:** SurveySparrow ticket descriptions may contain emoji, HTML entities (`&amp;`, `&lt;`), or RTL text. Ensure your transformation logic preserves UTF-8 encoding throughout. Front accepts UTF-8 HTML in the `body` field.

### Survey Context Loss

If a ticket was generated by an NPS score of 2, the ticket description might just say "The product is too slow." Without the score itself mapped into Front, the agent lacks context. Map the NPS/CSAT score to a Front custom field (type: `number`) and consider applying tags like `nps:detractor` (scores 0–6), `nps:passive` (7–8), or `nps:promoter` (9–10).

### Inline Images

SurveySparrow tickets may contain inline images hosted on their CDN (URLs like `https://cdn.surveysparrow.com/...`). When you move to Front, those URLs will break if SurveySparrow requires authentication or if your account is deactivated. Download the images during extraction and re-upload them as attachments to the Front message via multipart/form-data. Replace the `src` attributes in the HTML body with the new Front attachment URLs, or attach them as separate files and reference them by filename.

### Custom Field Type Mismatches

Front supports conversation custom fields with these types: `string`, `number`, `boolean`, `datetime`, and `enum` (dropdown). Custom fields are capped at **50 per category**. SurveySparrow custom fields that don't map cleanly (multi-select, formula, conditional fields) should be serialized into a text field or appended to the conversation body as structured text.

### Contact Handle Uniqueness

Front treats email addresses and phone numbers as unique contact handles. Account domains are also unique. Public domains like gmail.com, outlook.com, yahoo.com, and hotmail.com are excluded from automatic company association — do not use them to auto-create Front accounts. ([help.front.com](https://help.front.com/en/articles/2058))

### API Failures and Retries

Build a retry queue. When Front returns 429 (rate limit) or 5xx (server error):

- Read the `retry-after` header
- Queue the failed request with the original payload and ticket ID
- Resume after the specified delay
- Log every failure with ticket ID, status code, and response body for post-migration audit
- After migration, review the failure log and reprocess any permanently failed records

## Limitations and Constraints

### Front Limitations vs. SurveySparrow

- **No native priority field** — must use tags or custom dropdown
- **No parent-child conversation hierarchy** — flatten or merge
- **Comment timestamps cannot be set** — historical timestamps are lost on internal notes
- **Custom field types are limited** — no multi-select, no formula fields; capped at 50 per category
- **No ticket templates** — rebuild as canned responses or message templates
- **SLA data does not transfer** — `first_response_due` and `resolution_due` must be rebuilt as Front SLA rules
- **Escalation history** — no equivalent in Front
- **Imported message size** — Front does not document an explicit body size limit, but payloads exceeding 256 KB may trigger 400 errors; split very large ticket descriptions if needed

### API Rate Limits as a Practical Constraint

At Front's default 50 requests/minute, importing 5,000 tickets with an average of 2 comments each requires approximately 15,000 API calls (5,000 imports + 5,000 conversation lookups + 5,000 comments). At 50/min, that's roughly **5 hours of API time** — before accounting for retries, tag operations, and assignment updates. Plan accordingly. For larger datasets, contact Front support to request a temporary rate limit increase — describe the migration scope and expected API volume.

### Data That Has No Home in Front

Some data simply does not have a direct target in Front:

- Survey response data linked to tickets (stays in SurveySparrow or is exported separately)
- Ticket template associations (preserved only as tags)
- SLA compliance history (reporting-only data; rebuild metrics from scratch in Front)
- CRM-style objects like Leads or Opportunities (keep these in your CRM and surface them in Front through integrations or application objects, not by forcing them into the conversation model) ([help.front.com](https://help.front.com/en/articles/2285?utm_source=openai))

## Validation and Testing

### Pre-Production Validation

1. **Record count comparison:** Total tickets extracted vs. conversations created in Front (query by `migrated-from-surveysparrow` tag)
2. **Field-level spot checks:** Sample 50 records across different statuses, priorities, and custom field combinations
3. **Comment verification:** Confirm comments are attached to the correct conversation, are in correct chronological order, and public/private separation is correct
4. **Attachment verification:** Download and open at least 10 attachments from Front to confirm integrity
5. **Tag verification:** Confirm priority, status, and source tags are applied correctly
6. **Timestamp verification:** Compare `created_at` dates between SurveySparrow and Front for 20+ conversations to confirm timezone handling is correct

### UAT Process

1. Migrate a test batch (100–200 tickets) into a staging Front inbox (create a dedicated inbox named `migration-test`)
2. Have 2–3 agents review the migrated data against the source
3. Document any discrepancies in a shared spreadsheet with columns: `SS Ticket ID`, `Front Conversation ID`, `Issue`, `Severity`
4. Fix transformation logic
5. Delete test conversations and re-run test batch
6. Sign off before production run

### Rollback Plan

Front does not have a native "undo import" function. Your rollback options:

- **Delete imported conversations** via API (batch delete using the `external_id` prefix to identify migrated records — search for tag `migrated-from-surveysparrow` and delete matching conversations)
- **Use a dedicated migration inbox** — if something goes wrong, archive or delete the entire inbox without affecting production data
- **Keep SurveySparrow active** until validation is complete — don't cancel your SurveySparrow subscription until you've confirmed all data landed correctly in Front

## Post-Migration Tasks

### Rebuild in Front (These Don't Migrate)

- **Rules and workflows:** Recreate SurveySparrow automation rules as Front rules
- **SLA policies:** Configure new SLA rules based on your actual response/resolution targets
- **Canned responses:** Translate SurveySparrow macros into Front message templates
- **Team routing:** Configure inbox routing rules to match your former team assignments
- **Integrations:** Reconnect CRM, Slack, and other tools to Front
- **DNS cutover:** Update your MX records and forwarding rules to route mail to Front instead of SurveySparrow

### User Training

- Walk agents through Front's conversation model (it behaves differently from ticket-based systems)
- Explain how to search for migrated tickets (search by `migrated-from-surveysparrow` tag or `external_id`)
- Document the mapping decisions (e.g., "what was priority 'Urgent' in SurveySparrow is now the tag `priority:urgent` in Front")

### Monitoring

For 2 weeks post-migration:

- Monitor for missing conversations reported by agents
- Check that new incoming messages thread correctly (not accidentally merging with imported history due to `thread_ref` collisions)
- Validate that Front rules are not firing on imported historical conversations (verify `should_skip_rules` was set correctly)

For broader Front setup guidance, see [Mastering Front](https://clonepartner.com/blog/blog/ultimate-guide-front-2026/). For QA after cutover, our [Post-Migration QA Checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-qa-checklist/) is the right companion resource.

## Best Practices

1. **Back up before migration.** Export all SurveySparrow data in JSON format before starting. Store the raw export alongside your transformation logs. Keep this backup for at least 90 days post-migration.

2. **Run test migrations.** Never go straight to production. Migrate 100 tickets to a test inbox first. Validate. Fix. Repeat.

3. **Disable Front automations during import.** If Front rules auto-assign, auto-reply, or send notifications, they will fire on imported messages unless you disable them or use the `metadata.should_skip_rules: true` parameter on every imported message.

4. **Use `external_id` on every record.** This is your idempotency key. It prevents duplicates and lets you trace every Front conversation back to its SurveySparrow source.

5. **Log everything.** Every API call, every response code, every skipped record. Maintain a mapping table of `SurveySparrow_Ticket_ID` → `Front_Conversation_ID` for troubleshooting. Use structured logging (JSON format) so you can query it programmatically.

6. **Run a delta migration.** Do a full historical sync over a week. Then, on a Friday evening, pause SurveySparrow, run a delta script to catch any tickets created or updated during that week (filter by `updated_at` > last sync timestamp), and go live Monday morning.

7. **Never mutate source data.** Your extraction scripts should only have READ access to SurveySparrow. Verify this by using scopes that exclude write permissions.

8. **Preserve source context.** Add a `migrated-from-surveysparrow` tag and include the original ticket ID in either the `external_id` or a custom field. Agents will need to cross-reference during the transition period.

9. **Handle encoding explicitly.** Set `Content-Type: application/json; charset=utf-8` on all API requests. Validate that emoji and special characters render correctly in Front after import.

10. **Maintain a source-to-target mapping file.** Export the mapping table (`ss_ticket_id` → `front_conversation_id`) as a CSV. This is essential for post-migration debugging and audit compliance.

## Making the Right Call

SurveySparrow Ticket Management to Front is a niche migration path — there's no pre-built connector, no native importer, and the data models are structurally different. The API-based approach is the only path to full data fidelity.

If your ticket volume is low and your data is simple, the export + API import method works. If you have thousands of tickets with comments, custom fields, and attachments, invest in a proper ETL script or bring in a team that's done this before.

The biggest risk isn't the migration itself — it's the silent data loss from fields that don't map, comments that get dropped, private notes that get exposed as public messages, and timestamps that get overwritten. Plan the mapping upfront, test on a real subset, and validate before you cut over.

> Need help migrating from SurveySparrow Ticket Management to Front? ClonePartner's engineering team handles the API orchestration, field mapping, and validation — so your team doesn't have to build a one-off migration script from scratch. We'll deliver a sample migration within 48 hours so you can see exactly what your data looks like in Front before committing.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I migrate SurveySparrow tickets to Front using a built-in importer?

No. Front has no native SurveySparrow importer — its documented history imports cover only Help Scout, Freshdesk, and Zendesk. No third-party migration tool currently offers a pre-built SurveySparrow connector either. You must extract data via SurveySparrow's REST API or file export, transform it, and import using Front's imported_messages API endpoint.

### Does SurveySparrow ticket export include comments and attachments?

No. SurveySparrow's built-in ticket export (Settings → Export Data) includes ticket fields, assignee details, and requester details in Excel or JSON format, but not ticket comments or attachments. You need the Ticket Comments API endpoint (GET /v3/tickets/:id/comments) to extract those separately.

### How do I handle public vs. private comments during migration?

SurveySparrow ticket comments carry a private flag. Public comments must become Front messages (imported via the imported_messages endpoint), while private comments must become Front conversation comments. If you flatten both into one type, you either lose customer history or leak internal notes.

### What are Front's API rate limits for importing messages?

Front's default API rate limit is 50 requests per minute, enforced per company (not per token). Tier 2 endpoints like imported_messages allow 5 requests per resource per second. Front returns x-ratelimit-remaining and retry-after headers on every response. Creating multiple tokens does not increase throughput.

### How long does a SurveySparrow to Front migration take?

For a small dataset (under 1,000 tickets), expect 1–3 days including setup, test migration, and validation. For 5,000+ tickets with comments and attachments, plan for 5–10 business days. At Front's default 50 req/min, 5,000 tickets with 2 comments each requires roughly 5 hours of API time before accounting for retries and other operations.
