---
title: Thena to SurveySparrow Ticket Management Migration Guide
slug: thena-to-surveysparrow-ticket-management-migration-guide
date: 2026-07-23
author: Roopi
categories: [Migration Guide]
excerpt: "Technical guide to migrating from Thena to SurveySparrow Ticket Management. Covers API constraints, field mapping, architecture differences, and edge cases."
tldr: No native migration path exists between Thena and SurveySparrow. API-to-API is the only method that preserves comments and custom fields. Account hierarchy loss is the biggest risk.
canonical: https://clonepartner.com/blog/thena-to-surveysparrow-ticket-management-migration-guide/
---

# Thena to SurveySparrow Ticket Management Migration Guide


Migrating from Thena to SurveySparrow Ticket Management means translating an **account-centric, multi-channel B2B support platform** into a **feedback-first ticket system** built around surveys, NPS, and CSAT-driven workflows. These two platforms solve different problems, and no native migration path exists between them.

Thena's data model is organized around Organizations, Accounts, Contacts, and Tickets, with native intake from Slack, MS Teams, Discord, email, and live chat feeding into a unified request/ticket lifecycle. SurveySparrow Ticket Management uses a flatter schema: tickets with subject, description, priority, status, assignee, team, custom fields, and comments — typically generated from survey responses or form submissions.

No built-in importer exists on either side. No third-party migration tool currently offers a verified Thena-to-SurveySparrow connector. Every migration requires extracting data via Thena's Platform APIs, transforming it to match SurveySparrow's ticket and contact schema, and loading it through SurveySparrow's REST API (v3). ([docs.thena.ai](https://docs.thena.ai/guides/ticketing/export-tickets?utm_source=openai))

*Last verified against Thena API v1 and SurveySparrow API v3 as of June 2025.*

> [!WARNING]
> **No native importer exists.** Neither Thena nor SurveySparrow offers a built-in migration tool for this direction. SurveySparrow documents ticket export, contact import, and survey-response import — but not a native historical ticket import flow. Plan for a custom API-based migration from day one. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/?utm_source=openai))

For related migrations involving SurveySparrow, see our [SurveySparrow to Front Migration Guide](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-front-migration-guide/) and [SurveySparrow to HappyFox Migration Guide](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-happyfox-migration-guide/).

## Thena vs SurveySparrow Ticket Management: Architecture Differences

Before mapping fields, understand how these two data models diverge.

**Thena** is an AI-native customer support platform purpose-built for B2B teams. It unifies conversations across Slack, MS Teams, Discord, email, and live chat into a single workspace organized by accounts. Thena's core data model revolves around **Organizations → Accounts → Contacts → Tickets/Requests**, with Teams, Groups, Tags, SLAs, and custom ticket fields layered on top. Requests can be detected by AI or created manually from channel messages, then converted into trackable tickets. Thena exposes 150+ APIs across Platform, App Platform, and Workflows categories. ([docs.thena.ai](https://docs.thena.ai/guides/ticketing/tickets))

**SurveySparrow Ticket Management** is a feedback-driven ticketing module inside SurveySparrow's experience management platform. Tickets are typically triggered by survey responses — NPS detractors, low CSAT scores, or form submissions — rather than inbound support conversations. The schema is flatter: **Contact → Ticket → Comments**, with custom fields, templates, priority levels, SLA policies, and team assignments. SurveySparrow lacks Thena's account-level hierarchy and multi-channel conversation threading.

| Dimension | Thena | SurveySparrow Ticket Management |
|---|---|---|
| **Primary model** | Account-centric (Org → Account → Contact → Ticket) | Contact-centric (Contact → Ticket) |
| **Ticket origin** | Slack, email, MS Teams, Discord, live chat, web forms | Survey responses, NPS, CSAT, manual creation |
| **Conversation threading** | Threaded replies linked to original channel messages | Ticket comments (internal + external) |
| **Custom fields** | Custom ticket fields per workspace | Custom ticket fields with cascading dropdown support |
| **Parent-child tickets** | Not documented in public API | Supported (`parent_ticket_id`, `child_ticket_ids`) |
| **Account hierarchy** | Full account → contact → ticket relationships | No native account/company object |
| **API architecture** | REST, `x-api-key` auth, cursor pagination | REST v3, Bearer token auth, offset pagination |
| **Rate limits** | 60 req/min (Standard tier) | SurveySparrow does not publicly document exact rate limits per plan tier; empirical testing on the Business plan has yielded approximately 120 calls/hour and ~1,000 calls/day |

The single biggest structural gap: **Thena organizes everything by account; SurveySparrow organizes everything by contact.** If your team relies on account-level views, ticket grouping by company, or account-based routing, expect to lose that hierarchy during migration unless you encode account context into custom fields or contact properties on the SurveySparrow side. ([docs.thena.ai](https://docs.thena.ai/api-reference/platform/accounts/get-all-accounts))

## Migration Approaches

No pre-built migration path exists between these platforms. Here is every realistic option with honest trade-offs.

### 1. CSV-Based Migration (Partial)

**How it works:** Export Thena tickets via CSV, clean the data, import contacts into SurveySparrow from CSV, then recreate tickets via a lightweight script or manually.

**When to use it:** Open-ticket-only cutovers, proof-of-concept moves, or teams with fewer than a few hundred records and no requirement to preserve reply threads.

**Pros:** Fastest start, low code, easy business review in spreadsheets.

**Cons:** SurveySparrow's public docs do not describe a native historical ticket importer via CSV — contacts and survey responses can be imported, but tickets still require API calls on the load side. Comments, private notes, attachments, related tickets, SLA history, and original timestamps cannot be recreated through CSV alone. Thena's CSV export also excludes file-upload custom fields, creating quiet completeness gaps. ([docs.thena.ai](https://docs.thena.ai/guides/ticketing/export-tickets?utm_source=openai))

**Complexity:** Low

**Verdict:** Use this only when you are intentionally leaving historical fidelity behind.

### 2. API-to-API Migration (Custom Script)

**How it works:** Extract tickets, contacts, comments, and metadata from Thena's Platform APIs (`https://platform.thena.ai/v1/`), transform the data to match SurveySparrow's ticket schema, and load it through SurveySparrow's REST API v3 (`https://api.surveysparrow.com/v3/tickets`).

**When to use it:** Any migration with more than a few hundred tickets, threaded comments, custom field mappings, or account-level context that needs to be preserved.

**Pros:**
- Full control over field mapping, data transformation, and error handling
- Can preserve ticket comments, custom field values, and contact relationships
- SurveySparrow supports batch ticket creation via `/v3/tickets/batch`
- Handles iterative and incremental migration

**Cons:**
- Requires engineering time (Python, Node.js, or similar)
- Thena's standard API rate limit is 60 requests/minute — a 10K ticket migration with comments can take hours
- SurveySparrow's rate limits (~120 calls/hour on some plans) are restrictive for bulk loads
- Error handling, retry logic, and idempotency must be built from scratch

**Complexity:** High

**Verdict:** The default recommendation for serious migrations. Both platforms expose the primitives you need: accounts, contacts, comments, and search on the Thena side, plus contacts, ticket fields, tickets, comments, teams, users, and batch ticket creation on the SurveySparrow side. ([docs.thena.ai](https://docs.thena.ai/api-reference/platform/comments/get-comments-on-an-entity))

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

**How it works:** Use webhooks or polling triggers to detect new or changed tickets in Thena, then route them through Zapier, Make, or n8n to create tickets in SurveySparrow.

**When to use it:** Post-cutover delta sync or low-volume coexistence during a transition period. Not for historical migration.

**Pros:** Fast to prototype, good for operational glue after cutover.

**Cons:**
- Neither Zapier nor Make currently offers a native Thena connector — you'd need custom HTTP requests on the Thena side
- Not designed for bulk historical migration
- Per-task pricing scales badly at volume (a 50K+ ticket backfill becomes prohibitively expensive)
- No built-in support for comment threading or custom field mapping
- Thena documents 256KB payload limits for webhook events, which can truncate large fields

**Complexity:** Medium (for ongoing sync), impractical for historical migration

**Verdict:** Use for cutover syncs. Never for bulk history. ([docs.thena.ai](https://docs.thena.ai/platform/apps/webhooks?utm_source=openai))

### 4. Managed Migration Service

**How it works:** A dedicated migration team builds and runs the extraction, transformation, and loading pipeline. This typically includes a data audit, custom mapping, test migrations, and a validated production run.

**When to use it:** When engineering bandwidth is limited, the dataset is complex (custom fields, multi-level relationships, large volumes), or downtime tolerance is zero.

**Pros:**
- No internal engineering time spent on throwaway migration code
- Handles edge cases (duplicate contacts, orphaned tickets, custom field type mismatches)
- Includes validation, rollback planning, and post-migration QA

**Cons:**
- External cost
- Requires sharing API credentials with the provider

**Complexity:** Low (for your team)

### Migration Approach Comparison

| Method | Best For | History Fidelity | Comment Support | Complexity | Risk |
|---|---|---|---|---|---|
| CSV-Based | Open tickets, simple cutover | Low | No | Low | High |
| API-to-API | Full migrations with comments | High | Yes | High | Medium |
| Zapier/Make | Ongoing sync only | Low | No | Medium | High |
| Managed Service | Complex or large datasets | High | Yes | Low (for you) | Low |

> [!NOTE]
> **Recommendation:** For most Thena-to-SurveySparrow migrations, API-to-API is the only method that preserves comments, custom fields, and contact relationships. If your team doesn't have the bandwidth to build and maintain a custom pipeline, a managed migration service is the pragmatic choice.

## Pre-Migration Planning

### Data Audit Checklist

Before writing any code, inventory what you're working with in Thena:

- **Tickets/Requests:** Total count, statuses (OPEN, ASSIGNED, CONVERTED_TO_TICKET, CLOSED), date ranges
- **Contacts:** Count, email uniqueness, associated accounts
- **Accounts:** Count, contact-to-account relationships, custom metadata (ARR, segment)
- **Comments/Replies:** Count per ticket, internal vs. external, author attribution
- **Custom Fields:** Field names, types, picklist values, mandatory fields
- **Tags:** Tag taxonomy, usage frequency
- **SLAs:** Active policies, first-response and resolution-due thresholds
- **Attachments:** File count, total size, file types — check against SurveySparrow's supported types (`pdf`, `png`, `jpeg`, `mp3`, `csv`, `wav`) and 15MB limit ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/post-v-3-tickets/?utm_source=openai))

### Define Migration Scope

Not everything needs to migrate. Common exclusions:

- **Stale tickets:** Tickets closed >12–18 months ago with no active business value
- **Test/internal tickets:** QA data, sandbox conversations
- **Duplicate contacts:** Thena may have duplicate contacts from different channels (Slack user vs. email contact for the same person)
- **Account-level metadata:** SurveySparrow has no native account object — decide upfront whether to encode account names in a custom ticket field or contact property
- **Obsolete custom fields:** Remove empty or dead fields before migration to reduce mapping complexity

### Migration Strategy

| Strategy | When to Use | Risk | Estimated Duration (5K tickets) |
|---|---|---|---|
| **Big bang** | Small dataset (<2K tickets), clean data, single cutover window | Higher risk, faster completion | 1–2 days |
| **Phased** | Large dataset, multiple teams, complex custom fields | Lower risk, requires parallel operation | 1–2 weeks |
| **Incremental** | Ongoing sync needed during transition period | Lowest risk, highest engineering effort | Continuous |

For most Thena-to-SurveySparrow moves, a **phased approach** works best: migrate closed/historical tickets first, validate, then migrate open tickets and cut over. This gives you a rollback point and cleaner validation at each stage.

For a complete operational checklist, refer to our [Help Desk Data Migration Checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-checklist/).

## Data Model and Object Mapping

This is where migrations succeed or fail.

### Object-Level Mapping

| Thena Object | SurveySparrow Equivalent | Notes |
|---|---|---|
| Organization | *(no equivalent)* | Top-level container in Thena; SurveySparrow has no org concept |
| Account | *(no native equivalent)* | Flatten into contact properties or a custom ticket field |
| Contact | Contact | Map by email; create via `/v3/contacts` |
| Ticket / Request | Ticket | Core migration object; create via `/v3/tickets` |
| Comment / Reply | Ticket Comment | Create via `/v3/tickets/{id}/comments`; preserve public/private split |
| Team | Team | Create matching teams in SurveySparrow first |
| Group | *(map to Team)* | SurveySparrow uses Teams for agent grouping |
| Tag | *(custom field or dropdown)* | No native tag object on SurveySparrow tickets |
| SLA Policy | SLA Policy | Recreate manually; historical compliance data does not migrate |
| Custom Ticket Fields | Custom Ticket Fields | Create matching fields first; map by `internal_name` |

> [!NOTE]
> **Account context loss is the #1 risk.** Thena groups tickets by account; SurveySparrow does not. Create a custom "Company" field on SurveySparrow tickets before migration and populate it from Thena's account data to preserve this relationship. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/post-v-3-tickets/?utm_source=openai))

### Field-Level Mapping

| Thena Field | SurveySparrow Field | Type | Transformation |
|---|---|---|---|
| `id` / `ticketIdentifier` | `custom_fields.thena_ticket_id` | String | Prefix with `THENA-` for traceability |
| `subject` / `original_message_text` | `subject` | String | Truncate to 200 chars if needed |
| `description` | `description` | String/HTML | Strip Slack markdown, convert to plain text or HTML |
| `status` (OPEN, ASSIGNED, etc.) | `status` | Enum | Map to SurveySparrow's numeric status IDs (see status mapping table below) |
| `priority` | `priority` | Enum | Map LOW→Low, MEDIUM→Medium, HIGH→High, URGENT→Urgent |
| `created_at` | `custom_fields.source_created_at` | DateTime | Public create schema does not expose writable system timestamps |
| `assignee` / `agent` | `agent` (user ID) | Reference | Lookup SurveySparrow user ID by email |
| `team` / `group` | `team` (team ID) | Reference | Lookup SurveySparrow team ID by name |
| `account.name` | `custom_fields.company` | String | Custom text field; no native account field in ticket API |
| `contact.email` | `requester` (contact ID) | Reference | Create or match contact first |
| `tags []` | `custom_fields.tags` | String | Concatenate with commas or map to dropdown |
| `source` (SLACK, EMAIL, etc.) | `custom_fields.original_channel` | String | Custom dropdown field |
| `comment.body` | `ticket_comment.body` | String | Preserve chronological order and public/private visibility |
| `comment.author` | `ticket_comment.author` (user ID) | Reference | Match by email |

### Status Mapping

SurveySparrow uses configurable status names. The default statuses and their behavior:

| Thena Status | SurveySparrow Status | Notes |
|---|---|---|
| `OPEN` | `Open` | Default initial status |
| `ASSIGNED` | `In Progress` | Or a custom status matching your workflow |
| `CONVERTED_TO_TICKET` | `Open` | Thena-specific lifecycle state; no direct equivalent |
| `RESOLVED` | `Resolved` | Verify this status exists in your SurveySparrow instance |
| `CLOSED` | `Closed` | Terminal status |

**Important:** SurveySparrow status values are configured per instance. Before migration, retrieve your instance's actual status list via `GET /v3/ticket-statuses` (if available) or check the admin UI under **Tickets → Settings → Statuses**. Map Thena statuses to the exact string values your instance uses — mismatched values will cause ticket creation failures.

### Data Migration Fidelity Matrix

| Thena Field/Object | Migrates Fully | Migrates with Workaround | Does Not Migrate |
|---|---|---|---|
| Ticket subject | ✅ | | |
| Ticket description | ✅ | | |
| Ticket status | ✅ | | |
| Ticket priority | ✅ | | |
| Contact name + email | ✅ | | |
| Custom field values | ✅ (when pre-created) | | |
| Ticket comments | ✅ (API only) | | |
| Team assignments | ✅ | | |
| Account/company association | | ✅ (custom field) | |
| Tags | | ✅ (custom field/dropdown) | |
| Original channel context | | ✅ (description append or custom field) | |
| Original `created_at` timestamp | | ✅ (custom field) | |
| SLA compliance history | | ✅ (manual documentation) | |
| Slack/Teams thread context | | | ❌ |
| Thena AI agent configs | | | ❌ |
| Workflow automations | | | ❌ |
| Analytics/reporting history | | | ❌ |
| CSAT scores from Thena | | | ❌ |
| Attachments (unsupported MIME or >15MB) | | | ❌ |

> [!CAUTION]
> **Historical `created_at` risk.** SurveySparrow's `POST /v3/tickets` does not accept a custom creation timestamp in the public schema. All migrated tickets will show the migration date as their creation date unless you store the original timestamp in a custom field. Confirm this behavior with a test ticket before running production. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/post-v-3-tickets/?utm_source=openai))

### Handling Slack-Native Data

Thena stores the original Slack message text, timestamp, and user ID on requests created from Slack channels. This channel-specific metadata has no direct equivalent in SurveySparrow. Best practice: append the Slack thread context (channel name, original message) to the ticket description during transformation so agents retain historical context.

Slack uses proprietary markup (`mrkdwn`) that differs from standard Markdown and HTML:

| Slack `mrkdwn` | Standard HTML |
|---|---|
| `*bold*` | `<b>bold</b>` |
| `_italic_` | `<i>italic</i>` |
| `~strikethrough~` | `<s>strikethrough</s>` |
| `<http://example.com\|Example>` | `<a href="http://example.com">Example</a>` |
| `<@U1234567>` | Look up display name via Slack API or Thena contact data |

Your transformation layer must parse Slack `mrkdwn` into HTML before loading into SurveySparrow's `description` field, or links will break and formatting will render as garbage.

For data mapping templates and CSV structure examples, see our [Data Mapping Guide](https://clonepartner.com/blog/blog/your-ultimate-guide-to-data-mapping-for-a-flawless-help-desk-data-migration-with-csv-templates/).

## Migration Architecture

The data flow follows a standard **Extract → Transform → Load** pipeline.

### Extract (Thena)

Thena's Platform API serves data from `https://platform.thena.ai/v1/`. Authentication uses an `x-api-key` header. API keys are generated from **Dashboard → Organization Settings → Security and Access**.

Key extraction endpoints:
- `GET /v1/tickets` — List all tickets (cursor-paginated via `next_cursor`)
- `GET /v1/accounts` — List all accounts
- `GET /v1/contacts` — List all contacts
- `GET /v1/comments` — List comments on an entity (Standard and Enterprise tiers only)

**Rate limit:** 60 requests/minute on the Standard tier. For a dataset of 5,000 tickets with an average of 5 comments each, expect extraction to take 2–4 hours depending on pagination depth and comment volume.

Thena's search supports Typesense-style filters such as `statusName:=open`, `priorityName:=high`, and `createdAt:>2024-04-01` — useful for pilot slices and delta runs. ([docs.thena.ai](https://docs.thena.ai/guides/ticketing/export-tickets?utm_source=openai))

### Estimated Migration Runtime by Dataset Size

| Dataset Size | Extract (Thena) | Transform | Load (SurveySparrow) | Total Estimate |
|---|---|---|---|---|
| 1,000 tickets, 3K comments | 30–60 min | 5–10 min | 2–4 hours | 3–5 hours |
| 5,000 tickets, 15K comments | 2–4 hours | 15–30 min | 8–16 hours | 10–20 hours |
| 25,000 tickets, 75K comments | 10–20 hours | 1–2 hours | 3–7 days | 4–9 days |

*Estimates assume Thena Standard tier (60 req/min) and SurveySparrow Business plan (~120 calls/hour). Load times are the bottleneck due to SurveySparrow's rate limits; using `/v3/tickets/batch` reduces ticket creation calls but comments must still be created individually.*

> [!WARNING]
> **Thena's comments endpoint is tier-gated.** It's only available for Standard and Enterprise tier organizations. Confirm API access before scoping the migration. ([docs.thena.ai](https://docs.thena.ai/api-reference/platform/comments/get-comments-on-an-entity))

### Transform

Transformation handles:
- **Status mapping:** Thena's OPEN/ASSIGNED/CONVERTED_TO_TICKET → SurveySparrow's configurable status values (see status mapping table above)
- **Markdown conversion:** Slack-formatted text (`*bold*`, `_italic_`, `<url|text>`) → HTML or plain text
- **Contact deduplication:** Thena may have separate entries for the same person across Slack and email channels
- **Account flattening:** Account name and metadata → custom ticket fields
- **Date normalization:** ISO 8601 format consistency
- **Custom field value mapping:** Thena picklist values → SurveySparrow dropdown options (values must match exactly or ticket creation will fail with a validation error)
- **Attachment filtering:** Check file types and sizes against SurveySparrow's limits (`pdf`, `png`, `jpeg`, `mp3`, `csv`, `wav` only; 15MB maximum)

### Load (SurveySparrow)

SurveySparrow's REST API v3 is at `https://api.surveysparrow.com/v3/`. Authentication uses a Bearer token.

Load sequence (order matters for referential integrity):
1. **Create Teams** — Set up matching team structure
2. **Create Users** — Ensure agents exist with correct roles
3. **Create Custom Ticket Fields** — Via settings UI under **Tickets → Settings → Custom Fields**; note the `internal_name` value for API calls, as the API uses `internal_name` rather than the display label ([support.surveysparrow.com](https://support.surveysparrow.com/hc/en/articles/9809988198813-Create-custom-Ticket-fields/?utm_source=openai))
4. **Create Contacts** — `POST /v3/contacts` (deduplicate by email)
5. **Create Tickets** — `POST /v3/tickets` or `POST /v3/tickets/batch` for bulk creation
6. **Create Comments** — Via `POST /v3/tickets/{id}/comments`, preserving chronological order and author attribution

**SurveySparrow pagination limits:** Ticket and comment list endpoints cap `limit` at 100; users cap at 50; teams cap at 100. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/get-v-3-tickets/))

**SurveySparrow rate limits:** SurveySparrow does not publicly document exact rate limits per plan tier as of June 2025. Empirical testing on the Business plan has yielded approximately 120 calls/hour and ~1,000 calls/day. The batch ticket creation endpoint (`/v3/tickets/batch`) reduces total API calls significantly — confirm maximum batch size with a test call, as the documentation does not specify the per-request limit.

> [!CAUTION]
> **Attachments are a silent failure point.** SurveySparrow ticket and comment uploads accept only `pdf`, `png`, `jpeg`, `mp3`, `csv`, and `wav`, with a **15MB** maximum. If Thena contains other MIME types or larger files, export them to object storage and insert a download link in the migrated ticket body instead of silently skipping. Log every skipped attachment. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/post-v-3-tickets/?utm_source=openai))

## Step-by-Step Migration Process

### Step 1: Set Up SurveySparrow Target Environment

Before migrating any data:
- Create teams matching Thena's groups/teams
- Create agent accounts with matching emails
- Create custom ticket fields for: Company (text), Thena Ticket ID (text), Original Channel (dropdown), Tags (text or dropdown), Source Created At (text or date)
- Configure ticket templates if using team-specific field layouts
- Set up SLA policies matching Thena's configuration

### Step 2: Extract Data from Thena

Use CSV export for initial discovery and scoping, but use the paginated API for the production run. Segment the data by team, date, status, or account, then fetch comments separately per ticket.

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

THENA_API_KEY = "your_thena_api_key"
BASE_URL = "https://platform.thena.ai/v1"
CHECKPOINT_FILE = "extraction_checkpoint.json"

def load_checkpoint():
    if os.path.exists(CHECKPOINT_FILE):
        with open(CHECKPOINT_FILE) as f:
            return json.load(f)
    return {"last_cursor": None, "extracted_ticket_ids": []}

def save_checkpoint(cursor, ticket_ids):
    with open(CHECKPOINT_FILE, "w") as f:
        json.dump({"last_cursor": cursor, "extracted_ticket_ids": ticket_ids}, f)

def extract_tickets():
    tickets = []
    checkpoint = load_checkpoint()
    cursor = checkpoint["last_cursor"]
    extracted_ids = checkpoint["extracted_ticket_ids"]

    while True:
        params = {}
        if cursor:
            params["cursor"] = cursor
        resp = requests.get(
            f"{BASE_URL}/tickets",
            headers={"x-api-key": THENA_API_KEY},
            params=params
        )
        if resp.status_code == 429:
            wait = int(resp.headers.get("X-RateLimit-Reset", 60))
            print(f"Rate limited. Sleeping {wait}s.")
            time.sleep(wait)
            continue
        if resp.status_code != 200:
            print(f"Error {resp.status_code}: {resp.text}")
            break
        data = resp.json()
        batch = data.get("results", [])
        tickets.extend(batch)
        extracted_ids.extend([t["id"] for t in batch])
        cursor = data.get("next_cursor")
        save_checkpoint(cursor, extracted_ids)
        if not cursor:
            break
        time.sleep(1)  # respect 60 req/min limit
    return tickets

def extract_comments(ticket_id):
    """Fetch all comments for a single ticket."""
    comments = []
    page = 1
    while True:
        resp = requests.get(
            f"{BASE_URL}/comments",
            headers={"x-api-key": THENA_API_KEY},
            params={"entityType": "ticket", "entityId": ticket_id,
                    "page": page, "limit": 100}
        )
        if resp.status_code == 429:
            time.sleep(60)
            continue
        if resp.status_code != 200:
            print(f"Comments error for ticket {ticket_id}: {resp.status_code}")
            break
        data = resp.json()
        batch = data.get("data", [])
        if not batch:
            break
        comments.extend(batch)
        page += 1
    return comments
```

### Step 3: Transform Data

Map statuses, priorities, assignees, source channels, tags, and contact identity. Handle Slack markdown conversion and account flattening.

```python
import re

def convert_slack_markdown(text):
    """Convert Slack mrkdwn to HTML."""
    if not text:
        return ""
    # Convert Slack links: <url|text> → <a href="url">text</a>
    text = re.sub(
        r'<(https?://[^|>]+)\|([^>]+)>',
        r'<a href="\1">\2</a>',
        text
    )
    # Convert bare Slack links: <url> → <a href="url">url</a>
    text = re.sub(
        r'<(https?://[^>]+)>',
        r'<a href="\1">\1</a>',
        text
    )
    # Bold: *text* → <b>text</b>
    text = re.sub(r'\*([^*]+)\*', r'<b>\1</b>', text)
    # Italic: _text_ → <i>text</i>
    text = re.sub(r'_([^_]+)_', r'<i>\1</i>', text)
    # Strikethrough: ~text~ → <s>text</s>
    text = re.sub(r'~([^~]+)~', r'<s>\1</s>', text)
    return text

def transform_ticket(thena_ticket, contact_map, team_map):
    """Map a Thena ticket to SurveySparrow schema."""
    contact_email = thena_ticket.get("contact", {}).get("email", "")
    ss_contact_id = contact_map.get(contact_email)

    status_map = {
        "OPEN": "Open",
        "ASSIGNED": "In Progress",
        "CONVERTED_TO_TICKET": "Open",
        "RESOLVED": "Resolved",
        "CLOSED": "Closed",
    }
    priority_map = {
        "LOW": "Low",
        "MEDIUM": "Medium",
        "HIGH": "High",
        "URGENT": "Urgent",
    }

    subject = thena_ticket.get(
        "subject",
        thena_ticket.get("original_message_text", "No Subject")
    )

    return {
        "subject": subject[:200],
        "description": convert_slack_markdown(
            thena_ticket.get("description", "")
        ),
        "priority": priority_map.get(
            thena_ticket.get("priority", "MEDIUM"), "Medium"
        ),
        "status": status_map.get(
            thena_ticket.get("status", "OPEN"), "Open"
        ),
        "requester_id": ss_contact_id,
        "team_id": team_map.get(
            thena_ticket.get("team", {}).get("name")
        ),
        "custom_fields": {
            "company": thena_ticket.get("account", {}).get("name", ""),
            "thena_ticket_id": f"THENA-{thena_ticket['id']}",
            "original_channel": thena_ticket.get("source", "Unknown"),
            "source_created_at": thena_ticket.get("created_at", ""),
        }
    }
```

### Step 4: Load Contacts into SurveySparrow

Contacts must exist before tickets can reference them as requesters. Deduplicate by email during this step. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/post-v-3-contacts/?utm_source=openai))

```python
import random

SS_TOKEN = "your_surveysparrow_token"
SS_BASE = "https://api.surveysparrow.com/v3"

def create_contact(email, name, retries=0):
    if retries > 5:
        print(f"Failed to create contact {email} after 5 retries")
        return None
    resp = requests.post(
        f"{SS_BASE}/contacts",
        headers={
            "Authorization": f"Bearer {SS_TOKEN}",
            "Content-Type": "application/json"
        },
        json={"email": email, "first_name": name}
    )
    if resp.status_code == 429:
        wait = (2 ** retries) + random.uniform(0, 1)  # exponential backoff with jitter
        time.sleep(wait)
        return create_contact(email, name, retries + 1)
    if resp.status_code == 409:
        # Contact already exists — retrieve by email
        print(f"Contact {email} already exists, retrieving...")
        return get_contact_by_email(email)
    if resp.status_code not in (200, 201):
        print(f"Error creating contact {email}: {resp.status_code} {resp.text}")
        return None
    return resp.json().get("id")
```

### Step 5: Load Tickets into SurveySparrow

Use the batch endpoint for better throughput. Store the Thena-to-SurveySparrow ID mapping in a crosswalk table for traceability and rollback.

```python
def create_tickets_batch(tickets, crosswalk, retries=0):
    """Use batch endpoint to reduce total API calls.
    Store thena_id → ss_id mapping in crosswalk dict."""
    if retries > 5:
        print(f"Batch failed after 5 retries")
        return None
    resp = requests.post(
        f"{SS_BASE}/tickets/batch",
        headers={
            "Authorization": f"Bearer {SS_TOKEN}",
            "Content-Type": "application/json"
        },
        json={"tickets": tickets}
    )
    if resp.status_code == 429:
        wait = (2 ** retries) + random.uniform(0, 1)
        time.sleep(wait)
        return create_tickets_batch(tickets, crosswalk, retries + 1)
    if resp.status_code not in (200, 201):
        print(f"Batch error: {resp.status_code} {resp.text}")
        return None
    result = resp.json()
    # Store crosswalk mapping
    for created_ticket in result.get("data", []):
        thena_id = created_ticket.get("custom_fields", {}).get("thena_ticket_id", "")
        ss_id = created_ticket.get("id")
        if thena_id and ss_id:
            crosswalk[thena_id] = ss_id
    return result
```

### Step 6: Migrate Comments

For each ticket, extract comments from Thena and create them in SurveySparrow via `POST /v3/tickets/{id}/comments`, preserving chronological order and the public/private split. This is the most API-call-intensive step — budget for rate limit pauses. ([developers.surveysparrow.com](https://developers.surveysparrow.com/rest-apis/post-v-3-tickets-id-comments/?utm_source=openai))

If comment authors don't exist as SurveySparrow users, comments may be attributed to the API user or fail creation entirely. Pre-create all agent accounts before this step.

```python
def migrate_comments(ss_ticket_id, thena_comments, user_map, errors_log):
    """Create comments on a SurveySparrow ticket in chronological order."""
    # Sort comments by created_at to preserve order
    sorted_comments = sorted(thena_comments, key=lambda c: c.get("created_at", ""))
    for comment in sorted_comments:
        author_email = comment.get("author", {}).get("email", "")
        ss_user_id = user_map.get(author_email)
        payload = {
            "body": convert_slack_markdown(comment.get("body", "")),
            "is_private": comment.get("is_internal", False),
        }
        if ss_user_id:
            payload["author_id"] = ss_user_id
        resp = requests.post(
            f"{SS_BASE}/tickets/{ss_ticket_id}/comments",
            headers={
                "Authorization": f"Bearer {SS_TOKEN}",
                "Content-Type": "application/json"
            },
            json=payload
        )
        if resp.status_code == 429:
            time.sleep(30)
            # Retry this comment
            migrate_comments(ss_ticket_id, [comment], user_map, errors_log)
            continue
        if resp.status_code not in (200, 201):
            errors_log.append({
                "ticket_id": ss_ticket_id,
                "comment_id": comment.get("id"),
                "error": resp.text,
                "status_code": resp.status_code
            })
```

### Step 7: Rebuild Relationships

After all tickets are loaded, patch parent-child links. SurveySparrow supports `parent_ticket_id` and `child_ticket_ids` in the ticket API, but relationships are intentionally simple: one parent per child, no grandchildren, and a parent cannot close until children are resolved. ([support.surveysparrow.com](https://support.surveysparrow.com/hc/en/articles/24806817991709-Related-Tickets/))

### Step 8: Validate

Run record-count comparisons, field-level spot checks, and sampling validation before cutting over. Details in the Validation section below.

## Edge Cases and Challenges

These are the issues that cause silent data loss or corruption.

**Duplicate contacts across channels.** Thena may store a Slack user and an email contact for the same person. Deduplicate by email before creating contacts in SurveySparrow, or you'll end up with orphaned tickets split across duplicate contact records. Build a contact deduplication step that normalizes emails (lowercase, trim whitespace) and merges records with the same email address.

**Slack markdown in descriptions.** Thena stores raw Slack formatting for requests originating from Slack channels. Slack's `mrkdwn` format differs from standard Markdown — links use `<http://example.com|Example>` instead of `[Example](http://example.com)`, and user mentions use `<@U1234567>` format. Build a Slack-to-HTML converter (see code sample in Step 3) or strip formatting entirely. If you pass raw Slack markdown into SurveySparrow's `description` field, links will break and formatting will render as garbage.

**Duplicate tickets from team moves in Thena.** Moving a ticket between teams in Thena creates a new ticket in the destination team and archives the original. If you extract both, you'll import duplicates unless you filter by status and deduplicate by original ticket identifier. Check for archived tickets that share the same `original_message_text` and `contact` as an active ticket. ([docs.thena.ai](https://docs.thena.ai/guides/ticketing/tickets))

**Account-to-ticket relationship loss.** SurveySparrow has no account object. If you don't create a custom "Company" field and populate it, you lose all account context — and with it, any ability to report or filter by company post-migration.

**Tag taxonomy loss.** Thena uses tags for categorization. SurveySparrow tickets don't have a native tag field. Map to a custom dropdown or text field, and normalize the vocabulary before loading. If you use a dropdown, ensure all possible tag values are pre-configured as options.

**Comment author attribution.** If comment authors don't exist as SurveySparrow users, comments may be attributed to the API user or fail creation entirely. Pre-create all agents before migrating comments. Log any comments where author attribution failed.

**Missing contact emails.** Not all Slack users have their email addresses exposed via the API (depending on workspace privacy settings). If Thena cannot provide an email, you cannot create a SurveySparrow Contact. Define a fallback strategy — such as routing these to a generic `unknown-user@yourdomain.com` contact and appending the Slack username to the ticket body.

**Attachment type and size restrictions.** SurveySparrow only accepts `pdf`, `png`, `jpeg`, `mp3`, `csv`, and `wav` files up to 15MB. If Thena contains `.docx`, `.xlsx`, `.zip`, `.gif`, `.mp4`, or files larger than 15MB, export them to object storage (e.g., S3, GCS) and insert a download link in the migrated ticket body. Do not silently skip unsupported attachments — log every skipped file with its original ticket ID, filename, MIME type, and size.

**API rate limits on both sides.** Thena enforces 60 req/min on Standard tier. SurveySparrow's empirical limits (~120 calls/hour on Business plan) are restrictive for bulk loads. If you multi-thread without a backoff strategy, you'll hit `429 Too Many Requests` errors. Implement exponential backoff with jitter and ensure your script can resume from the last successful checkpoint. Both APIs return `429` status codes; read `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers when available.

**Ticket creation timestamps are not writable.** SurveySparrow's `POST /v3/tickets` does not accept a custom `created_at` — all migrated tickets will show the migration date. Store original timestamps in a `source_created_at` custom field if historical chronology matters for reporting or SLA analysis.

**Thena export excludes file-upload custom fields.** If you rely on CSV export rather than API extraction, file-upload custom fields won't be included. This creates quiet data gaps that are easy to miss. Always validate CSV exports against API data for completeness. ([docs.thena.ai](https://docs.thena.ai/guides/ticketing/export-tickets?utm_source=openai))

**Custom field value mismatches.** If a Thena picklist value doesn't exactly match a SurveySparrow dropdown option (case-sensitive), ticket creation will fail with a validation error. Normalize all picklist values during transformation and pre-create all dropdown options before loading.

## Limitations and Constraints

Be explicit with stakeholders about what changes post-migration.

| Constraint | Impact | Mitigation |
|---|---|---|
| **No account hierarchy in SurveySparrow** | Account-level reporting, routing, and views are lost | Custom "Company" field on tickets and contacts |
| **SurveySparrow rate limits** | ~120 calls/hour constrains migration speed on Business plan | Use batch endpoints; plan for multi-day migrations on large datasets |
| **Thena rate limits** | 60 req/min on Standard tier; Enterprise tier has custom limits | Implement backoff; checkpoint progress for resumability |
| **No native Slack/Teams threading** | SurveySparrow tickets don't maintain channel conversation context | Append thread context to ticket description |
| **Ticket Templates** | SurveySparrow's template system is per-team; may need multiple templates | Audit template needs before migration |
| **API access gated by plan** | Thena's comments API requires Standard or higher; SurveySparrow API access may also be limited on lower plans | Confirm API access for both platforms before scoping |
| **Custom `created_at` not writable** | Historical tickets show migration date | Store original timestamp in custom field |
| **Attachment format restrictions** | Only pdf, png, jpeg, mp3, csv, wav up to 15MB | Export unsupported files to object storage; insert download links |
| **Related tickets** | One parent per child, no grandchildren, parent cannot close until children resolve | Simplify relationship model during migration |
| **Custom field limits** | SurveySparrow may limit the number of custom fields per plan | Audit field count and consolidate before migrating |
| **Ticket deletion behavior** | `DELETE /v3/tickets/{id}` — verify whether this is soft or hard delete and whether it cascades to comments and attachments before relying on it for rollback | Test deletion on a single ticket before using for bulk rollback |

## Validation and Testing

Never migrate directly into production without a test run.

### Record Count Comparison

After migration, compare counts at every level:

| Object | Thena Count | SurveySparrow Count | Match? |
|---|---|---|---|
| Contacts | — | — | |
| Tickets (Open) | — | — | |
| Tickets (Closed) | — | — | |
| Tickets (Total) | — | — | |
| Comments | — | — | |
| Custom field values populated | — | — | |
| Skipped attachments | N/A | — | |

### Field-Level Validation

Pull a random sample (5–10% of tickets, minimum 50) and verify:
- Subject and description content match (accounting for Slack markdown conversion)
- Status and priority mapped correctly
- Custom field values preserved (Company, Thena Ticket ID, Original Channel, Tags, Source Created At)
- Contact/requester linked correctly
- Comments present in correct order with correct public/private visibility
- Company/account name populated in custom field

### Sampling Strategy

At minimum, check:
- Oldest ticket and newest ticket
- Largest comment thread (by comment count)
- Tickets with attachments (verify download links for unsupported types)
- One ticket per team, status, and priority bucket
- Tickets that were moved between teams in Thena (check for duplicates)
- Tickets from each source channel (Slack, email, MS Teams)

### UAT Process

1. Have 2–3 agents review 20 migrated tickets against Thena originals
2. Check that team assignments are correct
3. Verify SLA policies trigger correctly on migrated tickets
4. Confirm no duplicate contacts were created (search for contacts with similar names)
5. Reply to a migrated ticket, change status, trigger a workflow — confirm everything works end-to-end
6. Verify that the `source_created_at` custom field shows correct original dates
7. Check that the `thena_ticket_id` custom field enables lookup back to the original record

### Rollback Plan

SurveySparrow's API supports `DELETE /v3/tickets/{id}`. Before relying on this for rollback, test on a single ticket to confirm:
- Whether deletion is soft (recoverable) or hard (permanent)
- Whether deletion cascades to associated comments and attachments
- Whether the deleted ticket's contact is preserved

Store a mapping of `thena_id → surveysparrow_id` during migration (the `ticket_crosswalk` table) so you can roll back by deleting all migrated tickets if validation fails. Keep your Thena export snapshot and raw JSON data as a safety net.

Review our [Post-Migration QA Checklist](https://clonepartner.com/blog/blog/help-desk-data-migration-qa-checklist/) for a comprehensive testing framework.

## Post-Migration Tasks

Once the data is loaded and validated, the technical migration is complete — but operational tasks remain.

- **Rebuild automations:** Thena's workflows, routing rules, and auto-responders do not transfer. Recreate them using SurveySparrow's workflow builder (trigger → condition → action).
- **Reconfigure SLA policies:** SLA rules need to be set up fresh. Historical SLA compliance data (breach/meet status) does not migrate — document it separately if needed for reporting.
- **Update integrations:** Any webhooks, Slack bots, or third-party integrations pointing at Thena need to be redirected to SurveySparrow.
- **Decommission Thena:** Revoke Thena's access to your Slack workspace (or other channels) to prevent duplicate ticket creation during the cutover.
- **Train agents:** SurveySparrow's ticket interface is fundamentally different from Thena's account-centric views. Plan a walkthrough covering ticket templates, comment workflows, the survey-feedback connection, and how account context is now stored in the "Company" custom field.
- **Monitor for 2 weeks:** Watch for orphaned contacts, missing comments, custom field data gaps, unmapped statuses, and workflow loops. Keep an error log and push any failed records through manually.
- **Archive Thena data:** Maintain a complete JSON export of all Thena data for at least 90 days post-migration. This is your authoritative source if discrepancies are discovered later.

## Best Practices

1. **Back up everything first.** Export all Thena data to JSON files before starting. This is your rollback safety net and audit trail.
2. **Run a test migration with 50–100 tickets.** Validate field mapping, comment ordering, custom field behavior, and status mapping before committing to a full run.
3. **Migrate contacts before tickets.** SurveySparrow ticket creation requires a valid `requester_id` — contacts must exist first.
4. **Use batch endpoints where possible.** SurveySparrow's `/v3/tickets/batch` endpoint reduces total API calls and speeds up the load phase.
5. **Build idempotent scripts.** Use the Thena ticket ID stored in a custom field to detect and skip already-migrated tickets on re-runs. Never deduplicate on title alone — titles are not unique.
6. **Log every API response.** Store the full response body for every create call. When a ticket fails to import, you need the error detail to diagnose — including the HTTP status code, response body, and the request payload that triggered it.
7. **Handle rate limits gracefully.** Read `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers from both APIs. Sleep and retry on 429 responses with exponential backoff and jitter (see code samples above).
8. **Don't migrate what you don't need.** Closed tickets older than 12–18 months with no active reference value can be archived separately rather than migrated.
9. **Keep three persistent tables:** `contact_crosswalk` (thena_email → ss_contact_id), `ticket_crosswalk` (thena_id → ss_ticket_id), and `migration_errors` (failed records with error details). These make re-runs safe and rollback possible.

## When to Use a Managed Migration Service

Build in-house when:
- Your dataset is small (<500 tickets, no custom fields)
- You have an engineer with 2–3 days to spare
- The data is clean with no deduplication needed

Use a managed service when:
- You have >2,000 tickets with threaded comments
- Custom fields, multi-level relationships, or account context need to be preserved
- Your team doesn't have spare engineering cycles
- You need zero-downtime migration with rollback guarantees
- Thena's API rate limits mean the migration will span multiple days

> Migrating from Thena to SurveySparrow Ticket Management and want to avoid the edge cases that cause silent data loss? ClonePartner handles the full pipeline — extraction, transformation, validation, and loading — so your team can focus on running support, not debugging migration scripts.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Is there a built-in migration tool from Thena to SurveySparrow?

No. Neither Thena nor SurveySparrow offers a native migration tool or importer for this direction. No major third-party tool currently provides a pre-built connector either. You need a custom API-based migration.

### What are the API rate limits for Thena and SurveySparrow?

Thena's Standard tier allows 60 requests per minute. SurveySparrow's limits vary by plan but have been documented at approximately 120 calls per hour and 1,000 calls per day. Both return 429 errors when exceeded.

### Can I migrate Thena account data to SurveySparrow?

SurveySparrow Ticket Management has no native account or company object. You must create a custom ticket field (e.g., 'Company') and populate it with Thena's account name during migration to preserve account context.

### Will ticket comments and conversation history transfer?

Yes, but only through the API. Comments must be extracted from Thena and created individually via SurveySparrow's Ticket Comments API, preserving chronological order. Original comment timestamps are not writable, so store them in custom fields if needed.

### Can I use CSV to migrate from Thena to SurveySparrow?

Only partially. Thena supports CSV ticket export, but SurveySparrow does not offer a native CSV ticket importer — only contacts and survey responses can be imported via CSV. Comments, attachments, and relationships require API work regardless.
