> For the complete documentation index, see [llms.txt](https://docs.kick.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kick.co/api/migration.md).

# Migration

Move an entity's history from another accounting system into Kick, so new transactions are categorized by rules built from that history.

When you move an entity's books from another accounting system, you push its history to Kick as journal entries. An accounting migration tells Kick that history is coming. Kick waits for it, builds categorization rules from it, and then categorizes the entity's new transactions with those rules.

| Step                               | Request                                                                                 | What Kick does                                                |
| ---------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| 1. Create the accounting migration | `POST /platform/v1/entities/{entityId}/accounting-migration`                            | Pauses automatic categorization of the entity's transactions  |
| 2. Create journal entries          | `POST /platform/v1/entities/{entityId}/journal-entries/bulk`                            | Posts the historical entries to the entity's ledger           |
| 3. Finalize the migration          | `POST /platform/v1/entities/{entityId}/accounting-migration/generate-transaction-rules` | Generates rules from the history, then resumes categorization |

***

### Before you start

* **The entity has a `bookkeepingStartDate`.** It's the first day Kick keeps books for the entity, and the history you migrate is dated before it. Set it when you create the entity, or update it through [Entities](/api/api-reference/entities.md).
* **The entity has no opening balances.** An entity that already has opening balances can't start a migration.
* **Every account your history posts to exists in Kick.** Journal entry lines address accounts by uuid, so map each account in the source system to an account in the entity's [Chart of Accounts](/api/api-reference/chart-of-accounts.md), and create the missing ones, before step 2.
  * **Bank accounts and credit cards are the exception.** Migrate before you connect any bank feed. For now, create each bank account as an account with type `Cash` and each credit card with type `Credit Cards`, and post their history to those. After the migration, you connect the feeds and merge these accounts into the ones Plaid creates. See [Connect bank feeds](#connect-bank-feeds).

An entity keeps at most one accounting migration, so you run this process once per entity.

***

### 1. Create the accounting migration

The request has no body. The entity is addressed by its uuid in the path.

```bash
curl -X POST https://use.kick.co/api/platform/v1/entities/0192f3a4-7c1e-7b2d-9f40-5d8e6a1b3c27/accounting-migration \
  -H "Authorization: Bearer kick_org_..." \
  -H "Idempotency-Key: 4c6f1d2a-8e3b-4f57-a1c9-0b7d2e5f8a64"
```

```json
{
  "accountingMigration": {
    "id": "0192f3a5-1d8b-7e40-8c2f-6a9b0e4d7f13",
    "enrichmentRulesSeededAt": null,
    "createdAt": "2026-10-07T11:52:04.000Z"
  }
}
```

Kick answers `201` and pauses automatic categorization of the entity's transactions. Any transactions that arrive while the migration is open are imported but stay uncategorized until step 3. That way the rules built from your history apply before anything is categorized.

***

### 2. Create journal entries

Push the entity's history through the journal entries endpoints, up to 100 entries per bulk request. Each bulk request is all or nothing: one invalid entry rejects the whole batch, and nothing in it is written.

```bash
curl -X POST https://use.kick.co/api/platform/v1/entities/0192f3a4-7c1e-7b2d-9f40-5d8e6a1b3c27/journal-entries/bulk \
  -H "Authorization: Bearer kick_org_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8a1e5c3f-2b7d-4e90-b6a4-3f0c9d1e7b52" \
  -d '{
    "journalEntries": [
      {
        "date": "2025-11-14",
        "memo": "Imported from source system, entry 10482",
        "lines": [
          {
            "accountId": "0192f3a6-2e4c-7a18-b3d5-8f1a6c0e9b47",
            "debitAmount": 1250.00,
            "creditAmount": null,
            "description": "GUSTO PAYROLL 111425"
          },
          {
            "accountId": "0192f3a6-5b9f-7c03-a2e8-1d4f7b3c6e90",
            "debitAmount": null,
            "creditAmount": 1250.00,
            "description": "GUSTO PAYROLL 111425"
          }
        ]
      }
    ]
  }'
```

Send a new `Idempotency-Key` with each batch and reuse it only to retry that batch, so a timed-out request never posts twice.

→ [Idempotency](/api/idempotency.md)

→ [Journal Entries](/api/api-reference/journal-entries.md)

#### Which entries Kick learns from

You can migrate the entity's full history, but rule generation reads only lines that meet all of these conditions:

* **Ledger**. The entry is on the cash ledger, which is the default `ledgerBasis`. Entries on the accrual ledger are written but not used.
* **Account**. The line posts to an account whose `class` is `Income` or `Expenses`, and whose name doesn't start with "Uncategorized", such as Uncategorized Expenses.
* **Date**. The entry is dated in the year before `bookkeepingStartDate`. For a start date of `2026-01-01`, that's `2024-12-31` through `2025-12-31`. Older entries don't affect ongoing categorization.
* **Description**. The line has a non-empty `description`. When a line has none, Kick uses the entry's `memo` instead, unless the memo is longer than 1,024 bytes.

***

### 3. Finalize the migration

Call this once every journal entry from step 2 is written. The request has no body.

```bash
curl -X POST https://use.kick.co/api/platform/v1/entities/0192f3a4-7c1e-7b2d-9f40-5d8e6a1b3c27/accounting-migration/generate-transaction-rules \
  -H "Authorization: Bearer kick_org_..." \
  -H "Idempotency-Key: e2b9d4a7-6c1f-4a38-9e05-7d3b8f2c1a69"
```

Kick answers `202` with the migration and generates the rules in the background. When it's done, it sets `enrichmentRulesSeededAt`.

Rule generation can take between a minute to an hour, depending on how much history falls in the year before `bookkeepingStartDate`. Retrieve the migration and check `enrichmentRulesSeededAt` to see when it's done.

```bash
curl https://use.kick.co/api/platform/v1/entities/0192f3a4-7c1e-7b2d-9f40-5d8e6a1b3c27/accounting-migration \
  -H "Authorization: Bearer kick_org_..."
```

A migration is finalized once. After `enrichmentRulesSeededAt` is set, calling this endpoint again answers `409`.

***

### What happens after finalizing

* **Rules.** Kick groups lines with similar descriptions and creates a rule for each group. The rule assigns the account that the group's lines were posted to most often.
* **Categorization resumes.** Kick categorizes new transactions going forward with these rules, based on how the source system categorized the same descriptions. Transactions without a matching rule are categorized the usual way.
* **Existing transactions.** Kick runs categorization again on the entity's uncategorized transactions, including the ones imported while the migration was open. Transactions that already have a category keep it.
* **Visibility.** Kick manages these rules itself, so the [Transaction Rules](/api/api-reference/transaction-rules.md) endpoints don't return them. Rules you create through those endpoints work alongside them.

→ [AI Categorization](/api/ai-categorization.md)

***

### Connect bank feeds

Connect bank feeds after you finalize the migration, so the feed's transactions are categorized with the rules from step 3. Each Plaid connection creates its own account in the Chart of Accounts. That leaves two accounts for each bank account or credit card: the one you created for the migrated history, and the one that receives the live feed. Merge them:

1. Create the connection through [Plaid Connections](/api/api-reference/plaid-connections.md). The response's `account.id` identifies the connected bank account or credit card.
2. List the entity's Chart of Accounts and find the account whose `financialAccountId` equals that `account.id`. That's the feed's account.
3. Merge the account you created into the feed's account. Send yours as `sourceAccountId` and the feed's as `targetAccountId`.

```bash
curl -X POST https://use.kick.co/api/platform/v1/entities/0192f3a4-7c1e-7b2d-9f40-5d8e6a1b3c27/chart-of-accounts/merge \
  -H "Authorization: Bearer kick_org_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5d3a9e1c-7f2b-4c86-a0e4-9b1f6d2c8e37" \
  -d '{
    "sourceAccountId": "0192f3a6-5b9f-7c03-a2e8-1d4f7b3c6e90",
    "targetAccountId": "0192f3b1-8c4d-7e29-b7a1-2e6f0d9c5a14"
  }'
```

Kick answers `200` with the feed's account. The migrated journal entry lines move onto it, and the account you created is permanently deleted, so the history and the live feed are on one account.

A merge that's blocked answers `409` with a `blockers` list. Check these before you merge:

* **Direction**. An account connected to a bank feed can't be the source (`source_is_financial`), so always merge into the feed's account.
* **Class**. Both accounts must have the same class (`different_class`). A `Cash` account is in `Assets` and a `Credit Cards` account is in `Liabilities`, matching the accounts Plaid creates for bank accounts and credit cards.
* **Lock date**. Merge before you set a lock date that covers the migrated period. Activity on or before the lock date blocks the merge (`locked_period` or `locked_period_must_be_unlocked`). Check the entity's lock date with [Locks](/api/api-reference/locks.md).

→ [Chart of Accounts](/api/api-reference/chart-of-accounts.md)

***

### Errors

| Status | Request  | Cause                                                                                                                   | What to do                                                                                |
| ------ | -------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `400`  | Create   | The entity has no `bookkeepingStartDate`                                                                                | Set it through [Entities](/api/api-reference/entities.md), then retry                     |
| `404`  | Any      | The entity doesn't exist or is outside your book of business, or, on retrieve and finalize, the entity has no migration | Check the entity uuid, and create the migration first                                     |
| `409`  | Create   | The entity already has a migration                                                                                      | Retrieve the existing one and continue from where it stands                               |
| `409`  | Create   | The entity already has opening balances                                                                                 | This entity can't start a migration. Contact <platform@kick.co> if you need to migrate it |
| `409`  | Finalize | Rules were already generated for this migration                                                                         | Nothing to do. The migration is finalized                                                 |
| `422`  | Finalize | The workspace isn't eligible for rule generation                                                                        | Contact <platform@kick.co> with the workspace uuid                                        |

→ [Errors](/api/readme.md#errors)

→ [Accounting Migration reference](/api/api-reference/accounting-migration.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.kick.co/api/migration.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
