Skip to main content

Documentation Index

Fetch the complete documentation index at: https://ekso.dev/llms.txt

Use this file to discover all available pages before exploring further.

ekso migrate zendesk pulls organisations, tickets, comments, attachments, and tags from Zendesk Support (REST v2) into your Ekso tenant. Two commands — collect and apply. Read Migrate overview and Before you start first. Zendesk is a support-ticket platform, not an issue tracker. The data shape is different and so is the customer’s mental model. This page calls out the differences explicitly.

What gets imported

Zendesk conceptEkso shape
OrganisationDataContainer (or a synthetic zendesk-default container if the instance is flat)
TicketDataItem
Comment (public or private)DataAnnotation (visibility flag preserved as Tags=["public"] or ["private"])
Attachment (per-comment)DataFile
End-user (customer)DataUser (tagged migrated-from:zendesk-enduser)
Agent (staff)DataUser (tagged migrated-from:zendesk-agent)
Group (agent group)ConfigLabel (prefix group:) + DataItem.Tags
TagConfigLabel
Custom ticket fieldDataItem.Field[] (via --field-map) or Meta
Satisfaction rating (CSAT)DataItem.Meta.zendesk_csat
BrandDataItem.Meta.zendesk_brand_id

What does NOT get imported

  • SLA policies & breach data. Read-only fidelity in Meta.zendesk_sla only — Ekso v1 has no first-class SLA entity. Use Ekso clocks for SLA replacement going forward.
  • Macros & triggers. Workflow, not data. Re-author as Ekso rules.
  • Views & filters. Re-author on the Ekso side.
  • Talk / Chat / Sell / Explore — adjacent products, out of scope.
  • Side conversations. v1 limitation; tracked.
  • Article / KB content. Use Ekso docs and import separately.

Before you start

Pre-flight checklist on top of the general one:
  • A Zendesk Support instance.
  • A Zendesk admin account.
  • A Zendesk API token — generate one in Admin Centre → Channels → API → Token Access.
  • Your Zendesk subdomain — e.g. acme.zendesk.com.
Add a zendesk block to your migration.config.json:
{
  "ekso": { "tenant": "acme", "apiKey": "ek_live_..." },

  "source": "zendesk",

  "zendesk": {
    "url": "https://acme.zendesk.com",
    "email": "[email protected]",
    "apiToken": "abc123..."
  }
}
Or use the env-var override EKSO_MIGRATE_ZENDESK_TOKEN for CI use cases. Zendesk auth uses the email/token:API_TOKEN basic-auth format — the CLI assembles this from email and apiToken.

Step 1 — list orgs

ekso migrate zendesk list-orgs \
    --config migration.config.json \
    --tenant acme
If your Zendesk instance uses organisations:
NAME                    ID         DOMAIN              MEMBERS
ACME Internal           360...01   acme.com            18
ACME Customers          360...02   external            132
If your Zendesk instance is flat (no orgs), the list is empty — that’s expected. Every ticket will land in a single synthetic container called zendesk-default on apply.

Step 2 — collect

To collect every ticket in the workspace (recommended for first-run):
ekso migrate zendesk collect \
    --config migration.config.json \
    --tenant acme
To restrict to a specific org (or orgs — --org repeats):
ekso migrate zendesk collect \
    --config migration.config.json \
    --org 360001234567 \
    --tenant acme
Sample run:
fetching orgs... 2 fetched
fetching tickets... 8421/8421  ok
fetching comments... 26109/26109  ok
fetching attachments... 1247/1247  ok
saved cache: ~/.ekso/migrate/zendesk-2026-04-29T143205.sqlite
Useful flags:
  • --no-attachments — skip downloads.
  • --no-comments — skip comments. (Note: Zendesk has no separate “description” field — the first comment IS the description, so --no-comments will leave items with empty bodies.)
  • --exclude-closed — filter out closed tickets at the API layer.
  • --resume — pick up where a killed collect left off.
Zendesk’s rate limits are aggressive (~700 requests/minute on Enterprise, 200/minute on lower plans). A 50K-ticket migration on a Pro plan takes ~4 hours and benefits from --resume.

Step 3 — dry-run apply

ekso migrate zendesk apply \
    --config migration.config.json \
    --process proc_support \
    --tenant acme \
    --dry-run

Step 4 — apply for real

ekso migrate zendesk apply \
    --config migration.config.json \
    --process proc_support \
    --tenant acme
Sample run:
applying container ACME Internal...     ok
applying users (162)...                 ok (40 agents, 122 end-users)
applying items (8421)...                ok (8421 created)
applying annotations (26109)...         ok
applying files (1247)...                ok (1242 created, 5 skipped >20MB)
done in 18m12s — exit 0
If interrupted, re-run with --resume.

Identity resolution for Zendesk

Zendesk has two identity classes:
  • Agents are tenant-internal staff. They become Ekso DataUsers tagged migrated-from:zendesk-agent.
  • End-users are customers — the people who opened tickets. They become Ekso DataUsers tagged migrated-from:zendesk-enduser.
Both classes are minted by default with --user-strategy match-or-create. The end-user vs agent distinction stays visible via the migrated-from tag so you can later filter, separate by permissions, or remove the end-user accounts entirely if you only need them for audit. Anonymous end-users (Zendesk lets customers email in without registering) get a placeholder email like <ticket-id>@anon.zendesk.acme.local and are tagged migrated-from:zendesk-anon. Use --user-strategy migration-bot --fallback-user user_zendesk_admin if you’d rather collapse all anonymous end-users into one bot user. Read Identity resolution for the full mechanics.

Custom fields for Zendesk

Zendesk ticket field IDs are numeric strings — quote them in YAML:
zendesk:
  "360001234567": { ekso: AffectedComponent, kind: text }
  "360001234568":
    ekso: CustomerTier
    kind: picker
    picker:
      Free: tier-free
      Pro:  tier-pro
      Ent:  tier-ent
Discover your ticket field IDs:
curl -u [email protected]/token:API_TOKEN \
    https://acme.zendesk.com/api/v2/ticket_fields.json \
    | jq '.ticket_fields[] | {id, title, type}'
See Field mapping for the full format.

Iron rule — first comment as description

Zendesk has no separate “description” field on a ticket. The first comment IS the description. The migrator handles this:
  1. The first comment’s body is used as the item’s description (DataItem.Field[CoreFieldType.Description]).
  2. If the ticket has only one comment, no DataAnnotation is created — the comment is just the description.
  3. If the ticket has multiple comments, all comments (including the first) become DataAnnotations as well, so the audit log is preserved.
The Zendesk API natively returns this shape — you don’t need any special flags. But if you’re comparing the Ekso output against Zendesk’s UI, expect items where the description and the first comment look identical. That’s expected.

Public vs private comments

Zendesk distinguishes public-facing comments (visible to the requester) from internal notes (visible to agents only). The migrator preserves this:
  • Public comments → DataAnnotation with Tags=["public"].
  • Internal notes → DataAnnotation with Tags=["private"].
Filter with ekso annotation list --item <id> --tag private after migration.

Orgs vs flat instances

Zendesk has no project concept. The migrator uses organisations as the closest analog and maps each org to one Ekso DataContainer. If your instance is flat (no orgs configured — common for smaller Zendesk customers), the migrator creates a single synthetic container called zendesk-default and lands every ticket there. You can split tickets into multiple Ekso containers post-migration via the admin UI if the flat-container approach doesn’t fit.

Multi-brand Zendesk

If your Zendesk instance has multiple brands, the brand ID is preserved in DataItem.Meta.zendesk_brand_id. Per-brand container creation is on the v2 roadmap; in v1, the org-as-container model is preserved across brands.

Troubleshooting Zendesk-specific issues

SymptomFix
exit 3 — Zendesk credentials invalidThe email/token:API_TOKEN basic-auth format is unusual — check both fields. Re-issue the token in Admin Centre → API → Token Access.
Synthetic container createdExpected for flat instances. Every ticket lands in zendesk-default.
Anonymous end-user minted with placeholder emailSource user has no email. Tagged migrated-from:zendesk-anon.
Attachment over 20MB skippedEkso file-size limit. Skipped with warning; URL preserved in Meta.
exit 7 — Zendesk rate-limit exceededWait — Zendesk limits are tight. Re-run with --resume.
See Troubleshooting for the full per-source error table.

Why migrate to Ekso

Zendesk specialises in support; Ekso unifies support, project management, and time tracking with financial intelligence. Tickets that turn into work flow naturally without leaving the platform. See Ekso vs Zendesk for the broader comparison.

Where to next