Books: bank and inbox
34 operations in the Tolvi API, version 1. Paths are relative to https://api.tolvi.co.uk. Every amount is integer pence.
Can a bank be connected?
GET/api/v1/books/bank/providers
- Scopes
books:read
Whether open-banking connections are available to this business (and if not, why), and the banks to pick from when the provider doesn’t show its own picker. Statement files always work. Part of the books product: the firm must have it and you must have access to it.
Responses
200 OK, application/json
Body: object (BooksBankProviderInfo).
| Field | Type | Description |
|---|---|---|
availablerequired | boolean | Whether this business can connect a bank. Statement files always work. |
reasonrequired | string (BooksBankUnavailableReason)nullable | Why available is false: not_configured (the provider’s keys aren’t set), or sandbox_restricted (the provider is in its sandbox and this business isn’t one of the few allowed to try it). Null when available. One of: |
messagerequired | stringnullable | Why, in plain English, when available is false. |
providerrequired | string (BooksBankProvider)nullable | One of: |
environmentrequired | string (BooksBankEnvironment) | One of: |
banksrequired | array of BooksBankOption | Banks to pick from; empty when the provider shows its own picker. |
banks[].idrequired | string | |
banks[].namerequired | string | |
chooseBankrequired | boolean | The person chooses their bank in Tolvi before connecting (send its id as bankId): Yapily has no picker of its own. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
List bank accounts
GET/api/v1/books/bank/accounts
- Scopes
books:read
Each account with its connection (and where it stands against the 90-day reconfirmation), its balance in the books, the bank’s balance, pending lines and how many lines are still to explain. Part of the books product: the firm must have it and you must have access to it.
Responses
200 OK, application/json
Body: object (BooksBankAccountList).
| Field | Type | Description |
|---|---|---|
itemsrequired | array of BooksBankAccount | |
items[].idrequired | string (uuid) | |
items[].namerequired | string | |
items[].kindrequired | string (BooksBankAccountKind) | One of: |
items[].bankNamerequired | stringnullable | |
items[].sortCoderequired | stringnullable | |
items[].accountNumberEndingrequired | stringnullable | The last four digits only. |
items[].currencyrequired | string | |
items[].ledgerAccountIdrequired | string (uuid) | |
items[].ledgerAccountCoderequired | string | |
items[].ledgerAccountNamerequired | string | |
items[].connectionrequired | object (BooksBankConnection)nullable | Null for an account kept up to date with statement files. |
items[].connection.idrequired | string (uuid) | |
items[].connection.providerrequired | string (BooksBankProvider) | One of: |
items[].connection.statusrequired | string (BooksBankConnectionStatus) | One of: |
items[].connection.bankNamerequired | stringnullable | |
items[].connection.consentStaterequired | string (BooksBankConsentState)nullable | Where the connection stands against the 90 days. reconfirm_soon: in the last 14 days. One of: |
items[].connection.consentExpiresAtrequired | string (date-time)nullable | |
items[].connection.daysLeftrequired | integernullable | Whole days until access stops; negative once it has. |
items[].connection.lastSyncedAtrequired | string (date-time)nullable | |
items[].connection.lastErrorrequired | stringnullable | Why the last sync didn’t work, in plain English. |
items[].connection.createdAtrequired | string (date-time) | |
items[].lastSyncedAtrequired | string (date-time)nullable | |
items[].bankBalancePencerequired | integernullable | Integer pence (GBP). The bank’s own balance at the last sync. |
items[].bankBalanceAtrequired | string (date-time)nullable | |
items[].statementBalancePencerequired | integernullable | Integer pence (GBP). |
items[].statementBalanceDaterequired | string (date)nullable | example "2026-03-31" |
items[].ledgerBalancePencerequired | integer | Integer pence (GBP). The balance in the books (money in the account positive). |
items[].toExplainrequired | integer | Lines still to explain. |
items[].pendingrequired | array of BooksBankPendingLine | |
items[].pending[].daterequired | string (date) | example "2026-03-31" |
items[].pending[].descriptionrequired | string | |
items[].pending[].amountPencerequired | integer | Integer pence (GBP). Money in positive, money out negative. |
items[].createdAtrequired | string (date-time) | |
connectionsrequired | array of BooksBankConnection | Every bank connection, including ones waiting for the person to pick accounts. |
connections[].idrequired | string (uuid) | |
connections[].providerrequired | string (BooksBankProvider) | One of: |
connections[].statusrequired | string (BooksBankConnectionStatus) | One of: |
connections[].bankNamerequired | stringnullable | |
connections[].consentStaterequired | string (BooksBankConsentState)nullable | Where the connection stands against the 90 days. reconfirm_soon: in the last 14 days. One of: |
connections[].consentExpiresAtrequired | string (date-time)nullable | |
connections[].daysLeftrequired | integernullable | Whole days until access stops; negative once it has. |
connections[].lastSyncedAtrequired | string (date-time)nullable | |
connections[].lastErrorrequired | stringnullable | Why the last sync didn’t work, in plain English. |
connections[].createdAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Add a bank account kept by statements
POST/api/v1/books/bank/accounts
- Scopes
books:write- Retries
- Send an
Idempotency-Keyto make retries safe.
For a bank without a feed: upload its statements. Tied to an account in the books (the one given, a free bank account, or a new one). The business’s owners and admins, or an accountant whose access can post. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
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 (BooksBankCreateAccount).
| Field | Type | Description |
|---|---|---|
namerequired | string | up to 120 characters |
kind | string (BooksBankAccountKind) | One of: |
bankName | string | |
sortCode | string | example "12-34-56" |
accountNumber | string | |
ledgerAccountId | string (uuid) | The ledger account; left out, Tolvi picks a free one or adds one. |
Responses
201 Created, application/json
Body: object (BooksBankAccount).
A business bank, savings or card account, tied to one account in the books.
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
namerequired | string | |
kindrequired | string (BooksBankAccountKind) | One of: |
bankNamerequired | stringnullable | |
sortCoderequired | stringnullable | |
accountNumberEndingrequired | stringnullable | The last four digits only. |
currencyrequired | string | |
ledgerAccountIdrequired | string (uuid) | |
ledgerAccountCoderequired | string | |
ledgerAccountNamerequired | string | |
connectionrequired | object (BooksBankConnection)nullable | Null for an account kept up to date with statement files. |
connection.idrequired | string (uuid) | |
connection.providerrequired | string (BooksBankProvider) | One of: |
connection.statusrequired | string (BooksBankConnectionStatus) | One of: |
connection.bankNamerequired | stringnullable | |
connection.consentStaterequired | string (BooksBankConsentState)nullable | Where the connection stands against the 90 days. reconfirm_soon: in the last 14 days. One of: |
connection.consentExpiresAtrequired | string (date-time)nullable | |
connection.daysLeftrequired | integernullable | Whole days until access stops; negative once it has. |
connection.lastSyncedAtrequired | string (date-time)nullable | |
connection.lastErrorrequired | stringnullable | Why the last sync didn’t work, in plain English. |
connection.createdAtrequired | string (date-time) | |
lastSyncedAtrequired | string (date-time)nullable | |
bankBalancePencerequired | integernullable | Integer pence (GBP). The bank’s own balance at the last sync. |
bankBalanceAtrequired | string (date-time)nullable | |
statementBalancePencerequired | integernullable | Integer pence (GBP). |
statementBalanceDaterequired | string (date)nullable | example "2026-03-31" |
ledgerBalancePencerequired | integer | Integer pence (GBP). The balance in the books (money in the account positive). |
toExplainrequired | integer | Lines still to explain. |
pendingrequired | array of BooksBankPendingLine | |
pending[].daterequired | string (date) | example "2026-03-31" |
pending[].descriptionrequired | string | |
pending[].amountPencerequired | integer | Integer pence (GBP). Money in positive, money out negative. |
createdAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 409 Conflict, 422 Unprocessable content, 429 Too many requests, as problem details (application/problem+json).
Get a bank account
GET/api/v1/books/bank/accounts/{id}
- Scopes
books:read
Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankAccount).
A business bank, savings or card account, tied to one account in the books.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Rename a bank account or record its statement balance
PATCH/api/v1/books/bank/accounts/{id}
- Scopes
books:write
The statement balance and its date are what reconciliation checks the books against. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Request body
Required. application/json: object (BooksBankUpdateAccount).
| Field | Type | Description |
|---|---|---|
name | string | |
statementBalancePence | integernullable | Integer pence (GBP). |
statementBalanceDate | string (date)nullable | example "2026-03-31" |
Responses
200 OK, application/json
Body: object (BooksBankAccount).
A business bank, savings or card account, tied to one account in the books.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Reconcile a bank account
GET/api/v1/books/bank/accounts/{id}/reconciliation
- Scopes
books:read
The statement (or bank) balance against the balance in the books, and what makes up the difference: lines not yet explained, entries in the books no bank line explains, and anything still to find. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | |
asAt | query | string (date) | Default: the latest statement balance’s date, else today with the bank’s balance. |
Responses
200 OK, application/json
Body: object (BooksBankReconciliation).
The statement (or bank) balance against the balance in the books, with the difference explained.
| Field | Type | Description |
|---|---|---|
bankAccountIdrequired | string (uuid) | |
asAtrequired | string (date) | example "2026-03-31" |
statementBalancePencerequired | integernullable | Integer pence (GBP). |
statementSourcerequired | string (BooksBankBalanceSource)nullable | One of: |
ledgerBalancePencerequired | integer | Integer pence (GBP). |
differencePencerequired | integernullable | Integer pence (GBP). |
itemsrequired | array of BooksBankReconciliationItem | |
items[].kindrequired | string (BooksBankReconciliationKind) | One of: |
items[].labelrequired | string | |
items[].countrequired | integer | |
items[].amountPencerequired | integer | Integer pence (GBP). |
reconciledrequired | boolean | The difference is fully explained. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Preview a statement file
POST/api/v1/books/bank/accounts/{id}/imports/preview
- Scopes
books:write
Reads the file without storing anything: its format, lines, what is new and the rows left out; or, for a CSV Tolvi doesn’t recognise, its headings to choose the columns from. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Request body
Required. application/json: object (BooksBankStatementFile).
| Field | Type | Description |
|---|---|---|
fileNamerequired | string | |
contentBase64required | string | The file, base64 encoded (CSV, OFX, QFX, QIF or Santander’s text download; up to 8 MB). |
columns | object | For an unrecognised CSV: { date, amount | paidIn + paidOut, description: [..], headerRow?, amountSign? } (0-based columns). |
Responses
200 OK, application/json
Body: object (BooksBankStatementPreview).
| Field | Type | Description |
|---|---|---|
needsColumnsrequired | boolean | True when Tolvi needs the columns chosen (see csv). |
csvrequired | object (BooksBankCsvColumns)nullable | For a CSV Tolvi doesn’t recognise: the headings and first rows, for choosing the columns. |
csv.headersrequired | array of string | |
csv.samplerequired | array of array of string | The first rows, each an array of cells. |
csv.suggestedrequired | object | Tolvi’s best guess at the columns (0-based indexes). |
csv.headerRowrequired | integernullable | |
formatrequired | stringnullable | example "csv" |
layoutrequired | stringnullable | example "barclays" |
linesFoundrequired | integer | |
linesNewrequired | integer | |
linesAlreadyInrequired | integer | |
periodFromrequired | string (date)nullable | example "2026-03-31" |
periodTorequired | string (date)nullable | example "2026-03-31" |
closingBalancePencerequired | integernullable | Integer pence (GBP). |
issuesrequired | array of BooksBankStatementIssue | Rows left out. |
issues[].rowrequired | integernullable | The 1-based row or record number. |
issues[].messagerequired | string | |
samplerequired | array of BooksBankStatementLine | |
sample[].bookedAtrequired | string (date) | example "2026-03-31" |
sample[].descriptionrequired | string | |
sample[].amountPencerequired | integer | Integer pence (GBP). |
sample[].alreadyInrequired | boolean | Already in the books from an earlier file or the bank feed. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Import a statement file
POST/api/v1/books/bank/accounts/{id}/imports
- Scopes
books:write- Retries
- Send an
Idempotency-Keyto make retries safe.
CSV, OFX/QFX, QIF or Santander’s text download. Lines already in the books (same id, or same date, amount, description and balance) are skipped, so overlapping files are safe. Tolvi then suggests what each new line is. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | |
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 (BooksBankStatementFile).
| Field | Type | Description |
|---|---|---|
fileNamerequired | string | |
contentBase64required | string | The file, base64 encoded (CSV, OFX, QFX, QIF or Santander’s text download; up to 8 MB). |
columns | object | For an unrecognised CSV: { date, amount | paidIn + paidOut, description: [..], headerRow?, amountSign? } (0-based columns). |
Responses
201 Created, application/json
Body: object (BooksBankImport).
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
bankAccountIdrequired | string (uuid) | |
fileNamerequired | string | |
formatrequired | string | |
linesFoundrequired | integer | |
linesAddedrequired | integer | |
linesAlreadyInrequired | integer | |
issuesrequired | array of BooksBankStatementIssue | |
issues[].rowrequired | integernullable | The 1-based row or record number. |
issues[].messagerequired | string | |
periodFromrequired | string (date)nullable | example "2026-03-31" |
periodTorequired | string (date)nullable | example "2026-03-31" |
createdAtrequired | string (date-time) |
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).
Connect a bank
POST/api/v1/books/bank/connections
- Scopes
books:admin- Access
- A signed-in Tolvi session only, never an API token.
Returns the provider’s link: the person signs in at their bank there and is sent back to Tolvi Books (see callback). When providers.chooseBank is true (Yapily) bankId is required (422 without it). Read-only access, renewed every 90 days. Owners and admins, signed in. 503 when bank connections aren’t switched on. Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksBankStartConnection).
| Field | Type | Description |
|---|---|---|
bankId | string | The provider’s id for the bank the person chose (from providers.banks). Required when providers.chooseBank is true; otherwise it skips the provider’s own picker. |
Responses
201 Created, application/json
Body: object (BooksBankConnectStart).
| Field | Type | Description |
|---|---|---|
connectionIdrequired | string (uuid) | |
urlrequired | string | Send the person’s browser here. The provider handles the bank’s sign-in, then returns to Tolvi with a code. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Finish connecting a bank
POST/api/v1/books/bank/connections/callback
- Scopes
books:admin- Access
- A signed-in Tolvi session only, never an API token.
Send what the provider put on the redirect (state, and code or error). Returns the accounts the bank shared, to choose which to add. Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksBankConnectionCallback).
What the provider sent back on the redirect.
| Field | Type | Description |
|---|---|---|
staterequired | string | |
code | string | |
error | string | |
errorDescription | string | |
params | object | Every query parameter the provider sent back, as is. The provider reads its own (Yapily sends a one-time token, not a code). Up to 30. |
Responses
200 OK, application/json
Body: object (BooksBankConnectionAccounts).
| Field | Type | Description |
|---|---|---|
connectionrequired | object (BooksBankConnection) | One consent at one bank, through the open-banking provider. Access lasts 90 days at a time; the person reconfirms it in Tolvi (FCA SCA-RTS Article 10A). |
connection.idrequired | string (uuid) | |
connection.providerrequired | string (BooksBankProvider) | One of: |
connection.statusrequired | string (BooksBankConnectionStatus) | One of: |
connection.bankNamerequired | stringnullable | |
connection.consentStaterequired | string (BooksBankConsentState)nullable | Where the connection stands against the 90 days. reconfirm_soon: in the last 14 days. One of: |
connection.consentExpiresAtrequired | string (date-time)nullable | |
connection.daysLeftrequired | integernullable | Whole days until access stops; negative once it has. |
connection.lastSyncedAtrequired | string (date-time)nullable | |
connection.lastErrorrequired | stringnullable | Why the last sync didn’t work, in plain English. |
connection.createdAtrequired | string (date-time) | |
accountsrequired | array of BooksBankProviderAccount | |
accounts[].providerAccountIdrequired | string | |
accounts[].namerequired | string | |
accounts[].kindrequired | string (BooksBankAccountKind) | One of: |
accounts[].sortCoderequired | stringnullable | |
accounts[].accountNumberEndingrequired | stringnullable | The last four digits only. |
accounts[].currencyrequired | string | |
accounts[].bankAccountIdrequired | string (uuid)nullable | Already in Tolvi as this bank account. |
accounts[].suggestedLedgerAccountIdrequired | string (uuid)nullable | The ledger account Tolvi suggests; null means Tolvi adds one. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
The accounts a bank shared
GET/api/v1/books/bank/connections/{id}/accounts
- Scopes
books:admin- Access
- A signed-in Tolvi session only, never an API token.
Each with whether it is already added and the account in the books Tolvi would use. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Add accounts from a connected bank
POST/api/v1/books/bank/connections/{id}/accounts
- Scopes
books:admin- Access
- A signed-in Tolvi session only, never an API token.
Each is tied to an account in the books (the one given, a free bank account, or a new one) and read straight away. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Request body
Required. application/json: object (BooksBankAddAccounts).
| Field | Type | Description |
|---|---|---|
accountsrequired | array of BooksBankAddAccount | |
accounts[].providerAccountIdrequired | string | |
accounts[].ledgerAccountId | string (uuid) | |
accounts[].name | string |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Keep sharing for another 90 days
POST/api/v1/books/bank/connections/{id}/reconfirm
- Scopes
books:admin- Access
- A signed-in Tolvi session only, never an API token.
The person’s explicit reconfirmation (FCA SCA-RTS Article 10A). When the bank wants them to sign in again, url says where to send them. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Request body
Required. application/json: object (BooksBankReconfirm).
| Field | Type | Description |
|---|---|---|
keepSharingrequired | boolean | Must be true: the person ticked “Keep sharing”. |
Responses
200 OK, application/json
Body: object (BooksBankReconfirmResult).
| Field | Type | Description |
|---|---|---|
connectionrequired | object (BooksBankConnection) | One consent at one bank, through the open-banking provider. Access lasts 90 days at a time; the person reconfirms it in Tolvi (FCA SCA-RTS Article 10A). |
connection.idrequired | string (uuid) | |
connection.providerrequired | string (BooksBankProvider) | One of: |
connection.statusrequired | string (BooksBankConnectionStatus) | One of: |
connection.bankNamerequired | stringnullable | |
connection.consentStaterequired | string (BooksBankConsentState)nullable | Where the connection stands against the 90 days. reconfirm_soon: in the last 14 days. One of: |
connection.consentExpiresAtrequired | string (date-time)nullable | |
connection.daysLeftrequired | integernullable | Whole days until access stops; negative once it has. |
connection.lastSyncedAtrequired | string (date-time)nullable | |
connection.lastErrorrequired | stringnullable | Why the last sync didn’t work, in plain English. |
connection.createdAtrequired | string (date-time) | |
urlrequired | stringnullable | When the bank wants the person to sign in again: send them here. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Disconnect a bank
POST/api/v1/books/bank/connections/{id}/disconnect
- Scopes
books:admin- Access
- A signed-in Tolvi session only, never an API token.
Ends Tolvi’s access at the provider and forgets the tokens. The accounts and their lines stay; statements can still be uploaded. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankConnection).
One consent at one bank, through the open-banking provider. Access lasts 90 days at a time; the person reconfirms it in Tolvi (FCA SCA-RTS Article 10A).
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
providerrequired | string (BooksBankProvider) | One of: |
statusrequired | string (BooksBankConnectionStatus) | One of: |
bankNamerequired | stringnullable | |
consentStaterequired | string (BooksBankConsentState)nullable | Where the connection stands against the 90 days. reconfirm_soon: in the last 14 days. One of: |
consentExpiresAtrequired | string (date-time)nullable | |
daysLeftrequired | integernullable | Whole days until access stops; negative once it has. |
lastSyncedAtrequired | string (date-time)nullable | |
lastErrorrequired | stringnullable | Why the last sync didn’t work, in plain English. |
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).
Read the bank now
POST/api/v1/books/bank/connections/{id}/sync
- Scopes
books:write
Tolvi also reads each bank a few times a day. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankConnection).
One consent at one bank, through the open-banking provider. Access lasts 90 days at a time; the person reconfirms it in Tolvi (FCA SCA-RTS Article 10A).
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
List bank lines
GET/api/v1/books/bank/lines
- Scopes
books:read
Newest first, with Tolvi’s suggestion and its “Why?” for each line still to explain. Money in positive, money out negative. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
q | query | string | Search: words in the description, payee or reference, or an amount (12.50). up to 100 characters |
offset | query | integer | at least 0; default 0 |
limit | query | integer | 1 to 500; default 200 |
to | query | string (date) | |
from | query | string (date) | |
status | query | string | One of: |
bankAccountId | query | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankLineList).
| Field | Type | Description |
|---|---|---|
itemsrequired | array of BooksBankLine | |
items[].idrequired | string (uuid) | |
items[].bankAccountIdrequired | string (uuid) | |
items[].sourcerequired | string (BooksBankLineSource) | One of: |
items[].bookedAtrequired | string (date) | example "2026-03-31" |
items[].descriptionrequired | string | |
items[].counterpartyrequired | stringnullable | |
items[].referencerequired | stringnullable | |
items[].payeerequired | stringnullable | Who it was paid to or received from, cleaned up. |
items[].amountPencerequired | integer | Integer pence (GBP). |
items[].currencyrequired | string | |
items[].runningBalancePencerequired | integernullable | Integer pence (GBP). |
items[].statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
items[].suggestionrequired | object (BooksBankSuggestion)nullable | What Tolvi suggests for a line and why. Only an owner’s rule set to post automatically ever posts without a tap. |
items[].suggestion.levelrequired | string (BooksBankSuggestionLevel) | One of: |
items[].suggestion.stagerequired | string (BooksBankSuggestionStage) | Which step decided: a possible copy, a match, your rule, what you did before, Tolvi AI, or nothing yet (for_ai). One of: |
items[].suggestion.summaryrequired | string | |
items[].suggestion.reasonsrequired | array of string | The “Why?”. |
items[].suggestion.kindrequired | string (BooksBankSuggestionKind) | One of: |
items[].suggestion.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
items[].suggestion.categorisation.accountCoderequired | string | |
items[].suggestion.categorisation.accountNamerequired | stringnullable | |
items[].suggestion.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
items[].suggestion.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
items[].suggestion.categorisation.split[].accountCoderequired | string | |
items[].suggestion.categorisation.split[].vatCoderequired | stringnullable | |
items[].suggestion.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
items[].suggestion.transferBankAccountIdrequired | string (uuid)nullable | |
items[].suggestion.otherLineIdrequired | string (uuid)nullable | |
items[].suggestion.documentKindrequired | string (BooksBankDocumentKind)nullable | One of: |
items[].suggestion.documentIdsrequired | array of string | |
items[].suggestion.duplicateOfIdrequired | string (uuid)nullable | |
items[].suggestion.ruleIdrequired | string (uuid)nullable | |
items[].suggestion.ruleNamerequired | stringnullable | |
items[].suggestion.candidatesrequired | array of BooksBankCandidate | Other possible matches, best first. |
items[].suggestion.candidates[].summaryrequired | string | |
items[].suggestion.candidates[].scorerequired | integer | 0 to 100 |
items[].suggestion.candidates[].bandrequired | string (BooksBankMatchBand) | One of: |
items[].suggestion.candidates[].kindrequired | string | transfer, or the kind of document. |
items[].suggestion.candidates[].documentIdsrequired | array of string | |
items[].suggestion.candidates[].bankAccountIdrequired | string (uuid)nullable | |
items[].suggestion.candidates[].otherLineIdrequired | string (uuid)nullable | |
items[].explanationrequired | object (BooksBankExplanation)nullable | How the line is in the books. |
items[].explanation.kindrequired | string (BooksBankExplanationKind) | One of: |
items[].explanation.entryIdrequired | string (uuid) | |
items[].explanation.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
items[].explanation.categorisation.accountCoderequired | string | |
items[].explanation.categorisation.accountNamerequired | stringnullable | |
items[].explanation.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
items[].explanation.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
items[].explanation.categorisation.split[].accountCoderequired | string | |
items[].explanation.categorisation.split[].vatCoderequired | stringnullable | |
items[].explanation.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
items[].explanation.transferLineIdrequired | string (uuid)nullable | |
items[].explanation.receiptIdrequired | string (uuid)nullable | |
items[].explanation.ruleIdrequired | string (uuid)nullable | The rule that posted it without a tap. |
items[].explanation.ruleVersionrequired | integernullable | |
items[].explanation.explainedByrequired | string (uuid)nullable | |
items[].explanation.explainedAtrequired | string (date-time) | |
items[].bankChangerequired | objectnullable | The bank changed or withdrew this line after Tolvi read it: { kind: corrected | missing, changed, amountPence, description, bookedAt }. |
items[].createdAtrequired | string (date-time) | |
countsrequired | object (BooksBankLineCounts) | |
counts.toExplainrequired | integer | |
counts.explainedrequired | integer | |
counts.excludedrequired | integer | |
totalrequired | integer | Lines matching the filter (for paging with offset). |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Confirm a group of lines
POST/api/v1/books/bank/lines/confirm
- Scopes
books:write
Each line as suggested, or every line as one category. Each line posts on its own, so one that can’t be posted (a locked period, say) doesn’t stop the rest. Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksBankConfirmLines).
| Field | Type | Description |
|---|---|---|
lineIdsrequired | array of string | |
categorisation | object (BooksBankCategorisationInput) | The same category for every line; left out, each is confirmed as suggested. |
categorisation.accountCoderequired | string | |
categorisation.vatCode | stringnullable | |
categorisation.split | array of BooksBankSplitPartInput | |
categorisation.split[].accountCoderequired | string | |
categorisation.split[].vatCode | stringnullable | |
categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
Responses
200 OK, application/json
Body: object (BooksBankConfirmResults).
| Field | Type | Description |
|---|---|---|
resultsrequired | array of BooksBankConfirmResult | |
results[].lineIdrequired | string (uuid) | |
results[].okrequired | boolean | |
results[].problemrequired | stringnullable | Why this line wasn’t confirmed. |
confirmedrequired | integer |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Get a bank line
GET/api/v1/books/bank/lines/{id}
- Scopes
books:read
Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankLine).
A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative.
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
bankAccountIdrequired | string (uuid) | |
sourcerequired | string (BooksBankLineSource) | One of: |
bookedAtrequired | string (date) | example "2026-03-31" |
descriptionrequired | string | |
counterpartyrequired | stringnullable | |
referencerequired | stringnullable | |
payeerequired | stringnullable | Who it was paid to or received from, cleaned up. |
amountPencerequired | integer | Integer pence (GBP). |
currencyrequired | string | |
runningBalancePencerequired | integernullable | Integer pence (GBP). |
statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
suggestionrequired | object (BooksBankSuggestion)nullable | What Tolvi suggests for a line and why. Only an owner’s rule set to post automatically ever posts without a tap. |
suggestion.levelrequired | string (BooksBankSuggestionLevel) | One of: |
suggestion.stagerequired | string (BooksBankSuggestionStage) | Which step decided: a possible copy, a match, your rule, what you did before, Tolvi AI, or nothing yet (for_ai). One of: |
suggestion.summaryrequired | string | |
suggestion.reasonsrequired | array of string | The “Why?”. |
suggestion.kindrequired | string (BooksBankSuggestionKind) | One of: |
suggestion.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
suggestion.categorisation.accountCoderequired | string | |
suggestion.categorisation.accountNamerequired | stringnullable | |
suggestion.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
suggestion.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
suggestion.categorisation.split[].accountCoderequired | string | |
suggestion.categorisation.split[].vatCoderequired | stringnullable | |
suggestion.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
suggestion.transferBankAccountIdrequired | string (uuid)nullable | |
suggestion.otherLineIdrequired | string (uuid)nullable | |
suggestion.documentKindrequired | string (BooksBankDocumentKind)nullable | One of: |
suggestion.documentIdsrequired | array of string | |
suggestion.duplicateOfIdrequired | string (uuid)nullable | |
suggestion.ruleIdrequired | string (uuid)nullable | |
suggestion.ruleNamerequired | stringnullable | |
suggestion.candidatesrequired | array of BooksBankCandidate | Other possible matches, best first. |
suggestion.candidates[].summaryrequired | string | |
suggestion.candidates[].scorerequired | integer | 0 to 100 |
suggestion.candidates[].bandrequired | string (BooksBankMatchBand) | One of: |
suggestion.candidates[].kindrequired | string | transfer, or the kind of document. |
suggestion.candidates[].documentIdsrequired | array of string | |
suggestion.candidates[].bankAccountIdrequired | string (uuid)nullable | |
suggestion.candidates[].otherLineIdrequired | string (uuid)nullable | |
explanationrequired | object (BooksBankExplanation)nullable | How the line is in the books. |
explanation.kindrequired | string (BooksBankExplanationKind) | One of: |
explanation.entryIdrequired | string (uuid) | |
explanation.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
explanation.categorisation.accountCoderequired | string | |
explanation.categorisation.accountNamerequired | stringnullable | |
explanation.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
explanation.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
explanation.categorisation.split[].accountCoderequired | string | |
explanation.categorisation.split[].vatCoderequired | stringnullable | |
explanation.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
explanation.transferLineIdrequired | string (uuid)nullable | |
explanation.receiptIdrequired | string (uuid)nullable | |
explanation.ruleIdrequired | string (uuid)nullable | The rule that posted it without a tap. |
explanation.ruleVersionrequired | integernullable | |
explanation.explainedByrequired | string (uuid)nullable | |
explanation.explainedAtrequired | string (date-time) | |
bankChangerequired | objectnullable | The bank changed or withdrew this line after Tolvi read it: { kind: corrected | missing, changed, amountPence, description, bookedAt }. |
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).
Explain a bank line
POST/api/v1/books/bank/lines/{id}/explain
- Scopes
books:write
As suggested; as a category (with VAT, or split); as a transfer to or from another of the business’s accounts; or as the payment for a confirmed receipt. Posts one entry in the books. 422 when it can’t be posted (a locked period, an unknown category), 409 when the line is already explained. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Request body
Required. application/json: object (BooksBankExplainLine).
kind: suggested (confirm the suggestion), category ({ accountCode, vatCode?, split? }), transfer ({ toBankAccountId, otherLineId? }), match ({ receiptId, accountCode? }), bill ({ billId }: money out paying a bill, in full or in part) or vat_return ({ submissionId }: a payment to or from HMRC for a filed return).
| Field | Type | Description |
|---|---|---|
kindrequired | string (BooksBankExplainKind) | One of: |
billId | string (uuid) | |
accountCode | string | |
vatCode | stringnullable | |
split | array of BooksBankSplitPartInput | |
split[].accountCoderequired | string | |
split[].vatCode | stringnullable | |
split[].shareBpsrequired | integer | 1 to 9999 |
toBankAccountId | string (uuid) | |
otherLineId | string (uuid)nullable | |
receiptId | string (uuid) | |
submissionId | string (uuid) | The filed VAT return this payment settles. |
Responses
200 OK, application/json
Body: object (BooksBankLine).
A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Undo a line’s explanation
POST/api/v1/books/bank/lines/{id}/undo
- Scopes
books:write
Posts the reversal of its entry (dated in the first open period if the books are locked) and puts the line back to explain. A matched receipt goes back to waiting for its payment. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankLine).
A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Set a line aside
POST/api/v1/books/bank/lines/{id}/exclude
- Scopes
books:write
A copy of another line, or nothing to do with the business: it stays in the list but never in the books. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankLine).
A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Bring back a line set aside
POST/api/v1/books/bank/lines/{id}/include
- Scopes
books:write
Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksBankLine).
A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
List rules
GET/api/v1/books/bank/rules
- Scopes
books:read
Each rule’s current version, as a sentence, with how many lines it has explained. Part of the books product: the firm must have it and you must have access to it.
Responses
200 OK, application/json
Body: array of BooksBankRule.
An owner’s (or accountant’s) rule, current version. It suggests, or posts without a tap only when the owner chose that.
| Field | Type | Description |
|---|---|---|
[].idrequired | string (uuid) | |
[].versionrequired | integer | |
[].namerequired | stringnullable | |
[].descriptionrequired | string | The rule as a sentence. |
[].enabledrequired | boolean | |
[].priorityrequired | integer | |
[].matchrequired | string (BooksBankRuleMatch) | One of: |
[].conditionsrequired | array of BooksBankRuleCondition | |
[].conditions[].fieldrequired | string (BooksBankRuleField) | One of: |
[].conditions[].oprequired | string | example "contains" |
[].conditions[].value | string | |
[].conditions[].pence | integer | at least 0 |
[].conditions[].toPence | integer | Integer pence (GBP). at least 0 |
[].categoriserequired | object (BooksBankCategorisation) | A category (account code) and VAT code; a split gives each part’s share in basis points. |
[].categorise.accountCoderequired | string | |
[].categorise.accountNamerequired | stringnullable | |
[].categorise.vatCoderequired | stringnullable | Null or NONE: no VAT. |
[].categorise.splitrequired | array of BooksBankSplitPart | Empty when not split. |
[].categorise.split[].accountCoderequired | string | |
[].categorise.split[].vatCoderequired | stringnullable | |
[].categorise.split[].shareBpsrequired | integer | 1 to 9999 |
[].postWithoutAskingrequired | boolean | |
[].autoPostLimitPencerequired | integernullable | Integer pence (GBP). |
[].createdByrequired | string (BooksBankRuleAuthor) | One of: |
[].appliedrequired | integer | Lines this rule explained (all versions). |
[].updatedAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Add a rule
POST/api/v1/books/bank/rules
- Scopes
books:write- Retries
- Send an
Idempotency-Keyto make retries safe.
A rule needs a condition on the description, payee, reference or amount. postWithoutAsking: only the business’s owners and admins, signed in; even then a possible copy, a locked period, an unusual amount or a VAT-sensitive code is left for a person. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
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 (BooksBankRuleInput).
| Field | Type | Description |
|---|---|---|
name | string | |
priority | integer | |
enabled | boolean | |
match | string (BooksBankRuleMatch) | One of: |
conditionsrequired | array of BooksBankRuleCondition | |
conditions[].fieldrequired | string (BooksBankRuleField) | One of: |
conditions[].oprequired | string | example "contains" |
conditions[].value | string | |
conditions[].pence | integer | at least 0 |
conditions[].toPence | integer | Integer pence (GBP). at least 0 |
categoriserequired | object (BooksBankCategorisationInput) | An account code and VAT code (null: no VAT), or a split. |
categorise.accountCoderequired | string | |
categorise.vatCode | stringnullable | |
categorise.split | array of BooksBankSplitPartInput | |
categorise.split[].accountCoderequired | string | |
categorise.split[].vatCode | stringnullable | |
categorise.split[].shareBpsrequired | integer | 1 to 9999 |
postWithoutAsking | boolean | Post matching lines without a tap. The business’s owners and admins only, signed in. |
autoPostLimitPence | integernullable | Integer pence (GBP). |
Responses
201 Created, application/json
Body: object (BooksBankRule).
An owner’s (or accountant’s) rule, current version. It suggests, or posts without a tap only when the owner chose that.
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
versionrequired | integer | |
namerequired | stringnullable | |
descriptionrequired | string | The rule as a sentence. |
enabledrequired | boolean | |
priorityrequired | integer | |
matchrequired | string (BooksBankRuleMatch) | One of: |
conditionsrequired | array of BooksBankRuleCondition | |
conditions[].fieldrequired | string (BooksBankRuleField) | One of: |
conditions[].oprequired | string | example "contains" |
conditions[].value | string | |
conditions[].pence | integer | at least 0 |
conditions[].toPence | integer | Integer pence (GBP). at least 0 |
categoriserequired | object (BooksBankCategorisation) | A category (account code) and VAT code; a split gives each part’s share in basis points. |
categorise.accountCoderequired | string | |
categorise.accountNamerequired | stringnullable | |
categorise.vatCoderequired | stringnullable | Null or NONE: no VAT. |
categorise.splitrequired | array of BooksBankSplitPart | Empty when not split. |
categorise.split[].accountCoderequired | string | |
categorise.split[].vatCoderequired | stringnullable | |
categorise.split[].shareBpsrequired | integer | 1 to 9999 |
postWithoutAskingrequired | boolean | |
autoPostLimitPencerequired | integernullable | Integer pence (GBP). |
createdByrequired | string (BooksBankRuleAuthor) | One of: |
appliedrequired | integer | Lines this rule explained (all versions). |
updatedAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 409 Conflict, 422 Unprocessable content, 429 Too many requests, as problem details (application/problem+json).
Preview a rule
POST/api/v1/books/bank/rules/preview
- Scopes
books:read
What it would catch among the lines already in the books, before saving it: “This rule would apply to 14 past lines (£512.40).” Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksBankRuleInput).
The fields are as shown above.
Responses
200 OK, application/json
Body: object (BooksBankRulePreview).
| Field | Type | Description |
|---|---|---|
countrequired | integer | |
totalPencerequired | integer | Integer pence (GBP). |
wouldAutoPostrequired | integer | |
differentNowrequired | integer | |
sampleLineIdsrequired | array of string | |
sampleLines | array of BooksBankRulePreviewLine | The sample lines themselves (up to 10, newest first), so a preview can show them. |
sampleLines[].idrequired | string (uuid) | |
sampleLines[].bookedAtrequired | string (date) | example "2026-03-31" |
sampleLines[].descriptionrequired | string | |
sampleLines[].payeerequired | stringnullable | |
sampleLines[].amountPencerequired | integer | Integer pence (GBP). Money in positive, money out negative. |
sampleLines[].statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
summaryrequired | string | example "This rule would apply to 14 past lines (£512.40)." |
problemsrequired | array of string | Problems with the rule, if any. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Change a rule
PUT/api/v1/books/bank/rules/{id}
- Scopes
books:write
Saves a new version; earlier versions are kept, so each entry still names the version that posted it. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Request body
Required. application/json: object (BooksBankRuleInput).
The fields are as shown above.
Responses
200 OK, application/json
Body: object (BooksBankRule).
An owner’s (or accountant’s) rule, current version. It suggests, or posts without a tap only when the owner chose that.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Remove a rule
POST/api/v1/books/bank/rules/{id}/remove
- Scopes
books:write
It stops applying; lines it explained stay as they are (undo them one by one if needed). Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
204 No content
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get the inbox
GET/api/v1/books/inbox
- Scopes
books:read
Bank lines still to explain, laid out as questions (Tolvi isn’t sure: two or three answers), suggested groups (one payee, one category: one tap confirms them all) and matches; “Do this every time?” rule offers; receipts to check, reminders to approve and invoices to chase; VAT returns due; and bank or HMRC connections running out. Says whether Tolvi AI is waiting for next month’s allowance (nothing is charged) and whether the viewer can act. Part of the books product: the firm must have it and you must have access to it.
Responses
200 OK, application/json
Body: object (BooksInbox).
Everything that needs the owner, most of it already sorted, each with a “Why?”. Nothing in it posts until a person confirms (or the owner’s own rule does).
| Field | Type | Description |
|---|---|---|
viewerrequired | object (BooksInboxViewer) | Who is looking, and whether they can act on the inbox. |
viewer.canActrequired | boolean | False for an accountant whose link only lets them see the books. |
viewer.accountantrequired | boolean | An accountant working through the practice link. |
viewer.canAutoPostrequired | boolean | May let a rule post without asking (the business’s owners and admins, signed in). |
viewer.readOnlyReasonrequired | stringnullable | Why the viewer can’t act, in plain English. |
countsrequired | object (BooksInboxCounts) | |
counts.totalrequired | integer | |
counts.questionsrequired | integer | |
counts.suggestedrequired | integer | |
counts.matchedrequired | integer | |
counts.tasksrequired | integer | |
counts.ruleOffersrequired | integer | |
airequired | object (BooksInboxAi) | Tolvi AI in the inbox this month (fair use, D-83): past the allowance it waits, and nothing is charged. |
ai.staterequired | string (BooksInboxAiState) | One of: |
ai.allowancerequired | integer | AI suggestions included this month. |
ai.usedrequired | integer | |
ai.resetsOnrequired | string (date) | When the allowance starts again. example "2026-03-31" |
ai.waitingrequired | integer | Lines waiting for Tolvi AI. |
ai.messagerequired | stringnullable | |
questionsrequired | array of BooksInboxLine | Tolvi isn’t sure: answer in a tap. |
questions[].linerequired | object (BooksBankLine) | A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative. |
questions[].line.idrequired | string (uuid) | |
questions[].line.bankAccountIdrequired | string (uuid) | |
questions[].line.sourcerequired | string (BooksBankLineSource) | One of: |
questions[].line.bookedAtrequired | string (date) | example "2026-03-31" |
questions[].line.descriptionrequired | string | |
questions[].line.counterpartyrequired | stringnullable | |
questions[].line.referencerequired | stringnullable | |
questions[].line.payeerequired | stringnullable | Who it was paid to or received from, cleaned up. |
questions[].line.amountPencerequired | integer | Integer pence (GBP). |
questions[].line.currencyrequired | string | |
questions[].line.runningBalancePencerequired | integernullable | Integer pence (GBP). |
questions[].line.statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
questions[].line.suggestionrequired | object (BooksBankSuggestion)nullable | What Tolvi suggests for a line and why. Only an owner’s rule set to post automatically ever posts without a tap. |
questions[].line.suggestion.levelrequired | string (BooksBankSuggestionLevel) | One of: |
questions[].line.suggestion.stagerequired | string (BooksBankSuggestionStage) | Which step decided: a possible copy, a match, your rule, what you did before, Tolvi AI, or nothing yet (for_ai). One of: |
questions[].line.suggestion.summaryrequired | string | |
questions[].line.suggestion.reasonsrequired | array of string | The “Why?”. |
questions[].line.suggestion.kindrequired | string (BooksBankSuggestionKind) | One of: |
questions[].line.suggestion.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
questions[].line.suggestion.categorisation.accountCoderequired | string | |
questions[].line.suggestion.categorisation.accountNamerequired | stringnullable | |
questions[].line.suggestion.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
questions[].line.suggestion.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
questions[].line.suggestion.categorisation.split[].accountCoderequired | string | |
questions[].line.suggestion.categorisation.split[].vatCoderequired | stringnullable | |
questions[].line.suggestion.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
questions[].line.suggestion.transferBankAccountIdrequired | string (uuid)nullable | |
questions[].line.suggestion.otherLineIdrequired | string (uuid)nullable | |
questions[].line.suggestion.documentKindrequired | string (BooksBankDocumentKind)nullable | One of: |
questions[].line.suggestion.documentIdsrequired | array of string | |
questions[].line.suggestion.duplicateOfIdrequired | string (uuid)nullable | |
questions[].line.suggestion.ruleIdrequired | string (uuid)nullable | |
questions[].line.suggestion.ruleNamerequired | stringnullable | |
questions[].line.suggestion.candidatesrequired | array of BooksBankCandidate | Other possible matches, best first. |
questions[].line.suggestion.candidates[].summaryrequired | string | |
questions[].line.suggestion.candidates[].scorerequired | integer | 0 to 100 |
questions[].line.suggestion.candidates[].bandrequired | string (BooksBankMatchBand) | One of: |
questions[].line.suggestion.candidates[].kindrequired | string | transfer, or the kind of document. |
questions[].line.suggestion.candidates[].documentIdsrequired | array of string | |
questions[].line.suggestion.candidates[].bankAccountIdrequired | string (uuid)nullable | |
questions[].line.suggestion.candidates[].otherLineIdrequired | string (uuid)nullable | |
questions[].line.explanationrequired | object (BooksBankExplanation)nullable | How the line is in the books. |
questions[].line.explanation.kindrequired | string (BooksBankExplanationKind) | One of: |
questions[].line.explanation.entryIdrequired | string (uuid) | |
questions[].line.explanation.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
questions[].line.explanation.categorisation.accountCoderequired | string | |
questions[].line.explanation.categorisation.accountNamerequired | stringnullable | |
questions[].line.explanation.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
questions[].line.explanation.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
questions[].line.explanation.categorisation.split[].accountCoderequired | string | |
questions[].line.explanation.categorisation.split[].vatCoderequired | stringnullable | |
questions[].line.explanation.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
questions[].line.explanation.transferLineIdrequired | string (uuid)nullable | |
questions[].line.explanation.receiptIdrequired | string (uuid)nullable | |
questions[].line.explanation.ruleIdrequired | string (uuid)nullable | The rule that posted it without a tap. |
questions[].line.explanation.ruleVersionrequired | integernullable | |
questions[].line.explanation.explainedByrequired | string (uuid)nullable | |
questions[].line.explanation.explainedAtrequired | string (date-time) | |
questions[].line.bankChangerequired | objectnullable | The bank changed or withdrew this line after Tolvi read it: { kind: corrected | missing, changed, amountPence, description, bookedAt }. |
questions[].line.createdAtrequired | string (date-time) | |
questions[].bankAccountNamerequired | stringnullable | |
questions[].sourceLabelrequired | string | “From your rule”, “You usually choose this”, “Tolvi AI suggests”, “Matched”… |
questions[].questionrequired | stringnullable | For a question: what Tolvi asks, in plain English. |
questions[].choicesrequired | array of BooksInboxChoice | For a question: two or three likely answers. |
questions[].choices[].labelrequired | string | |
questions[].choices[].asSuggestedrequired | boolean | Confirm as Tolvi suggested (a copy set aside, a match). |
questions[].choices[].categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
questions[].choices[].categorisation.accountCoderequired | string | |
questions[].choices[].categorisation.accountNamerequired | stringnullable | |
questions[].choices[].categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
questions[].choices[].categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
questions[].choices[].categorisation.split[].accountCoderequired | string | |
questions[].choices[].categorisation.split[].vatCoderequired | stringnullable | |
questions[].choices[].categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
groupsrequired | array of BooksInboxGroup | |
groups[].keyrequired | string | |
groups[].labelrequired | string | |
groups[].payeerequired | string | |
groups[].directionrequired | string (BooksInboxDirection) | One of: |
groups[].categorisationrequired | object (BooksBankCategorisation) | A category (account code) and VAT code; a split gives each part’s share in basis points. |
groups[].categorisation.accountCoderequired | string | |
groups[].categorisation.accountNamerequired | stringnullable | |
groups[].categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
groups[].categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
groups[].categorisation.split[].accountCoderequired | string | |
groups[].categorisation.split[].vatCoderequired | stringnullable | |
groups[].categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
groups[].sourceLabelrequired | string | |
groups[].reasonsrequired | array of string | The “Why?” of the newest line. |
groups[].totalPencerequired | integer | Integer pence (GBP). |
groups[].lineIdsrequired | array of string | |
groups[].linesrequired | array of BooksInboxLine | |
groups[].lines[].linerequired | object (BooksBankLine) | A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative. |
groups[].lines[].line.idrequired | string (uuid) | |
groups[].lines[].line.bankAccountIdrequired | string (uuid) | |
groups[].lines[].line.sourcerequired | string (BooksBankLineSource) | One of: |
groups[].lines[].line.bookedAtrequired | string (date) | example "2026-03-31" |
groups[].lines[].line.descriptionrequired | string | |
groups[].lines[].line.counterpartyrequired | stringnullable | |
groups[].lines[].line.referencerequired | stringnullable | |
groups[].lines[].line.payeerequired | stringnullable | Who it was paid to or received from, cleaned up. |
groups[].lines[].line.amountPencerequired | integer | Integer pence (GBP). |
groups[].lines[].line.currencyrequired | string | |
groups[].lines[].line.runningBalancePencerequired | integernullable | Integer pence (GBP). |
groups[].lines[].line.statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
groups[].lines[].line.suggestionrequired | object (BooksBankSuggestion)nullable | What Tolvi suggests for a line and why. Only an owner’s rule set to post automatically ever posts without a tap. |
groups[].lines[].line.suggestion.levelrequired | string (BooksBankSuggestionLevel) | One of: |
groups[].lines[].line.suggestion.stagerequired | string (BooksBankSuggestionStage) | Which step decided: a possible copy, a match, your rule, what you did before, Tolvi AI, or nothing yet (for_ai). One of: |
groups[].lines[].line.suggestion.summaryrequired | string | |
groups[].lines[].line.suggestion.reasonsrequired | array of string | The “Why?”. |
groups[].lines[].line.suggestion.kindrequired | string (BooksBankSuggestionKind) | One of: |
groups[].lines[].line.suggestion.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
groups[].lines[].line.suggestion.categorisation.accountCoderequired | string | |
groups[].lines[].line.suggestion.categorisation.accountNamerequired | stringnullable | |
groups[].lines[].line.suggestion.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
groups[].lines[].line.suggestion.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
groups[].lines[].line.suggestion.categorisation.split[].accountCoderequired | string | |
groups[].lines[].line.suggestion.categorisation.split[].vatCoderequired | stringnullable | |
groups[].lines[].line.suggestion.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
groups[].lines[].line.suggestion.transferBankAccountIdrequired | string (uuid)nullable | |
groups[].lines[].line.suggestion.otherLineIdrequired | string (uuid)nullable | |
groups[].lines[].line.suggestion.documentKindrequired | string (BooksBankDocumentKind)nullable | One of: |
groups[].lines[].line.suggestion.documentIdsrequired | array of string | |
groups[].lines[].line.suggestion.duplicateOfIdrequired | string (uuid)nullable | |
groups[].lines[].line.suggestion.ruleIdrequired | string (uuid)nullable | |
groups[].lines[].line.suggestion.ruleNamerequired | stringnullable | |
groups[].lines[].line.suggestion.candidatesrequired | array of BooksBankCandidate | Other possible matches, best first. |
groups[].lines[].line.suggestion.candidates[].summaryrequired | string | |
groups[].lines[].line.suggestion.candidates[].scorerequired | integer | 0 to 100 |
groups[].lines[].line.suggestion.candidates[].bandrequired | string (BooksBankMatchBand) | One of: |
groups[].lines[].line.suggestion.candidates[].kindrequired | string | transfer, or the kind of document. |
groups[].lines[].line.suggestion.candidates[].documentIdsrequired | array of string | |
groups[].lines[].line.suggestion.candidates[].bankAccountIdrequired | string (uuid)nullable | |
groups[].lines[].line.suggestion.candidates[].otherLineIdrequired | string (uuid)nullable | |
groups[].lines[].line.explanationrequired | object (BooksBankExplanation)nullable | How the line is in the books. |
groups[].lines[].line.explanation.kindrequired | string (BooksBankExplanationKind) | One of: |
groups[].lines[].line.explanation.entryIdrequired | string (uuid) | |
groups[].lines[].line.explanation.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
groups[].lines[].line.explanation.categorisation.accountCoderequired | string | |
groups[].lines[].line.explanation.categorisation.accountNamerequired | stringnullable | |
groups[].lines[].line.explanation.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
groups[].lines[].line.explanation.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
groups[].lines[].line.explanation.categorisation.split[].accountCoderequired | string | |
groups[].lines[].line.explanation.categorisation.split[].vatCoderequired | stringnullable | |
groups[].lines[].line.explanation.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
groups[].lines[].line.explanation.transferLineIdrequired | string (uuid)nullable | |
groups[].lines[].line.explanation.receiptIdrequired | string (uuid)nullable | |
groups[].lines[].line.explanation.ruleIdrequired | string (uuid)nullable | The rule that posted it without a tap. |
groups[].lines[].line.explanation.ruleVersionrequired | integernullable | |
groups[].lines[].line.explanation.explainedByrequired | string (uuid)nullable | |
groups[].lines[].line.explanation.explainedAtrequired | string (date-time) | |
groups[].lines[].line.bankChangerequired | objectnullable | The bank changed or withdrew this line after Tolvi read it: { kind: corrected | missing, changed, amountPence, description, bookedAt }. |
groups[].lines[].line.createdAtrequired | string (date-time) | |
groups[].lines[].bankAccountNamerequired | stringnullable | |
groups[].lines[].sourceLabelrequired | string | “From your rule”, “You usually choose this”, “Tolvi AI suggests”, “Matched”… |
groups[].lines[].questionrequired | stringnullable | For a question: what Tolvi asks, in plain English. |
groups[].lines[].choicesrequired | array of BooksInboxChoice | For a question: two or three likely answers. |
groups[].lines[].choices[].labelrequired | string | |
groups[].lines[].choices[].asSuggestedrequired | boolean | Confirm as Tolvi suggested (a copy set aside, a match). |
groups[].lines[].choices[].categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
groups[].lines[].choices[].categorisation.accountCoderequired | string | |
groups[].lines[].choices[].categorisation.accountNamerequired | stringnullable | |
groups[].lines[].choices[].categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
groups[].lines[].choices[].categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
groups[].lines[].choices[].categorisation.split[].accountCoderequired | string | |
groups[].lines[].choices[].categorisation.split[].vatCoderequired | stringnullable | |
groups[].lines[].choices[].categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
suggestedrequired | array of BooksInboxLine | Suggested lines not in a group. |
suggested[].linerequired | object (BooksBankLine) | A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative. |
suggested[].line.idrequired | string (uuid) | |
suggested[].line.bankAccountIdrequired | string (uuid) | |
suggested[].line.sourcerequired | string (BooksBankLineSource) | One of: |
suggested[].line.bookedAtrequired | string (date) | example "2026-03-31" |
suggested[].line.descriptionrequired | string | |
suggested[].line.counterpartyrequired | stringnullable | |
suggested[].line.referencerequired | stringnullable | |
suggested[].line.payeerequired | stringnullable | Who it was paid to or received from, cleaned up. |
suggested[].line.amountPencerequired | integer | Integer pence (GBP). |
suggested[].line.currencyrequired | string | |
suggested[].line.runningBalancePencerequired | integernullable | Integer pence (GBP). |
suggested[].line.statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
suggested[].line.suggestionrequired | object (BooksBankSuggestion)nullable | What Tolvi suggests for a line and why. Only an owner’s rule set to post automatically ever posts without a tap. |
suggested[].line.suggestion.levelrequired | string (BooksBankSuggestionLevel) | One of: |
suggested[].line.suggestion.stagerequired | string (BooksBankSuggestionStage) | Which step decided: a possible copy, a match, your rule, what you did before, Tolvi AI, or nothing yet (for_ai). One of: |
suggested[].line.suggestion.summaryrequired | string | |
suggested[].line.suggestion.reasonsrequired | array of string | The “Why?”. |
suggested[].line.suggestion.kindrequired | string (BooksBankSuggestionKind) | One of: |
suggested[].line.suggestion.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
suggested[].line.suggestion.categorisation.accountCoderequired | string | |
suggested[].line.suggestion.categorisation.accountNamerequired | stringnullable | |
suggested[].line.suggestion.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
suggested[].line.suggestion.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
suggested[].line.suggestion.categorisation.split[].accountCoderequired | string | |
suggested[].line.suggestion.categorisation.split[].vatCoderequired | stringnullable | |
suggested[].line.suggestion.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
suggested[].line.suggestion.transferBankAccountIdrequired | string (uuid)nullable | |
suggested[].line.suggestion.otherLineIdrequired | string (uuid)nullable | |
suggested[].line.suggestion.documentKindrequired | string (BooksBankDocumentKind)nullable | One of: |
suggested[].line.suggestion.documentIdsrequired | array of string | |
suggested[].line.suggestion.duplicateOfIdrequired | string (uuid)nullable | |
suggested[].line.suggestion.ruleIdrequired | string (uuid)nullable | |
suggested[].line.suggestion.ruleNamerequired | stringnullable | |
suggested[].line.suggestion.candidatesrequired | array of BooksBankCandidate | Other possible matches, best first. |
suggested[].line.suggestion.candidates[].summaryrequired | string | |
suggested[].line.suggestion.candidates[].scorerequired | integer | 0 to 100 |
suggested[].line.suggestion.candidates[].bandrequired | string (BooksBankMatchBand) | One of: |
suggested[].line.suggestion.candidates[].kindrequired | string | transfer, or the kind of document. |
suggested[].line.suggestion.candidates[].documentIdsrequired | array of string | |
suggested[].line.suggestion.candidates[].bankAccountIdrequired | string (uuid)nullable | |
suggested[].line.suggestion.candidates[].otherLineIdrequired | string (uuid)nullable | |
suggested[].line.explanationrequired | object (BooksBankExplanation)nullable | How the line is in the books. |
suggested[].line.explanation.kindrequired | string (BooksBankExplanationKind) | One of: |
suggested[].line.explanation.entryIdrequired | string (uuid) | |
suggested[].line.explanation.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
suggested[].line.explanation.categorisation.accountCoderequired | string | |
suggested[].line.explanation.categorisation.accountNamerequired | stringnullable | |
suggested[].line.explanation.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
suggested[].line.explanation.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
suggested[].line.explanation.categorisation.split[].accountCoderequired | string | |
suggested[].line.explanation.categorisation.split[].vatCoderequired | stringnullable | |
suggested[].line.explanation.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
suggested[].line.explanation.transferLineIdrequired | string (uuid)nullable | |
suggested[].line.explanation.receiptIdrequired | string (uuid)nullable | |
suggested[].line.explanation.ruleIdrequired | string (uuid)nullable | The rule that posted it without a tap. |
suggested[].line.explanation.ruleVersionrequired | integernullable | |
suggested[].line.explanation.explainedByrequired | string (uuid)nullable | |
suggested[].line.explanation.explainedAtrequired | string (date-time) | |
suggested[].line.bankChangerequired | objectnullable | The bank changed or withdrew this line after Tolvi read it: { kind: corrected | missing, changed, amountPence, description, bookedAt }. |
suggested[].line.createdAtrequired | string (date-time) | |
suggested[].bankAccountNamerequired | stringnullable | |
suggested[].sourceLabelrequired | string | “From your rule”, “You usually choose this”, “Tolvi AI suggests”, “Matched”… |
suggested[].questionrequired | stringnullable | For a question: what Tolvi asks, in plain English. |
suggested[].choicesrequired | array of BooksInboxChoice | For a question: two or three likely answers. |
suggested[].choices[].labelrequired | string | |
suggested[].choices[].asSuggestedrequired | boolean | Confirm as Tolvi suggested (a copy set aside, a match). |
suggested[].choices[].categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
suggested[].choices[].categorisation.accountCoderequired | string | |
suggested[].choices[].categorisation.accountNamerequired | stringnullable | |
suggested[].choices[].categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
suggested[].choices[].categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
suggested[].choices[].categorisation.split[].accountCoderequired | string | |
suggested[].choices[].categorisation.split[].vatCoderequired | stringnullable | |
suggested[].choices[].categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
matchedrequired | array of BooksInboxLine | Paid invoices, receipts found and transfers between your accounts. |
matched[].linerequired | object (BooksBankLine) | A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative. |
matched[].line.idrequired | string (uuid) | |
matched[].line.bankAccountIdrequired | string (uuid) | |
matched[].line.sourcerequired | string (BooksBankLineSource) | One of: |
matched[].line.bookedAtrequired | string (date) | example "2026-03-31" |
matched[].line.descriptionrequired | string | |
matched[].line.counterpartyrequired | stringnullable | |
matched[].line.referencerequired | stringnullable | |
matched[].line.payeerequired | stringnullable | Who it was paid to or received from, cleaned up. |
matched[].line.amountPencerequired | integer | Integer pence (GBP). |
matched[].line.currencyrequired | string | |
matched[].line.runningBalancePencerequired | integernullable | Integer pence (GBP). |
matched[].line.statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
matched[].line.suggestionrequired | object (BooksBankSuggestion)nullable | What Tolvi suggests for a line and why. Only an owner’s rule set to post automatically ever posts without a tap. |
matched[].line.suggestion.levelrequired | string (BooksBankSuggestionLevel) | One of: |
matched[].line.suggestion.stagerequired | string (BooksBankSuggestionStage) | Which step decided: a possible copy, a match, your rule, what you did before, Tolvi AI, or nothing yet (for_ai). One of: |
matched[].line.suggestion.summaryrequired | string | |
matched[].line.suggestion.reasonsrequired | array of string | The “Why?”. |
matched[].line.suggestion.kindrequired | string (BooksBankSuggestionKind) | One of: |
matched[].line.suggestion.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
matched[].line.suggestion.categorisation.accountCoderequired | string | |
matched[].line.suggestion.categorisation.accountNamerequired | stringnullable | |
matched[].line.suggestion.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
matched[].line.suggestion.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
matched[].line.suggestion.categorisation.split[].accountCoderequired | string | |
matched[].line.suggestion.categorisation.split[].vatCoderequired | stringnullable | |
matched[].line.suggestion.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
matched[].line.suggestion.transferBankAccountIdrequired | string (uuid)nullable | |
matched[].line.suggestion.otherLineIdrequired | string (uuid)nullable | |
matched[].line.suggestion.documentKindrequired | string (BooksBankDocumentKind)nullable | One of: |
matched[].line.suggestion.documentIdsrequired | array of string | |
matched[].line.suggestion.duplicateOfIdrequired | string (uuid)nullable | |
matched[].line.suggestion.ruleIdrequired | string (uuid)nullable | |
matched[].line.suggestion.ruleNamerequired | stringnullable | |
matched[].line.suggestion.candidatesrequired | array of BooksBankCandidate | Other possible matches, best first. |
matched[].line.suggestion.candidates[].summaryrequired | string | |
matched[].line.suggestion.candidates[].scorerequired | integer | 0 to 100 |
matched[].line.suggestion.candidates[].bandrequired | string (BooksBankMatchBand) | One of: |
matched[].line.suggestion.candidates[].kindrequired | string | transfer, or the kind of document. |
matched[].line.suggestion.candidates[].documentIdsrequired | array of string | |
matched[].line.suggestion.candidates[].bankAccountIdrequired | string (uuid)nullable | |
matched[].line.suggestion.candidates[].otherLineIdrequired | string (uuid)nullable | |
matched[].line.explanationrequired | object (BooksBankExplanation)nullable | How the line is in the books. |
matched[].line.explanation.kindrequired | string (BooksBankExplanationKind) | One of: |
matched[].line.explanation.entryIdrequired | string (uuid) | |
matched[].line.explanation.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
matched[].line.explanation.categorisation.accountCoderequired | string | |
matched[].line.explanation.categorisation.accountNamerequired | stringnullable | |
matched[].line.explanation.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
matched[].line.explanation.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
matched[].line.explanation.categorisation.split[].accountCoderequired | string | |
matched[].line.explanation.categorisation.split[].vatCoderequired | stringnullable | |
matched[].line.explanation.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
matched[].line.explanation.transferLineIdrequired | string (uuid)nullable | |
matched[].line.explanation.receiptIdrequired | string (uuid)nullable | |
matched[].line.explanation.ruleIdrequired | string (uuid)nullable | The rule that posted it without a tap. |
matched[].line.explanation.ruleVersionrequired | integernullable | |
matched[].line.explanation.explainedByrequired | string (uuid)nullable | |
matched[].line.explanation.explainedAtrequired | string (date-time) | |
matched[].line.bankChangerequired | objectnullable | The bank changed or withdrew this line after Tolvi read it: { kind: corrected | missing, changed, amountPence, description, bookedAt }. |
matched[].line.createdAtrequired | string (date-time) | |
matched[].bankAccountNamerequired | stringnullable | |
matched[].sourceLabelrequired | string | “From your rule”, “You usually choose this”, “Tolvi AI suggests”, “Matched”… |
matched[].questionrequired | stringnullable | For a question: what Tolvi asks, in plain English. |
matched[].choicesrequired | array of BooksInboxChoice | For a question: two or three likely answers. |
matched[].choices[].labelrequired | string | |
matched[].choices[].asSuggestedrequired | boolean | Confirm as Tolvi suggested (a copy set aside, a match). |
matched[].choices[].categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
matched[].choices[].categorisation.accountCoderequired | string | |
matched[].choices[].categorisation.accountNamerequired | stringnullable | |
matched[].choices[].categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
matched[].choices[].categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
matched[].choices[].categorisation.split[].accountCoderequired | string | |
matched[].choices[].categorisation.split[].vatCoderequired | stringnullable | |
matched[].choices[].categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
ruleOffersrequired | array of BooksInboxRuleOffer | |
ruleOffers[].merchantKeyrequired | string | |
ruleOffers[].merchantrequired | string | |
ruleOffers[].directionrequired | string (BooksInboxDirection) | One of: |
ruleOffers[].questionrequired | string | |
ruleOffers[].reasonrequired | string | |
ruleOffers[].ruleTextrequired | string | The rule in plain words. |
ruleOffers[].categorisationrequired | object (BooksBankCategorisation) | A category (account code) and VAT code; a split gives each part’s share in basis points. |
ruleOffers[].categorisation.accountCoderequired | string | |
ruleOffers[].categorisation.accountNamerequired | stringnullable | |
ruleOffers[].categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
ruleOffers[].categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
ruleOffers[].categorisation.split[].accountCoderequired | string | |
ruleOffers[].categorisation.split[].vatCoderequired | stringnullable | |
ruleOffers[].categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
ruleOffers[].byrequired | string (BooksInboxDecider) | One of: |
tasksrequired | array of BooksInboxTask | |
tasks[].kindrequired | string (BooksInboxTaskKind) | One of: |
tasks[].idrequired | string | |
tasks[].titlerequired | string | |
tasks[].detailrequired | stringnullable | |
tasks[].amountPencerequired | integernullable | Integer pence (GBP). |
tasks[].daterequired | string (date)nullable | example "2026-03-31" |
tasks[].pathrequired | array of string | Where to go, relative to the Books area (e.g. ["sales", "invoices", "<id>"]). |
tasks[].queryrequired | objectnullable | Query parameters for the link. |
tasks[].ownerOnlyrequired | boolean | Only the business’s own people can do this (an accountant sees it but can’t). |
tasks[].tonerequired | string (BooksInboxTaskTone) | One of: |
sortedByRulesrequired | object (BooksInboxSorted) | Lines your rules posted without a tap in the last 7 days (in the activity, each with Undo). |
sortedByRules.countrequired | integer | |
sortedByRules.sincerequired | string (date) | example "2026-03-31" |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Confirm lines from the inbox
POST/api/v1/books/inbox/confirm
- Scopes
books:write
One line, a group or a selection: each as suggested, or all as one category (an answer to a question). Each line posts on its own, so one that can’t be posted doesn’t stop the rest. The business’s own people, or an accountant whose practice link lets them change the books (year_end or full_bookkeeping), as in Bank. A read-only link sees the inbox but gets 403 here. Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksBankConfirmLines).
The fields are as shown above.
Responses
200 OK, application/json
Body: object (BooksBankConfirmResults).
| Field | Type | Description |
|---|---|---|
resultsrequired | array of BooksBankConfirmResult | |
results[].lineIdrequired | string (uuid) | |
results[].okrequired | boolean | |
results[].problemrequired | stringnullable | Why this line wasn’t confirmed. |
confirmedrequired | integer |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Undo lines confirmed from the inbox
POST/api/v1/books/inbox/undo
- Scopes
books:write
Posts the reversal of each line’s entry and puts it back in the inbox. The business’s own people, or an accountant whose practice link lets them change the books (year_end or full_bookkeeping), as in Bank. A read-only link sees the inbox but gets 403 here. Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksInboxUndoLines).
| Field | Type | Description |
|---|---|---|
lineIdsrequired | array of string |
Responses
200 OK, application/json
Body: object (BooksBankConfirmResults).
| Field | Type | Description |
|---|---|---|
resultsrequired | array of BooksBankConfirmResult | |
results[].lineIdrequired | string (uuid) | |
results[].okrequired | boolean | |
results[].problemrequired | stringnullable | Why this line wasn’t confirmed. |
confirmedrequired | integer |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Why Tolvi suggested this
GET/api/v1/books/inbox/lines/{id}/why
- Scopes
books:read
Where the suggestion came from (your rule, your past choices, a match or Tolvi AI, with the model and prompt version), what this business chose before for the payee, and every decision on the line with who made it. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (BooksInboxWhy).
The “Why?” sheet: where a suggestion came from, what it rests on, and who has decided what on this line.
| Field | Type | Description |
|---|---|---|
itemrequired | object (BooksInboxLine) | A bank line in the inbox, with where its suggestion came from. |
item.linerequired | object (BooksBankLine) | A line from the bank or a statement: what happened, what Tolvi suggests and how it is in the books. Money in positive, money out negative. |
item.line.idrequired | string (uuid) | |
item.line.bankAccountIdrequired | string (uuid) | |
item.line.sourcerequired | string (BooksBankLineSource) | One of: |
item.line.bookedAtrequired | string (date) | example "2026-03-31" |
item.line.descriptionrequired | string | |
item.line.counterpartyrequired | stringnullable | |
item.line.referencerequired | stringnullable | |
item.line.payeerequired | stringnullable | Who it was paid to or received from, cleaned up. |
item.line.amountPencerequired | integer | Integer pence (GBP). |
item.line.currencyrequired | string | |
item.line.runningBalancePencerequired | integernullable | Integer pence (GBP). |
item.line.statusrequired | string (BooksBankLineStatus) | to_explain, explained (in the books), or excluded (set aside). One of: |
item.line.suggestionrequired | object (BooksBankSuggestion)nullable | What Tolvi suggests for a line and why. Only an owner’s rule set to post automatically ever posts without a tap. |
item.line.suggestion.levelrequired | string (BooksBankSuggestionLevel) | One of: |
item.line.suggestion.stagerequired | string (BooksBankSuggestionStage) | Which step decided: a possible copy, a match, your rule, what you did before, Tolvi AI, or nothing yet (for_ai). One of: |
item.line.suggestion.summaryrequired | string | |
item.line.suggestion.reasonsrequired | array of string | The “Why?”. |
item.line.suggestion.kindrequired | string (BooksBankSuggestionKind) | One of: |
item.line.suggestion.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
item.line.suggestion.categorisation.accountCoderequired | string | |
item.line.suggestion.categorisation.accountNamerequired | stringnullable | |
item.line.suggestion.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
item.line.suggestion.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
item.line.suggestion.categorisation.split[].accountCoderequired | string | |
item.line.suggestion.categorisation.split[].vatCoderequired | stringnullable | |
item.line.suggestion.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
item.line.suggestion.transferBankAccountIdrequired | string (uuid)nullable | |
item.line.suggestion.otherLineIdrequired | string (uuid)nullable | |
item.line.suggestion.documentKindrequired | string (BooksBankDocumentKind)nullable | One of: |
item.line.suggestion.documentIdsrequired | array of string | |
item.line.suggestion.duplicateOfIdrequired | string (uuid)nullable | |
item.line.suggestion.ruleIdrequired | string (uuid)nullable | |
item.line.suggestion.ruleNamerequired | stringnullable | |
item.line.suggestion.candidatesrequired | array of BooksBankCandidate | Other possible matches, best first. |
item.line.suggestion.candidates[].summaryrequired | string | |
item.line.suggestion.candidates[].scorerequired | integer | 0 to 100 |
item.line.suggestion.candidates[].bandrequired | string (BooksBankMatchBand) | One of: |
item.line.suggestion.candidates[].kindrequired | string | transfer, or the kind of document. |
item.line.suggestion.candidates[].documentIdsrequired | array of string | |
item.line.suggestion.candidates[].bankAccountIdrequired | string (uuid)nullable | |
item.line.suggestion.candidates[].otherLineIdrequired | string (uuid)nullable | |
item.line.explanationrequired | object (BooksBankExplanation)nullable | How the line is in the books. |
item.line.explanation.kindrequired | string (BooksBankExplanationKind) | One of: |
item.line.explanation.entryIdrequired | string (uuid) | |
item.line.explanation.categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
item.line.explanation.categorisation.accountCoderequired | string | |
item.line.explanation.categorisation.accountNamerequired | stringnullable | |
item.line.explanation.categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
item.line.explanation.categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
item.line.explanation.categorisation.split[].accountCoderequired | string | |
item.line.explanation.categorisation.split[].vatCoderequired | stringnullable | |
item.line.explanation.categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
item.line.explanation.transferLineIdrequired | string (uuid)nullable | |
item.line.explanation.receiptIdrequired | string (uuid)nullable | |
item.line.explanation.ruleIdrequired | string (uuid)nullable | The rule that posted it without a tap. |
item.line.explanation.ruleVersionrequired | integernullable | |
item.line.explanation.explainedByrequired | string (uuid)nullable | |
item.line.explanation.explainedAtrequired | string (date-time) | |
item.line.bankChangerequired | objectnullable | The bank changed or withdrew this line after Tolvi read it: { kind: corrected | missing, changed, amountPence, description, bookedAt }. |
item.line.createdAtrequired | string (date-time) | |
item.bankAccountNamerequired | stringnullable | |
item.sourceLabelrequired | string | “From your rule”, “You usually choose this”, “Tolvi AI suggests”, “Matched”… |
item.questionrequired | stringnullable | For a question: what Tolvi asks, in plain English. |
item.choicesrequired | array of BooksInboxChoice | For a question: two or three likely answers. |
item.choices[].labelrequired | string | |
item.choices[].asSuggestedrequired | boolean | Confirm as Tolvi suggested (a copy set aside, a match). |
item.choices[].categorisationrequired | object (BooksBankCategorisation)nullable | A category (account code) and VAT code; a split gives each part’s share in basis points. |
item.choices[].categorisation.accountCoderequired | string | |
item.choices[].categorisation.accountNamerequired | stringnullable | |
item.choices[].categorisation.vatCoderequired | stringnullable | Null or NONE: no VAT. |
item.choices[].categorisation.splitrequired | array of BooksBankSplitPart | Empty when not split. |
item.choices[].categorisation.split[].accountCoderequired | string | |
item.choices[].categorisation.split[].vatCoderequired | stringnullable | |
item.choices[].categorisation.split[].shareBpsrequired | integer | 1 to 9999 |
airequired | object (BooksInboxAiDetail)nullable | Tolvi AI’s part in a suggestion. |
ai.suggestionIdrequired | string (uuid) | |
ai.modelrequired | string | |
ai.promptVersionrequired | string | |
ai.confidencerequired | string (BooksInboxConfidence) | One of: |
ai.statusrequired | string (BooksInboxAiStatus) | One of: |
ai.atrequired | string (date-time) | |
memoryrequired | array of BooksInboxMemory | Most chosen first. |
memory[].labelrequired | string | |
memory[].timesrequired | integer | |
historyrequired | array of BooksInboxActivityItem | Every decision on this line, newest first. |
history[].idrequired | string (uuid) | |
history[].lineIdrequired | string (uuid) | |
history[].atrequired | string (date-time) | |
history[].actionrequired | string (BooksInboxActivityAction) | One of: |
history[].byrequired | object (BooksInboxPerson) | Who decided: a person at the business, an accountant (with their firm) or a rule. |
history[].by.kindrequired | string (BooksInboxDeciderKind) | One of: |
history[].by.namerequired | string | |
history[].by.firmNamerequired | stringnullable | |
history[].payeerequired | stringnullable | |
history[].amountPencerequired | integer | Integer pence (GBP). |
history[].whatrequired | stringnullable | What it went in as. |
history[].undoablerequired | boolean | Still stands and can be undone. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Who confirmed what
GET/api/v1/books/inbox/activity
- Scopes
books:read
The latest decisions on bank lines, newest first: confirmed, changed, answered, set aside, posted by a rule or undone, by whom (an accountant with their firm), and whether each can still be undone. Part of the books product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | 1 to 200; default 50 |
Responses
200 OK, application/json
Body: object (BooksInboxActivity).
| Field | Type | Description |
|---|---|---|
itemsrequired | array of BooksInboxActivityItem | |
items[].idrequired | string (uuid) | |
items[].lineIdrequired | string (uuid) | |
items[].atrequired | string (date-time) | |
items[].actionrequired | string (BooksInboxActivityAction) | One of: |
items[].byrequired | object (BooksInboxPerson) | Who decided: a person at the business, an accountant (with their firm) or a rule. |
items[].by.kindrequired | string (BooksInboxDeciderKind) | One of: |
items[].by.namerequired | string | |
items[].by.firmNamerequired | stringnullable | |
items[].payeerequired | stringnullable | |
items[].amountPencerequired | integer | Integer pence (GBP). |
items[].whatrequired | stringnullable | What it went in as. |
items[].undoablerequired | boolean | Still stands and can be undone. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Accept a rule offer (“Yes, always”)
POST/api/v1/books/inbox/rule-offers/accept
- Scopes
books:write
Saves the offered rule for the payee, so its lines are suggested that way from now on. It only suggests unless postWithoutAsking is set, which the business’s owners and admins can do signed in (403 otherwise). 404 when the offer no longer applies. The business’s own people, or an accountant whose practice link lets them change the books (year_end or full_bookkeeping), as in Bank. A read-only link sees the inbox but gets 403 here. Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksInboxAcceptRuleOffer).
| Field | Type | Description |
|---|---|---|
merchantKeyrequired | string | |
directionrequired | string (BooksInboxDirection) | One of: |
postWithoutAsking | boolean | Post matching lines without a tap. The business’s owners and admins only, signed in. |
Responses
201 Created, application/json
Body: object (BooksInboxRuleCreated).
| Field | Type | Description |
|---|---|---|
ruleIdrequired | string (uuid) | |
descriptionrequired | string | |
postWithoutAskingrequired | boolean |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Decline a rule offer
POST/api/v1/books/inbox/rule-offers/decline
- Scopes
books:write
“Not now” asks again after the next correction; “Ask me each time” stops offering for that payee. The business’s own people, or an accountant whose practice link lets them change the books (year_end or full_bookkeeping), as in Bank. A read-only link sees the inbox but gets 403 here. Part of the books product: the firm must have it and you must have access to it.
Request body
Required. application/json: object (BooksInboxDeclineRuleOffer).
| Field | Type | Description |
|---|---|---|
merchantKeyrequired | string | |
directionrequired | string (BooksInboxDirection) | One of: |
answerrequired | string (BooksInboxDeclineAnswer) | One of: |
Responses
204 No content
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).