Trial balance
6 operations in the Tolvi API, version 1. Paths are relative to https://api.tolvi.co.uk. Every amount is integer pence.
Preview a trial balance import
POST/api/v1/engagements/{id}/tb/preview
- Scopes
engagements:writeandengagements:read
Nothing is stored. Shows the header row and columns read (layout; send columns to choose them yourself), whether it balances, new and removed codes, the journals a re-import would remove and the sign-offs it would put out of date. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Request body
Required. application/json: object (TbImport).
A CSV export (format: csv, content), one worksheet of a workbook (format: sheet, sheet) or structured accounts (format: json, accounts).
| Field | Type | Description |
|---|---|---|
formatrequired | string (TbImportFormat) | One of: |
content | string | up to 2000000 characters |
sheet | object (TbSheet) | One worksheet read from a workbook (.xlsx/.xls) by the caller, e.g. SheetJS |
sheet.name | string | Worksheet name, used as the source label when there is no file name. up to 100 characters |
sheet.firstRow | integer | Spreadsheet row number of rows[0], for messages. Default 1. at least 1 |
sheet.rowsrequired | array of array of one of: string, number, boolean, null | The worksheet’s cells, row by row. up to 20000 items |
accounts | array of TbAccountInput | up to 20000 items |
accounts[].coderequired | string | up to 50 characters |
accounts[].namerequired | string | up to 200 characters |
accounts[].cyPencerequired | integer | Integer pence (GBP). |
accounts[].pyPence | integer | Integer pence (GBP). |
accounts[].type | string (AccountType) | One of: |
asAt | string (date) | Overrides any date in the file. example "2026-03-31" |
sourceLabel | string | File name, shown in the version list. up to 200 characters |
provider | string (TbProvider) | One of: |
confirmJournalRemoval | boolean | Required when the import removes journals (see the preview). |
reopenFile | boolean | Required to import into a file signed off at every stage (the preview’s |
columns | object (TbColumns) | csv and sheet only: read these columns instead of detecting them. The preview’s |
columns.headerRowrequired | integernullable | 1 to 1000000 |
columns.code | integer | 0 to 49 |
columns.name | integer | 0 to 49 |
columns.type | integer | Account type (asset, liability…), when the file has one. 0 to 49 |
columns.debit | integer | 0 to 49 |
columns.credit | integer | 0 to 49 |
columns.balance | integer | 0 to 49 |
columns.pyDebit | integer | 0 to 49 |
columns.pyCredit | integer | 0 to 49 |
columns.pyBalance | integer | 0 to 49 |
Responses
200 OK, application/json
Body: object (TbImportPreview).
What an import would do. Nothing is stored.
| Field | Type | Description |
|---|---|---|
accountCountrequired | integer | |
addedCodesrequired | array of string | |
removedCodesrequired | array of string | |
changedCountrequired | integer | Accounts whose balances differ from the current version. |
cyTotalPencerequired | integer | Integer pence (GBP). |
balancedrequired | boolean | |
hashrequired | string | |
sameAsCurrentrequired | boolean | Identical to the current version (an import would be refused). |
journalsToRemoverequired | array of JournalRef | Journals whose accounts are not in the new TB: confirm before they are removed. |
journalsToRemove[].idrequired | string (uuid) | |
journalsToRemove[].refrequired | string | |
journalsToRemove[].statusrequired | string (JournalStatus) | One of: |
unmappedCountrequired | integer | Accounts nothing could map. |
debitTotalPencerequired | integer | Current year debit balances added up, in integer pence. |
creditTotalPencerequired | integer | Current year credit balances added up, in integer pence (negative). |
accountsrequired | array of TbPreviewAccount | The accounts as read, in file order. |
accounts[].coderequired | string | |
accounts[].namerequired | string | |
accounts[].cyPencerequired | integer | Current year balance in integer pence: debits positive, credits negative. |
accounts[].pyPencerequired | integer | Prior year balance in integer pence: debits positive, credits negative. |
accounts[].lineIdrequired | string (LeadLineId)nullable | The mapping already held for this code, else the one found automatically; null when it needs mapping. One of: |
warningsrequired | array of TbWarning | |
warnings[].coderequired | string | |
warnings[].messagerequired | string | |
layoutrequired | object (TbColumns)nullable | The header row and columns read (detected, or as given in |
layout.headerRowrequired | integernullable | 1 to 1000000 |
layout.code | integer | 0 to 49 |
layout.name | integer | 0 to 49 |
layout.type | integer | Account type (asset, liability…), when the file has one. 0 to 49 |
layout.debit | integer | 0 to 49 |
layout.credit | integer | 0 to 49 |
layout.balance | integer | 0 to 49 |
layout.pyDebit | integer | 0 to 49 |
layout.pyCredit | integer | 0 to 49 |
layout.pyBalance | integer | 0 to 49 |
staleSignOffsrequired | array of TbStaleSignOff | Sections whose current sign-offs this import would put out of date. |
staleSignOffs[].sectionCoderequired | string | |
staleSignOffs[].sectionNamerequired | string | |
staleSignOffs[].stagesrequired | array of string | The stages whose sign-off would go out of date, e.g. "Preparer", "Reviewer". |
importLock | object (TbImportLock)nullable | Set when the file is signed off at every stage: the import is refused unless it reopens the file. |
importLock.coderequired | string (TbImportLockCode) | One of: |
importLock.reasonrequired | string | In plain words, for the import screen. |
importLock.canReopenrequired | boolean | The caller can import anyway by reopening the file (send |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
List trial balance versions, newest first
GET/api/v1/engagements/{id}/tb/versions
- Scopes
engagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Responses
200 OK, application/json
Body: array of TbVersion.
| Field | Type | Description |
|---|---|---|
[].idrequired | string (uuid) | |
[].versionrequired | integer | at least 1 |
[].sourceKindrequired | string (TbSourceKind) | One of: |
[].sourceLabelrequired | stringnullable | |
[].providerrequired | stringnullable | |
[].asAtrequired | string (date)nullable | example "2026-03-31" |
[].booksThroughSeqrequired | integernullable | Pulled from Tolvi Books: the ledger point it was read at (the last entry included), so the same trial balance can be recomputed and later entries found exactly. Null for any other source. |
[].hashrequired | string | Content hash (sha256): the same balances give the same hash. |
[].accountCountrequired | integer | at least 0 |
[].cyTotalPencerequired | integer | Integer pence (GBP). Net of current year balances (nil when balanced). |
[].pyTotalPencerequired | integer | Integer pence (GBP). |
[].balancedrequired | boolean | |
[].removedJournalRefsrequired | array of string | |
[].createdByrequired | string (uuid)nullable | |
[].createdAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Import a trial balance (CSV or JSON)
POST/api/v1/engagements/{id}/tb/versions
- Scopes
engagements:writeandengagements:read- Retries
- Send an
Idempotency-Keyto make retries safe.
Adds an immutable version in pence. If journals would be removed, send confirmJournalRemoval: true after showing the preview (409 otherwise). A file signed off at every stage takes a new trial balance only with reopenFile: true, which reopens it for re-approval in the same transaction: 409 without it, 403 for anyone who cannot reopen a file (a reviewer or above does). The preview’s importLock and GET …/mappings say so first. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Idempotency-Key | header | string | A unique key (1-255 visible characters) to make retries safe. Replayed for 24 hours. 1 to 255 characters |
Request body
Required. application/json: object (TbImport).
A CSV export (format: csv, content), one worksheet of a workbook (format: sheet, sheet) or structured accounts (format: json, accounts).
The fields are as shown above.
Responses
201 Created, application/json
Body: object (TbImportResult).
| Field | Type | Description |
|---|---|---|
versionrequired | object (TbVersion) | |
version.idrequired | string (uuid) | |
version.versionrequired | integer | at least 1 |
version.sourceKindrequired | string (TbSourceKind) | One of: |
version.sourceLabelrequired | stringnullable | |
version.providerrequired | stringnullable | |
version.asAtrequired | string (date)nullable | example "2026-03-31" |
version.booksThroughSeqrequired | integernullable | Pulled from Tolvi Books: the ledger point it was read at (the last entry included), so the same trial balance can be recomputed and later entries found exactly. Null for any other source. |
version.hashrequired | string | Content hash (sha256): the same balances give the same hash. |
version.accountCountrequired | integer | at least 0 |
version.cyTotalPencerequired | integer | Integer pence (GBP). Net of current year balances (nil when balanced). |
version.pyTotalPencerequired | integer | Integer pence (GBP). |
version.balancedrequired | boolean | |
version.removedJournalRefsrequired | array of string | |
version.createdByrequired | string (uuid)nullable | |
version.createdAtrequired | string (date-time) | |
removedJournalRefsrequired | array of string | |
warningsrequired | array of TbWarning | |
warnings[].coderequired | string | |
warnings[].messagerequired | string | |
unmappedCountrequired | integer |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 409 Conflict, 422 Unprocessable content, 429 Too many requests, as problem details (application/problem+json).
Get the final trial balance
GET/api/v1/engagements/{id}/tb
- Scopes
engagements:read
Current version + posted journals, derived on every request and never stored. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Responses
200 OK, application/json
Body: object (FinalTrialBalance).
Draft TB + posted journals, derived on every request (never stored).
| Field | Type | Description |
|---|---|---|
versionrequired | object (TbVersion)nullable | |
version.idrequired | string (uuid) | |
version.versionrequired | integer | at least 1 |
version.sourceKindrequired | string (TbSourceKind) | One of: |
version.sourceLabelrequired | stringnullable | |
version.providerrequired | stringnullable | |
version.asAtrequired | string (date)nullable | example "2026-03-31" |
version.booksThroughSeqrequired | integernullable | Pulled from Tolvi Books: the ledger point it was read at (the last entry included), so the same trial balance can be recomputed and later entries found exactly. Null for any other source. |
version.hashrequired | string | Content hash (sha256): the same balances give the same hash. |
version.accountCountrequired | integer | at least 0 |
version.cyTotalPencerequired | integer | Integer pence (GBP). Net of current year balances (nil when balanced). |
version.pyTotalPencerequired | integer | Integer pence (GBP). |
version.balancedrequired | boolean | |
version.removedJournalRefsrequired | array of string | |
version.createdByrequired | string (uuid)nullable | |
version.createdAtrequired | string (date-time) | |
accountsrequired | array of FinalTbAccount | |
accounts[].coderequired | string | |
accounts[].namerequired | string | |
accounts[].journalOnly | boolean | Added in Tolvi for journals (journal-only): in no TB version, so draft and prior year are nil and the final balance is journals only. |
accounts[].chartOnly | boolean | An account on the client’s chart of accounts with no balance in this TB, used here by C or a journal: shown under the client’s own code, with nil draft and prior year, so it matches their books once they post it. |
accounts[].lineIdrequired | string (LeadLineId)nullable | One of: |
accounts[].lineLabelrequired | stringnullable | |
accounts[].sectionCoderequired | stringnullable | |
accounts[].pyPencerequired | integer | Integer pence (GBP). |
accounts[].draftPencerequired | integer | Integer pence (GBP). |
accounts[].adjustmentsPencerequired | integer | Integer pence (GBP). |
accounts[].draftJournalsPencerequired | integer | Integer pence (GBP). |
accounts[].finalPencerequired | integer | Integer pence (GBP). |
accounts[].needsExplanationrequired | boolean | Final against prior year moved by at least both of the year-end file’s review thresholds: the same test as a lead schedule line. T’s accounts are explained by the tax computation, so never. |
draftTotalPencerequired | integer | Integer pence (GBP). |
finalTotalPencerequired | integer | Integer pence (GBP). |
pyTotalPencerequired | integer | Integer pence (GBP). |
balancedrequired | boolean |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get account mappings
GET/api/v1/engagements/{id}/mappings
- Scopes
engagements:read
Each account’s lead schedule line and why (client memory, firm rule or built-in). Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Responses
200 OK, application/json
Body: object (Mappings).
| Field | Type | Description |
|---|---|---|
accountsrequired | array of AccountMapping | |
accounts[].coderequired | string | |
accounts[].namerequired | string | |
accounts[].lineIdrequired | string (LeadLineId)nullable | One of: |
accounts[].sourcerequired | string (MappingSource)nullable | chart: an account on the client’s chart of accounts with no balance in this TB, put on the line its use in C implies or the mapping order suggested. books: the account’s lead schedule line in the client’s Tolvi Books. One of: |
accounts[].explanationrequired | string | |
accounts[].allowedrequired | boolean | The line can be used for this entity type. |
linesrequired | array of MappingLine | |
lines[].idrequired | string (LeadLineId) | One of: |
lines[].labelrequired | string | |
lines[].sectionCoderequired | string | |
lines[].allowedrequired | boolean | |
unmappedCountrequired | integer | |
lockedReason | stringnullable | Why the caller cannot change mappings now (B reviewed): ask the reviewer to reopen it. |
importLock | object (TbImportLock)nullable | Set when a new trial balance cannot simply be imported: the file is signed off at every stage. B’s import panel says so before anyone starts. |
importLock.coderequired | string (TbImportLockCode) | One of: |
importLock.reasonrequired | string | In plain words, for the import screen. |
importLock.canReopenrequired | boolean | The caller can import anyway by reopening the file (send |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Set account mappings
PUT/api/v1/engagements/{id}/mappings
- Scopes
engagements:writeandengagements:read
Remembered for the client’s future engagements unless remember is false. Once B’s review sign-off is current, only a reviewer or above can change mappings (403 with the reason, as for T and C); lockedReason on GET says so first. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Request body
Required. application/json: object (SetMappings).
| Field | Type | Description |
|---|---|---|
mappingsrequired | array of MappingChange | 1 to 5000 items |
mappings[].coderequired | string | |
mappings[].lineIdrequired | string (LeadLineId)nullable | Null to unmap. One of: |
remember | boolean | Remember for this client’s future engagements (client memory). Default true. |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).