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.

Guides: Switch from QuickBooks

List migration runs

GET/v1/migrations

client.migrations.list()

Migration runs into these books, the newest first.

Parameters

NameTypeDescription
limitQueryinteger

Page size, 1–200.

Default 50
cursorQuerystring or null

The next_cursor of the previous page.

Response

200MigrationRunPage

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
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

MigrationCreate

Response

201MigrationRunDetailOut

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
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

201MigrationRunDetailOut

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
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

201MigrationRunDetailOut

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
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

QuickBooksOnlineStart

Response

201MigrationRunDetailOut

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
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

201MigrationRunDetailOut

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
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

NameTypeDescription
migration_idPath · Requiredstring

An id that starts with mig_.

Response

200MigrationRunDetailOut

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
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

NameTypeDescription
migration_idPath · Requiredstring

An id that starts with mig_.

Response

200MigrationRunDetailOut

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
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

NameTypeDescription
migration_idPath · Requiredstring

An id that starts with mig_.

control_total_idPath · Requiredstring

An id that starts with mig_total_.

Request body

MigrationExplanationIn

Response

200MigrationRunDetailOut

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
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

NameTypeDescription
migration_idPath · Requiredstring

An id that starts with mig_.

Request body

MigrationMappingsUpdate

Response

200MigrationRunDetailOut

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
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

NameTypeDescription
migration_idPath · Requiredstring

An id that starts with mig_.

Request body

MigrationPartyMappingsUpdate

Response

200MigrationRunDetailOut

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
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

NameTypeDescription
migration_idPath · Requiredstring

An id that starts with mig_.

Response

200MigrationRunDetailOut

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
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

NameTypeDescription
dataRequiredarray of MigrationRunOut
has_moreRequiredboolean
next_cursorRequiredstring or null

MigrationCreate

NameTypeDescription
cutover_dateRequiredstring (date)

The last day the old books cover. These books carry on from the next.

modeRequiredstring

opening_balances: what every account held on the cutover day, with the open invoices and bills. full_history: every entry of the old books up to the cutover day, entry for entry.

snapshotRequiredMigrationSource
sourceRequiredstring

Where the books come from: qbo, xero or csv (a file, or anything else).

MigrationRunDetailOut

NameTypeDescription
control_totalsRequiredarray of MigrationControlTotalOut
created_atRequiredstring (date-time)
cutover_dateRequiredstring (date)
idRequiredstring

An id that starts with mig_.

mappingsRequiredarray of MigrationMappingOut
modeRequiredstring
opening_entry_idRequiredstring or null

An id that starts with je_.

partiesRequiredarray of MigrationPartyMappingOut

The customers and vendors its open items name.

posted_atRequiredstring (date-time) or null
row_versionRequiredinteger
sourceRequiredstring
source_snapshot_hashRequiredstring

sha256 of the snapshot it was given.

statusRequiredstring

draft (accounts to map), mapped, posted (differences to explain), tied_out or abandoned.

tied_outRequiredboolean

Every control total ties to the cent or has a reason.

QuickBooksOnlineStart

NameTypeDescription
connection_idRequiredstring

The QuickBooks Online connection of these books to read.

cutover_dateRequiredstring (date)

The last day the old books cover: every report is read as of it.

MigrationExplanationIn

NameTypeDescription
explanationRequiredstring or null

Why the source and these books differ here; null takes the reason back.

MigrationMappingsUpdate

NameTypeDescription
mappingsRequiredarray of MigrationMappingIn

MigrationPartyMappingsUpdate

NameTypeDescription
partiesRequiredarray of MigrationPartyMappingIn

MigrationRunOut

NameTypeDescription
created_atRequiredstring (date-time)
cutover_dateRequiredstring (date)
idRequiredstring

An id that starts with mig_.

modeRequiredstring
opening_entry_idRequiredstring or null

An id that starts with je_.

posted_atRequiredstring (date-time) or null
row_versionRequiredinteger
sourceRequiredstring
source_snapshot_hashRequiredstring

sha256 of the snapshot it was given.

statusRequiredstring

draft (accounts to map), mapped, posted (differences to explain), tied_out or abandoned.

MigrationSource

The old books on the cutover day.

NameTypeDescription
accountsRequiredarray of SourceAccount
entriesarray of SourceEntry

Every entry of the old books up to the cutover day, for a full_history migration.

open_itemsarray 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.

partiesarray of SourceParty

The customers and vendors the open items name.

trial_balanceRequiredarray of SourceBalance

The source's trial balance at the end of the cutover day: the figures every account is tied out against.

MigrationControlTotalOut

NameTypeDescription
controlRequiredstring

trial_balance, cash_by_account, or ar_aging / ap_aging when the source gives its open items.

differenceRequiredstring
explained_atRequiredstring (date-time) or null
explained_byRequiredstring or null

An id that starts with usr_.

explanationRequiredstring or null

Why the two differ, when they do.

idRequiredstring

An id that starts with mig_total_.

imported_totalRequiredstring

What these books say on the cutover day.

source_totalRequiredstring

What the old books say: positive on the debit side, or for an aging total, positive for what is owed.

subjectRequiredstring

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_nameRequiredstring or null

MigrationMappingOut

NameTypeDescription
account_codeRequiredstring or null
account_idRequiredstring or null

An id that starts with gla_.

account_nameRequiredstring or null
source_account_idRequiredstring
source_balanceRequiredstring

Its balance on the cutover day, positive on the debit side.

source_nameRequiredstring
source_numberRequiredstring or null
source_typeRequiredstring or null

MigrationPartyMappingOut

NameTypeDescription
open_itemsRequiredinteger

How many of the source's open items name it.

party_idRequiredstring or null

An id that starts with party_.

party_nameRequiredstring or null
source_nameRequiredstring
source_party_idRequiredstring
source_totalRequiredstring

What its open items add up to: positive for what is owed.

MigrationMappingIn

NameTypeDescription
account_idRequiredstring or null

An id that starts with gla_.

source_account_idRequiredstring

MigrationPartyMappingIn

NameTypeDescription
party_idRequiredstring or null

An id that starts with party_.

source_party_idRequiredstring

SourceAccount

NameTypeDescription
idRequiredstring

The source's own id for the account.

is_cashboolean

A bank or cash account: its balance is tied out on its own.

Default false
nameRequiredstring

Its full name in the source, Parent:Child included.

numberstring or null

Its account number, when the source numbers accounts.

typestring or null

asset, liability, equity, income or expense, when the source says. An account is only matched automatically to one of the same type.

SourceEntry

One entry of the old books, for a full-history migration.

NameTypeDescription
dateRequiredstring (date)
descriptionRequiredstring
idRequiredstring

The source's own id for the entry; unique.

linesRequiredarray of SourceLine
numberstring 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.

NameTypeDescription
account_idRequiredstring

The SourceAccount.id of the receivable or payable account it sits on.

amountRequirednumber | 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_onstring (date) or null

The day it falls due; without one it falls due on the party's terms from the cutover day.

issued_onstring (date) or null

The day it was issued.

numberstring or null

The invoice's or bill's number, when it has one.

party_idRequiredstring

A SourceParty.id: who owes it, or is owed it.

SourceParty

A customer or vendor of the old books.

NameTypeDescription
idRequiredstring

The source's own id for the customer or vendor.

nameRequiredstring

Its name in the source.

SourceBalance

NameTypeDescription
account_idRequiredstring

A SourceAccount.id.

creditnumber | stringDefault 0
debitnumber | stringDefault 0

SourceLine

One line of an entry of the old books.

NameTypeDescription
account_idRequiredstring

A SourceAccount.id.

creditnumber | stringDefault 0
debitnumber | stringDefault 0
memostring or null
party_idstring or null

A SourceParty.id: who the line is with. Required on a receivable or payable.