Sync health
9 operations in the Tolvi API, version 1. Paths are relative to https://api.tolvi.co.uk. Every amount is integer pence.
Sync health overview
GET/api/v1/sync
- Scopes
work:read
Counts by sync state (open engagements, plus completed ones the sign-off check still covers), the last and any running sign-off check, the schedule and rules, and counts by software. Part of the working_papers product: the firm must have it and you must have access to it.
Responses
200 OK, application/json
Body: object (SyncOverview).
| Field | Type | Description |
|---|---|---|
countsrequired | object (SyncCounts) | |
counts.connectedrequired | integer | Engagements on bookkeeping software (open, plus completed ones still checked). at least 0 |
counts.manualrequired | integer | at least 0 |
counts.inSyncrequired | integer | at least 0 |
counts.changedrequired | integer | Open engagements whose figures changed since sign-off (the Work queue’s Changed view). at least 0 |
counts.changedCompletedrequired | integer | Completed engagements whose figures changed. at least 0 |
counts.expiredrequired | integer | at least 0 |
counts.errorrequired | integer | at least 0 |
counts.neverSyncedrequired | integer | at least 0 |
counts.manualOpenrequired | integer | Open engagements on manual upload: not checkable. at least 0 |
counts.resolvedTodayrequired | integer | Accepted, acknowledged or recorded as post-filing today (firm time zone). at least 0 |
lastRunrequired | object (SyncCheckRun)nullable | The last run that finished. |
lastRun.idrequired | string (uuid) | |
lastRun.triggerrequired | string (SyncCheckTrigger) | schedule: the nightly or weekly run; manual: Run check now. One of: |
lastRun.startedAtrequired | string (date-time) | |
lastRun.finishedAtrequired | string (date-time)nullable | Null while engagements are still being checked. |
lastRun.windowStartrequired | string (date-time) | Checks start from here; a scheduled run spreads them over windowMinutes. |
lastRun.windowMinutesrequired | integer | at least 0 |
lastRun.totalrequired | integer | Engagements in scope. at least 0 |
lastRun.checkedrequired | integer | Engagements checked so far. at least 0 |
lastRun.changedrequired | integer | at least 0 |
lastRun.failedrequired | integer | Expired connections and errors. at least 0 |
lastRun.durationMinutesrequired | integernullable | From start to finish. at least 0 |
lastRun.requestedByrequired | string (uuid)nullable | |
runningrequired | object (SyncCheckRun)nullable | A run still checking engagements. |
running.idrequired | string (uuid) | |
running.triggerrequired | string (SyncCheckTrigger) | schedule: the nightly or weekly run; manual: Run check now. One of: |
running.startedAtrequired | string (date-time) | |
running.finishedAtrequired | string (date-time)nullable | Null while engagements are still being checked. |
running.windowStartrequired | string (date-time) | Checks start from here; a scheduled run spreads them over windowMinutes. |
running.windowMinutesrequired | integer | at least 0 |
running.totalrequired | integer | Engagements in scope. at least 0 |
running.checkedrequired | integer | Engagements checked so far. at least 0 |
running.changedrequired | integer | at least 0 |
running.failedrequired | integer | Expired connections and errors. at least 0 |
running.durationMinutesrequired | integernullable | From start to finish. at least 0 |
running.requestedByrequired | string (uuid)nullable | |
schedulerequired | object (SyncSchedule) | |
schedule.frequencyrequired | string (SyncFrequency) | One of: |
schedule.timerequired | string | example "02:00" |
schedule.timeZonerequired | string | example "Europe/London" |
schedule.ignoreUnderPencerequired | integer | Integer pence (GBP). Movements smaller than this are ignored (firm default; a client can override it). at least 0 |
schedule.comparePriorYearrequired | boolean | |
schedule.onChangerequired | string (SyncOnChange) | One of: |
schedule.digestrequired | string (SyncDigest) | One of: |
bySoftwarerequired | array of SyncSoftwareRow | |
bySoftware[].softwarerequired | string (Software) | One of: |
bySoftware[].engagementsrequired | integer | at least 0 |
bySoftware[].inSyncrequired | integer | at least 0 |
bySoftware[].changedrequired | integer | at least 0 |
bySoftware[].fixrequired | integer | Expired, error or never synced. at least 0 |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
List engagements for Sync health
GET/api/v1/sync/engagements
- Scopes
work:read
Earliest deadline first, each with its latest sign-off check result. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | 1 to 500; default 100 |
includeCompleted | query | string | changed only: include completed engagements. One of: |
list | query | string | changed (default), fix (reconnect or fix), manual (open manual uploads), resolved (today). One of: |
Responses
200 OK, application/json
Body: object (SyncRowPage).
| Field | Type | Description |
|---|---|---|
listrequired | string (SyncList) | One of: |
rowsrequired | array of SyncRow | Earliest deadline first; at most |
rows[].engagementIdrequired | string (uuid) | |
rows[].clientIdrequired | string (uuid) | |
rows[].clientNamerequired | string | |
rows[].entityTyperequired | string (EntityType) | One of: |
rows[].periodEndrequired | string (date) | example "2026-03-31" |
rows[].dueDaterequired | string (date) | example "2026-03-31" |
rows[].statusrequired | string (EngagementStatus) | One of: |
rows[].softwarerequired | string (Software) | One of: |
rows[].syncStaterequired | string (SyncState) | One of: |
rows[].lastSyncedAtrequired | string (date-time)nullable | |
rows[].resultrequired | object (SyncResultSummary)nullable | The latest sign-off check result. |
rows[].result.idrequired | string (uuid) | |
rows[].result.statusrequired | string (SyncCheckStatus) | One of: |
rows[].result.checkedAtrequired | string (date-time) | |
rows[].result.signedOffAtrequired | string (date-time)nullable | |
rows[].result.accountCountrequired | integer | Accounts that moved. at least 0 |
rows[].result.grossPencerequired | integer | Integer pence (GBP). Sum of the absolute current year movements. at least 0 |
rows[].result.affectedSectionsrequired | array of string | Affected lead sections (W/P refs). |
rows[].result.resolutionrequired | string (SyncResolution)nullable | One of: |
rows[].result.resolvedAtrequired | string (date-time)nullable | |
rows[].result.outcomerequired | stringnullable | The outcome in words (who resolved it and how). |
rows[].result.messagerequired | stringnullable | Why the check could not run, in words. |
totalrequired | integer | at least 0 |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Run the sign-off check now
POST/api/v1/sync/runs
- Scopes
engagements:write- Retries
- Send an
Idempotency-Keyto make retries safe.
Starts a check of every connected engagement with a sign-off (or only engagementIds). The books are read in the background; poll GET /sync/runs/{id}. A run already in progress is returned instead of starting another. Part of the working_papers 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
Optional. application/json: object (StartSyncCheckRequest).
| Field | Type | Description |
|---|---|---|
engagementIds | array of string | Only these engagements (Work queue bulk action); default every engagement in scope. up to 500 items |
Responses
202 Accepted, application/json
Body: object (SyncCheckRun).
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
triggerrequired | string (SyncCheckTrigger) | schedule: the nightly or weekly run; manual: Run check now. One of: |
startedAtrequired | string (date-time) | |
finishedAtrequired | string (date-time)nullable | Null while engagements are still being checked. |
windowStartrequired | string (date-time) | Checks start from here; a scheduled run spreads them over windowMinutes. |
windowMinutesrequired | integer | at least 0 |
totalrequired | integer | Engagements in scope. at least 0 |
checkedrequired | integer | Engagements checked so far. at least 0 |
changedrequired | integer | at least 0 |
failedrequired | integer | Expired connections and errors. at least 0 |
durationMinutesrequired | integernullable | From start to finish. at least 0 |
requestedByrequired | string (uuid)nullable |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 409 Conflict, 422 Unprocessable content, 429 Too many requests, as problem details (application/problem+json).
Remind people to reconnect bookkeeping
POST/api/v1/sync/reminders
- Scopes
engagements:write- Retries
- Send an
Idempotency-Keyto make retries safe.
For engagements whose connection has expired, failed or never synced: tells the preparer (else the manager, else the partner) which clients need reconnecting, in the app and by email unless they have turned those emails off. One reminder per person. You are never reminded yourself. Part of the working_papers 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 (SyncRemindersRequest).
| Field | Type | Description |
|---|---|---|
engagementIdsrequired | array of string | Engagements whose bookkeeping connection needs reconnecting or fixing (Sync health’s Reconnect or fix list). 1 to 500 items |
Responses
200 OK, application/json
Body: object (SyncReminders).
Who was reminded.
| Field | Type | Description |
|---|---|---|
notifiedrequired | integer | People sent a reminder (one each, listing their clients). at least 0 |
engagementsrequired | integer | Engagements covered by a reminder. at least 0 |
skippedrequired | integer | Engagements left out: not broken, on manual upload, or with nobody to remind but you. at least 0 |
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 sign-off check run
GET/api/v1/sync/runs/{id}
- Scopes
work:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Run id. |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get an engagement’s sign-off check
GET/api/v1/engagements/{id}/sync-check
- Scopes
engagements:read
The latest result: account-by-account differences from the signed-off trial balance, the transactions behind them and the affected sections, with the engagement’s earlier resolutions. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Responses
200 OK, application/json
Body: object (SyncCheckDetail).
| Field | Type | Description |
|---|---|---|
engagementIdrequired | string (uuid) | |
clientNamerequired | string | |
periodEndrequired | string (date) | example "2026-03-31" |
statusrequired | string (EngagementStatus) | One of: |
filedOnrequired | string (date)nullable | When the accounts were filed; a change after that is a post-filing change. example "2026-03-31" |
softwarerequired | string (Software) | One of: |
syncStaterequired | string (SyncState) | One of: |
latestrequired | object (SyncCheckResult)nullable | The latest result; null if never checked. |
latest.idrequired | string (uuid) | |
latest.statusrequired | string (SyncCheckStatus) | One of: |
latest.checkedAtrequired | string (date-time) | |
latest.signedOffAtrequired | string (date-time)nullable | |
latest.accountCountrequired | integer | Accounts that moved. at least 0 |
latest.grossPencerequired | integer | Integer pence (GBP). Sum of the absolute current year movements. at least 0 |
latest.affectedSectionsrequired | array of string | Affected lead sections (W/P refs). |
latest.resolutionrequired | string (SyncResolution)nullable | One of: |
latest.resolvedAtrequired | string (date-time)nullable | |
latest.outcomerequired | stringnullable | The outcome in words (who resolved it and how). |
latest.messagerequired | stringnullable | Why the check could not run, in words. |
latest.runIdrequired | string (uuid) | |
latest.providerrequired | string (IntegrationProvider)nullable | The OAuth provider read; null for Tolvi Books (see source). One of: |
latest.sourcerequired | string (BooksSource)nullable | Where the books were read: an OAuth provider, or tolvi_books (the client’s Tolvi Books, through the firm’s practice link). One of: |
latest.sourceLabelrequired | stringnullable | example "Tolvi Books" |
latest.tbVersionIdrequired | string (uuid)nullable | The trial balance version compared against. |
latest.tbVersionrequired | integernullable | |
latest.asAtrequired | string (date)nullable | example "2026-03-31" |
latest.priorAsAtrequired | string (date)nullable | example "2026-03-31" |
latest.accountsrequired | array of SyncDiffAccount | |
latest.accounts[].coderequired | string | |
latest.accounts[].namerequired | string | |
latest.accounts[].statusrequired | string (SyncDiffStatus) | One of: |
latest.accounts[].signedOffrequired | object (SyncBalances)nullable | In the trial balance that was signed off. |
latest.accounts[].signedOff.cyPencerequired | integer | Integer pence (GBP). |
latest.accounts[].signedOff.pyPencerequired | integer | Integer pence (GBP). |
latest.accounts[].nowrequired | object (SyncBalances)nullable | In the bookkeeping software now. |
latest.accounts[].now.cyPencerequired | integer | Integer pence (GBP). |
latest.accounts[].now.pyPencerequired | integer | Integer pence (GBP). |
latest.accounts[].cyDiffPencerequired | integer | Integer pence (GBP). |
latest.accounts[].pyDiffPencerequired | integer | Integer pence (GBP). |
latest.accounts[].sectionsrequired | array of string | |
latest.accounts[].transactionsrequired | array of SyncTransaction | Postings behind the movement, when the software lists them. |
latest.accounts[].transactions[].idrequired | string | The provider’s id for the source document or journal. |
latest.accounts[].transactions[].accountCoderequired | string | |
latest.accounts[].transactions[].daterequired | string (date) | example "2026-03-31" |
latest.accounts[].transactions[].descriptionrequired | string | |
latest.accounts[].transactions[].amountPencerequired | integer | Integer pence (GBP). Debit positive. |
latest.accounts[].transactions[].updatedAtrequired | string (date-time)nullable | When it was entered or last changed in the software. |
latest.accounts[].transactions[].sourceTyperequired | stringnullable | |
latest.accounts[].transactions[].postedByrequired | stringnullable | Who entered it, when the software says (Tolvi Books does). example "Jo Owner (the business)" |
latest.accounts[].transactions[].explanationrequired | string | example "Credit note CN-0192 (Kestrel Homes), dated 28 Mar 2026, entered 14 Oct 2026" |
latest.accounts[].unexplainedPencerequired | integer | Integer pence (GBP). The movement the transactions do not account for. |
latest.transactionsAvailablerequired | boolean | The software could list the transactions behind the movements. From Tolvi Books they are exactly the ledger entries posted after the signed-off trial balance’s ledger point, each with who posted it. |
latest.reasonrequired | stringnullable | |
latest.noterequired | stringnullable | |
latest.resolvedByrequired | string (uuid)nullable | |
latest.resolvedTbVersionIdrequired | string (uuid)nullable | Accept and re-sync: the trial balance version created. |
historyrequired | array of SyncResultSummary | Earlier resolved results, newest first (the engagement’s sync history). |
history[].idrequired | string (uuid) | |
history[].statusrequired | string (SyncCheckStatus) | One of: |
history[].checkedAtrequired | string (date-time) | |
history[].signedOffAtrequired | string (date-time)nullable | |
history[].accountCountrequired | integer | Accounts that moved. at least 0 |
history[].grossPencerequired | integer | Integer pence (GBP). Sum of the absolute current year movements. at least 0 |
history[].affectedSectionsrequired | array of string | Affected lead sections (W/P refs). |
history[].resolutionrequired | string (SyncResolution)nullable | One of: |
history[].resolvedAtrequired | string (date-time)nullable | |
history[].outcomerequired | stringnullable | The outcome in words (who resolved it and how). |
history[].messagerequired | stringnullable | Why the check could not run, in words. |
canResolverequired | boolean | Accept and re-sync, Acknowledge or Record as post-filing change can be used now. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Accept and re-sync
POST/api/v1/engagements/{id}/sync-check/accept
- Scopes
engagements:write- Retries
- Send an
Idempotency-Keyto make retries safe.
Pulls the books again into a new immutable trial balance version and reopens the affected sections; their sign-offs go stale through the content fingerprint (a new amendment round). 409 if there is no open change, or if journals would be removed without confirmJournalRemoval. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Idempotency-Key | header | string | A unique key (1-255 visible characters) to make retries safe. Replayed for 24 hours. 1 to 255 characters |
Request body
Optional. application/json: object (AcceptSyncChangeRequest).
| Field | Type | Description |
|---|---|---|
confirmJournalRemoval | boolean | Confirm removing journals on accounts no longer in the books (409 lists them otherwise). |
Responses
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).
Acknowledge a change without re-syncing
POST/api/v1/engagements/{id}/sync-check/acknowledge
- Scopes
review:write
Records why no action is needed (a note is required for “Other”). The same differences are not raised again by later checks. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Request body
Required. application/json: object (AcknowledgeSyncChangeRequest).
| Field | Type | Description |
|---|---|---|
reasonrequired | string | 1 to 200 characters; example "Posted in error by client; they will reverse it" |
note | string | Required when the reason is “Other”. Shown in the sync history. up to 2000 characters |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Record a post-filing change
POST/api/v1/engagements/{id}/sync-check/post-filing
- Scopes
review:write
For a completed engagement: records the change as a post-filing change (tell the client; consider amended accounts or next year’s opening balances). The same differences are not raised again. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Request body
Optional. application/json: object (PostFilingChangeRequest).
| Field | Type | Description |
|---|---|---|
note | string | up to 2000 characters |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).