Skip to content

SurveySparrow Ticket Management to Front Migration Guide

Technical guide to migrating from SurveySparrow Ticket Management to Front — covering API constraints, field mapping, migration methods, and edge cases.

Nachi Nachi · · 30 min read
SurveySparrow Ticket Management to Front Migration Guide
TALK TO AN ENGINEER

Planning a migration?

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

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

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) 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)

For a deeper look at Front's architecture, see Mastering Front: A Technical Deep Dive. If you are migrating from other platforms alongside SurveySparrow, review our guides on Zendesk to Front Migration and Help Scout to Front Migration.

Info

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)

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)

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)

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 and HubSpot-to-Front migrations 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.

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) 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:

{
  "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:

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) 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)

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)

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:

{
  "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
}
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).

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).

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

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.

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

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:

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)
Info

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:

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) 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)

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
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)

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 transferfirst_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)

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. For QA after cutover, our Post-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_IDFront_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_idfront_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.

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.

More from our Blog

Front Migration Checklist
Checklist/Front

Front Migration Checklist

Master your Front migration with this step-by-step checklist. Learn what can move via API, what requires manual setup, and how to protect your ticket history and workflows.

Tejas Mondeeri Tejas Mondeeri · · 9 min read