API reference
Migrations
Books moved in from QuickBooks, Xero or a file as of a cutover day, mapped account by account and tied out to the cent.
- GET
/v1/migrationsList migration runs - POST
/v1/migrationsStart a migration from other books - POST
/v1/migrations/csvStart a migration from a trial balance file - POST
/v1/migrations/quickbooksStart a migration from QuickBooks reports - POST
/v1/migrations/quickbooks_onlineStart a migration from a connected QuickBooks Online company - POST
/v1/migrations/xeroStart a migration from Xero reports - GET
/v1/migrations/{migration_id}Get a migration run and its tie-out - POST
/v1/migrations/{migration_id}/abandonAbandon a migration before it posts - PATCH
/v1/migrations/{migration_id}/control_totals/{control_total_id}Explain a difference in a migration's tie-out - PATCH
/v1/migrations/{migration_id}/mappingsMap source accounts to these books - PATCH
/v1/migrations/{migration_id}/partiesMap source customers and vendors to counterparties - POST
/v1/migrations/{migration_id}/postPost a migration and tie it out
Guides: Switch from QuickBooks
List migration runs
GET/v1/migrations
client.migrations.list()
Migration runs into these books, the newest first.
Parameters
| Name | Type | Description |
|---|---|---|
limitQuery | integer | Page size, 1–200. |
cursorQuery | string or null | The |
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl "https://api.ledgerit.ai/v1/migrations" \
-H "Authorization: Bearer $LEDGERIT_TOKEN"Start a migration from other books
POST/v1/migrations· Idempotent
client.migrations.create()
Start a migration with the old books as they stood on the cutover day:
their accounts and trial balance, and their open invoices and bills when
there are any. Each source account is matched to an account of these
books by number, then by name, and only to one of the same type; each
customer or vendor to the counterparty of the same name. The run is
mapped once every account with a balance lands somewhere, and draft
until then.
Request body
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X POST "https://api.ledgerit.ai/v1/migrations" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"cutover_date":"2026-09-30","mode":"…","snapshot":{"accounts":[{}],"trial_balance":[{}]},"source":"…"}'Start a migration from a trial balance file
POST/v1/migrations/csv· Idempotent
client.migrations.createFromCsv()
Start an opening-balances migration from books kept anywhere else,
written into LedgerIt's own file shape: a trial balance as of the cutover
day and, optionally, the open invoices, bills and credits on it, each on
its customer or vendor. The shape is in the switch-from-quickbooks
guide, with templates to copy. Refused with unprocessable for a file not in
that shape, naming the row that is not.
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X POST "https://api.ledgerit.ai/v1/migrations/csv" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"Start a migration from QuickBooks reports
POST/v1/migrations/quickbooks· Idempotent
client.migrations.createFromQuickbooks()
Start an opening-balances migration from QuickBooks Online's own
exports: the Trial Balance report as of the cutover day and, so accounts
are matched by type and number too, the Account List. With the A/R and
A/P Aging Detail reports, every open invoice, bill, credit and unapplied
payment comes in on its customer or vendor, and the aging is tied out
party by party. Refused with unprocessable for a file that is not one
of those reports, or a trial balance with no "As of" day and none
given.
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X POST "https://api.ledgerit.ai/v1/migrations/quickbooks" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"Start a migration from a connected QuickBooks Online company
POST/v1/migrations/quickbooks_online· Idempotent
client.migrations.createFromQuickbooksOnline()
Start an opening-balances migration from a connected QuickBooks Online
company: its Trial Balance, Account List and A/R and A/P Aging Detail
reports, read as of the cutover day — the same reports the export path
asks a person to download, tied out the same way. Refused with
unprocessable for a connection that is not QuickBooks Online, and with
connection_rejected when QuickBooks refuses the sign-in: connect it
again.
Request body
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X POST "https://api.ledgerit.ai/v1/migrations/quickbooks_online" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"connection_id":"…","cutover_date":"2026-09-30"}'Start a migration from Xero reports
POST/v1/migrations/xero· Idempotent
client.migrations.createFromXero()
Start an opening-balances migration from Xero's own exports: the
Trial Balance report as at the cutover day and, so accounts are matched
by type and code too, the chart of accounts. With the Aged Receivables
and Aged Payables Detail reports, every open invoice, bill and credit
note comes in on its contact, and the aging is tied out party by party.
Each report is read from Excel or CSV. Refused with unprocessable for
a file that is not one of those reports, or a trial balance with no "As
at" day and none given.
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X POST "https://api.ledgerit.ai/v1/migrations/xero" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"Get a migration run and its tie-out
GET/v1/migrations/{migration_id}
client.migrations.get()
A migration run with where each source account lands and, once posted, every control total: what the old books and these say, and why they differ where they do.
Parameters
| Name | Type | Description |
|---|---|---|
migration_idPath · Required | string | An id that starts with |
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl "https://api.ledgerit.ai/v1/migrations/{migration_id}" \
-H "Authorization: Bearer $LEDGERIT_TOKEN"Abandon a migration before it posts
POST/v1/migrations/{migration_id}/abandon· Idempotent
client.migrations.abandon()
Give up a run before it posts. It stays listed, as abandoned; one in
the books is corrected through its entry instead.
Parameters
| Name | Type | Description |
|---|---|---|
migration_idPath · Required | string | An id that starts with |
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X POST "https://api.ledgerit.ai/v1/migrations/{migration_id}/abandon" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"Explain a difference in a migration's tie-out
PATCH/v1/migrations/{migration_id}/control_totals/{control_total_id}· Idempotent
client.migrations.explain()
Give a control total's difference its reason, or take it back. The run
is tied_out once every difference has one.
Parameters
| Name | Type | Description |
|---|---|---|
migration_idPath · Required | string | An id that starts with |
control_total_idPath · Required | string | An id that starts with |
Request body
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X PATCH "https://api.ledgerit.ai/v1/migrations/{migration_id}/control_totals/{control_total_id}" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"explanation":"…"}'Map source accounts to these books
PATCH/v1/migrations/{migration_id}/mappings· Idempotent
client.migrations.updateMappings()
Say which account of these books each listed source account lands in
(null to unmap). Only before the run posts; refused with conflict
after.
Parameters
| Name | Type | Description |
|---|---|---|
migration_idPath · Required | string | An id that starts with |
Request body
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X PATCH "https://api.ledgerit.ai/v1/migrations/{migration_id}/mappings" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"mappings":[{"account_id":"…","source_account_id":"…"}]}'Map source customers and vendors to counterparties
PATCH/v1/migrations/{migration_id}/parties· Idempotent
client.migrations.updateParties()
Say which counterparty of these books each listed source customer or
vendor is (null to add one by its name when the run posts). Only before
the run posts; refused with conflict after.
Parameters
| Name | Type | Description |
|---|---|---|
migration_idPath · Required | string | An id that starts with |
Request body
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X PATCH "https://api.ledgerit.ai/v1/migrations/{migration_id}/parties" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"parties":[{"party_id":"…","source_party_id":"…"}]}'Post a migration and tie it out
POST/v1/migrations/{migration_id}/post· Idempotent
client.migrations.post()
Put a mapped run into the books, and tie it out. In
opening_balances mode the source trial balance becomes the opening
balances as of the cutover day; income and expense before it are
carried in retained earnings, and the run says so beside the difference
that makes. Open items come in on their receivable or payable, each
naming its party and falling due when the old books say, and the AR and
AP aging is tied out by account and by party. In full_history mode
every entry of the old books is posted as a migration entry, each
fiscal year before the cutover's is closed into retained earnings, and
the trial balance, cash and the receivables and payables are tied out;
an entry these books refuse is unprocessable, naming it. Refused with conflict
while accounts are unmapped, when an open item's account is not mapped
to a receivable or payable, or when another run is already in these
books, and unprocessable when the source trial balance does not
balance.
Parameters
| Name | Type | Description |
|---|---|---|
migration_idPath · Required | string | An id that starts with |
Response
Errors
- 400
- The request was malformed or a parameter is invalid.
- 401
- No valid credential was sent.
- 403
- The credential or the member's role does not allow this.
- 404
- No such resource in this ledger.
- 409
- A conflicting request, e.g. one with the same Idempotency-Key still running.
- 422
- A well-formed request a business rule refused.
- 429
- Too many requests; retry after the Retry-After header.
- 500
- Something failed on our side; the request_id identifies it.
Example
curl -X POST "https://api.ledgerit.ai/v1/migrations/{migration_id}/post" \
-H "Authorization: Bearer $LEDGERIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"Objects
The objects these operations take and return.
MigrationRunPage
| Name | Type | Description |
|---|---|---|
dataRequired | array of MigrationRunOut | |
has_moreRequired | boolean | |
next_cursorRequired | string or null |
MigrationCreate
| Name | Type | Description |
|---|---|---|
cutover_dateRequired | string (date) | The last day the old books cover. These books carry on from the next. |
modeRequired | string |
|
snapshotRequired | MigrationSource | |
sourceRequired | string | Where the books come from: |
MigrationRunDetailOut
| Name | Type | Description |
|---|---|---|
control_totalsRequired | array of MigrationControlTotalOut | |
created_atRequired | string (date-time) | |
cutover_dateRequired | string (date) | |
idRequired | string | An id that starts with |
mappingsRequired | array of MigrationMappingOut | |
modeRequired | string | |
opening_entry_idRequired | string or null | An id that starts with |
partiesRequired | array of MigrationPartyMappingOut | The customers and vendors its open items name. |
posted_atRequired | string (date-time) or null | |
row_versionRequired | integer | |
sourceRequired | string | |
source_snapshot_hashRequired | string | sha256 of the snapshot it was given. |
statusRequired | string |
|
tied_outRequired | boolean | Every control total ties to the cent or has a reason. |
QuickBooksOnlineStart
| Name | Type | Description |
|---|---|---|
connection_idRequired | string | The QuickBooks Online connection of these books to read. |
cutover_dateRequired | string (date) | The last day the old books cover: every report is read as of it. |
MigrationExplanationIn
| Name | Type | Description |
|---|---|---|
explanationRequired | string or null | Why the source and these books differ here; null takes the reason back. |
MigrationMappingsUpdate
| Name | Type | Description |
|---|---|---|
mappingsRequired | array of MigrationMappingIn |
MigrationPartyMappingsUpdate
| Name | Type | Description |
|---|---|---|
partiesRequired | array of MigrationPartyMappingIn |
MigrationRunOut
| Name | Type | Description |
|---|---|---|
created_atRequired | string (date-time) | |
cutover_dateRequired | string (date) | |
idRequired | string | An id that starts with |
modeRequired | string | |
opening_entry_idRequired | string or null | An id that starts with |
posted_atRequired | string (date-time) or null | |
row_versionRequired | integer | |
sourceRequired | string | |
source_snapshot_hashRequired | string | sha256 of the snapshot it was given. |
statusRequired | string |
|
MigrationSource
The old books on the cutover day.
| Name | Type | Description |
|---|---|---|
accountsRequired | array of SourceAccount | |
entries | array of SourceEntry | Every entry of the old books up to the cutover day, for a |
open_items | array of SourceOpenItem | The AR and AP aging on the cutover day, item by item. They come in on their parties, and the aging is tied out party by party. Opening balances only. |
parties | array of SourceParty | The customers and vendors the open items name. |
trial_balanceRequired | array of SourceBalance | The source's trial balance at the end of the cutover day: the figures every account is tied out against. |
MigrationControlTotalOut
| Name | Type | Description |
|---|---|---|
controlRequired | string |
|
differenceRequired | string | |
explained_atRequired | string (date-time) or null | |
explained_byRequired | string or null | An id that starts with |
explanationRequired | string or null | Why the two differ, when they do. |
idRequired | string | An id that starts with |
imported_totalRequired | string | What these books say on the cutover day. |
source_totalRequired | string | What the old books say: positive on the debit side, or for an aging total, positive for what is owed. |
subjectRequired | string | The account's code in these books; for an aging total, a receivable's or payable's code (the account as a whole) or a counterparty's slug (one party). |
subject_nameRequired | string or null |
MigrationMappingOut
| Name | Type | Description |
|---|---|---|
account_codeRequired | string or null | |
account_idRequired | string or null | An id that starts with |
account_nameRequired | string or null | |
source_account_idRequired | string | |
source_balanceRequired | string | Its balance on the cutover day, positive on the debit side. |
source_nameRequired | string | |
source_numberRequired | string or null | |
source_typeRequired | string or null |
MigrationPartyMappingOut
| Name | Type | Description |
|---|---|---|
open_itemsRequired | integer | How many of the source's open items name it. |
party_idRequired | string or null | An id that starts with |
party_nameRequired | string or null | |
source_nameRequired | string | |
source_party_idRequired | string | |
source_totalRequired | string | What its open items add up to: positive for what is owed. |
MigrationMappingIn
| Name | Type | Description |
|---|---|---|
account_idRequired | string or null | An id that starts with |
source_account_idRequired | string |
MigrationPartyMappingIn
| Name | Type | Description |
|---|---|---|
party_idRequired | string or null | An id that starts with |
source_party_idRequired | string |
SourceAccount
| Name | Type | Description |
|---|---|---|
idRequired | string | The source's own id for the account. |
is_cash | boolean | A bank or cash account: its balance is tied out on its own. |
nameRequired | string | Its full name in the source, |
number | string or null | Its account number, when the source numbers accounts. |
type | string or null |
|
SourceEntry
One entry of the old books, for a full-history migration.
| Name | Type | Description |
|---|---|---|
dateRequired | string (date) | |
descriptionRequired | string | |
idRequired | string | The source's own id for the entry; unique. |
linesRequired | array of SourceLine | |
number | string or null | Its number in the source, when it has one. |
SourceOpenItem
An invoice, bill, credit or unapplied payment still open on the cutover day: one row of the source's AR or AP aging.
| Name | Type | Description |
|---|---|---|
account_idRequired | string | The |
amountRequired | number | string | What is left of it on the cutover day: positive for what a customer owes or a vendor is owed, negative for a credit or a payment not yet applied. |
due_on | string (date) or null | The day it falls due; without one it falls due on the party's terms from the cutover day. |
issued_on | string (date) or null | The day it was issued. |
number | string or null | The invoice's or bill's number, when it has one. |
party_idRequired | string | A |
SourceParty
A customer or vendor of the old books.
| Name | Type | Description |
|---|---|---|
idRequired | string | The source's own id for the customer or vendor. |
nameRequired | string | Its name in the source. |
SourceBalance
| Name | Type | Description |
|---|---|---|
account_idRequired | string | A |
credit | number | string | Default 0 |
debit | number | string | Default 0 |
SourceLine
One line of an entry of the old books.
| Name | Type | Description |
|---|---|---|
account_idRequired | string | A |
credit | number | string | Default 0 |
debit | number | string | Default 0 |
memo | string or null | |
party_id | string or null | A |