> ## Documentation Index
> Fetch the complete documentation index at: https://ekso.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Jira

> Move Atlassian Jira projects, issues, comments, attachments, links, and worklogs into Ekso — in two commands.

`ekso migrate jira` pulls projects, issues, comments, worklogs, attachments, and links from Atlassian Jira — Cloud, Data Center, or Server — into your Ekso tenant. Two commands — `collect` and `apply`. Read [Migrate overview](/cli/migrate/overview) and [Before you start](/cli/migrate/before-you-start) first.

## What gets imported

| Jira concept                              | Ekso shape                                                                            |
| ----------------------------------------- | ------------------------------------------------------------------------------------- |
| Project                                   | [`DataContainer`](/data-model/data-container) (`Code = ACME`, `Name = ACME Platform`) |
| Issue (any type — Bug, Task, Story, Epic) | [`DataItem`](/data-model/data-item)                                                   |
| Comment                                   | [`DataAnnotation`](/data-model/data-annotation)                                       |
| Attachment                                | [`DataFile`](/data-model/data-file) (multipart upload)                                |
| Worklog                                   | [`DataItem.Time[]`](/data-model/data-item) (in the same item POST)                    |
| Issue link (relates, blocks, subtask, …)  | [`DataLink`](/data-model/data-link)                                                   |
| Sprint (GreenHopper)                      | [`DataCycle`](/data-model/data-cycle)                                                 |
| Fix Version                               | [`DataCycle`](/data-model/data-cycle)                                                 |
| Component                                 | [`ConfigLabel`](/data-model/config-label) (prefix `component:`) + item `Tags`         |
| User (assignee, reporter, commenter)      | [`DataUser`](/data-model/data-user) (matched or minted)                               |
| Story Points custom field                 | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`)           |
| Other custom fields                       | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) or `Meta` |
| Affects Version                           | `DataItem.Meta.jira_affects_versions[]` (lossless)                                    |

Authoring fidelity is preserved: every comment, every worklog, every item is attributed to the original Atlassian user (matched by email, or minted via [identity resolution](/cli/migrate/identity-resolution)).

## What does NOT get imported

* **Workflow definitions / transition rules.** The migrator snapshots the *current* status of each issue. Build the workflow on the Ekso side.
* **Dashboards, filters, JQL queries.** Migrate operationally, then re-author in Ekso.
* **Automations** (Jira Automation rules). Re-author as Ekso [rules](/cli/reference/rule).
* **Permission schemes / project roles.** Configure access on the Ekso side.
* **Brand customisation, themes, project icons.** Atlassian-specific.

## Editions — Cloud vs Data Center / Server

The CLI detects your Jira deployment automatically by calling `GET /rest/api/2/serverInfo` on first use — no configuration needed in the common case.

| Edition                    | Detection                            | Auth                            |
| -------------------------- | ------------------------------------ | ------------------------------- |
| Cloud (`cloud`)            | `deploymentType: Cloud`              | `username` (email) + `apiToken` |
| Data Center (`datacenter`) | `deploymentType: Server` (hosted DC) | `personalAccessToken`           |
| Server (`server`)          | `deploymentType: Server` (on-prem)   | `personalAccessToken`           |

To override detection, set `jira.edition` in your config to one of `auto` (default), `cloud`, `datacenter`, or `server`. You can also pass `--edition <value>` directly on the `list-projects` and `collect` commands.

## Before you start

Pre-flight checklist on top of [the general one](/cli/migrate/before-you-start):

* An Atlassian account with read access to the projects you want to migrate.
* Your Jira URL.
* **Cloud:** An [Atlassian API token](https://id.atlassian.com/manage-profile/security/api-tokens) (free). Site URL is `https://acme.atlassian.net`.
* **Data Center / Server:** A Personal Access Token (PAT) — create one in your profile under *Security → Personal Access Tokens*. Requires Jira 8.14 or later; attachment download requires Jira 8.17 or later.

### Cloud config

```json theme={null}
{
  "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." },

  "source": "jira",

  "jira": {
    "url": "https://acme.atlassian.net",
    "username": "you@acme.com",
    "apiToken": "ATATT3..."
  }
}
```

Use `EKSO_MIGRATE_JIRA_TOKEN` to pass the API token via environment variable instead.

### Data Center / Server config

```json theme={null}
{
  "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." },

  "source": "jira",

  "jira": {
    "url": "https://jira.mycompany.com",
    "edition": "datacenter",
    "personalAccessToken": "..."
  }
}
```

Use `EKSO_MIGRATE_JIRA_PAT` to pass the PAT via environment variable instead.

### Data Center — comments and descriptions

On Data Center / Server, issue descriptions and comments are returned as server-rendered HTML. The migrator imports them as-is; formatting is preserved.

### Data Center — SSO-fronted instances and attachments

If your Data Center instance sits behind SSO (SAML/OIDC), attachment downloads require a browser session that the CLI cannot always establish automatically. When a session cannot be obtained for a specific attachment, the download is skipped and logged — the rest of the migration continues. Check the collect output for any `attachment skipped` lines and download those files manually if needed.

## Step 1 — list projects

Verify your credentials work and see what Jira shows you:

```bash theme={null}
ekso migrate jira list-projects \
    --config migration.config.json \
    --url https://ekso.acme.com
```

Sample output:

```
NAME              KEY    ID       LEAD
ACME Platform     ACME   10001    Jane Doe
ACME Mobile       ACMEM  10002    John Smith
```

If this exits with code `3`, your credentials are wrong — check the API token (Cloud) or PAT (Data Center/Server). Pass `--exclude-closed` to filter projects in archived state. Pass `--edition <cloud|datacenter|server>` to override auto-detection.

## Step 2 — collect

Pull a single project's issues, comments, worklogs, and attachments into a local SQLite cache:

```bash theme={null}
ekso migrate jira collect \
    --config migration.config.json \
    --project ACME \
    --url https://ekso.acme.com
```

`--project` takes the Jira project **Key** (e.g. `ACME`), not the project **Name** ("ACME Platform"). The key is shown in the `KEY` column of `list-projects` above.

Repeat `--project` for multiple. Sample run:

```
fetching projects... 1 fetched
fetching ACME items... 1247/1247  ok
fetching comments... 4083/4083  ok
fetching worklogs... 891/891  ok
fetching attachments... 312/312  ok
saved cache: ~/.ekso/migrate/jira-2026-04-29T143205.sqlite
```

Useful flags:

* `--no-attachments` — skip binary download. \~10x faster on attachment-heavy projects.
* `--no-comments` — skip comments.
* `--exclude-closed` — JQL-filter out resolved issues at the source.
* `--resume` — pick up where a killed collect left off.
* `--edition <cloud|datacenter|server>` — override the auto-detected edition.

Expect roughly 200 issues/minute on a good network. A 10,000-issue project takes \~50 minutes including comments and attachments.

## Step 3 — dry-run apply

Before writing anything to your Ekso tenant, walk the cache and see what would happen:

```bash theme={null}
ekso migrate jira apply \
    --config migration.config.json \
    --process proc_engineering \
    --url https://ekso.acme.com \
    --dry-run
```

Sample output:

```
DRY-RUN — no writes.
would create:  1 container, 47 users, 1247 items, 4083 annotations,
               312 files, 89 links, 8 cycles
field-map check: ok (3 process fields would be auto-created)
```

If `field-map check` fails (e.g. picker without a value list), exit code is `2` and apply won't run for real until you fix the YAML.

## Step 4 — apply for real

```bash theme={null}
ekso migrate jira apply \
    --config migration.config.json \
    --process proc_engineering \
    --url https://ekso.acme.com
```

Sample run:

```
applying container ACME...               ok
applying users (47)...                   ok (45 created, 2 matched)
applying items (1247)...                 ok (1247 created)
applying annotations (4083)...           ok
applying files (312)...                  ok
applying links (89)...                   ok
applying cycles (8)...                   ok (5 sprints, 3 fix-versions)
done in 7m43s — exit 0
```

If the run is interrupted, re-run with `--resume`:

```bash theme={null}
ekso migrate jira apply \
    --config migration.config.json \
    --process proc_engineering \
    --resume
```

## Identity resolution for Jira

Atlassian users have email — `--user-strategy match-or-create` (the default) is reliable. Source authors are matched against your Ekso tenant; users without a match are minted with no invite email.

Read [Identity resolution](/cli/migrate/identity-resolution) for the full mechanics.

## Custom fields for Jira

Story Points, Sprint, custom Severity — these are Jira `customfield_*` fields. Map them to Ekso process fields with a `migration.fields.yaml`:

```yaml theme={null}
jira:
  customfield_10026: { ekso: StoryPoints, kind: decimal }
  customfield_10018: { ekso: Sprint,      kind: text }
  customfield_10031:
    ekso: Severity
    kind: picker
    picker:
      Critical: P0
      High:     P1
      Medium:   P2
      Low:      P3
```

Pass it to apply:

```bash theme={null}
ekso migrate jira apply \
    --config migration.config.json \
    --process proc_engineering \
    --field-map migration.fields.yaml
```

`ProcessFieldApplier` runs first; any field referenced by the YAML that doesn't exist on `proc_engineering` is auto-created via `POST /api/field` before any item write. See [Field mapping](/cli/migrate/field-mapping) for the full format and how to discover your custom-field IDs.

Without `--field-map`, custom fields fall through to `DataItem.Meta` losslessly (e.g. `Meta.jira_customfield_10026`).

## Sprints, Fix Versions, Components

Per the migrator's design, **every** Jira concept maps. There are no opt-in flags:

| Jira                           | Ekso                                                     |
| ------------------------------ | -------------------------------------------------------- |
| Sprint (GreenHopper)           | `DataCycle` (one cycle per sprint, items linked)         |
| Fix Version                    | `DataCycle` (sprint-shaped: name + dates + member items) |
| Affects Version                | `DataItem.Meta.jira_affects_versions[]`                  |
| Component                      | `ConfigLabel` (`component:Backend`) + `DataItem.Tags`    |
| Release (a closed Fix Version) | `DataCycle`                                              |

Cycles attach to a board on the Ekso side. Pass `--board <BOARD_ID>` if the tenant has more than one board; the CLI auto-discovers when there's exactly one.

## Issue type → Process

Multiple Jira issue types (Bug, Task, Story, Epic) feed into a single Ekso `DataProcess` per `apply` invocation. The mapping is one-way; the issue type is preserved in `DataItem.Meta.jira_issuetype`. If you need separate processes per issue type, run `apply` multiple times with different `--process` values and JQL-filtered caches.

## Iron rule — Atlassian's date format

Jira returns timestamps in the form `2024-08-21T16:33:21.000+0000` — note the `+0000` offset has no colon. The standard .NET `DateTime` parser trips on this. The CLI's `JiraDateTimeNormaliser` handles it correctly, and there's a regression test pinning the behaviour.

If you ever see "Date format unparseable" in apply output, the cache was generated by an older CLI version. Re-collect with the latest CLI.

## Troubleshooting Jira-specific issues

| Symptom                                         | Fix                                                                                                                    |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `exit 3 — Jira credentials invalid` (Cloud)     | Regenerate the API token. Check the email matches the token owner.                                                     |
| `exit 3 — Jira credentials invalid` (DC/Server) | Regenerate the PAT. Confirm Jira version is 8.14 or later.                                                             |
| `exit 7 — Jira rate-limit exceeded`             | Wait a few minutes; Atlassian's rate limits are per-tenant. Re-run with `--resume`.                                    |
| Item created with empty body                    | Atlassian returned no rendered HTML for an ADF description. Original ADF is preserved in `Meta.jira_description_html`. |
| `attachment skipped` lines in DC collect output | SSO is fronting the instance and a download session could not be established. Download those attachments manually.     |
| `Custom field unknown to field-map`             | Check the `customfield_*` ID exists on this Jira tenant.                                                               |

See [Troubleshooting](/cli/migrate/troubleshooting) for the full per-source error table.

## Why migrate to Ekso

Once you've moved a Jira project to Ekso, you get financial intelligence (cost / profitability) and AI-native primitives ([MCP](/cli/reference/mcp)) that don't exist on the Jira side. See [Ekso vs Jira](https://ekso.app/compare/ekso-vs-jira) for the broader comparison.

## Where to next

* **[Command reference](/cli/migrate/command-reference)** — full flag surface.
* **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive.
* **[Field mapping](/cli/migrate/field-mapping)** — write your `migration.fields.yaml`.
* **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery.
