Skip to content

SurveySparrow Ticket Management to Dixa Migration Guide

Technical guide to migrating SurveySparrow Ticket Management to Dixa. Covers API extraction, object mapping, rate limits, edge cases, and validation.

Roopi Roopi · · 30 min read
SurveySparrow Ticket Management to Dixa 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

Last verified against SurveySparrow API v3 and Dixa API v1, June 2025.

Migrating from SurveySparrow Ticket Management to Dixa means translating a feedback-first, survey-driven ticketing system into a conversation-centric, omnichannel support platform with real-time offer-based routing. These platforms solve fundamentally different problems, and no native migration path exists between them.

SurveySparrow's ticket schema is flat: tickets carry a subject, description, priority, status, assignee, team, custom fields, and a threaded comment stream — typically spawned from survey responses, NPS triggers, or form submissions. Dixa organizes everything around conversations with typed messages, end users, agents, tags, custom attributes, queues, and channel-specific metadata. You cannot lift-and-shift. Every ticket must be decomposed and reconstructed as a Dixa conversation with properly linked end users, ordered messages, and mapped metadata.

No built-in importer exists on either side for this direction. No third-party migration tool currently offers a verified SurveySparrow-to-Dixa connector. Every migration requires extracting data via SurveySparrow's REST API (v3) or native export, transforming it, and loading it through Dixa's API.

Warning

No native importer exists. Neither SurveySparrow nor Dixa offers a built-in migration tool for this direction. SurveySparrow supports ticket export in Excel and JSON format via Settings → Ticket Management → Export Data, and CSV export from the ticket list view. Dixa's Import Conversation endpoint (POST /v1/conversations/import) accepts historical conversations with preserved timestamps but requires end users to exist before import. Plan for a custom API-based migration from day one. (support.surveysparrow.com)

For related migrations, see our SurveySparrow to Front Migration Guide, SurveySparrow to HappyFox Migration Guide, and Dixa to Zendesk Migration Guide.

SurveySparrow vs Dixa: Architecture Differences That Shape the Migration

Before mapping any fields, understand how the two data models differ.

SurveySparrow Ticket Management is an add-on to SurveySparrow's survey platform. Tickets are created manually from survey responses, via automation rules (e.g., low NPS scores trigger ticket creation), or through direct submission. The schema is intentionally simple: subject, description, priority (Low/Medium/High/Urgent), status (Open/Pending/Resolved/Closed — extendable with custom values), assignee, team, custom fields, and a flat comment thread. There is no deep conversation threading, no multi-channel routing, and no concept of real-time agent queuing. (support.surveysparrow.com)

Dixa is an omnichannel contact center platform organized around real-time conversations. Every interaction — email, chat, phone, Messenger, WhatsApp, contact form — is a conversation with typed messages, an end user (requester), an assigned agent, queue placement, tags, custom attributes, and optional internal notes. Dixa's routing engine pushes conversations to agents based on skills, availability, and queue priority — fundamentally different from SurveySparrow's manual or workflow-based assignment model.

Dimension SurveySparrow Ticket Management Dixa
Core unit Ticket Conversation
Threading Flat comment thread Typed message sequence (inbound/outbound)
Requester model Contact (email-linked) End User (UUID, email/phone contact endpoints)
Assignment Agent + Team Agent + Queue (offer-based routing)
Channels Email, survey response, form Email, chat, phone, Messenger, WhatsApp, SMS, contact form
Custom data Custom ticket fields (including nested) Custom attributes (on conversations and end users)
Status model Open, Pending, Resolved, Closed (extendable) Open, Closed
Priority Low / Medium / High / Urgent No native priority field
Hierarchy Supports parent-child ticket links No native parent-child relationship
Automation Workflow triggers (ticket events) Flow Builder + queue routing rules
API auth OAuth 2.0 bearer token API key (bearer token)

Why Companies Migrate

Teams typically move from SurveySparrow to Dixa when:

  • They need to consolidate voice, email, chat, and social into a single routing engine — SurveySparrow lacks native phone and live chat channels
  • They are transitioning from asynchronous feedback management to real-time customer support with sub-60-second response targets
  • They want Dixa's skill-based, push routing (conversations offered to the best-matched available agent) instead of SurveySparrow's pull-based or manual assignment where agents self-select from a shared queue
  • Their agents are context-switching between SurveySparrow's ticket view and other tools for chat/phone — Dixa unifies all channels in one agent workspace

Some teams keep SurveySparrow for NPS, CSAT, and feedback programs while moving only operational support to Dixa. That split is viable and common — SurveySparrow can continue triggering surveys and forwarding results to Dixa via webhook or Zapier integration.

Migration Approaches: Every Viable Method Compared

There are five practical approaches. Each has clear trade-offs around complexity, fidelity, and engineering effort.

1. CSV/Excel Export → Scripted API Import

How it works: Export ticket data from SurveySparrow via Settings → Ticket Management → Export Data in Excel or JSON format, or export the current ticket list view as CSV with selected columns and filters. Clean and transform the export, then script the import into Dixa via API. (support.surveysparrow.com)

When to use it: Small datasets (under 500 tickets), one-time migration, no developer bandwidth for full API integration.

Pros: Simple extraction, no API rate-limit concerns on the source side, human-reviewable intermediate format.

Cons: The UI export omits comment-level metadata: the private/public flag, per-comment author details, and attachment URLs are often missing or incomplete compared to API extraction. Attachments require separate handling. Manual transformation is error-prone at scale. No automation.

Complexity: Low (extraction) / Medium (transformation and import)

2. API-to-API Migration (SurveySparrow REST API → Dixa API)

How it works: Extract tickets, contacts, comments, and custom fields programmatically via SurveySparrow's v3 REST API. Transform in-memory or in a staging layer. Load into Dixa via the End Users API and Conversation Import API. (developers.surveysparrow.com)

When to use it: Any dataset over 500 tickets, when comment/thread fidelity matters, when you need repeatable test migrations.

Pros: Full data fidelity including comments, timestamps, and authorship. Repeatable. Scriptable validation. Handles custom fields properly.

Cons: Must respect rate limits on both sides. Dixa's import endpoint requires end users to pre-exist — sequencing matters. Attachments need to be hosted at accessible URLs for Dixa to download.

Complexity: High

3. Middleware/Integration Platforms (Zapier, Make, Tray.io)

How it works: Use a no-code/low-code platform to connect SurveySparrow triggers to Dixa actions. SurveySparrow publishes Zapier integration guidance and has a Make connector. (support.surveysparrow.com)

When to use it: Ongoing sync of new tickets only. Never for bulk historical migration — iPaaS tools are designed for single-event processing, not batch data transfer.

Pros: No code required. Quick setup for forward-looking sync.

Cons: Not designed for bulk historical migration. Rate limits and execution time caps on middleware platforms (Zapier caps at 30 seconds per step; Make caps at 40 minutes per scenario) make large backfills impractical. No batch import support. Per-execution pricing adds up: at Zapier's Professional tier ($49/month for 2,000 tasks), migrating 10,000 tickets with 3–4 tasks each would consume 30,000–40,000 tasks. High risk of silent partial failures with no reconciliation mechanism.

Complexity: Low (for ongoing sync) / Impractical (for historical migration)

4. Custom ETL Pipeline

How it works: An API migration with durable infrastructure — raw staging tables, transformed canonical tables, checkpoints, replay support, audit logs, and often a temporary delta-sync layer. This is the right choice when compliance, repeatability, or cross-system reconciliation matter more than quick setup. (developers.surveysparrow.com)

When to use it: Enterprise, regulated, or repeatable migration programs.

Pros: Auditability, replay support, and better rollback posture. Full control over data mapping. Enables parallel test migrations without re-extracting.

Cons: Highest engineering cost. Risk of project sprawl if you overbuild for a one-time move. Typically requires PostgreSQL or similar for staging, adding infrastructure overhead.

Complexity: High

5. Managed Migration Service

How it works: A specialist team builds custom extraction, transformation, and loading scripts tailored to your specific data shape, custom fields, and business rules. Handles edge cases, validation, and rollback planning.

When to use it: Enterprise datasets, complex custom field mappings, zero tolerance for data loss, limited internal engineering bandwidth, or a hard delivery window.

Pros: Highest fidelity. Handles edge cases (duplicate contacts, orphaned tickets, attachment migration). Includes validation and QA. No internal engineering distraction.

Cons: External cost. Requires sharing API credentials with the migration partner. You depend on their timeline and availability.

Complexity: Low (for your team) / High (for the service provider)

Migration Approach Comparison

Approach Historical Data Ongoing Sync Scalability Complexity Best For
CSV/Excel Export Partial (missing comment metadata) No Small (<500) Low–Medium Quick one-off, small dataset
API-to-API Full Scriptable Medium–Large High Engineering teams, repeatable migrations
Middleware (Zapier/Make) No Yes Small Low Forward-looking sync only
Custom ETL Full Yes Enterprise High Regulated, compliance-heavy migrations
Managed Service Full Optional Any size Low (for you) Complex mappings, tight timelines

Recommended path by scenario:

  • Small business, <500 tickets, one-time move: CSV export + scripted API import
  • Mid-market, 500–50K tickets, dedicated dev: API-to-API with test migration cycles
  • Enterprise, 50K+ tickets, complex custom fields: Managed migration service
  • Ongoing sync after migration: Webhook-based forwarding via middleware or custom integration

When a Managed Migration Service Makes Sense

Building a SurveySparrow-to-Dixa migration in-house sounds straightforward until you hit the edge cases: duplicate contacts with different email casing, tickets with deleted requesters, custom field types that have no Dixa equivalent, attachment URLs that expire before Dixa can download them, and private comments that must be separated from public messages.

Hidden engineering costs of DIY migration:

  • Sequencing logic: Dixa requires end users to exist before conversation import. Contacts must be deduplicated and pre-created. Getting this wrong means your conversation import calls return 404 or 400 errors for missing requesters.
  • Rate-limit management: SurveySparrow's rate limits vary by plan tier (undocumented — in practice, we've observed throttling between 100–150 requests/minute on Business plans). Dixa enforces 10 requests/second per token with a burst allowance of 4 requests and a daily ceiling of 864,000 requests.
  • Error handling: Dixa returns HTTP 400 for missing requesters (requesterId not found), 404 for invalid conversation IDs when adding notes/tags, and 422 for malformed payloads. Each failure mode needs retry logic and dead-letter handling.
  • Private vs. public comment separation: SurveySparrow comments have a visibility flag (private: true/false). Public comments become Dixa messages; private comments must be loaded separately via Dixa's internal notes endpoint (POST /v1/conversations/{id}/notes). (developers.surveysparrow.com)
  • Attachment re-hosting: SurveySparrow attachment URLs may require authentication or expire after session timeout. Dixa's import endpoint downloads attachments from provided URLs at import time — if those URLs are inaccessible, attachments silently fail with no error in the API response.
  • Validation: Record-count comparisons, field-level spot checks, and comment integrity verification across two different data models.

Building a custom script typically takes a mid-level engineer 3 to 5 weeks of dedicated time once you account for understanding both APIs, writing the ETL logic, testing, handling rate limits, and fixing edge cases. This estimate comes from our experience running these migrations — the first week usually goes to API exploration and schema mapping, weeks 2–3 to core ETL and edge cases, and weeks 4–5 to test migrations, validation, and production cutover.

When to use ClonePartner: We handle contact deduplication, custom field translation, private/public comment separation, attachment re-hosting, and multi-pass validation — typically completing migrations in days, not weeks. If your team's time is better spent on product work than building one-off migration scripts, talk to us.

Pre-Migration Planning

Do not write a line of code or initiate a transfer without a strict pre-migration plan.

Data Audit Checklist

Before extracting anything, inventory what you actually have:

  • Tickets: Total count by status (Open, Pending, Resolved, Closed). Decide which statuses to migrate.
  • Contacts: Total contacts. Check for duplicates (same email, different records, different casing).
  • Comments/Replies: Average comments per ticket. Identify tickets with long threads (>20 comments). Note which comments are private.
  • Custom Fields: List all custom ticket fields with types (Dropdown, Text, Number, Date, nested fields). Map each to a Dixa custom attribute type (Text, Number, Select, Date).
  • Attachments: Count and total size. Check if attachment URLs are publicly accessible or require authentication. Test a sample URL in an incognito browser.
  • Teams/Agents: Map SurveySparrow teams and agents to Dixa teams and agents. Create agents in Dixa first.
  • Parent/Child Tickets: Identify any parent-child ticket relationships — Dixa has no native equivalent.
  • Workflows/Automations: Document SurveySparrow ticket workflows — these cannot be migrated and must be rebuilt in Dixa's Flow Builder. (support.surveysparrow.com)

Identify What Not to Migrate

Not all data deserves to move:

  • Tickets closed more than 2–3 years ago with no business relevance
  • Test tickets and spam
  • Deleted or trashed tickets (still in export but marked with deleted_at)
  • Contacts with no associated tickets
  • Draft or incomplete survey responses that never became tickets
  • Bounced email notifications and inactive users

Migration Strategy

Strategy Description Best For
Big Bang Migrate everything in one pass over a maintenance window Small datasets (<1,000 tickets), simple mappings
Phased Migrate by date range or ticket status (closed first, then open) Medium datasets (1,000–50,000), risk-averse teams
Incremental Migrate history first, then run a short delta sync until final cutover Large datasets (50,000+), no-downtime requirements

For most SurveySparrow-to-Dixa migrations, phased by status works well: migrate all closed/resolved tickets first (low risk, validates the pipeline), then migrate open/pending tickets in a final cutover window.

A big bang cutover deserves consideration when your dataset is small. Because Dixa relies on real-time routing, running both systems in parallel for active support creates routing conflicts — agents would need to monitor both platforms, and customers could receive responses from different systems. If you do need parallel operation, SurveySparrow ticket workflows can push to external APIs, and Dixa supports webhook-driven integration patterns for a short-lived delta-sync window.

Data Model and Object Mapping

This is where migrations succeed or fail. SurveySparrow's flat ticket model must map to Dixa's richer conversation model.

Object-Level Mapping

SurveySparrow Object Dixa Equivalent Notes
Ticket Conversation One ticket = one conversation with message thread
Contact (Requester) End User Must be created before conversation import; match by email
Agent Agent Must exist in Dixa; map by email to Dixa agent UUID
Team Team / Queue Teams map to Dixa teams; routing requires queue setup
Ticket Comment (public) Message (Inbound/Outbound) Each public comment becomes a typed message
Ticket Comment (private) Internal Note Use Dixa's separate notes endpoint
Custom Ticket Field Custom Attribute (Conversation) Must create attribute definitions in Dixa first
Priority Tag or Custom Attribute Dixa has no native priority field
Status Conversation State Open→Open, Pending→Open, Resolved→Closed, Closed→Closed
Tag Tag Direct mapping
SLA fields SLA Policy (rebuilt) Historical SLA data is metadata only; rebuild policies in Dixa
Parent/Child Ticket Custom attribute or backlink note No direct equivalent in Dixa
Attachment Message Attachment Must re-host to publicly accessible URL

Field-Level Mapping Table

SurveySparrow Field Type Dixa Field Transformation
ticket.id number Conversation.custom_attribute.legacy_id Store as reference; Dixa generates its own UUID
ticket.subject string Conversation.subject (email channel) Direct map
ticket.description string First inbound message content.value Becomes the first message in the conversation
ticket.description_html string First message (Text type) Strip unsupported HTML or convert to plain text
ticket.status enum Conversation.state Open→Open, Pending→Open, Resolved→Closed, Closed→Closed
ticket.priority enum Tag or custom attribute Map Low/Medium/High/Urgent to tags (e.g., priority:high)
ticket.created_at datetime Conversation.createdAt ISO 8601 format required
ticket.updated_at datetime No direct equivalent; use latest message timestamp
contact.email string End User contact endpoint Primary key for user matching
agent.email string agentId on assignment Look up agent UUID by email in Dixa
team.name string Team or Queue Map to Dixa team/queue by name
comment.body text/html Message.content Retain thread order using timestamps
comment.private boolean Private→Internal Note, Public→Message
custom_fields object Conversation custom attributes Create definitions first, then PATCH
first_response_due datetime Metadata only; rebuild SLA policy in Dixa
resolution_due datetime Metadata only

Handling Relationships and Dependencies

Creation order matters. Dixa's conversation import endpoint requires strict sequencing:

  1. End users must exist first. Create all contacts as Dixa end users before importing any conversations. Use POST /v1/endusers with email as the contact endpoint. If a requester doesn't exist, the import call returns HTTP 400.
  2. Agents must exist. All agents referenced in conversation assignments must be active Dixa agents. Map SurveySparrow agent emails to Dixa agent UUIDs. Agents are created through Dixa's admin UI, not the API.
  3. Custom attribute definitions must exist. Create all custom attribute definitions in Dixa (Settings → Custom Attributes) before patching attribute values onto conversations.
  4. Tags must exist or be created inline. Dixa supports adding tags to conversations via POST /v1/conversations/{id}/tags/{tagId}. Create tag definitions first via Dixa's admin UI or API.
  5. Conversations imported. Only after steps 1–4 are complete.
  6. Internal notes and metadata applied. After conversation creation, add private comments as notes and patch custom attributes.

Handling Custom Fields and Survey Data

SurveySparrow supports custom ticket fields of types: Dropdown, Text, Number, Date, and nested fields. Dixa supports custom attributes on both conversations and end users with types including Text, Number, Select (single-value dropdown), and Date.

The mapping is mostly 1:1, but watch for:

  • Multi-select dropdowns in SurveySparrow have no direct Dixa equivalent (Dixa's Select is single-value). Flatten to comma-separated text or split into multiple boolean attributes.
  • Checkbox fields — convert to boolean text or a Select with Yes/No options.
  • Nested ticket fields — SurveySparrow allows nested custom fields (e.g., Category → Subcategory) that need flattening before import. Store as category_subcategory or two separate attributes. (support.surveysparrow.com)
  • Field validation rules don't transfer. Rebuild in Dixa's custom attribute configuration.
  • Survey data (NPS scores, CSAT ratings, specific feedback text) should be mapped to Dixa custom attributes on the Conversation or User object. Alternatively, concatenate survey responses into an internal note at the beginning of the Dixa Conversation to provide context to agents. Complex matrix survey answers will look messy as raw text — format them into clean markdown tables for readability.

Migration Architecture

Data Flow: Extract → Transform → Load

┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│  SurveySparrow   │     │   Transform      │     │      Dixa        │
│  REST API v3     │────▶│   (Staging DB/   │────▶│   REST API v1    │
│                  │     │    Script)       │     │                  │
│  GET /v3/tickets │     │  - Map fields    │     │  POST /v1/       │
│  GET /v3/contacts│     │  - Dedupe        │     │    endusers      │
│  GET /v3/ticket- │     │  - Split public/ │     │  POST /v1/       │
│    comments      │     │    private       │     │    conversations/│
│  GET /v3/ticket- │     │  - Rehost        │     │    import        │
│    fields        │     │    attachments   │     │  POST /v1/.../   │
│  GET /v3/teams   │     │  - Build message │     │    notes         │
│  GET /v3/users   │     │    threads       │     │  PATCH custom    │
└──────────────────┘     └──────────────────┘     │    attributes    │
                                                  └──────────────────┘

SurveySparrow API Details

  • Base URL: https://api.surveysparrow.com (varies by data center; EU accounts may use https://eu-api.surveysparrow.com)
  • Auth: OAuth 2.0 bearer token, generated in Settings → Apps & Integrations → Custom Apps
  • Key endpoints:
    • GET /v3/tickets — paginated list of all tickets (default page size: 10, max: 100)
    • GET /v3/tickets/{id} — single ticket with full detail
    • GET /v3/tickets/{id}/comments — thread history including private/public flag
    • GET /v3/ticket-fields — custom field definitions
    • GET /v3/contacts — all contacts
    • GET /v3/teams — team definitions with business hours and round-robin settings
    • GET /v3/users — agent/user list
  • Rate limits: Not publicly documented with exact numbers. In practice, we've observed throttling (HTTP 429) at approximately 100–150 requests/minute on Business-tier plans. Implement exponential backoff with jitter on HTTP 429. (developers.surveysparrow.com)
  • Pagination: Follow next links in the links object of each response. Example response structure:
{
  "data": [
    {"id": 12345, "subject": "Billing issue", "status": {"name": "Open"}, ...}
  ],
  "links": {
    "next": "https://api.surveysparrow.com/v3/tickets?page=2&limit=10"
  }
}

Dixa API Details

  • Base URL (main API): https://dev.dixa.io/v1/
  • Exports URL: https://exports.dixa.io/v1/ (for bulk extraction and BI, not for import)
  • Auth: API key as bearer token, generated in Settings → Integrations → API Tokens
  • Key endpoints for import:
    • POST /v1/endusers — create end user
    • POST /v1/conversations/import — import historical conversation with messages and timestamps
    • POST /v1/conversations/{conversationId}/notes — add internal notes
    • PATCH /v1/conversations/{conversationId}/custom-attributes — set conversation custom attributes
    • PATCH /v1/endusers/{userId}/custom-attributes — set contact custom attributes
    • POST /v1/conversations/{conversationId}/tags/{tagId} — add tags
  • Rate limits: 10 requests/second per token, burst allowance of 4 requests, daily ceiling of 864,000 requests per token. HTTP 429 on breach — Dixa does not include a Retry-After header; implement your own backoff (start at 1 second, double on consecutive 429s, cap at 30 seconds). (docs.dixa.io)
  • Historical import channels: The documented import channel types are email and genericapimessaging. Conversations imported as genericapimessaging appear under a custom channel name you specify — they won't appear in native email or chat channel views in Dixa's UI. Agents will see them in their conversation list but the channel indicator will show the custom name (e.g., "SurveySparrowMigration"). (docs.dixa.io)
  • API access: Restricted to Growth plan and above. Confirm your Dixa plan includes API access before starting.
  • Import response structure: A successful import returns HTTP 201 with the new conversation ID. Failed imports return structured errors:
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "Requester with id '550e8400-...' not found",
    "details": []
  }
}

Watch for partialErrors in the response body on successful imports — messages within a conversation can fail individually (e.g., an inaccessible attachment URL) while the conversation itself is created.

Info

Rate limit math: At 10 req/s, importing 10,000 conversations (each requiring ~4 API calls: create end user, import conversation, add notes, patch attributes) takes roughly 67 minutes of pure API time — before accounting for backoff, retries, and validation queries. With realistic 429 responses and backoff, budget 2–3 hours. For 50,000 conversations, budget a full working day of API time spread across multiple tokens.

Tip

Keep Dixa load workers conservative at first. Historical import is one conversation per API call, so a smaller pool (2–3 concurrent workers) with backoff and idempotency keyed to SurveySparrow ticket ID is easier to make trustworthy than chasing peak throughput on day one. Increase concurrency only after validating your first 500 imports. (docs.dixa.io)

Step-by-Step Migration Process

Step 1: Extract Data from SurveySparrow

import requests
import json
import time
import random
 
SS_BASE_URL = "https://api.surveysparrow.com/v3"
SS_TOKEN = "your-surveysparrow-bearer-token"
 
def get_all_tickets():
    tickets = []
    url = f"{SS_BASE_URL}/tickets?limit=100"
    headers = {"Authorization": f"Bearer {SS_TOKEN}"}
    while url:
        resp = requests.get(url, headers=headers)
        if resp.status_code == 429:
            wait = 5 + random.uniform(0, 2)  # backoff with jitter
            time.sleep(wait)
            continue
        resp.raise_for_status()
        data = resp.json()
        tickets.extend(data.get("data", []))
        url = data.get("links", {}).get("next")  # follow pagination
    return tickets
 
def get_ticket_comments(ticket_id):
    """Fetch all comments for a ticket. Returns list of comment dicts.
    Each comment includes: id, body, private (bool), created_at, author_type, author_email.
    """
    url = f"{SS_BASE_URL}/tickets/{ticket_id}/comments"
    headers = {"Authorization": f"Bearer {SS_TOKEN}"}
    resp = requests.get(url, headers=headers)
    if resp.status_code == 429:
        time.sleep(5)
        return get_ticket_comments(ticket_id)
    resp.raise_for_status()
    return resp.json().get("data", [])
 
def get_all_contacts():
    contacts = []
    url = f"{SS_BASE_URL}/contacts?limit=100"
    headers = {"Authorization": f"Bearer {SS_TOKEN}"}
    while url:
        resp = requests.get(url, headers=headers)
        if resp.status_code == 429:
            time.sleep(5)
            continue
        resp.raise_for_status()
        data = resp.json()
        contacts.extend(data.get("data", []))
        url = data.get("links", {}).get("next")
    return contacts

Important: SurveySparrow has no bulk API for comments — they must be fetched per-ticket via GET /v3/tickets/{id}/comments. For 10,000 tickets, that's 10,000 additional API calls. At ~120 requests/minute (observed Business-tier limit), comment extraction alone takes ~83 minutes. Budget for this in your timeline.

Step 2: Transform Data

Map SurveySparrow tickets into Dixa's conversation import schema. Split public and private comments into separate payloads — public comments become conversation messages, private comments become internal notes loaded via a separate endpoint. (developers.surveysparrow.com)

def transform_ticket_to_dixa_conversation(ticket, comments, agent_map):
    """Transform a SurveySparrow ticket into a Dixa conversation import payload.
    
    Args:
        ticket: SurveySparrow ticket dict from GET /v3/tickets/{id}
        comments: List of comment dicts from GET /v3/tickets/{id}/comments
        agent_map: Dict mapping agent email → Dixa agent UUID
    
    Returns:
        Tuple of (conversation_payload, private_notes_list)
    """
    messages = []
    private_notes = []
 
    # First message: ticket description
    description = ticket.get("description") or ticket.get("subject") or "(No content)"
    messages.append({
        "content": {"value": description, "_type": "Text"},
        "attachments": [],
        "createdAt": ticket["created_at"],
        "_type": "InboundImport"  # From the requester
    })
 
    # Subsequent messages: comments sorted by timestamp
    for comment in sorted(comments, key=lambda c: c["created_at"]):
        is_private = comment.get("private", False)
        is_agent = comment.get("author_type") == "agent"
 
        if is_private:
            # Collect for separate internal notes endpoint
            private_notes.append({
                "body": comment["body"],
                "created_at": comment["created_at"],
                "author_email": comment.get("author_email")
            })
            continue
 
        msg = {
            "content": {"value": comment["body"], "_type": "Text"},
            "attachments": [],
            "createdAt": comment["created_at"],
            "_type": "OutboundImport" if is_agent else "InboundImport"
        }
        if is_agent and comment.get("author_email") in agent_map:
            msg["agentId"] = agent_map[comment["author_email"]]
        messages.append(msg)
 
    conversation = {
        "genericChannelName": "SurveySparrowMigration",
        "requesterId": None,  # Set after end user creation
        "requesterConnectionStatus": "Disconnected",
        "direction": "Inbound",
        "messages": messages,
        "createdAt": ticket["created_at"],
        "_type": "GenericConversationImport"
    }
 
    # If ticket is closed/resolved, add closing
    status_name = ticket.get("status", {}).get("name", "")
    if status_name in ["Resolved", "Closed"]:
        conversation["closing"] = {
            "closedAt": ticket.get("updated_at") or ticket["created_at"]
        }
 
    # If assigned, add assignment
    agent_email = ticket.get("agent", {}).get("email")
    if agent_email and agent_email in agent_map:
        conversation["assignment"] = {
            "agentId": agent_map[agent_email],
            "assignedAt": ticket["created_at"]
        }
 
    return conversation, private_notes

Step 3: Create End Users and Agents in Dixa

Before importing any conversations, create all contacts as Dixa end users. Ensure all SurveySparrow agents exist as Dixa agents (agents must be created through Dixa's admin UI — there is no public API endpoint for agent creation).

DIXA_BASE_URL = "https://dev.dixa.io/v1"
DIXA_TOKEN = "your-dixa-api-token"
 
# Track created users to avoid duplicates
created_users = {}  # email → Dixa UUID
 
def normalize_email(email):
    """Normalize email for deduplication."""
    return email.strip().lower() if email else None
 
def create_dixa_end_user(email, name=None, retry_count=0):
    """Create an end user in Dixa. Returns the user UUID.
    
    Handles deduplication by checking local cache first.
    Implements exponential backoff on rate limiting.
    """
    normalized = normalize_email(email)
    if not normalized:
        return None
    
    # Check cache first
    if normalized in created_users:
        return created_users[normalized]
    
    headers = {
        "Authorization": f"Bearer {DIXA_TOKEN}",
        "Content-Type": "application/json"
    }
    payload = {
        "email": normalized,
        "displayName": name or normalized
    }
    resp = requests.post(f"{DIXA_BASE_URL}/endusers", json=payload, headers=headers)
    
    if resp.status_code == 201:
        user_id = resp.json()["data"]["id"]
        created_users[normalized] = user_id
        return user_id
    elif resp.status_code == 409:
        # User already exists — look up by email
        # Dixa returns the existing user ID in the error response
        existing_id = resp.json().get("data", {}).get("id")
        if existing_id:
            created_users[normalized] = existing_id
            return existing_id
    elif resp.status_code == 429:
        if retry_count >= 5:
            log_error(f"Rate limited 5x creating user {normalized}, giving up")
            return None
        wait = min(2 ** retry_count + random.uniform(0, 1), 30)
        time.sleep(wait)
        return create_dixa_end_user(email, name, retry_count + 1)
    else:
        log_error(f"Failed to create end user {normalized}: {resp.status_code} {resp.text}")
        return None

Deduplication matters here. SurveySparrow may have multiple contacts with the same email (different casing, leading/trailing spaces). The normalize_email function above handles this, but also check for contacts that differ only by display name — these should map to a single Dixa end user.

Step 4: Import Conversations into Dixa

def import_dixa_conversation(conversation_payload, source_ticket_id, retry_count=0):
    """Import a historical conversation into Dixa.
    
    Args:
        conversation_payload: Transformed conversation dict
        source_ticket_id: SurveySparrow ticket ID for idempotency tracking
        retry_count: Current retry attempt
    
    Returns:
        Dixa conversation ID on success, None on failure
    """
    headers = {
        "Authorization": f"Bearer {DIXA_TOKEN}",
        "Content-Type": "application/json"
    }
    resp = requests.post(
        f"{DIXA_BASE_URL}/conversations/import",
        json=conversation_payload,
        headers=headers
    )
    
    if resp.status_code == 201:
        conv_id = resp.json()["data"]["id"]
        # Check for partial errors (e.g., failed attachment downloads)
        partial_errors = resp.json().get("partialErrors", [])
        if partial_errors:
            log_warning(f"Ticket {source_ticket_id} → Conv {conv_id}: partial errors: {partial_errors}")
        log_success(f"Ticket {source_ticket_id} → Conv {conv_id}")
        return conv_id
    elif resp.status_code == 429:
        if retry_count >= 5:
            log_error(f"Rate limited 5x importing ticket {source_ticket_id}, sending to dead letter queue")
            dead_letter_queue.append(source_ticket_id)
            return None
        wait = min(2 ** retry_count + random.uniform(0, 1), 30)
        time.sleep(wait)
        return import_dixa_conversation(conversation_payload, source_ticket_id, retry_count + 1)
    elif resp.status_code == 400:
        log_error(f"Bad request importing ticket {source_ticket_id}: {resp.text}")
        dead_letter_queue.append(source_ticket_id)
        return None
    else:
        log_error(f"Unexpected {resp.status_code} importing ticket {source_ticket_id}: {resp.text}")
        dead_letter_queue.append(source_ticket_id)
        return None

Design your script so it can be run multiple times without creating duplicate conversations: maintain a mapping file (source_ticket_id → dixa_conversation_id) and skip already-imported tickets on re-runs.

Step 5: Apply Metadata — Tags, Custom Attributes, and Internal Notes

After importing conversations:

  1. Add internal notes via POST /v1/conversations/{id}/notes — load all private comments collected during transformation. Preserve original timestamps in the note body since the notes endpoint uses the current time as the creation timestamp.
  2. Apply tags via POST /v1/conversations/{id}/tags/{tagId} — map SurveySparrow priority values to Dixa tags (e.g., priority:high). Map custom SurveySparrow statuses to tags (e.g., status:escalated).
  3. Set custom attributes via PATCH /v1/conversations/{id}/custom-attributes — include survey data (NPS scores, CSAT ratings) and the legacy SurveySparrow ticket ID.
  4. Store the legacy ID as a custom attribute (e.g., surveysparrow_ticket_id) on every migrated conversation — invaluable for troubleshooting, reconciliation, and linking back to original survey response data in SurveySparrow.

Step 6: Validate

Run validation queries against both systems:

  • Record counts: Total ticket count in SurveySparrow vs. total conversation count in Dixa (query via GET /v1/conversations with filters)
  • Sample comparison: Pull 5–10% of records (minimum 50) for field-level comparison
  • Message integrity: Verify public comment counts match message counts per conversation
  • Note integrity: Verify private comment counts match internal note counts per conversation
  • End user completeness: Confirm all unique requester emails in SurveySparrow have corresponding Dixa end users
  • Attachment spot-check: Open 10–20 conversations with attachments in Dixa's UI and verify files are downloadable
  • Private comment isolation: Confirm private comments were loaded as internal notes, not visible as public messages

Edge Cases and Common Failure Modes

Warning

The failures that hurt most are usually silent: a private comment loaded as a public message (exposing internal discussion to the customer), a custom status mapped to the wrong Dixa state, a parent/child ticket link dropped, or survey source metadata flattened into nothing. (support.surveysparrow.com)

Dixa Error Codes During Import

HTTP Status Error Code Cause Fix
400 INVALID_REQUEST Missing or invalid requesterId Ensure end user exists; check UUID format
400 INVALID_REQUEST Empty messages array Add at least one message (use subject as fallback content)
404 NOT_FOUND Invalid conversation ID on notes/tags endpoint Verify conversation was created; check for typos in UUID
409 CONFLICT End user with email already exists Use the returned existing user ID
422 UNPROCESSABLE_ENTITY Malformed payload (e.g., invalid date format) Validate ISO 8601 timestamps; check required fields
429 RATE_LIMITED Too many requests Backoff: start 1s, double on consecutive 429s, cap 30s

Data-Level Edge Cases

Duplicate contacts. SurveySparrow may have multiple contacts with the same email (different casing, leading spaces). Dixa's end user creation will return 409 or create duplicates. Fix: Normalize and deduplicate emails before import. Merge contact metadata from duplicates into a single end user record.

Deleted requesters. Tickets may reference contacts that have been deleted in SurveySparrow (contact returns 404 or has deleted_at set). Dixa's import requires a valid requesterId. Fix: Create a placeholder end user (e.g., deleted-user@migration.internal) and assign orphaned conversations to it. Store the original requester email as a custom attribute for reference.

Tickets with no description. Some SurveySparrow tickets auto-created from survey responses may have empty or null descriptions. Dixa's import endpoint requires at least one message with non-empty content. Fix: Use the ticket subject as the first message body. If both are empty, synthesize a message from survey response data: " [Auto-created from survey response. NPS score: 3. Feedback: Service was slow.]"

Attachment URL expiration. SurveySparrow attachment URLs may require authentication or expire after session timeout. Dixa's import endpoint downloads attachments from the provided URLs at import time — if those URLs are inaccessible, the conversation is created but attachments silently fail (no error in the response, the attachment simply doesn't appear). Fix: Before starting the Dixa import phase, download all attachments from SurveySparrow and re-host them to a publicly accessible location (S3 bucket with public-read ACL, or GCS with signed URLs valid for 7+ days).

Inline images. SurveySparrow inline images in HTML descriptions and comments are hosted on authenticated SurveySparrow URLs. Your script must: (1) parse HTML for <img src="..."> tags, (2) download each image, (3) upload to a public location, (4) replace the old URL with the new one in the message content.

Multi-select custom fields. SurveySparrow supports multi-select dropdowns. Dixa's custom attributes support only single-select (Select type). Fix: Flatten to comma-separated string in a Text attribute, or create multiple boolean attributes (e.g., category_billing: true, category_shipping: true).

Status mapping mismatch. SurveySparrow has four standard statuses (Open, Pending, Resolved, Closed) plus custom extensions (e.g., "Escalated", "Waiting on Vendor"). Dixa conversations are either Open or Closed. Mapping: Import Open and Pending as Open; Resolved and Closed as Closed. Preserve the original SurveySparrow status as a tag (e.g., ss-status:escalated) for reporting continuity and agent context.

Parent/child ticket links. SurveySparrow supports parent-child ticket relationships. No direct equivalent exists in Dixa's public API. Fix: Store parent/child relationships as custom attributes on the migrated conversations (e.g., parent_ticket_id: 12345) and add a backlink internal note: "This conversation was migrated from SurveySparrow child ticket #67890. Parent ticket: #12345."

System messages. SurveySparrow logs state changes (e.g., "Ticket assigned to John", "Priority changed from Low to High") in the comment thread alongside real comments. These are not customer-facing messages. Recommendation: Filter system messages during transformation and either drop them or consolidate them into a single internal note at the end of the conversation: "Activity log: [timestamp] Assigned to John. [timestamp] Priority changed to High."

N+1 query pattern on comments. SurveySparrow has no bulk API for comments — they must be fetched per-ticket via GET /v3/tickets/{id}/comments. For 10,000 tickets, that's 10,000 additional API calls just for comments. At observed rate limits of ~120 req/min, this adds ~83 minutes to extraction. Budget this into your timeline and consider parallelizing comment extraction across tickets (while respecting the overall rate limit).

HTML sanitization. SurveySparrow ticket descriptions may contain rich HTML (tables, embedded styles, scripts). Dixa's message content type is Text — strip all HTML tags or use a library like bleach (Python) to sanitize to safe HTML. Preserve paragraph breaks and links; remove inline styles, scripts, and iframes.

Limitations and Constraints

Dixa Limitations

  • No native priority field. Must use tags or custom attributes to represent priority.
  • Simplified conversation states. Only Open and Closed — no Pending, Resolved, Waiting, or custom states.
  • No bulk import endpoint. Conversations must be imported one at a time via POST /v1/conversations/import.
  • Historical import channels. Only email and genericapimessaging are documented for historical import. Imported conversations won't appear in native chat or phone channel views.
  • API access tier-gated. API access requires Growth plan or above. Essential/Starter plans do not include API access.
  • Rate limits per token, not per account. Splitting work across multiple API tokens can increase throughput (each token gets its own 10 req/s limit).
  • No native parent-child conversation support in the public API.
  • No bulk delete. If import validation fails, conversations must be deleted individually via API — there is no bulk cleanup endpoint.
  • Notes endpoint uses current timestamp. Internal notes created via API are timestamped at creation time, not at the original comment time. Embed the original timestamp in the note body.

SurveySparrow Limitations

  • Rate limits undocumented. Exact rate limits vary by plan and are not published. Monitor for 429 responses. Observed: ~100–150 req/min on Business-tier plans.
  • Export completeness. The UI export (Excel/JSON) may not include all comment metadata, private/public flags, or attachment URLs. API extraction is more complete and should be preferred for any migration over 100 tickets.
  • No bulk API for comments. Comments must be fetched per-ticket, creating N+1 query patterns that dominate extraction time.
  • Custom status extensions and nested fields may need flattening or controlled-vocab mapping during transformation.
  • Contact deduplication not enforced. SurveySparrow allows multiple contacts with the same email address.

Validation and Testing

Record Count Comparison

Metric SurveySparrow Count Dixa Count Match?
Total tickets/conversations
Open tickets → Open conversations
Closed tickets → Closed conversations
Total unique contacts/end users
Total public comments/messages
Internal notes (from private comments)
Conversations with attachments
Dead letter queue (failed imports)

Field-Level Validation

For a random sample of 50–100 records:

  • Subject / first message content matches
  • Requester email matches end user contact endpoint
  • Agent assignment matches
  • Custom attribute values match source custom fields
  • Created timestamp matches (within 1-second tolerance for timezone/rounding)
  • Conversation state matches expected status mapping
  • Private comments appear as internal notes, not public messages
  • Legacy ticket ID is stored as a custom attribute
  • Priority tag matches source priority value

UAT Process

  1. Test migration on a subset (e.g., last 30 days of tickets) into a Dixa sandbox or test organization. Contact Dixa support to provision a test environment if your plan doesn't include one.
  2. Have 2–3 agents review their migrated conversations in Dixa's UI — check that the conversation timeline reads correctly, agent names are right, and internal notes are not customer-visible.
  3. Verify search works — search for specific ticket subjects or requester emails in Dixa's search bar.
  4. Validate reporting — check that Dixa analytics (Settings → Analytics) reflect the imported conversation counts and date ranges.
  5. Test workflow triggers — ensure imported conversations don't fire Dixa Flow Builder automations unexpectedly (consider temporarily disabling automations during import).
  6. Check note visibility — confirm internal notes are not visible in the customer-facing conversation view.
  7. Test the dead letter queue — re-process any failed imports and verify fixes resolve the root cause.

Rollback Planning

Dixa does not offer a native bulk delete. If validation fails:

  • Before cutover: Delete imported conversations individually via DELETE /v1/conversations/{id} — at 10 req/s, deleting 10,000 conversations takes ~17 minutes. Script this as part of your migration tooling.
  • Best practice: Run the full migration in a Dixa test environment first. Only proceed to production after UAT passes.
  • Point of no return: Once agents start working on migrated conversations in production (adding replies, changing states), rollback becomes impractical. Define a clear go/no-go decision point with stakeholders before cutover.
  • Preserve your backup: Keep a full JSON export of SurveySparrow data stored independently (S3, GCS, or local). You'll need it for validation, re-runs, and as a fallback read-only reference.

For a deeper QA framework, use our Post-Migration QA Checklist. For general help desk migration planning, see How to Do a Help Desk Data Migration: The 7-Step Checklist.

Post-Migration Tasks

Once the data is verified, finalize the operational cutover.

  • Rebuild routing: Configure Dixa queues, skills, and Flow Builder rules to replace SurveySparrow's team-based assignment and workflow triggers. These cannot be migrated — they must be rebuilt from scratch. Map each SurveySparrow team to a Dixa queue, then configure skill-based routing rules within each queue.
  • Set up SLA policies: Rebuild in Dixa (Settings → SLAs) to replace SurveySparrow's first_response_due and resolution_due fields. Historical SLA data is metadata only — it cannot drive Dixa's SLA engine. New SLA policies will apply only to conversations created or reopened after the policy is active.
  • Redirect channels: Update DNS records (MX records for email), email forwarding rules, and website chat widgets to point to Dixa instead of SurveySparrow. Update any SurveySparrow-specific email addresses in auto-responders, knowledge base articles, and customer-facing documentation.
  • Agent training: Walk agents through Dixa's conversation-first UI. Key differences to highlight: agents no longer "pick" tickets from a list — they "accept" conversations pushed to them by Dixa's routing engine. Migrated conversations appear under the Generic API Messaging channel (or whatever genericChannelName you specified), not native email. Show agents how to find legacy ticket IDs via custom attributes.
  • Monitor for inconsistencies: For the first 2 weeks post-migration, spot-check 10 conversations per day against SurveySparrow source data. Monitor the dead letter queue for any failed imports. Watch for duplicate end users from new conversations (new customer emails) colliding with migrated data.

Best Practices

  1. Back up everything before starting. Export SurveySparrow data to JSON and store it independently (S3, GCS, or local storage). You'll need it for validation, re-runs, and as a read-only fallback.
  2. Run at least two test migrations before production cutover. The first reveals mapping issues; the second validates fixes. Keep metrics from each run (record counts, error rates, timing) to track improvement.
  3. Create end users before conversations. Dixa's import endpoint returns HTTP 400 if the requester doesn't exist. Batch-create all end users first, then import conversations.
  4. Use idempotency markers. Store the SurveySparrow ticket ID as a custom attribute on each Dixa conversation. Maintain a source_id → target_id mapping file. Design your script so it can be run multiple times without creating duplicate conversations.
  5. Migrate closed tickets first. Lower risk, validates the pipeline, and gives agents time to verify historical data before open tickets move.
  6. Run a delta migration. Migrate historical data over a weekend. On go-live day, run a delta script that only migrates tickets created or updated since the initial extraction timestamp.
  7. Separate public from private. Always split public and private comments before loading. Public comments become messages; private comments become internal notes via POST /v1/conversations/{id}/notes. Getting this wrong exposes internal discussions to customers.
  8. Log everything. Every API call, every error, every skipped record, every source-to-target ID mapping. You will need this for post-migration reconciliation. Write logs to a file, not just stdout.
  9. Don't migrate workflows — rebuild them. SurveySparrow's workflow automation model (trigger on ticket event → perform action) is fundamentally different from Dixa's Flow Builder (visual routing trees with conditions). Attempting to replicate workflows programmatically creates fragile configurations. Document existing SurveySparrow workflows, then rebuild equivalent logic in Dixa's Flow Builder.
  10. Disable Dixa automations during import. If you have Flow Builder rules or queue routing active, imported conversations may trigger automated responses or routing. Temporarily disable automations until import is complete.

For zero-downtime migration strategies, see our Zero-Downtime Help Desk Data Migration guide.

What This Migration Actually Takes

Migrating from SurveySparrow Ticket Management to Dixa is a translation exercise, not a copy-paste. SurveySparrow's flat ticket schema must be decomposed into Dixa's conversation model with proper end user linking, message threading, private/public comment separation, and metadata mapping. The API path is well-defined but requires careful sequencing, rate-limit management, and edge-case handling.

Realistic timeline estimates by dataset size:

Dataset Size Extraction Time Transformation Import Time Validation + UAT Total
<500 tickets 1–2 hours 2–4 hours 30 minutes 1 day 2–3 days
500–5,000 tickets 4–8 hours 1–2 days 2–4 hours 2–3 days 1–2 weeks
5,000–50,000 tickets 1–2 days 3–5 days 8–16 hours 3–5 days 2–4 weeks
50,000+ tickets 2–5 days 1–2 weeks 1–3 days 1 week 4–8 weeks

These estimates assume a single mid-level engineer working on the migration. Complex custom field mappings, attachment re-hosting, and parent-child ticket handling add to the higher end of each range.

If you'd rather spend your engineering hours on product work instead of one-off migration scripts, ClonePartner has done this before.

Frequently Asked Questions

Can I migrate tickets from SurveySparrow to Dixa without coding?
Not for historical data. SurveySparrow supports Excel/JSON/CSV export, but Dixa has no native import UI for historical conversations. You need scripted API calls to load data into Dixa via POST /v1/conversations/import. Middleware tools like Zapier can forward new tickets going forward but cannot bulk-import history.
What are Dixa's API rate limits for importing conversations?
Dixa enforces 10 requests per second per API token, with a burst allowance of 4 requests and a daily ceiling of 864,000 requests per token. There is no Retry-After header on 429 responses — implement exponential backoff with jitter in your migration script.
How do SurveySparrow ticket statuses map to Dixa?
SurveySparrow has four standard statuses (Open, Pending, Resolved, Closed) plus custom extensions. Dixa only has Open and Closed. Map Pending to Open and Resolved to Closed. Use tags or custom attributes in Dixa to preserve the original granular status for reporting.
How should I handle SurveySparrow private comments in Dixa?
Map public comments to Dixa conversation messages and private comments to Dixa internal notes. SurveySparrow exposes comment visibility with the private field, and Dixa has a dedicated notes endpoint (POST /v1/conversations/{id}/notes) separate from the conversation import.
How long does a SurveySparrow to Dixa migration take?
For under 500 tickets with simple fields, expect 1–2 days of scripting and testing. For 5,000–50,000 tickets with custom fields and attachments, plan for 1–3 weeks including test migrations and validation. A managed service like ClonePartner typically completes migrations in days.

More from our Blog

Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration
Help Desk

Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration

Ensure your help desk migration is a success with this comprehensive 20-point post-migration QA checklist. This expert guide details the 20 essential tests needed to validate your data integrity, system functionality, user-friendliness, and performance . Learn exactly how to check everything from ticket data, attachments, and knowledge base articles to critical workflows, automations, and integrations before you go live. This process is your final line of defense against lost tickets, broken workflows, and unhappy customers.

Raaj Raaj · · 8 min read