Skip to content

Thena to SurveySparrow Ticket Management Migration Guide

Technical guide to migrating from Thena to SurveySparrow Ticket Management. Covers API constraints, field mapping, architecture differences, and edge cases.

Roopi Roopi · · 25 min read
Thena to SurveySparrow Ticket Management Migration Guide
TALK TO AN ENGINEER

Planning a migration?

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

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

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

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)

For related migrations involving SurveySparrow, see our SurveySparrow to Front Migration Guide and SurveySparrow 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)

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)

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)

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)

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)

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
Info

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)

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.

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
Info

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)

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

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)

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.

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)

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)

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)
  4. Create ContactsPOST /v3/contacts (deduplicate by email)
  5. Create TicketsPOST /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)

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.

Danger

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)

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.

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.

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)

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.

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)

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.

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)

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)

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)

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

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.

More from our Blog