---
title: "Thena to Zoho Desk Migration: A Technical Guide"
slug: thena-to-zoho-desk-migration-a-technical-guide
date: 2026-08-26
author: Abdul Aleem
categories: [Zoho Desk, Migration Guide, Help Desk, Thena]
excerpt: "Technical guide for migrating from Thena to Zoho Desk. Covers API endpoints, credit-based rate limits, Slack identity resolution, mrkdwn conversion, and cutover planning."
tldr: "No native Thena-to-Zoho Desk path exists. Build a custom ETL pipeline, budget for Zoho's credit-based limits, and plan for Slack mrkdwn conversion and 20 MB attachment caps."
canonical: https://clonepartner.com/blog/thena-to-zoho-desk-migration-a-technical-guide
---

# Thena to Zoho Desk Migration: A Technical Guide


# Thena to Zoho Desk Migration: A Technical Guide

> [!NOTE]
> **TL;DR:** Thena is a Slack-native, AI-first B2B ticketing platform. Zoho Desk is a department-centric, traditional helpdesk with a credit-based API system. No native migration path exists. Thena's Account → Contact → Request model must be transformed into Zoho Desk's Department → Account → Contact → Ticket → Thread hierarchy using a custom ETL pipeline. Key challenges: Slack user identity resolution, mrkdwn-to-HTML conversion, Zoho Desk's credit-based API limits (not simple rate limits), a 20 MB attachment ceiling, and the department constraint — every Zoho ticket must belong to a department Thena does not have. Budget 72–120 engineer-hours for a mid-size dataset (5K–30K requests).
>
> **Last reviewed:** August 2026 against Thena Platform API and Zoho Desk API v1.

Migrating from [Thena](https://www.thena.ai/) to [Zoho Desk](https://www.zoho.com/desk/) means leaving a Slack-first, AI-driven B2B support tool for a traditional multi-channel helpdesk organized around departments, tickets, and email-based threads. The platforms serve overlapping use cases with fundamentally different data architectures.

There is no built-in connector, no third-party tool supporting Thena as a source, and Zoho Desk's CSV import omits conversation threading. Just as with a [Thena to Kustomer migration](https://clonepartner.com/blog/blog/thena-to-kustomer-migration-a-technical-guide), this is a custom data transformation project — treat it as one from the start.

This guide covers API endpoints, data-model mapping, rate-limit math, attachment handling, and cutover logic. If you are still evaluating your data export options, start with our [guide to exporting data from Thena](https://clonepartner.com/blog/blog/how-to-export-data-from-thena-methods-api-limits-formats).

## What This Migration Actually Involves

**Thena** organizes support around **Requests** — conversations captured from Slack, email, MS Teams, web chat, and Discord. Each Request holds threaded messages, AI-generated metadata (title, summary, tags, sentiment), and is linked to an Account and Contact. User identity is often tied to Slack User IDs. Thena also documents both Requests and Tickets — some workspaces have request objects that never became formal tickets. Decide before the first export whether to migrate these as tickets or skip them. Thena's data model: **Account → Contact → Request → Messages**.

**Zoho Desk** organizes support around **Tickets** within **Departments**. Each ticket belongs to a department, links to a Contact (and optionally an Account), and contains **Threads** (conversation entries visible to the customer) and **Comments** (internal notes). In Zoho, a ticket comment is not the same thing as a channel thread — that separation is the first design constraint to internalize. Zoho Desk's data model: **Organization → Department → Ticket → Threads/Comments**, with Contacts and Accounts as cross-department entities.

The core transformation:

| Thena Entity | Zoho Desk Entity | Notes |
|---|---|---|
| Account | Account | Direct map. `accountName` required. |
| Contact | Contact | `lastName` or `email` required. |
| Request | Ticket | Department assignment required — Thena has no equivalent. |
| Request Messages (customer) | Thread (incoming) | Slack mrkdwn → HTML conversion needed. |
| Request Messages (agent) | Thread (outgoing) | `fromEmailAddress` required for email threads. |
| Internal Notes | Comment | `isPublic: false` for private comments. |
| AI Tags/Summary | Custom Fields (`cf_*`) | Create custom fields in Zoho Desk first. |
| Attachments | Uploads → Ticket/Thread attachments | 20 MB per-file limit. Two-step upload flow. |

### The Department Constraint

In Zoho Desk, every ticket *must* belong to a Department — `departmentId` is a required field on ticket creation. As we've noted in our [Zoho Desk to Freshdesk migration guide](https://clonepartner.com/blog/blog/zoho-desk-to-freshdesk-migration-the-complete-technical-guide), departments are top-level organizational units that control ticket routing and agent access. Thena does not use departments natively. You need a routing table in your migration middleware before the first ticket is created.

The most common approach: map Thena Slack Channels (or Thena teams) to specific Zoho Desk Departments. If Thena teams do not map cleanly to Zoho departments, decide early whether a Thena team becomes a Desk department, a team within a department, or a reporting-only field. Making this choice after tickets are loaded is where rework starts.

## Why Teams Move from Thena to Zoho Desk

The most common triggers:

- **Pricing pressure:** Thena's paid plans start above $20/user/month and scale with seat count. Zoho Desk starts at $14/agent/month (Standard tier) with broader feature coverage at lower price points. Check [Thena's current pricing page](https://www.thena.ai/pricing) and [Zoho Desk's pricing page](https://www.zoho.com/desk/zoho-desk-pricing.html) for current rates — both vendors adjust pricing periodically.
- **Multi-channel requirements:** Teams outgrowing Slack-only support need email, phone, social, and web form channels that Zoho Desk handles natively.
- **Zoho ecosystem consolidation:** Organizations already using Zoho CRM, Zoho Analytics, or Zoho Projects want a unified platform with native data sharing.
- **Workflow automation:** Zoho Desk's Blueprint (process enforcement), SLA escalation rules, and round-robin assignment automation are more mature than Thena's workflow layer.

## Source-Side Extraction: Thena Platform API

Thena's Platform API is the only reliable extraction path for migration-quality data. The UI export (XLSX/JSON) gives you request metadata but strips message threading and attachment references. ([docs.thena.ai](https://docs.thena.ai/guides/ticketing/export-tickets?utm_source=openai))

### Authentication and Rate Limits

All Thena API requests require an `x-api-key` header. Generate your key from **Organization Settings → Security and Access**.

**Rate limits:**
- **Standard tier:** 60 requests per minute per user, org, and IP
- **Enterprise tier:** Custom limits based on your plan

Monitor `X-RateLimit-Remaining` in response headers and back off when it approaches zero. At 60 req/min, extracting 10,000 requests with individual detail calls takes roughly 3–4 hours.

> [!WARNING]
> **Plan-gated access:** Thena's comments API is documented as available only for Standard and Enterprise tier organizations. Confirm your plan gives you comment API access before designing the pipeline. ([docs.thena.ai](https://docs.thena.ai/api-reference/platform/comments/get-comments-on-an-entity))

### Key Extraction Endpoints

```bash
# List all requests (paginated)
GET https://platform.thena.ai/v1/requests
  -H "x-api-key: YOUR_API_KEY"

# Get single request with messages
GET https://platform.thena.ai/v1/requests/{requestId}
  -H "x-api-key: YOUR_API_KEY"

# List accounts
GET https://platform.thena.ai/v1/accounts
  -H "x-api-key: YOUR_API_KEY"

# List contacts/users
GET https://platform.thena.ai/v1/users
  -H "x-api-key: YOUR_API_KEY"
```

Thena also exposes a Typesense-powered search API useful for scoping incremental exports and building delta passes ([docs.thena.ai](https://docs.thena.ai/api-reference/search/tickets-search?utm_source=openai)):

```json
{
  "q": "*",
  "query_by": "title,description",
  "filter_by": "createdAt:>2026-01-01&&statusName:!=resolved",
  "sort_by": "createdAt:asc"
}
```

Use this same search API for delta extraction during cutover. To extract only records modified after your initial snapshot, filter by `updatedAt` rather than `createdAt`:

```json
{
  "q": "*",
  "query_by": "title,description",
  "filter_by": "updatedAt:>2026-08-01T00:00:00Z",
  "sort_by": "updatedAt:asc"
}
```

Replace the timestamp with your initial extraction cutoff. Run this query once before freezing Thena to capture any requests updated during the historical load.

### Extraction Order

1. **Accounts** — extract all, build `thena_account_id → zoho_account_id` mapping
2. **Contacts/Users** — extract all, resolve Slack identities to emails, build mapping
3. **Requests** — extract with full message history and attachment URLs
4. **Attachments** — download binary files from Thena/Slack CDN URLs to local staging

Slack file URLs are authenticated and time-limited. Download them during extraction, not at load time.

### Thena API Response Field Names

The field names below are drawn from Thena Platform API v1 response payloads. Verify against your own API responses before hardcoding — Thena's schema is not yet published as a versioned OAS document.

Key fields observed in request objects:
- `id` — Thena request UUID
- `title` — AI-generated or manually set title
- `original_message_text` — raw Slack mrkdwn content of the originating message
- `original_message_user_slack_name` — Slack handle of the originating user
- `original_message_user_slack_profile_real_name` — display name from Slack profile
- `original_message_source` — source channel type (SLACK, EMAIL, etc.)
- `createdAt` / `updatedAt` — ISO 8601 timestamps
- `statusName` — current request status string
- `aiSummary` — AI-generated summary text
- `tags` — array of AI-assigned tag strings

> [!WARNING]
> **Field name verification:** Inspect actual API responses from your Thena workspace rather than relying solely on this list. Thena does not publish a versioned field schema, and field names may vary by plan tier or workspace configuration.

## Destination-Side Constraints: Zoho Desk API

### Authentication

Zoho Desk uses OAuth 2.0. Every request needs two headers:

```
Authorization: Zoho-oauthtoken {access_token}
orgId: {your_org_id}
```

Access tokens expire periodically — store your refresh token and regenerate as needed.

**Determining your data center region:** Your org's data center region is encoded in your Zoho account URL. If your Zoho portal loads at `yourdomain.zoho.eu`, your data center is EU. The pattern:

| Portal URL pattern | API base URL |
|---|---|
| `*.zoho.com` | `https://desk.zoho.com/api/v1/` |
| `*.zoho.eu` | `https://desk.zoho.eu/api/v1/` |
| `*.zoho.in` | `https://desk.zoho.in/api/v1/` |
| `*.zoho.com.au` | `https://desk.zoho.com.au/api/v1/` |
| `*.zoho.jp` | `https://desk.zoho.jp/api/v1/` |

Using the wrong regional base URL returns authentication errors that resemble token failures. Check your portal URL before writing a single API call. ([desk.zoho.com](https://desk.zoho.com/DeskAPIDocument?src=rest-api&utm_source=openai))

### Credit-Based Rate Limiting

Zoho Desk does **not** use simple requests-per-minute rate limiting. It uses a **credit-based system** where each API call costs credits, and your daily credit pool depends on your edition and user count.

| Edition | Base Credits | + Per Agent | Concurrency Limit |
|---|---|---|---|
| Free/Trial | 5,000 | 0 | 5 |
| Standard | 50,000 | 250 | 10 |
| Professional | 75,000 | 500 | 15 |
| Enterprise/Zoho One | 100,000 | 1,000 | 25 |

**Credit costs by operation:**
- Create a ticket: **1 credit**
- Create a thread/comment: **1 credit**
- Upload an attachment: **1 credit**
- List tickets (0–2,000 records): **3 credits**
- Search operations: **3 credits**

Credits reset every 24 hours based on your data center's timezone. Once exhausted, the API returns `429 Too Many Requests` with a `Retry-After` header.

**Concurrency limits** restrict the number of API calls active simultaneously across your entire org — not per token or per app. Your migration job competes with existing CRM syncs, marketplace apps, and custom integrations. Exceeding this limit triggers `EXCEEDED_MAXIMUM_ALLOWED_CONCURRENT_REQUESTS` errors.

> [!TIP]
> **Migration math example:** Enterprise edition with 50 agents = 100,000 + (50 × 1,000) = **150,000 credits/day**. Migrating 10,000 tickets with 3 messages each requires ~10,000 ticket creates + ~30,000 thread creates + contact/account creates — roughly 50K–60K credits. Completable in one day on Enterprise, but would take 2–3 days on Standard with 20 agents (~55K credits/day). For larger datasets, contact Zoho Support to request a temporary API limit increase during the migration window.

### Pre-Creating Custom Fields in Zoho Desk

Before loading any ticket data, create the custom fields that will hold Thena-specific metadata. Without these fields, the `cf_*` values in your ticket payloads will be silently dropped.

**Via Zoho Desk Setup UI:** Navigate to **Setup → Customization → Layouts and Fields → Ticket Fields**. Use "Add Field" to create each custom field, then note the system-generated field API name (formatted as `cf_fieldname`).

**Via API:** Custom fields can also be created programmatically, but the field management API is under the admin/setup scope. Confirm your OAuth scope includes `Desk.settings.UPDATE` before attempting API-based field creation.

Fields to create before migration:

| Field Label | API Name | Type | Purpose |
|---|---|---|---|
| Thena Request ID | `cf_thena_request_id` | Text (255) | Deduplication key |
| AI Summary | `cf_ai_summary` | Multi-line Text | Thena AI-generated summary |
| Slack Handle | `cf_slack_handle` | Text (255) | Original Slack username |
| Thena Source | `cf_thena_source` | Text (255) | Source channel (SLACK, EMAIL, etc.) |

Zoho Desk caps custom fields per edition: 50 (Standard), 150 (Professional), 230 (Enterprise). If you are on Standard and already have custom fields, audit your field count before migration.

### Key Load Endpoints

```bash
# Create account
POST https://desk.zoho.com/api/v1/accounts
  -H "Authorization: Zoho-oauthtoken {token}"
  -H "orgId: {orgId}"
  -d '{"accountName": "Acme Corp", "email": "support@acme.com"}'

# Create contact
POST https://desk.zoho.com/api/v1/contacts
  -d '{"lastName": "Smith", "email": "smith@acme.com", "accountId": "..."}'

# Create ticket
POST https://desk.zoho.com/api/v1/tickets
  -d '{"subject": "...", "departmentId": "...", "contactId": "...", "description": "...", "status": "Open"}'

# Add thread to ticket
POST https://desk.zoho.com/api/v1/tickets/{ticketId}/sendReply
  -d '{"channel": "EMAIL", "content": "...", "fromEmailAddress": "...", "to": "...", "contentType": "html"}'

# Upload attachment (two-step: upload first, then reference ID)
POST https://desk.zoho.com/api/v1/uploads
  (multipart/form-data with file — returns an id to reference in thread/comment)
```

A sample ticket creation payload with custom fields:

```json
{
  "departmentId": "7189000000012345",
  "contactId": "7189000000098765",
  "subject": "API Authentication Failure",
  "description": "User reported a 401 error on the /v2/users endpoint.",
  "status": "Closed",
  "createdTime": "2024-03-15T14:30:00.000Z",
  "cf": {
    "cf_thena_request_id": "req_987654321",
    "cf_ai_summary": "User experienced auth issues due to expired token."
  }
}
```

## Field Mapping: Thena → Zoho Desk

### Ticket Fields

| Thena Request Field | Zoho Desk Ticket Field | Transform |
|---|---|---|
| `title` / AI-generated title | `subject` | Direct map. Max 255 chars. |
| `original_message_text` | `description` | Slack mrkdwn → HTML. Max 65,535 chars. |
| `id` | Custom Field: `cf_thena_request_id` | Essential for deduplication and delta syncs. |
| Status (Open/Closed/etc.) | `status` | Map to layout-specific picklist values (see crosswalk below). |
| Priority | `priority` | Map to High/Medium/Low or custom values (see crosswalk below). |
| `original_message_source` (AI, MANUAL) | `cf_thena_source` | Custom field. |
| `aiSummary` | `cf_ai_summary` | Custom text field. |
| `tags` | Tags API | Use `POST /tickets/{id}/associateTag`. |
| `original_message_source` | `channel` | Map SLACK→Web, EMAIL→Email, etc. |
| `createdAt` | `createdTime` | ISO 8601 format. Zoho Desk **does** accept `createdTime` on ticket creation and stores it as the ticket's creation timestamp. Confirmed against API v1 — test in your environment with a 5-ticket proof of concept before relying on this for historical fidelity. |
| Assignee | `assigneeId` | Map Thena user → Zoho Desk agent ID. |

### Status and Priority Crosswalk

Thena and Zoho Desk both use configurable status and priority values. The defaults typically align as follows — but pull your live Zoho Desk field values from **Setup → Customization → Layouts and Fields** before hardcoding this crosswalk, because layout-specific picklists override defaults.

**Status mapping (Thena default → Zoho Desk default):**

| Thena Status | Zoho Desk Status |
|---|---|
| Open | Open |
| In Progress | In Progress |
| Pending | On Hold |
| Resolved | Resolved |
| Closed | Closed |

**Priority mapping (Thena default → Zoho Desk default):**

| Thena Priority | Zoho Desk Priority |
|---|---|
| Urgent | High |
| High | High |
| Medium | Medium |
| Low | Low |
| None / unset | (omit field; Zoho assigns default) |

Store this crosswalk in a config file, not hardcoded logic. Both systems allow admins to add or rename values — a config-driven approach means reruns stay deterministic if the mapping changes.

### Contact Fields

| Thena Field | Zoho Desk Field | Notes |
|---|---|---|
| `original_message_user_slack_profile_real_name` | `firstName` + `lastName` | Split on first space. If single token, use as `lastName`. |
| Slack email (resolved via `users.info`) | `email` | Primary join key. Required for reliable ticket association. |
| `original_message_user_slack_name` | `cf_slack_handle` | Preserve as custom field for auditability. |
| Account association | `accountId` | Map via account ID lookup table. |

### Thread Timestamp Behavior

> [!CAUTION]
> **Confirmed limitation — thread timestamps are not overridable via `sendReply`:** The Zoho Desk `sendReply` and `addComment` endpoints do not accept a timestamp parameter. Thread and comment timestamps are set to the API call time, not the original message time. This is confirmed against the Zoho Desk OAS spec ([raw.githubusercontent.com](https://raw.githubusercontent.com/zoho/zohodesk-oas/master/v1.0/Thread.json)) and the v1 API documentation. There is no import-mode API for threads.
>
> **Consequence:** Conversation chronology is preserved only by insertion order, not by displayed timestamps. Import messages **strictly in the order they were sent** (ascending `createdAt`), sequentially per ticket, to maintain readable conversation flow. If exact timestamps matter for compliance or SLA reporting, embed the original timestamp in the thread body: `<p><em>Original message: 2024-03-15 14:30 UTC</em></p>`.
>
> **Ticket `createdTime`** behaves differently — Zoho Desk does accept and store a supplied `createdTime` on ticket creation. Run a 5-ticket proof of concept in your environment to verify this holds for your Zoho Desk edition and configuration before relying on it.

## Slack Identity Resolution

This is the hardest part of extraction. Thena stores Slack user IDs and profile fields, but Zoho Desk requires a valid `contactId` (for customers) or `assigneeId` (for agents). If you inject a ticket without resolving identity, Zoho Desk attributes it to the API service account — ruining historical metrics.

**The resolution pipeline:**

1. Extract all unique Slack user IDs from Thena request messages and contact records.
2. Call the Slack `users.info` API for each unique Slack user ID to retrieve the canonical email address: `GET https://slack.com/api/users.info?user={slack_user_id}`. Slack's `users.info` endpoint is Tier 4 (100+ calls/minute allowed per workspace token), but workspace-level rate limits still apply — add a 0.5-second sleep between calls if resolving more than a few hundred users to avoid 429s on large workspaces.
3. Query Zoho Desk: `GET /api/v1/contacts/search?email={email}`.
4. If a match exists, cache the `contactId`.
5. If no match, create the contact (`POST /api/v1/contacts`), capture the new `contactId`, and cache it.
6. Store the full mapping in a persistent store (Redis, SQLite, or flat file) for lookup during ticket and thread creation.

**Slack-only contacts without email addresses:** Zoho Desk requires either `lastName` or `email` to create a contact. If a Thena contact only has a Slack profile name and no resolvable email, create the contact with the Slack display name as `lastName` and store the Slack ID in `cf_slack_handle`.

**Agent identity:** Build a separate mapping of Thena assignee → Zoho Desk agent ID. Retrieve all Zoho Desk agents via `GET /api/v1/agents` and match by email.

## Slack mrkdwn → HTML Conversion

Thena stores message content in Slack's mrkdwn format. Zoho Desk threads accept HTML (`contentType: "html"`). Without a conversion layer, your Zoho tickets will be littered with unreadable syntax like `<@U12345678>` and `<http://example.com|Click Here>`.

| Slack mrkdwn | HTML equivalent | Notes |
|---|---|---|
| `*bold*` | `<b>bold</b>` | |
| `_italic_` | `<i>italic</i>` | |
| `~strikethrough~` | `<s>strikethrough</s>` | |
| `` `code` `` | `<code>code</code>` | |
| ` ```code block``` ` | `<pre>code block</pre>` | |
| `<@U12345>` | Resolve to display name via identity cache | Fall back to `@slack_handle` if unresolved |
| `<#C12345\|channel>` | `#channel` (plain text) | |
| `<https://url\|text>` | `<a href="https://url">text</a>` | |
| `:emoji:` | Unicode character or text fallback | Use an emoji shortcode library |
| `> quoted text` | `<blockquote>quoted text</blockquote>` | |

Use a library like `slackify-html` (Node.js) or build a regex-based converter. Common edge cases: nested formatting (e.g., `*_bold italic_*`), emoji shortcodes, and user mentions where the Slack ID is not in your identity cache.

If Thena provides a `contentHtml` field on the message object, prefer that over converting raw mrkdwn — it avoids conversion errors entirely. ([docs.thena.ai](https://docs.thena.ai/api-reference/platform/comments/get-comments-on-an-entity))

## Attachment Handling

Zoho Desk enforces a **20 MB maximum attachment size** per file. Attachment uploads follow a two-step flow:

1. **Download** the attachment from Thena/Slack CDN URL to local staging during extraction (not at load time — Slack URLs are authenticated and time-limited).
2. **Check file size.** If >20 MB, upload to external cloud storage (S3, GCS) and embed the link in the thread body instead.
3. **Upload** to Zoho Desk via `POST /api/v1/uploads` (returns an `id`).
4. **Reference** the upload `id` in the thread's `attachmentIds` array when creating the thread or comment.

> [!WARNING]
> **Oversized files:** Your script must catch failures for files exceeding 20 MB, log them, and append a note to the ticket body: *"Attachment [filename] exceeded Zoho Desk's 20 MB limit and was stored externally at [URL]."* Log the filename, original size, and Thena request ID to a separate audit file for post-migration review.

## CSV Import: Where It Helps and Where It Doesn't

Zoho Desk supports CSV import for tickets, contacts, and accounts through **Setup > Data Administration > Import**. Record limits per batch ([help.zoho.com](https://help.zoho.com/portal/en/kb/desk/data-administration/import-export/articles/importing-data-in-desk)):

- Enterprise: 30,000 records
- Professional: 20,000 records
- Standard: 10,000 records

**CSV import does not preserve:**
- Ticket threads/conversations
- Attachments
- Comment history
- Thread authorship or timestamps

CSV is useful for **bulk-loading accounts and contacts** before running the API-based ticket migration — it saves API credits and is faster for parent records. But it is **not** a substitute for API-driven migration when conversation history, attachments, or comments matter.

## ETL Pipeline Architecture

### Execution Order

```
1. Pre-flight
   ├── Create Zoho Desk departments (map from Thena channels/teams)
   ├── Create custom fields (cf_thena_request_id, cf_ai_summary, cf_slack_handle, cf_thena_source)
   ├── Build agent email → Zoho Desk agentId mapping
   └── Verify data center region matches API base URL

2. Extract from Thena
   ├── Accounts → local staging
   ├── Contacts → local staging (with Slack ID resolution via users.info)
   └── Requests + Messages + Attachments → local staging

3. Load to Zoho Desk
   ├── Accounts (CSV or POST /api/v1/accounts) → build ID map
   ├── Contacts (CSV or POST /api/v1/contacts) → build ID map
   ├── Tickets (POST /api/v1/tickets) → build ID map
   ├── Threads per ticket (POST /tickets/{id}/sendReply) → chronological order, ascending createdAt
   └── Comments per ticket (POST /tickets/{id}/comments)

4. Validation
   ├── Record count comparison (Thena total vs. Zoho Desk total)
   ├── Thread count per ticket spot-check (sample 50 tickets)
   └── Attachment integrity verification (sample 20 attachments)
```

### Idempotency and Error Recovery

Store the mapping of `thena_id → zoho_id` in a persistent store. If the pipeline crashes mid-run:

- **Accounts/Contacts:** Check if the email already exists in Zoho Desk before creating (use `GET /api/v1/contacts/search?email={email}`).
- **Tickets:** Query `GET /api/v1/tickets/search?cf_thena_request_id={id}` before creating. If a result exists, skip creation and use the existing `ticketId`.
- **Threads:** No native deduplication in Zoho — track which `thena_message_id` values have been loaded per ticket in your local state store.

**Duplicate contact handling in Zoho Desk:** Org-level settings control whether contacts with matching emails are merged, rejected, or duplicated (**Setup → Data Administration → Duplicate Handling**). Verify your org's setting before the full load — the wrong configuration can silently break contact-ticket associations or create phantom duplicates. Test with 10 contacts before running the full contact import.

## Edge Cases and Failure Modes

**Multi-channel conversations.** A single Thena Request might span Slack, email, and web chat messages. In Zoho Desk, a ticket's threads are channel-tagged but live in a single conversation view. Map all messages to email-channel threads unless channel-level fidelity is a reporting requirement.

**Nested Thena comments.** Thena comments can be nested via `parentCommentId`. Zoho Desk comments are flat. Flatten nested comments into chronological order and add lightweight context markers (e.g., "In reply to [original message excerpt]:") to preserve thread context. ([docs.thena.ai](https://docs.thena.ai/api-reference/platform/comments/get-comments-on-an-entity))

**AI metadata with no Zoho equivalent.** Thena's AI-generated summaries, sentiment scores, and auto-detected request sources have no standard fields in Zoho Desk. Pre-create custom fields before migration. Custom field caps by edition: 50 (Standard), 150 (Professional), 230 (Enterprise). Audit your existing custom field count on Standard before adding migration fields.

**Data center region mismatch.** See the authentication section above. Using the wrong regional base URL returns authentication errors that look like token failures. Check your portal URL first.

**Duplicate contact handling.** Verify your Zoho Desk org setting at **Setup → Data Administration → Duplicate Handling** before running contact imports. Test behavior with a small batch.

**Requests with no messages.** Some Thena requests may have been created programmatically or via API with no message content. These create tickets in Zoho Desk with empty descriptions — valid but worth flagging in validation.

## Cutover Strategy

1. **Keep Thena active during migration.** Route new requests normally while the historical load runs.
2. **Set a cutover timestamp.** All Thena requests created before this timestamp get migrated; new ones will go directly to Zoho Desk.
3. **Run the historical load.** Extract and load closed/older Thena requests first to validate the pipeline before processing recent open tickets.
4. **Delta sync.** Re-extract requests with `updatedAt` greater than your initial extraction timestamp using the Thena search API filter shown in the extraction section. Load the delta into Zoho Desk, using `cf_thena_request_id` to detect and skip already-loaded tickets.
5. **Freeze Thena.** Disable new request creation in Thena. Run one final delta extraction for any records updated in the window between delta sync and freeze.
6. **Validate.** Spot-check 50–100 tickets across different accounts, channels, and date ranges.
7. **Go live.** Update Slack bot integrations, email forwarding rules, and web form targets to point at Zoho Desk.

**Validation sampling strategy — do not sample randomly.** Target these specific cases, as mapping bugs surface disproportionately in them:

- The oldest migrated ticket (tests timestamp handling)
- The newest migrated ticket (tests delta sync accuracy)
- The ticket with the most threads (tests thread ordering and credit consumption)
- A ticket with private/internal comments (tests `isPublic: false` handling)
- A ticket with attachments (tests two-step upload flow)
- A ticket that was reassigned or changed status multiple times (tests field mapping)
- A ticket from a contact that was Slack-only with no email (tests fallback identity handling)

For a detailed approach to minimizing downtime, see our [guide to zero-downtime help desk migrations](https://clonepartner.com/blog/blog/zero-downtime-help-desk-data-migration).

## Estimated Timeline and Effort

| Dataset Size | Extraction | Transform | Load | Validation | Total |
|---|---|---|---|---|---|
| Small (<5K requests) | 4–8 hrs | 8–16 hrs | 8–16 hrs | 4–8 hrs | 24–48 hrs |
| Mid (5K–30K requests) | 8–16 hrs | 16–32 hrs | 24–48 hrs | 8–16 hrs | 56–112 hrs |
| Large (30K+ requests) | 16–24 hrs | 32–48 hrs | 48–72 hrs | 16–24 hrs | 112–168 hrs |

The transform phase dominates. Slack identity resolution and mrkdwn conversion take more engineering time than the API calls themselves. The load phase duration depends entirely on your Zoho Desk edition and credit budget — Standard tier with a small team can stretch a mid-size load across 2–3 days due to the daily credit ceiling.

Load phase estimates assume single-threaded execution constrained by concurrency limits. Parallelizing up to your edition's concurrency ceiling (10 for Standard, 25 for Enterprise) can compress load time proportionally, but requires careful state management to avoid race conditions on shared contacts and accounts.

---

> Need help migrating from Thena to Zoho Desk? Book a 30-minute technical scoping call — we'll map your data, estimate your credit budget, and give you a realistic timeline.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I migrate from Thena to Zoho Desk using CSV import?

Only partially. Zoho Desk's CSV import handles accounts, contacts, and base ticket records, but it does not preserve ticket threads, attachments, comments, or conversation history. API-driven migration is required for full ticket history.

### What are Zoho Desk API rate limits for a migration?

Zoho Desk uses a credit-based system, not simple rate limits. Daily credits depend on your edition and user count — for example, Enterprise gets 100,000 base + 1,000 per user. Creating a ticket costs 1 credit. Org-wide concurrency limits (5–25 simultaneous calls depending on edition) also apply.

### How long does a Thena to Zoho Desk migration take?

For a mid-size dataset (5K–30K requests), expect 72–120 engineer-hours including pipeline development. The load phase alone can take 1–3 days depending on your Zoho Desk credit budget and concurrency limits.

### How do I handle Slack user identities when migrating to Zoho Desk?

Thena stores Slack user IDs that must be resolved to email addresses. Build a Slack-to-email mapping using the Slack users.info API, then create or match Zoho Desk contacts with those emails. Store the original Slack handle in a custom field for reference.

### What happens to Thena AI metadata like summaries and tags in Zoho Desk?

Zoho Desk has no native fields for AI summaries or sentiment. Pre-create custom fields (cf_ai_summary, cf_thena_source) in your Zoho Desk layout before migration. Thena tags can be mapped to Zoho Desk's tag system via the associateTag API.
