Puzzel Case Management to Tidio Migration: Technical Guide
Technical guide to migrating from Puzzel Case Management to Tidio. Covers API extraction, JSONL ticket import, object mapping, and zero-downtime cutover.
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
Puzzel Case Management to Tidio Migration: Technical Guide
TL;DR: Puzzel Case Management → Tidio Migration
Puzzel's CSV export captures ticket metadata but strips full message threads, internal notes, and attachments — API extraction is mandatory. For loading historical tickets, Tidio's JSONL file import (Plus plan and above) is the correct path; the live POST /tickets/as-contact endpoint does not support createdAt overrides, so it stamps everything with the import time. Puzzel's Teams, Categories, Forms, and Form Fields do not map 1:1 to Tidio's operators, tags, departments, contact properties, and ticket custom fields. The JSONL import schema does not include tag or custom-field payloads — those require a post-load enrichment pass via the Tidio OpenAPI. A typical mid-size migration (10,000–50,000 tickets) takes 2–4 weeks.
What Is a Puzzel Case Management to Tidio Migration?
A Puzzel Case Management to Tidio migration moves tickets, contacts, organisations, conversation history, internal notes, and attachments from Puzzel's case management system into Tidio while preserving message threads, timestamps, and relational integrity.
Puzzel Case Management is a Case/Ticket management tool that provides your contact centre or helpdesk with the ability to manage written interactions in a secure and efficient way. Each e-mail in the queue is converted into a support ticket and assigned a unique case/ticket ID which is then filtered, categorised and distributed to the right team or agent. It sits inside the broader Puzzel CX ecosystem — built for contact centres handling email, SMS, and API-based ticket channels with skill-based routing, SLA tracking, and category hierarchies.
Tidio is a customer service and engagement platform that combines live chat, automated chatbots, and help desk tools into a single interface. It helps businesses manage customer conversations more effectively by pulling interactions from various channels into one shared workspace. Core components include an omnichannel inbox that aggregates website chat, email, Instagram, Messenger, and WhatsApp; a no-code automation builder called Flows; a ticketing system with SLA management; and deep native integrations with major ecommerce, CRM, and marketing tools.
Tidio publicly documents migration paths from Zendesk, Intercom, Gorgias, and LiveChat — but not from Puzzel. There is no vendor-authored Puzzel connector. This is a custom migration, and the work is about preserving message threads, structured fields, attachments, assignees, and status semantics across two fundamentally different data models.
Why This Migration Is Not a Simple Export/Import
Puzzel and Tidio have different data architectures. Puzzel is built around structured ticket queues with skill-based routing, category hierarchies, form fields, and organisation-level grouping. Every inquiry is a "Case" with strict states (New, Open, Pending, Resolved, Closed, On-Hold) assigned to Teams based on routing rules. Tidio organises data around contacts with attached conversations, a newer ticketing layer, contact properties, and departments.
The CSV export from Puzzel captures ticket-level metadata — reference numbers, statuses, teams, categories, timestamps — but does not include full message threads, agent replies, internal notes, or file attachments. You will then be given the option of which columns you want to include in your CSV export. These columns include all of the available columns for the Tickets List, including columns not currently shown on the Tickets List. All Categories are also available to select as Columns for the CSV export. That is useful for reporting and reconciliation, not for a full migration.
To get complete conversation data, you need the Puzzel REST API. The Help menu (Help -> API Documentation) now links directly to the newly introduced API documentation powered by Swagger UI. The Swagger docs are tenant-specific — your instance's base URL will be unique to your Puzzel deployment.
On the Tidio side, the loading mechanism matters just as much. Tidio separates contact import (CSV or API) from ticket import (JSONL file or API). For historical tickets with preserved timestamps and message threads, Tidio's JSONL file import is the right tool. The live ticket-creation API endpoints do not expose createdAt controls, meaning every ticket gets stamped with the current server time — useless for historical data.
Puzzel API access requires an API Ticket Channel. You must create one under Settings > Ticket Channels > API in Puzzel, selecting either Basic Token or OAuth authentication. Without this, there is no programmatic way to extract full ticket threads.
Object Mapping: Puzzel Case Management → Tidio
This is where the architectural decisions happen. There is no 1:1 mapping between these platforms.
| Puzzel Object | Tidio Object | Migration Method | Notes |
|---|---|---|---|
| Customers | Contacts | POST /contacts/batch or JSONL ticket import |
Match on email. API has no upsert — always creates new contacts. |
| Agents / Users | Operators | Manual setup in Tidio panel | No bulk operator import API in Tidio. |
| Teams | Departments | Pre-create in Tidio | departmentName in JSONL import must match an existing department. |
| Categories | Tags or Ticket Custom Fields | Post-load enrichment via API | Fixed-choice categories → dropdowns; free-text → text fields or tags. |
| Form Fields | Ticket Custom Fields or Contact Properties | Pre-create target schema, enrich post-load | Types: text, email, number, phone, url. |
| Organisations | Contact Properties (custom) | Flatten into contact or ticket properties | No native organisation entity in Tidio. |
| Tickets | Tickets | JSONL file import | Preserves createdAt timestamps and message ordering. |
| Ticket Messages | Messages within ticket | Included in JSONL ticket payload | Sequenced by timestamp within the message array. |
| Internal Notes | Internal messages | JSONL type: internal |
Supported in JSONL import format. |
| Attachments | Attachments | JSONL attachments [].publicUrl |
Must be publicly accessible URLs. Local uploads not supported in import. |
| Tags | Ticket Tags | Post-load enrichment via PATCH /tickets/{ticketId} |
JSONL import schema does not include tag fields. |
| Priorities | Priority | Direct map in JSONL | Tidio supports low, normal, urgent. |
| SLAs | SLA Policies | Manual rebuild | Not migratable via API or import. |
| Automated Workflows | Flows | Manual rebuild | Must be recreated in Tidio's no-code Flows builder. |
Key structural gap: tags and custom fields in the import. Tidio's documented JSONL import schema covers contact, status, subject, messages, createdAt, operatorEmail, mailbox, priority, and departmentName — but does not include tag or custom-field payloads. If you need tags or ticket custom fields preserved, run a post-load enrichment pass through PATCH /tickets/{ticketId}, which supports tag_ids and custom_fields.
Step 1: Audit and Scope the Migration
Before writing extraction code, answer these questions:
- How many tickets? Under 5,000 is small, 5,000–50,000 is mid-size, 50,000+ is large. This determines whether rate limits will bottleneck the enrichment phase — and whether a single JSONL file or multiple split files is appropriate.
- What date range matters? Many teams only migrate the last 12–24 months. Older tickets can be archived as PDFs from Puzzel.
- Do you need full conversation threads or just metadata? Metadata-only: CSV may suffice. Full threads: API extraction is mandatory.
- Which custom fields carry operational value? Map only what agents will actually use in Tidio. Don't migrate dead taxonomy.
- Are there attachments? Attachments inflate extraction time, staging costs, and import complexity. Quantify the volume before committing. A dataset with 50,000 tickets averaging 2 attachments each is a different problem than 50,000 tickets with none.
- What character encodings are in play? Puzzel is widely used in Nordic markets. Email threads archived before 2015 may be stored in Windows-1252, ISO-8859-1, or other legacy encodings. Identify this before the transformation phase — invalid UTF-8 sequences cause JSONL file rejection at the Tidio validation stage.
- Freeze the source schema. If admins rename categories, forms, or teams mid-project, your mapping tables drift immediately. Lock down Puzzel configuration before the final extraction run.
Step 2: Extract Data from Puzzel via API
Extraction runs through the Puzzel REST API, authenticated via your API Ticket Channel credentials.
Puzzel's API capabilities expanded significantly between 2022 and 2025: API access appeared in July 2022; tag/category/form-field search improvements landed in March 2024; message-content lookup in June 2024; HTML and TEXT message retrieval in August 2024; customer and organisation CRUD in February 2025; and reply plus inbound-message endpoints in March 2025. Modern Puzzel tenants should have full extraction coverage without depending on CSV workflows.
Extraction sequence
- Agents and Teams — Pull agent list and team assignments. These inform your Tidio operator and department setup.
- Customers and Organisations — Extract all customer records with email, name, phone, and organisation associations.
- Categories and Form Field definitions — These become your mapping blueprint for Tidio tags and custom fields.
- Tickets with full threads — For each ticket, extract metadata (reference, status, priority, team, categories, timestamps) and message content (replies, notes, attachments). The initial ticket payload rarely contains the full conversation history — make secondary calls to message/thread endpoints for each ticket.
- Attachments — Download binary files linked to tickets. Store them locally with ticket-reference-keyed filenames, then stage them on a cloud bucket with public URLs for Tidio import.
If your tenant exposes PCM Raw Data, use tables like cm_category_choices, cm_form_field_answers, and cm_tags_tickets as validation sources to cross-check API extracts.
Puzzel's Swagger docs enumerate all available endpoints per tenant. Pagination patterns and response structures vary — test with a small batch (50–100 tickets) before running a full extraction. Be aware that private notes flagged in the API will only be visible if your API channel has global access. When adding a 'note' to a ticket using the API, it is now possible to add a flag to a submitted 'note' to mark it as public or private. When a note has been marked as private, it will not be exposed when retrieving ticket content via an API channel.
Plan for network controls early. Puzzel supports IP whitelisting that can block OAuth token requests for non-whitelisted clients. If your extractor runs from ephemeral CI workers or rotating NAT egress, get those IPs approved before your migration window.
For a deeper look at what Puzzel's export options can and can't do, see our guide on how to export data from Puzzel Case Management.
Handling rate limits during extraction
Puzzel enforces API rate limits to protect tenant performance. The exact per-minute or per-hour ceiling is tenant-dependent and not published in Puzzel's public documentation — test incrementally starting at 5–10 concurrent requests and increase until you observe 429 Too Many Requests responses, then back off to 70–80% of that ceiling as your sustained rate. Implement exponential backoff in your HTTP client. Migration scripts often run for days — if you use short-lived OAuth tokens without automated refresh logic, your script will crash halfway through extraction. Always implement token refresh.
Step 3: Configure the Tidio Environment
Before loading data, configure the target platform.
Tidio plan requirements
All other OpenAPI endpoints are available exclusively on the Plus and Premium plans. Rate limits range from 10 requests per minute (entry plans) to 120 requests per minute (Premium).
For a migration of any meaningful size, you need a Plus plan at minimum. The Premium plan's 120 requests/minute rate limit is strongly recommended for datasets over 10,000 tickets — at 10 requests/minute (entry plans), enriching 50,000 tickets via PATCH /tickets/{ticketId} would take over 83 hours of continuous API calls before accounting for failures and retries. At 120 requests/minute (Premium), the same operation takes approximately 7 hours.
Tidio's developer documentation is in beta, and they are constantly working to improve and expand the capabilities of their API. Always verify endpoint behaviour against the live docs at developers.tidio.com before writing production code.
Pre-migration checklist
- Create Tidio OpenAPI credentials — Settings > Developer > OpenAPI. Generate your
Client-IdandClient-Secretpair. Authentication usesX-Tidio-Openapi-Client-IdandX-Tidio-Openapi-Client-Secretheaders. - Define Contact Properties — In Tidio there are some default properties like name, email, phone etc. and any other properties that can be defined. Each property has a specific type, such as text, email, number, phone, or url. Create custom properties to receive Puzzel Form Field data before importing contacts.
- Set up operators manually — Tidio has no bulk operator creation API. Add agents through the admin panel and assign roles. For departed agents, decide whether to create placeholder operator accounts to preserve historical attribution or map all legacy responses to a single "Migration" operator. Placeholder accounts preserve better reporting context.
- Create departments — Map Puzzel Teams to Tidio departments. The
departmentNamefield in the JSONL import must match an existing department exactly;Generalis the default fallback. - Plan your tag taxonomy — Decide how Puzzel categories flatten into Tidio tags. A two-level Puzzel category like
Product > Returnsmight become a tagproduct-returns. - Define ticket custom fields — Create text or dropdown fields to receive Puzzel Form Field data and structured category values.
Step 4: Transform and Load Contacts
Contacts must exist in Tidio before you load tickets that reference them. You can pre-create them via the API or let the JSONL ticket import create stub contacts from the contact object in each ticket line. If you need richer contact data — custom properties, phone numbers, organisation metadata — pre-create them via the API.
Contact creation via API
When creating a contact, you need to provide at least one of the following: email, first_name, last_name, or phone. Please note that using this method will always add new contacts. No data will be overwritten, even if it includes the email or a distinct_id.
Tidio's POST /contacts endpoint has no upsert behaviour. Running the import twice creates duplicates. Your migration script must:
- De-duplicate source data before loading
- Track which contacts have been created (store the returned Tidio contact ID against the Puzzel customer ID in a persistent mapping table — a SQLite database works; a plain CSV file does not if the process crashes mid-run)
- Use
GET /contactswith an email filter to check for existing contacts if running incremental loads
import requests
import time
TIDIO_BASE = "https://api.tidio.co"
HEADERS = {
"X-Tidio-Openapi-Client-Id": "YOUR_CLIENT_ID",
"X-Tidio-Openapi-Client-Secret": "YOUR_CLIENT_SECRET",
"Content-Type": "application/json"
}
def create_contacts_batch(contacts):
"""Create up to 100 contacts per batch."""
resp = requests.post(
f"{TIDIO_BASE}/contacts/batch",
headers=HEADERS,
json={"contacts": contacts}
)
if resp.status_code == 429:
time.sleep(60) # back off on rate limit
return create_contacts_batch(contacts)
resp.raise_for_status()
return resp.json()Contacts are saved with all or nothing strategy, so all contacts must be valid. The maximum amount of contacts to create in a single request is 100. A single invalid contact in a batch rejects the entire batch. Validate all payloads locally before submitting — check email format, field type constraints, and required-field presence before any network call.
| Puzzel Customer Field | Tidio Contact Field | Notes |
|---|---|---|
email |
Primary matching key | |
| Name | first_name, last_name |
Split if Puzzel stores as single field |
| Phone | phone |
|
| Organisation | Custom property (e.g. organisation) |
Must define property first |
| Custom Form Fields | Custom contact properties | Type-matched: text, number, url, etc. |
Step 5: Transform and Load Historical Tickets
This is where the JSONL-vs-API choice matters most.
Do not use POST /tickets/as-contact for historical backfill. Tidio's live ticket-creation endpoints do not support setting arbitrary createdAt timestamps. Every ticket created through the live API gets the current server time. For historical data with preserved timestamps and thread ordering, use Tidio's JSONL file import — a Plus-plan feature that accepts UTF-8 JSONL files up to 1 GB and supports createdAt on both tickets and individual messages.
JSONL file structure and limits
Each line in the JSONL file is one JSON object representing one ticket. The documented schema fields are: contact, status, subject, messages (array), createdAt, operatorEmail, mailbox, priority, and departmentName. Tags and custom fields are not part of this schema — they require post-load enrichment.
The 1 GB file size limit is the published constraint. There is no officially documented maximum line count per file. In practice, for datasets above 100,000 tickets, split into multiple files of 20,000–30,000 tickets each to keep files manageable and to allow partial retries if validation fails. Tidio validates the entire file before importing anything — a validation failure on one file does not affect others.
Building the JSONL file
{
"contact": { "email": "alex@example.com", "name": "Alex Reed" },
"status": "pending",
"subject": "[PCM-184233] Refund request for order 4419",
"createdAt": "2025-02-11T09:14:03Z",
"operatorEmail": "agent@company.com",
"departmentName": "Billing",
"priority": "urgent",
"messages": [
{
"author": { "type": "operator", "email": "agent@company.com" },
"htmlContent": "<p>Migrated metadata: source_status=On-Hold; source_team=Nordics Billing; source_form=Refund</p>",
"type": "internal",
"createdAt": "2025-02-11T09:14:03Z"
},
{
"author": { "type": "contact", "email": "alex@example.com" },
"htmlContent": "<p>I still haven't received my refund.</p>",
"plainTextContent": "I still haven't received my refund.",
"createdAt": "2025-02-11T09:14:03Z",
"recipients": { "to": "billing@company.com" }
}
]
}Embedding the Puzzel reference in the subject line (as shown above with [PCM-184233]) serves a dual purpose: it makes tickets searchable by source ID post-migration, and it provides a reliable anchor for delta sync deduplication. During a follow-up delta import, query Tidio's ticket search API for the reference pattern before creating new tickets to avoid duplicates. This is more reliable than matching on contact + timestamp combinations, which can collide when a single contact submits multiple tickets within the same minute.
Status mapping
Puzzel supports granular states: Open, Pending, On-Hold, Resolved, Closed, Deleted. Tidio's import accepts open, pending, and solved. Map Puzzel Open → open, Pending and On-Hold → pending, Resolved and Closed → solved. To preserve granular state history, insert the original Puzzel status as a metadata note (using type: internal) or as a tag applied during post-load enrichment. The type: internal message approach is preferable because it survives even if the enrichment pass fails.
Internal notes
Tidio's JSONL import format supports type: internal messages, which provides a path for migrating Puzzel's private notes. Include them in the message array with the internal type flag. This also makes a useful container for migration metadata — source ticket IDs, original statuses, category values — that keeps the data human-visible even if a post-load enrichment job fails.
HTML sanitization and encoding
Puzzel often stores email threads with complex, legacy HTML formatting — nested tables, inline CSS, Outlook-specific markup, and tracking pixels. Tidio's inbox prefers cleaner formatting. Your transformation script should include a sanitization pass (using a library like bleach in Python or DOMPurify in JavaScript) to strip inline styles, remove tracking pixels, and convert complex tables into readable text before generating the JSONL file. If you push raw Puzzel HTML into Tidio, the UI may render broken layouts or unreadable text blocks.
Character encoding is a specific risk in Puzzel deployments. Puzzel is widely used in Nordic and European markets where historical email content may be stored in Windows-1252 or ISO-8859-1 rather than UTF-8. Characters like ø, æ, å, ü, and ß frequently appear in customer names, subjects, and message bodies. The JSONL file must be strict UTF-8 — any byte sequence that is not valid UTF-8 will cause file rejection at the Tidio validation stage. Your transformation pipeline should explicitly decode source strings using the detected encoding and re-encode to UTF-8 before writing to JSONL. In Python: text.encode('windows-1252').decode('utf-8', errors='replace') is a safe fallback for undetected encoding; chardet provides better detection.
What Tidio returns when JSONL validation fails
When a JSONL import file fails validation, Tidio returns an error response identifying the problematic lines and the reason for rejection. Common rejection reasons include:
operatorEmaildoes not match any existing Tidio operatordepartmentNamedoes not match any existing department (case-sensitive)- Malformed JSON on a given line number
- Invalid
statusvalue (must beopen,pending, orsolved) - Contact missing both email and name fields
- Invalid UTF-8 byte sequences in the file body
If the file contains 100 or more invalid tickets, Tidio stops validation and imports nothing from that file. Test with a representative sample of 2–5 tickets first, then scale to batches of 500–1,000 before submitting full files. When a file partially fails, the error report identifies which lines failed — fix those lines and resubmit only the corrected records.
Step 6: Handle Attachments
Attachments require a two-phase process:
- Download from Puzzel — Iterate through ticket messages, extract attachment references, download files, and validate MIME types. Store locally with ticket-reference-keyed filenames.
- Stage on public URLs — Tidio's JSONL import requires
attachments [].publicUrl; local file uploads are not supported in the import format. Upload files to a cloud bucket (AWS S3, GCS) with public or pre-signed URLs.
If you use pre-signed URLs with expiration times, ensure the links remain reachable for the entire import window. Tidio fetches files during import processing, not at upload time. Set pre-signed URL TTLs to at least 72 hours to account for Tidio's import queue delays. For large attachment volumes (10,000+ files), budget extra time and implement retry logic with exponential backoff for failed uploads. A single failed upload should not block the entire migration — log failures separately and handle them in a follow-up pass.
Resource planning for attachments: A dataset of 50,000 tickets averaging 2 attachments at 200 KB each produces approximately 20 GB of staged files. At typical cloud egress rates, downloading this volume from Puzzel may take 4–8 hours depending on your API concurrency and network bandwidth. Budget cloud storage costs accordingly, and plan to delete staged files after successful import confirmation.
Step 7: Post-Load Enrichment
Since Tidio's JSONL import schema does not include tags or ticket custom field values, structured metadata needs a second pass.
After the import completes:
- Build a mapping between Puzzel ticket references and newly created Tidio ticket IDs. Use the embedded Puzzel reference in the subject line (e.g.,
[PCM-184233]) to search Tidio's ticket API and retrieve the Tidio-assigned ID. Store this mapping in a persistent database — SQLite is sufficient — indexed by both Puzzel reference and Tidio ticket ID. - Apply tags and custom fields using
PATCH /tickets/{ticketId}withtag_idsandcustom_fieldsvalues. - Build checkpointing into your enrichment scripts. At 120 requests/minute (Premium plan), enriching 50,000 tickets takes approximately 7 hours of continuous operation. Your script must checkpoint progress — record each successfully enriched ticket ID — so that failures or interruptions can be resumed from where they stopped rather than restarting from zero.
- Verify enrichment completeness by querying a random sample of tickets and confirming tag and custom field values match the source data.
Post-load: Tidio's handling of imported tickets
Imported historical tickets are fully searchable and visible in Tidio's inbox. They do not trigger automation Flows by default — Flows only activate on new incoming conversations, not imported historical records. This means post-import enrichment via API is the only path to applying tags and custom field values to historical tickets; you cannot rely on Flows to do this work automatically.
Step 8: Reconciliation, Delta Sync, and Cutover
Validation checklist
- Record counts — Total contacts and tickets in Puzzel vs. Tidio.
- Message integrity — Sample 50–100 tickets across different date ranges. Verify all messages appear in correct order with correct attribution.
- Custom field accuracy — Spot-check that Puzzel Form Field values landed in the right Tidio contact properties and ticket custom fields.
- Attachment availability — Confirm attachments are downloadable from Tidio.
- Tag mapping — Verify that Puzzel categories translated correctly into Tidio tags.
- Character rendering — Specifically check tickets containing non-ASCII characters (Nordic, German, French characters) for encoding corruption.
- Edge cases — Tickets with 50+ messages, tickets without customer email (anonymous), tickets with multiple organisation associations, SMS-originated tickets without email identity.
Delta sync and cutover
Between the test migration and production cutover, new tickets will arrive in Puzzel.
- Initial historical load — Import the bulk of historical data (all tickets older than 14 days) while agents continue working normally in Puzzel.
- Validation — Run the checklist above against the loaded data. Fix any systematic issues before the final extraction.
- Final extraction — Run a delta extraction filtering by
created_atorupdated_atafter the initial load's cutoff timestamp. Before creating new tickets in Tidio, query for the embedded Puzzel reference pattern in existing ticket subjects to prevent duplicates. - Final import — Load the delta JSONL file into Tidio.
- Switch mail flow — Connect or forward support mailboxes into Tidio Help Desk. Update DNS records, email forwarding rules, and website chat widgets. This is the go-live moment for new ticket traffic.
- Monitor — Watch for issues during the first 48–72 hours post-cutover. Check that new tickets thread correctly, that Flows trigger as expected, and that no duplicate tickets appear from the delta boundary.
For a detailed playbook on the operational side, see our zero-downtime migration guide.
What Cannot Be Migrated Programmatically
- SLA policies and timers — Must be recreated manually in Tidio's SLA settings. Breach conditions and business hours don't transfer.
- Automated workflows / Inbound Rules — Puzzel's event rules and routing logic have no API migration path. Rebuild using Tidio's Flows builder.
- Chatbot flows — Puzzel's virtual agents are not portable. Tidio uses its own Flows and Lyro AI system.
- Agent performance history and reporting data — Export Puzzel reports as CSVs for archival. Tidio analytics starts fresh post-migration.
- Knowledge base content — If you used Puzzel or an integrated tool for FAQ hosting, those articles exist outside the core case management data model and must be migrated separately.
- Email threading configuration — Puzzel Case Management uses a comprehensive method to ensure that email messages are correctly threaded into tickets. Tidio handles threading differently through its own conversation model. Imported historical threads are static — they will not continue threading with future replies unless Tidio matches the email subject and contact correctly. Test this behaviour with a live agent before cutover.
Common Failure Modes
Organised by migration phase:
Extraction failures
- Lost internal notes — Teams discover post-migration that agent notes are missing because the extraction script didn't pull private notes. Puzzel's API only exposes private notes to API channels with global access — verify this permission before extraction, not after.
- Hardcoded OAuth tokens — Migration scripts run for days. Short-lived tokens without automated refresh crash the extraction mid-run. Implement token refresh as a first-class concern, not an afterthought.
- IP whitelisting blocks — OAuth token requests from non-whitelisted IPs fail silently or return authentication errors. Confirm your extractor's egress IP is approved before starting a long-running extraction job.
- Non-email tickets with no email identity — Puzzel handles email, SMS, and API channels. SMS or API-originated tickets without a stable email identity cannot be directly loaded into Tidio's import format, which requires a contact email. Handle separately: synthetic contacts, note-only archives, or documented exclusion.
Transformation failures
- Encoding corruption — Non-UTF-8 byte sequences from legacy Windows-1252 or ISO-8859-1 email archives cause JSONL file rejection. Detect and convert encoding explicitly for every text field — do not assume UTF-8.
- Broken HTML rendering — Raw Puzzel HTML (Outlook markup, nested tables, tracking pixels) renders as broken layout in Tidio's UI. Sanitize all
htmlContentfields before generating the JSONL file. - Mismatched
operatorEmailordepartmentName— A single mismatched value in a large JSONL file causes the entire file to fail validation if it pushes the invalid-ticket count past 100. Validate all operator emails and department names against your pre-created Tidio configuration before generating the file.
Load failures
- Duplicate contacts from re-runs — Tidio's contact creation doesn't de-duplicate. Running the import script twice without tracking creates a mess. Maintain a persistent ID mapping table from the start.
- Batch rejection from a single bad record — Tidio's batch contact endpoint uses all-or-nothing validation. One malformed email kills 99 valid contacts in the same batch. Validate everything locally first.
- Attachment URLs expiring mid-import — Pre-signed URLs with short TTLs may expire before Tidio processes them. Set minimum 72-hour expiration windows.
Post-load failures
- Rate limit exhaustion without backoff — Hitting
429responses and hammering retries without exponential backoff gets your client temporarily blocked. At 120 requests/minute (Premium), enriching 50,000 tickets takes ~7 hours — plan accordingly. - Lost enrichment progress — An enrichment script that crashes without checkpointing has no record of which tickets have been processed. On restart, it either re-enriches already-processed tickets (wasting time) or skips them (leaving gaps). Use a persistent checkpoint store.
- Stale API docs — Tidio's developer documentation is in beta. Endpoint behaviour can change between when you read the docs and when you run production code. Always verify against the live docs at developers.tidio.com immediately before your production run.
Timeline for a Mid-Size Migration
| Phase | Duration | Activities |
|---|---|---|
| Planning & mapping | 3–4 days | Scope audit, object mapping, Tidio environment setup |
| Extraction | 3–5 days | API extraction from Puzzel, attachment download |
| Transformation & test load | 3–5 days | JSONL generation, encoding conversion, HTML sanitization, test import, validation |
| Production load & delta sync | 2–3 days | Full JSONL import, delta catch-up, attachment staging |
| Post-load enrichment | 1–2 days | Tag and custom field enrichment via API |
| Validation & cutover | 2–3 days | Reconciliation, stakeholder sign-off, mail routing switch |
Total: 2–4 weeks for 10,000–50,000 tickets. Smaller datasets (under 5,000) can compress to 1–2 weeks. Larger datasets (50,000+) or complex custom-field mappings push toward 4–6 weeks. The attachment volume and the richness of custom field taxonomy are the two variables that most affect timeline beyond ticket count alone.
Compute requirements: Transformation of 50,000 tickets with full message threads, HTML sanitization, and JSONL serialisation typically requires 4–8 GB of RAM and runs in 2–6 hours on a single machine. This is not a distributed processing problem at this scale. For 500,000+ tickets, consider chunked processing with a job queue.
Getting This Right
This migration is manageable for an engineering team with REST API integration experience, 2–4 weeks of focused effort, and willingness to handle edge cases like orphaned tickets, encoding issues, and attachment failures. The failure modes most likely to derail timelines are encoding issues discovered late in transformation, JSONL validation failures from operator or department name mismatches, and post-load enrichment without checkpointing.
For related migration paths, see our guides on Puzzel Case Management to Kayako and Zendesk to Tidio. If you are evaluating other platforms in the Puzzel ecosystem, we also cover migrations into Puzzel from HappyFox and Enchant.
Frequently Asked Questions
- Can I use Puzzel's CSV export for a full Tidio migration?
- No. Puzzel's CSV export captures ticket metadata (reference, status, team, categories) but strips full message threads, agent replies, internal notes, and attachments. For a complete migration with preserved conversation history, you must extract via the Puzzel REST API.
- What Tidio plan do I need for an API-based migration?
- You need at least the Plus plan for full OpenAPI access and the JSONL ticket import feature. Rate limits range from 10 requests/minute on entry plans to 120 requests/minute on Premium. For datasets over 10,000 tickets, Premium is strongly recommended to avoid multi-day enrichment times.
- Does Tidio deduplicate contacts on import?
- No. Tidio's POST /contacts endpoint always creates new contacts and does not support upsert. Running the import twice creates duplicates. You must de-duplicate source data before loading and maintain a Puzzel-to-Tidio ID mapping table to track created contacts.
- How long does a Puzzel Case Management to Tidio migration take?
- A mid-size migration (10,000–50,000 tickets) typically takes 2–4 weeks: 3–4 days planning, 3–5 days extraction, 3–5 days transformation and testing, 2–3 days production loading, and 2–3 days validation and cutover. Smaller datasets can compress to 1–2 weeks.
- Can I migrate SLAs and automated workflows from Puzzel to Tidio?
- No. SLA policies, inbound rules, event-based workflows, and virtual agent configurations cannot be migrated programmatically. These must be manually recreated in Tidio using its SLA settings and Flows builder.

