---
title: SurveySparrow Ticket Management to Dixa Migration Guide
slug: surveysparrow-ticket-management-to-dixa-migration-guide
date: 2026-07-27
author: Roopi
categories: [Migration Guide, Dixa]
excerpt: "Technical guide to migrating SurveySparrow Ticket Management to Dixa. Covers API extraction, object mapping, rate limits, edge cases, and validation."
tldr: "No native migration path exists. Extract SurveySparrow tickets via API, translate flat tickets into Dixa conversations, create end users before importing, and separate private comments into internal notes."
canonical: https://clonepartner.com/blog/surveysparrow-ticket-management-to-dixa-migration-guide/
---

# SurveySparrow Ticket Management to Dixa Migration Guide


*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](https://support.surveysparrow.com/hc/en/articles/20191180486045-Export-ticket-details/))

For related migrations, see our [SurveySparrow to Front Migration Guide](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-front-migration-guide/), [SurveySparrow to HappyFox Migration Guide](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-happyfox-migration-guide/), and [Dixa to Zendesk Migration Guide](https://clonepartner.com/blog/blog/dixa-to-zendesk-migration-the-ctos-technical-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](https://support.surveysparrow.com/hc/en/articles/7079244530077-An-overview-of-SurveySparrow-core-features/))

**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](https://support.surveysparrow.com/hc/en/articles/20191180486045-Export-ticket-details/))

**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](https://developers.surveysparrow.com/rest-apis/get-v-3-tickets/))

**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](https://support.surveysparrow.com/hc/en/articles/7080445429917-Integrating-SurveySparrow-with-Zapier/))

**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](https://developers.surveysparrow.com/rest-apis/get-v-3-tickets/))

**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](https://developers.surveysparrow.com/rest-apis/get-v-3-tickets-id-comments/))
- **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](https://cal.com/clonepartner/meet?duration=30).

## 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](https://support.surveysparrow.com/hc/en/articles/12633989912605-Workflows-in-Ticket-Management/))

### 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](https://support.surveysparrow.com/hc/en/articles/9809988198813-Create-custom-Ticket-fields/))
- **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](https://developers.surveysparrow.com/rest-apis/Introduction/))
- **Pagination:** Follow `next` links in the `links` object of each response. Example response structure:

```json
{
  "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](https://docs.dixa.io/docs/api-standards-rules))
- **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](https://docs.dixa.io/openapi/dixa-api/v1/conversations/postconversations))
- **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:

```json
{
  "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.

> [!NOTE]
> **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](https://docs.dixa.io/openapi/dixa-api/v1/conversations/postconversations))

## Step-by-Step Migration Process

### Step 1: Extract Data from SurveySparrow

```python
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](https://developers.surveysparrow.com/rest-apis/get-v-3-tickets-id-comments/))

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

```python
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

```python
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](https://support.surveysparrow.com/hc/en/articles/9809988198813-Create-custom-Ticket-fields/))

### 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](https://clonepartner.com/blog/blog/help-desk-data-migration-qa-checklist/). For general help desk migration planning, see [How to Do a Help Desk Data Migration: The 7-Step Checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-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](https://clonepartner.com/blog/blog/zero-downtime-help-desk-data-migration/).

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

> Need help migrating from SurveySparrow Ticket Management to Dixa? Our team handles the extraction, transformation, validation, and loading — including private/public comment separation, attachment re-hosting, and multi-pass validation. Typical turnaround: days, not weeks.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

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