eDesk to SurveySparrow Ticket Migration: Technical Guide
Technical guide to migrating tickets from eDesk to SurveySparrow. Covers API constraints, field mapping, eCommerce data handling, rate limits, and step-by-step process.
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
eDesk to SurveySparrow Ticket Migration: Technical Guide
Verified against eDesk API v1 and SurveySparrow API v3.
Migrating from eDesk to SurveySparrow Ticket Management means translating an eCommerce-native helpdesk — where tickets are coupled to marketplace channels, sales orders, tracking links, and order-specific ticket types like ReturnRequest and WrongItemReceived — into a feedback-first ticketing module designed around survey responses, NPS detractors, and form submissions. These two platforms model support data under fundamentally different architectural assumptions.
No native migration path exists between them. No built-in importer on either side. No major third-party migration tool currently offers a verified eDesk-to-SurveySparrow connector. Zapier and n8n can connect both platforms for real-time event-based workflows, but neither handles bulk migration of thousands of tickets with threaded conversations and attachments.
Every migration requires extracting data via eDesk's REST API, transforming payloads to match SurveySparrow's ticket and contact schema, and loading through SurveySparrow's Ticket API (v3). This guide covers the full technical path: API constraints, field mapping, extraction strategies, the step-by-step migration process, delta sync, timeline estimation, and the edge cases that silently corrupt your data.
Plan for a custom API migration. eDesk's CSV exports from Insights contain ticket metadata only — no conversation threads, no attachments, no internal notes. The Search Download is Enterprise-only and capped at 1,000 rows. SurveySparrow's CSV import only captures the initial ticket body — it cannot thread historical replies or preserve private notes. A custom API-to-API migration is mandatory for preserving complete ticket histories. (support.edesk.com)
For related migrations, see our eDesk to Pylon Migration Guide, Freshdesk to SurveySparrow Ticket Migration Guide, and Zammad to SurveySparrow Ticket Migration Guide.
eDesk vs SurveySparrow: Architecture Differences
Before writing extraction scripts, understand how these data models diverge. Forcing eDesk's structure into SurveySparrow without transformation results in orphaned messages and lost context.
eDesk is an eCommerce-native helpdesk for multi-channel sellers. Tickets are organized around marketplace channels (Amazon, eBay, Shopify) and linked to sales orders, tracking links, and order notes. Each ticket contains a thread of messages — separate API objects with their own endpoints. Ticket types are eCommerce-specific: Cancellation, ReturnRequest, OrderQuery, RefundRequest, ShippingQuery, and 29 total enumerated types. Contacts are often secondary — marketplaces frequently mask customer emails (e.g., 12345abcde@marketplace.amazon.com). (support.edesk.com)
SurveySparrow Ticket Management is a feedback-first ticketing module. Tickets originate from survey responses, NPS detractor alerts, form submissions, or manual creation. The data model centers on contacts, ticket fields, priorities, statuses, and agents — not marketplace channels or sales orders. (support.surveysparrow.com)
| Concept | eDesk | SurveySparrow |
|---|---|---|
| Ticket origin | Marketplace messages, email, chat, social | Surveys, NPS, forms, manual |
| Ticket types | 29 eCommerce-specific enums | No built-in type taxonomy |
| Conversations | Separate Messages API (per-message endpoints) | Comments/replies on ticket object |
| Order data | Sales orders, tracking links, order notes linked to tickets | No equivalent — must use custom fields |
| Channels | Amazon, eBay, Shopify, email, chat, social (per-channel SLAs) | No channel concept |
| Custom fields | Text, Date, Date & Time, Selectable, Multi-select, Number, Yes/No, URL, Email | Text, Dropdown, Multiselect, Date |
| Statuses | Open, Pending, Closed, Unread, Read, Spam, Scheduled, Archived, Priority, Unpriority | Open, Pending, Resolved, Closed (configurable) |
| Contacts | channel_id, client_id, full_name, phone_number, email | name, email, phone, custom properties |
| Authentication | Bearer token (Enterprise plan) | OAuth 2.0 or API token |
The biggest structural mismatch: eDesk's eCommerce context has no native home in SurveySparrow. Sales orders, tracking links, marketplace channel identifiers, and order-specific ticket types have no equivalent objects. You must flatten this data into SurveySparrow custom fields, append it to ticket descriptions, or accept the loss. Decide before migration, not during.
API Constraints
Both platforms enforce constraints that dictate your migration architecture and timeline.
eDesk API (Source)
- Authentication: Bearer token, generated per third-party application in eDesk settings. API access is an Enterprise-plan feature. If you don't have Enterprise, you can fall back to admin JSON exports (described below), but those are a poor fit for continuous sync. (support.edesk.com)
- Rate limit: 60 requests per minute with a restoration rate of 2 requests per second. Exceeding returns HTTP 429 with
"Out of quota". You can request a higher limit from eDesk support. (developers.edesk.com) - Pagination: Offset-based using
pageanditemsPerPagequery parameters. Deep pagination on large accounts degrades performance — use date-range filtering (filter_created_at_gte,filter_created_at_lte) to keep payloads manageable. - Messages are separate API objects. Each ticket returns a
messages_idsarray. You must callGET /v1/messages/{messageId}for each message to get the full body, sender, timestamps, and attachments. For a ticket with 10 messages, that's 11 API calls (1 ticket + 10 messages).
The N+1 message problem. At 60 requests/minute, a single 10-message ticket consumes ~11 seconds of your rate limit budget. For 5,000 tickets averaging 5 messages each, message extraction alone requires ~25,000 API calls — roughly 7 hours of continuous extraction at max throughput.
eDesk Non-Enterprise Fallback
If your eDesk account is on the Growth or Basic plan, the REST API is not available. Your only extraction path is the admin JSON export:
- In eDesk, navigate to Settings → Data Management → Export
- Request a full export in JSON format
- The export includes ticket metadata and message bodies, but excludes attachment binaries — you cannot retrieve attachment files through this path
- The export is delivered as a download link via email, typically within 1–4 hours for accounts under 50,000 tickets
- Attachment URLs in the exported JSON point to authenticated eDesk storage and will expire — they cannot be re-fetched without API access
For non-Enterprise migrations, attachments must either be abandoned, or you must upgrade to Enterprise temporarily to retrieve them via API before downgrading.
SurveySparrow API (Target)
- Base URL: Region-specific — separate US, EU, AP, ME, UK, Sydney, and Canada endpoints. Confirm your data center before writing scripts. (developers.surveysparrow.com)
- Authentication: OAuth 2.0 or API token (Bearer header).
- Rate limit: Plan-dependent. Documented limits vary by tier; the API returns HTTP 429 with a
Retry-Afterheader when exceeded. Request your plan's specific quota from SurveySparrow support before starting, and ask for a temporary increase in writing before promising a cutover window. If you cannot get official documentation of your limit, run a 100-ticket test batch while monitoring 429 frequency to estimate your actual ceiling empirically. - Batch endpoints: Contacts and tickets have async batch-create endpoints that return a status token. Comments are only exposed as per-ticket create operations — one comment per request. The batch ticket endpoint does not expose the same field surface as single-ticket create (notably, attachment-capable fields are missing on batch). (developers.surveysparrow.com)
- Attachment restrictions: SurveySparrow only accepts
pdf,png,jpeg,mp3,csv, andwavattachments, with a 15 MB maximum per file. DOCX, ZIP, EML, and larger binaries must be externalized. (developers.surveysparrow.com) - Custom field ID resolution: SurveySparrow custom fields are referenced by numeric ID in API payloads, not by label. Before any write operation, call
GET /v3/ticket_fieldsto retrieve the ID for each custom field you created. Build a label-to-ID lookup table and use it in every transform step. Writing to an incorrect field ID silently drops the data — no error is returned. - Sandbox environment: SurveySparrow does not currently offer a publicly documented sandbox environment. Run all test loads against a dedicated test workspace (separate account) rather than your production workspace, and bulk-delete test tickets before switching to production data.
SurveySparrow's rate limit is your bottleneck. Comment writes — one per request — dominate the write operation count. At conservative rate limits, a 5,000-ticket migration with 5 comments each requires roughly 30,000 write operations. Request a rate limit increase from SurveySparrow before you begin, confirm the increased limit in writing, and calculate your migration window accordingly.
The Timestamp Problem
This constraint changes the shape of every eDesk-to-SurveySparrow migration.
SurveySparrow's current create-ticket and create-comment endpoints do not accept created_at, updated_at, or an explicit comment author field. You can set requester, assignee, team, status, priority, and custom fields on tickets, and body plus private on comments — but not historical timestamps or authorship metadata. Imported records receive new platform timestamps. (developers.surveysparrow.com)
You cannot do a forensic replay of eDesk thread history in native SurveySparrow chronology. If exact authorship and timing matter for your team, store source timestamps in custom fields and prepend metadata to every imported comment body.
A practical imported comment header:
[Imported from eDesk]
Original author: jane@example.com
Original direction: Incoming
Original sent at: 2026-02-14T18:42:11Z
Original channel: Amazon UK
<original message body>Not clean, but auditable, searchable, and honest about what the target platform preserves.
Field Mapping: eDesk → SurveySparrow
Every field must be explicitly mapped or deliberately dropped.
Ticket-Level Fields
| eDesk Field | SurveySparrow Field | Transformation Notes |
|---|---|---|
id |
Custom field: edesk_ticket_id |
Idempotency key and audit trail |
subject |
subject |
Enforce SurveySparrow's 200-character limit. Keep full original in custom field if truncated. |
status |
status (numeric) |
See status mapping below |
created_at |
Custom field: source_created_at |
SurveySparrow does not accept historical timestamps on create |
owner_user_id |
assignee_id |
Pre-create agents. Map eDesk user IDs to SurveySparrow agent IDs. If an eDesk agent has no SurveySparrow account, map to a "Legacy Agent" placeholder. |
contact_id |
contact |
Pre-create and deduplicate contacts first |
tags_ids |
tags |
Pre-create matching tags, then map by name |
channel_id |
Custom field: source_channel (Dropdown) |
No channel concept in SurveySparrow |
sales_order_id |
Custom field: sales_order_id (Text) |
No order object in SurveySparrow |
external_order_id |
Custom field: external_order_id (Text) |
Preserve lookup keys for external systems |
type (e.g., ReturnRequest) |
Custom field: edesk_ticket_type (Dropdown) |
No equivalent taxonomy — pre-create all 29 values if reporting depends on type-based filtering |
custom_fields |
custom_fields |
Map individually by numeric field ID. eDesk's Number, Yes/No, URL, Email types → flatten to Text or Dropdown |
time_left_to_reply |
— | SLA metadata, not transferable |
Status Mapping
eDesk uses a granular status system. SurveySparrow uses Open, Pending, Resolved, and Closed, with ticket creation expecting numeric status values. Build the numeric map by calling GET /v3/ticket_statuses in a seeded SurveySparrow workspace — do not hardcode status IDs, as they vary by workspace.
| eDesk Status | SurveySparrow Status | Notes |
|---|---|---|
| Open | Open | Direct map |
| Unread | Open | — |
| Read | Open | — |
| Pending | Pending | Direct map |
| Scheduled | Pending | Store source_status=Scheduled in custom field |
| Closed | Closed or Resolved | — |
| Archived | Closed | Store source_status=Archived in custom field |
| Spam | Exclude | Unless audit requirements say otherwise |
Message → Comment Mapping
eDesk stores conversations as separate Message objects. SurveySparrow stores them as comments on the ticket. For each eDesk message:
- Extract
body(HTML), sender,created_at, and any attachment URLs - Determine if it's an agent reply, customer message, or internal note
- Map to SurveySparrow comment with the appropriate visibility: public (
private=false) or private (private=true) - Sort chronologically. The first message becomes the ticket
description; every subsequent message becomes a comment with a metadata header for author and timestamp fidelity.
| eDesk Message Type | SurveySparrow Comment | Notes |
|---|---|---|
| Incoming (customer) | Public comment (private=false) |
Prepend metadata header |
| Outgoing (agent) | Public comment (private=false) |
Prepend metadata header |
| Internal note | Private comment (private=true) |
Verify SurveySparrow supports private flag on your plan |
HTML sanitization: eDesk message bodies are HTML. Before inserting into SurveySparrow comments, sanitize to strip potentially dangerous or unsupported tags while preserving readable content. Use Python's bleach library (bleach.clean(body, tags=ALLOWED_TAGS, strip=True)) or BeautifulSoup with explicit tag filtering. Test your sanitization against a sample of your actual eDesk messages before running the full migration — marketplace-specific formatting and embedded order widgets frequently produce malformed output that breaks comment display.
Contact Mapping
Match on email first. Use SurveySparrow's referenceId to store the eDesk source ID — prefer referenceId over unique_id if your source identifier contains dashes or symbols, because unique_id is alphanumeric-only.
Deduplication is critical. A single customer in eDesk may have multiple contact records — one per channel (Amazon, eBay, Shopify). SurveySparrow expects one contact per person. Deduplicate by email before import, and merge marketplace-specific identifiers into custom contact properties.
For marketplace-masked emails (e.g., buyer-123@amazon.com), retain the exact masked string so future replies route correctly if the marketplace proxy is still active.
Custom Field Pre-Creation
Because SurveySparrow lacks native eCommerce fields, create these custom ticket fields before migrating anything. After creation, call GET /v3/ticket_fields to retrieve the numeric ID for each field — these IDs are required in all subsequent API write payloads.
| Field Name | Type | Purpose |
|---|---|---|
edesk_ticket_id |
Text | Idempotency key and audit trail |
source_channel |
Dropdown | eDesk channel names |
edesk_ticket_type |
Dropdown | All 29 eDesk ticket types if needed for reporting |
sales_order_id |
Text | eDesk order reference |
external_order_id |
Text | Marketplace order ID |
source_created_at |
Text | Original eDesk timestamp (ISO 8601) |
source_status |
Text | For statuses without a clean target mapping (Scheduled, Archived) |
Step-by-Step Migration Process
Step 1: Audit Your eDesk Instance
Before writing any code, inventory what you have:
# Get total ticket count
curl -s -H "Authorization: Bearer $EDESK_TOKEN" \
"https://api.edesk.com/v1/tickets?itemsPerPage=1" | jq '.paginator.totalItemsCount'
# List all channels
curl -s -H "Authorization: Bearer $EDESK_TOKEN" \
"https://api.edesk.com/v1/channels" | jq '.data[].name'
# List all tags
curl -s -H "Authorization: Bearer $EDESK_TOKEN" \
"https://api.edesk.com/v1/tags" | jq '.data[] | {id, name}'
# Get ticket type distribution (sample 1,000 tickets)
curl -s -H "Authorization: Bearer $EDESK_TOKEN" \
"https://api.edesk.com/v1/tickets?itemsPerPage=100&page=1" | jq '[.data[].type] | group_by(.) | map({type: .[0], count: length})'Document: total ticket count, total messages (estimate: tickets × average messages/ticket), active channels, tags, custom fields, ticket type distribution, and attachment volume. This determines your rate limit budget and migration window.
Decide what is worth moving. In most eDesk exits, the defensible scope is:
- All contacts tied to open or recently active tickets
- All open and pending tickets with full thread history
- A closed-history window sized to your compliance or SLA audit requirements (typically 12–24 months)
- Only tags, order identifiers, and custom fields agents still reference
Skip spam tickets, SystemMessage-type tickets with no customer interaction, and channel-specific tickets for marketplaces you're discontinuing. A migration that moves 60% of tickets with 100% fidelity is better than one that moves 100% with corrupted or orphaned data.
Step 2: Pre-Create Target Infrastructure in SurveySparrow
Before importing a single ticket:
- Create custom ticket fields — see Custom Field Pre-Creation above
- Call
GET /v3/ticket_fields— retrieve and store the numeric ID for every field; write them to afield_id_map.jsonlookup file - Create agents matching your eDesk users
- Create contacts — deduplicate by email across eDesk channels first (see Step 2a below)
- Create tags matching your eDesk tag names
- Call
GET /v3/ticket_statuses— retrieve status IDs and build your status numeric map
Do not leave agent routing or field ID resolution as a migration-day exercise.
Step 2a: Pre-Create Contacts via Batch API
The contact creation step must happen before any ticket load. SurveySparrow's contact batch-create endpoint returns a status token — poll it until the batch completes before proceeding.
import requests
import time
SS_BASE = "https://api.surveysparrow.com/v3" # Confirm your region endpoint
SS_HEADERS = {
"Authorization": f"Bearer {SS_TOKEN}",
"Content-Type": "application/json"
}
def create_contacts_batch(contacts):
"""
contacts: list of dicts with keys: name, email, phone, referenceId
Returns: dict mapping edesk_contact_id -> surveysparrow_contact_id
"""
# Submit batch
resp = requests.post(
f"{SS_BASE}/contacts/batch",
headers=SS_HEADERS,
json={"contacts": contacts}
)
resp.raise_for_status()
batch_token = resp.json().get("data", {}).get("token")
# Poll for completion
while True:
status_resp = requests.get(
f"{SS_BASE}/contacts/batch/{batch_token}",
headers=SS_HEADERS
)
result = status_resp.json().get("data", {})
if result.get("status") == "completed":
return {
c["referenceId"]: c["id"]
for c in result.get("contacts", [])
if c.get("referenceId") and c.get("id")
}
elif result.get("status") == "failed":
raise RuntimeError(f"Contact batch failed: {result}")
time.sleep(5)
def deduplicate_contacts(edesk_contacts):
"""
Merge eDesk contacts by email. One eDesk customer may have multiple
contact records (one per channel). Returns a deduplicated list.
"""
seen = {}
for contact in edesk_contacts:
email = contact.get("email") or f"masked-{contact['id']}@migration.internal"
if email not in seen:
seen[email] = {
"name": contact.get("full_name", ""),
"email": email,
"phone": contact.get("phone_number", ""),
"referenceId": str(contact["id"]) # eDesk contact ID as reference
}
return list(seen.values())Step 3: Extract from eDesk
Paginate through all tickets using GET /v1/tickets, ordered by created_at ascending:
import requests
import time
import json
EDESK_BASE = "https://api.edesk.com/v1"
HEADERS = {"Authorization": f"Bearer {EDESK_TOKEN}"}
def extract_all_tickets(start_date=None, end_date=None):
page = 1
all_tickets = []
params = {
"itemsPerPage": 50,
"order_by": "created_at",
"order_direction": "asc"
}
if start_date:
params["filter_created_at_gte"] = start_date # ISO 8601
if end_date:
params["filter_created_at_lte"] = end_date
while True:
params["page"] = page
resp = requests.get(f"{EDESK_BASE}/tickets", headers=HEADERS, params=params)
if resp.status_code == 429:
time.sleep(60)
continue
data = resp.json()
tickets = data.get("data", [])
if not tickets:
break
all_tickets.extend(tickets)
page += 1
time.sleep(1) # Respect 60 req/min limit
return all_tickets
def extract_messages(ticket):
messages = []
for msg_id in ticket.get("messages_ids", []):
resp = requests.get(f"{EDESK_BASE}/messages/{msg_id}", headers=HEADERS)
if resp.status_code == 429:
time.sleep(60)
resp = requests.get(f"{EDESK_BASE}/messages/{msg_id}", headers=HEADERS)
messages.append(resp.json().get("data", {}))
time.sleep(1)
return sorted(messages, key=lambda m: m.get("created_at", ""))
def save_raw(ticket_id, ticket, messages):
with open(f"raw/{ticket_id}.json", "w") as f:
json.dump({"ticket": ticket, "messages": messages}, f)Save raw extracts to disk. Write each ticket + messages to a local JSON file before any transformation. This gives you a re-runnable source of truth without hitting eDesk's API again. Structure files as raw/{ticket_id}.json. If the migration spans multiple days, this cache prevents re-extraction and eliminates the risk of API changes mid-migration.
Timezone warning: eDesk's filter_last_updated_at_gte and filter_last_updated_at_lte are evaluated in the user's timezone. If your extraction script runs in UTC and your eDesk account is set to US/Eastern, you'll miss or double-count tickets at timezone boundaries. Normalize to UTC in your extraction logic, and use overlapping 48–72 hour windows for delta syncs to avoid gaps.
Step 4: Transform
Map each eDesk ticket + messages into a SurveySparrow payload. Load field_id_map.json (created in Step 2) before running any transforms.
import bleach
import json
# Load field ID map created in Step 2
with open("field_id_map.json") as f:
FIELD_IDS = json.load(f)
# Example: {"edesk_ticket_id": 1042, "source_channel": 1043, ...}
ALLOWED_HTML_TAGS = ["p", "br", "b", "i", "u", "ul", "ol", "li", "a", "pre", "code"]
def sanitize_html(body):
return bleach.clean(body or "", tags=ALLOWED_HTML_TAGS, strip=True)
def build_comment_header(msg):
return (
f"[Imported from eDesk]\n"
f"Original author: {msg.get('sender', {}).get('email', 'unknown')}\n"
f"Original direction: {msg.get('type', 'unknown')}\n"
f"Original sent at: {msg.get('created_at', 'unknown')}\n"
f"Original channel: {msg.get('channel', 'unknown')}\n\n"
)
def transform_ticket(edesk_ticket, messages, agent_map, contact_map, channel_map):
first_message = messages[0] if messages else {}
return {
"subject": edesk_ticket.get("subject", "(no subject)")[:200],
"description": sanitize_html(first_message.get("body", "")),
"status": map_status(edesk_ticket.get("status")),
"priority": "Medium", # eDesk uses Priority/Unpriority flags, not levels
"assignee_id": agent_map.get(edesk_ticket.get("owner_user_id")),
"contact_id": contact_map.get(edesk_ticket.get("contact_id")),
"custom_fields": {
FIELD_IDS["edesk_ticket_id"]: str(edesk_ticket.get("id")),
FIELD_IDS["source_channel"]: channel_map.get(
edesk_ticket.get("channel_id"), ""
),
FIELD_IDS["edesk_ticket_type"]: edesk_ticket.get("type", ""),
FIELD_IDS["sales_order_id"]: str(
edesk_ticket.get("sales_order_id", "")
),
FIELD_IDS["source_created_at"]: edesk_ticket.get("created_at", ""),
},
"comments": [
{
"body": build_comment_header(msg)
+ sanitize_html(msg.get("body", "")),
"private": msg.get("type") == "internal"
}
for msg in messages[1:]
]
}
def map_status(edesk_status):
mapping = {
"Open": "Open",
"Pending": "Pending",
"Closed": "Resolved",
"Unread": "Open",
"Read": "Open",
"Archived": "Resolved",
"Spam": "Resolved",
}
return mapping.get(edesk_status, "Open")Attachment handling is where many migrations fail quietly. eDesk attachment URLs require eDesk authentication and will expire. You must:
- Download the file from eDesk into a local buffer
- Check file type — SurveySparrow only accepts
pdf,png,jpeg,mp3,csv, andwav - Check file size — 15 MB maximum
- Upload accepted files to SurveySparrow's attachment endpoint
- For unsupported types or oversized files, upload to external object storage (S3, GCS) and inject a signed URL into the comment body: [Attachment externalized — original file type/size not supported by SurveySparrow. Access at: {url}]
Step 5: Load into SurveySparrow
Follow this load order to avoid foreign key failures and automation side effects:
- Contacts first — batch create (Step 2a) must complete before any ticket load
- Ticket shells — batch create for simple tickets without opener attachments; single-ticket create when the opener includes attachments (batch create does not expose the attachment field surface)
- Comments last — via
POST /v3/tickets/:id/comments, one comment per request, in chronological order - Final status and assignment after thread replay — set assignments and statuses after comments are loaded, or disable automation first
def load_ticket(payload, ledger):
"""
ledger: dict mapping edesk_ticket_id -> surveysparrow_ticket_id
Skips tickets already in ledger (idempotent retry).
"""
edesk_id = payload["custom_fields"].get(FIELD_IDS["edesk_ticket_id"])
if edesk_id in ledger:
return ledger[edesk_id] # Already migrated
resp = requests.post(
f"{SS_BASE}/tickets",
headers=SS_HEADERS,
json={k: v for k, v in payload.items() if k != "comments"}
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
time.sleep(retry_after)
return load_ticket(payload, ledger)
ss_ticket_id = resp.json().get("data", {}).get("id")
ledger[edesk_id] = ss_ticket_id
save_ledger(ledger) # Persist after every write
# Load comments chronologically
for comment in payload.get("comments", []):
comment_resp = requests.post(
f"{SS_BASE}/tickets/{ss_ticket_id}/comments",
headers=SS_HEADERS,
json=comment
)
if comment_resp.status_code == 429:
retry_after = int(comment_resp.headers.get("Retry-After", 60))
time.sleep(retry_after)
requests.post(
f"{SS_BASE}/tickets/{ss_ticket_id}/comments",
headers=SS_HEADERS,
json=comment
)
time.sleep(0.5)
return ss_ticket_id
def save_ledger(ledger):
with open("migration_ledger.json", "w") as f:
json.dump(ledger, f)Suppress webhooks and automation. If SurveySparrow has active automation rules or webhooks — "send survey when ticket is created," auto-assignment rules, email notifications — importing thousands of historical tickets will trigger mass email floods and automation runs. Disable all outbound notifications, survey triggers, and webhooks before starting. Re-enable only after validation is complete. (support.surveysparrow.com)
Keep your own migration ledger. SurveySparrow's list endpoints do not support lookup by custom field value. Store edesk_ticket_id → surveysparrow_ticket_id in a local migration_ledger.json file (or SQLite/Redis for larger volumes). Persist the ledger to disk after every successful write. If the process crashes at ticket 2,847, resume from that point — not from the beginning. Without a ledger, retries create duplicate tickets with no way to distinguish originals from duplicates. (developers.surveysparrow.com)
Step 5a: Partial Failure Recovery
If the migration crashes mid-run — network failure, rate limit exhaustion, API error — do not restart from scratch. The migration ledger in migration_ledger.json is your recovery checkpoint:
- Load the ledger:
edesk_id → ss_ticket_idfor all successfully created tickets - Resume ticket creation from the first ticket not in the ledger
- For tickets in the ledger but with incomplete comment threads, query
GET /v3/tickets/:id/commentsto count existing comments, then append only the missing ones (match by position in the original chronological order)
SurveySparrow does not currently expose a bulk-delete ticket endpoint. If a test load goes wrong, tickets must be deleted individually via DELETE /v3/tickets/:id — scripted, but slow. For large bad loads, contact SurveySparrow support directly and request a workspace reset, which is faster than per-record deletion.
Step 6: Delta Sync
A complete migration of large ticket volumes takes days at standard rate limits. During this time, your team continues working in eDesk. Handle this in two phases:
- Historical load: Migrate all closed tickets and tickets created before the migration start date.
- Delta sync: After the historical load completes, re-query eDesk with
filter_last_updated_at_gteset to your last extraction timestamp — with an overlapping 48–72 hour window to account for timezone drift. For each ticket returned, check the migration ledger:- If
edesk_ticket_idis not in the ledger → create the ticket as new - If it exists in the ledger → call
GET /v3/tickets/:ss_id/commentsto count existing comments, then append only the delta messages by position
- If
The edesk_ticket_id custom field combined with the local ledger is your idempotency mechanism. Without it, retries create duplicates with no reconciliation path.
Step 7: Validate
After loading, run validation checks before go-live:
- Count match: Total ticket count in eDesk (scoped to migration window) vs. total in SurveySparrow
- Comment count match: Per-ticket message count in eDesk vs. comment count in SurveySparrow for a sampled subset
- Field integrity: Spot-check 50–100 tickets for correct status, assignee, contact, and custom field values — including numeric field IDs resolving to the correct labels
- Attachment verification: Confirm attachments are accessible (not returning 403/404), and externalized files have working signed URLs
- Automation state: Confirm no spurious survey sends or notification emails were triggered during load
Use both platforms' export tooling for reconciliation. Hand-audit a statistically meaningful sample stratified across channels, ticket types, and status buckets — not just the most recent tickets.
Timeline Estimation
SurveySparrow's API rate limit is the binding constraint. The table below uses conservative estimates assuming comment writes dominate the operation count. Adjust the "Total API Writes" column based on your actual average comment count per ticket.
| Volume | Avg Messages/Ticket | Total API Writes (tickets + comments) | Time at 120/hr | Time at 500/hr (increased limit) |
|---|---|---|---|---|
| 1,000 tickets | 3 | ~4,000 | ~33 hours | ~8 hours |
| 5,000 tickets | 5 | ~30,000 | ~250 hours | ~60 hours |
| 10,000 tickets | 5 | ~60,000 | ~500 hours | ~120 hours |
| 25,000 tickets | 5 | ~150,000 | ~1,250 hours | ~300 hours |
These estimates assume no errors, no retries, and no attachment uploads. Real-world migrations add 20–40% overhead for retries, validation, and partial failure recovery. Attachment upload rounds are additive and depend on your attachment volume.
For any migration above 5,000 tickets, request a rate limit increase from SurveySparrow before starting. The difference between default and increased limits is the difference between a multi-week project and a few-day execution.
Edge Cases That Cause Silent Failures
Amazon PII Purge
Amazon enforces strict PII retention policies. eDesk automatically redacts or deletes buyer names and addresses from Amazon tickets after 30 days. When you extract historical tickets, many Amazon tickets will have null or redacted contact fields. Your migration script must detect null contact values and assign these tickets to a fallback contact (e.g., amazon-redacted@yourdomain.com) to satisfy SurveySparrow's requirement that every ticket has a valid contact.
Multi-Channel Contact Deduplication
A single customer in eDesk might have 3 contact records: one from Amazon (masked email), one from eBay, and one from Shopify. eDesk treats these as separate contacts. SurveySparrow expects one contact per person. Define merge rules before migration: which email wins? What happens to tickets linked to the Amazon contact with no real email? These rules must be encoded in your deduplication logic before any contacts are created.
HTML Message Bodies
eDesk messages can contain HTML with inline images, marketplace-specific formatting, and embedded order widgets. SurveySparrow ticket comments may not render arbitrary HTML consistently. Use bleach.clean() with an explicit allowlist of safe tags (see Step 4 above) before inserting any message body into SurveySparrow. Test sanitization against a sample of 20–30 of your most complex historical tickets — Amazon and eBay message formatting in particular tends to produce malformed output.
Unsupported Attachment Types
SurveySparrow only accepts pdf, png, jpeg, mp3, csv, and wav. eDesk histories commonly include DOCX, ZIP, and large buyer-uploaded images of damaged goods. For blocked types, upload to external object storage and inject a reference URL into the comment. For oversized files (> 15 MB), append: [Attachment omitted — original file exceeded 15 MB limit. Stored at: {url}]. Log every externalized attachment to a separate file for post-migration audit.
Custom Field ID Mismatch
SurveySparrow custom fields are referenced by numeric ID in API payloads. If you hardcode IDs based on a test workspace and then run against a production workspace, every custom field write will fail silently — the API does not return an error for writes to non-existent field IDs; it simply drops the data. Always resolve field IDs dynamically from GET /v3/ticket_fields at the start of each migration run, never from a static constant.
eCommerce Data Without a Home
eDesk tickets can reference sales orders with tracking links, order notes, and marketplace-specific identifiers. SurveySparrow has no order object. If you flatten into custom fields, you preserve the data but lose relational context. If you append it to ticket descriptions, it becomes unsearchable noise. Neither option is ideal — pick the least bad one for your operations and commit to it before writing a single line of migration code.
Ticket Types Without Equivalents
eDesk's 29 ticket types (BuyerNotes, Cancellation, DefectiveItemReceived, NegativeFeedback, OrderClaim, etc.) encode business meaning. SurveySparrow has no built-in ticket type taxonomy. If your reporting depends on type-based filtering, pre-create a Dropdown custom field with all 29 values. Otherwise, this data is lost.
When SurveySparrow Is the Wrong Target
If your support operation lives inside marketplace-native eCommerce workflows — if agents rely on channel context, seller order IDs, and order-linked SLAs for every interaction — SurveySparrow may be a downgrade in operating fit even if the migration succeeds technically. eDesk is built around multi-channel commerce support. SurveySparrow Ticket Management is strongest when tickets exist to act on feedback, surveys, NPS, and lighter support flows.
If that's your end state, the migration is workable. If you still need a deep eCommerce mailbox, challenge the destination before you move a single record. (edesk.com)
What ClonePartner Handles
We've run helpdesk migrations across every major platform, including eDesk. For this specific migration path, we typically handle:
- Full eDesk API extraction with rate limit orchestration and resume-on-failure
- Contact deduplication across marketplace channels
- Custom field pre-creation and numeric ID resolution in SurveySparrow
- eCommerce data preservation strategy (sales orders, tracking, ticket types)
- Timestamp metadata injection for historical auditability
- HTML sanitization for marketplace-formatted message bodies
- Attachment triage — upload supported formats, externalize the rest with signed URLs
- Delta sync to capture tickets created during the migration window
- Post-migration validation with count matching, field integrity checks, and attachment verification
- Zero-downtime execution so your support team never stops working in eDesk during the process
For a deeper look at managing live cutover windows, see our Zero-Downtime Help Desk Data Migration guide.
Frequently Asked Questions
- Can I migrate tickets from eDesk to SurveySparrow using CSV export?
- Not for a production migration. eDesk's CSV exports from Insights contain ticket metadata only — no conversation threads, attachments, or internal notes. The Search Download is Enterprise-only and capped at 1,000 rows. You need a custom API-to-API pipeline for a complete migration.
- Can SurveySparrow preserve original eDesk comment timestamps?
- No. SurveySparrow's current create-ticket and create-comment endpoints do not accept historical created_at or explicit comment-author fields. Imported records receive new platform timestamps. Preserve source time and author in custom fields or comment body headers.
- How long does an eDesk to SurveySparrow migration take?
- SurveySparrow's default API rate limit (~120 calls/hour) is the bottleneck. A 5,000-ticket migration with 5 messages per ticket takes roughly 250 hours at default limits. With an increased limit (500/hr), the same migration takes about 60 hours. Request a rate limit increase before starting.
- What happens to eDesk sales order data during migration?
- SurveySparrow has no native sales order object. You can preserve order IDs, tracking info, and channel names by creating custom Dropdown and Text fields. The relational link between tickets and orders is lost — only flat metadata survives.
- Do I need eDesk Enterprise for an API-based migration?
- For direct API access, yes — eDesk documents the API as an Enterprise-plan feature. Without it, you can fall back to admin JSON exports, but that approach is capped and unsuitable for delta syncs.