Engagements
27 operations in the Tolvi API, version 1. Paths are relative to https://api.tolvi.co.uk. Every amount is integer pence.
Start an engagement for a client
POST/api/v1/engagements
- Scopes
engagements:writeandengagements:read- Role
- Preparer or above
- Retries
- Send an
Idempotency-Keyto make retries safe.
Creates the entity-aware section index and checklists (firm template, else built-in, plus the client’s own steps). Billable (engagement.started); AI tokens come from the plan’s monthly allowance, not from each start. 402 when billing blocks starting; 409 when the period overlaps one of the client’s existing engagements.
Needs the Preparer role or above. 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 (CreateEngagement).
Start an engagement for a client. The entity type defaults to the client’s.
| Field | Type | Description |
|---|---|---|
clientIdrequired | string (uuid) | |
entityType | string (EntityType) | One of: |
periodStartrequired | string (date) | example "2026-03-31" |
periodEndrequired | string (date) | example "2026-03-31" |
isFirstPeriod | boolean | First period after incorporation (21-month Companies House deadline). |
workflowTemplateId | string | Workflow template id (a preset id until firm templates exist). Default by entity type. |
preparerId | string (uuid)nullable | Leave out to make the caller the preparer (when they are staff); null for nobody yet. |
reviewerId | string (uuid) | |
managerId | string (uuid) | |
partnerId | string (uuid) | |
risk | string (RiskLevel) | One of: |
bringForward | object (RollForwardOptions) | What to bring forward from the client’s previous engagement when it starts. Each part defaults to true. Set every part false to leave it as a deliberate step in the new file (POST /engagements/{id}/roll-forward). |
bringForward.priorYearFigures | boolean | Last year’s final TB (draft + posted journals) as this year’s comparatives, kept when this year’s TB is imported. |
bringForward.mappings | boolean | Last year’s account mappings (codes not yet mapped here). |
bringForward.fixedAssets | boolean | The fixed asset register: closing cost and depreciation as opening balances, disposed assets left out. |
bringForward.taxBroughtForward | boolean | Main and special rate pool WDVs and trading losses carried forward. |
bringForward.notes | boolean | Last year’s variance notes, as hints on the lead schedules (never as this year’s explanations). |
Responses
201 Created, application/json
Body: object (EngagementOverview).
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
clientIdrequired | string (uuid) | |
clientNamerequired | string | |
entityTyperequired | string (EntityType) | One of: |
periodStartrequired | string (date) | example "2026-03-31" |
periodEndrequired | string (date) | example "2026-03-31" |
isFirstPeriodrequired | boolean | |
dueDaterequired | string (date) | example "2026-03-31" |
riskrequired | string (RiskLevel) | One of: |
teamrequired | object (EngagementTeam) | |
team.preparerIdrequired | string (uuid)nullable | |
team.reviewerIdrequired | string (uuid)nullable | |
team.managerIdrequired | string (uuid)nullable | |
team.partnerIdrequired | string (uuid)nullable | |
thresholdsrequired | object (Thresholds) | Movements at or above both thresholds need an explanation. |
thresholds.absolutePencerequired | integer | Integer pence (GBP). |
thresholds.percentrequired | integer | at least 0 |
workflowrequired | object (EngagementWorkflow) | |
workflow.templateIdrequired | string | |
workflow.templateNamerequired | string | |
workflow.statusrequired | string (EngagementStatus) | One of: |
workflow.workflowStatusrequired | string (WorkflowStatus) | One of: |
workflow.currentStageIdrequired | string | |
workflow.currentStageNamerequired | string | |
workflow.currentStageIndexrequired | integer | at least 0 |
workflow.stagesrequired | array of WorkflowStage | |
workflow.stages[].idrequired | string | |
workflow.stages[].namerequired | string | |
workflow.stages[].staterequired | string (TimelineStepState) | One of: |
workflow.stages[].signedByrequired | string (uuid)nullable | |
workflow.stages[].signedAtrequired | string (date-time)nullable | |
workflow.stages[].assigneeIdrequired | string (uuid)nullable | Who the engagement team assigns to this stage (preparer, reviewer, manager or partner), or null when nobody is. |
workflow.stages[].sentBackByrequired | string (uuid)nullable | While the workflow is returned: who sent it back from this stage. |
workflow.stages[].sentBackAtrequired | string (date-time)nullable | While the workflow is returned: when it was sent back from this stage. |
workflow.roundrequired | integer | Amendment round (each return or reopen starts a new one). at least 1 |
workflow.returnCountrequired | integer | at least 0 |
workflow.reopenCountrequired | integer | at least 0 |
workflow.changedAfterApprovalrequired | boolean | |
workflow.returnTargetsrequired | array of string | Stages a return can go back to. |
workflow.actionsrequired | object (EngagementActions) | The engagement-level actions, checked for the caller. |
workflow.actions.submitrequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
workflow.actions.submit.allowedrequired | boolean | |
workflow.actions.submit.reasonsrequired | array of string | |
workflow.actions.submit.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
workflow.actions.approverequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
workflow.actions.approve.allowedrequired | boolean | |
workflow.actions.approve.reasonsrequired | array of string | |
workflow.actions.approve.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
workflow.actions.returnrequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
workflow.actions.return.allowedrequired | boolean | |
workflow.actions.return.reasonsrequired | array of string | |
workflow.actions.return.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
workflow.actions.reopenrequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
workflow.actions.reopen.allowedrequired | boolean | |
workflow.actions.reopen.reasonsrequired | array of string | |
workflow.actions.reopen.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
trialBalancerequired | object (TbVersion)nullable | |
trialBalance.idrequired | string (uuid) | |
trialBalance.versionrequired | integer | at least 1 |
trialBalance.sourceKindrequired | string (TbSourceKind) | One of: |
trialBalance.sourceLabelrequired | stringnullable | |
trialBalance.providerrequired | stringnullable | |
trialBalance.asAtrequired | string (date)nullable | example "2026-03-31" |
trialBalance.booksThroughSeqrequired | integernullable | Pulled from Tolvi Books: the ledger point it was read at (the last entry included), so the same trial balance can be recomputed and later entries found exactly. Null for any other source. |
trialBalance.hashrequired | string | Content hash (sha256): the same balances give the same hash. |
trialBalance.accountCountrequired | integer | at least 0 |
trialBalance.cyTotalPencerequired | integer | Integer pence (GBP). Net of current year balances (nil when balanced). |
trialBalance.pyTotalPencerequired | integer | Integer pence (GBP). |
trialBalance.balancedrequired | boolean | |
trialBalance.removedJournalRefsrequired | array of string | |
trialBalance.createdByrequired | string (uuid)nullable | |
trialBalance.createdAtrequired | string (date-time) | |
sectionsrequired | array of SectionSummary | |
sections[].coderequired | string | example "C" |
sections[].namerequired | string | |
sections[].kindrequired | string (SectionKind) | One of: |
sections[].statusrequired | string (SectionStatus) | One of: |
sections[].issueCountrequired | integer | at least 0 |
sections[].openReviewPointsrequired | integer | at least 0 |
sections[].signOffsrequired | array of SignOff | |
sections[].signOffs[].stageIdrequired | string | |
sections[].signOffs[].stageNamerequired | string | |
sections[].signOffs[].signAsrequired | string (SignOffRole) | Who signs at this stage, for labels such as "Sign as reviewer". One of: |
sections[].signOffs[].userIdrequired | string (uuid)nullable | |
sections[].signOffs[].atrequired | string (date-time)nullable | |
sections[].signOffs[].currentrequired | boolean | Signed against the content as it is now. |
sections[].currentStageIdrequired | stringnullable | Stage the section is at. |
openReviewPointsrequired | integer | at least 0 |
unmappedAccountsrequired | integer | Trial balance accounts not mapped to a lead schedule line (nil accounts are not counted). Figures from the final TB are incomplete while this is above 0. at least 0 |
journalOnlyAccounts | integer | Accounts Tolvi added for journals (journal-only: the tax and disposal accounts). B lists them with the TB’s own accounts, so B shows at least 0 |
finalTbAccounts | integer | Every account on the final TB (B): the TB version’s accounts, Tolvi’s journal-only accounts and accounts only in last year’s TB. at least 0 |
nextSteprequired | object (NextStep) | The first outstanding item and the one action that deals with it. |
nextStep.messagerequired | string | |
nextStep.actionrequired | string (NextStepAction) | One of: |
nextStep.labelrequired | stringnullable | Button label, e.g. "Post tax journal". |
nextStep.targetrequired | stringnullable | Section code, journal id, account code or review point id the action is about. |
nextStep.donerequired | boolean | Nothing left for anyone to do here. |
nextStep.morerequired | integer | Further outstanding items after this one. 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).
Cancel an engagement
POST/api/v1/engagements/{id}/cancel
- Scopes
engagements:writeandengagements:read- Role
- Manager or above
Only while nothing is signed off. Within 14 days of starting it is credited (not billed). The file stays readable; it leaves the Work queue.
Needs the Manager role or above. 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 (CancelResult).
| Field | Type | Description |
|---|---|---|
cancelledrequired | boolean | |
creditedrequired | boolean | Cancelled within 14 days with no sign-offs: a billing credit was written. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get an engagement overview
GET/api/v1/engagements/{id}
- Scopes
engagements:read
Sections and statuses, workflow (with the actions checked for you), trial balance and your next step. 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
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
List the engagement’s sections and their statuses
GET/api/v1/engagements/{id}/sections
- Scopes
engagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Responses
200 OK, application/json
Body: array of SectionSummary.
| Field | Type | Description |
|---|---|---|
[].coderequired | string | example "C" |
[].namerequired | string | |
[].kindrequired | string (SectionKind) | One of: |
[].statusrequired | string (SectionStatus) | One of: |
[].issueCountrequired | integer | at least 0 |
[].openReviewPointsrequired | integer | at least 0 |
[].signOffsrequired | array of SignOff | |
[].signOffs[].stageIdrequired | string | |
[].signOffs[].stageNamerequired | string | |
[].signOffs[].signAsrequired | string (SignOffRole) | Who signs at this stage, for labels such as "Sign as reviewer". One of: |
[].signOffs[].userIdrequired | string (uuid)nullable | |
[].signOffs[].atrequired | string (date-time)nullable | |
[].signOffs[].currentrequired | boolean | Signed against the content as it is now. |
[].currentStageIdrequired | stringnullable | Stage the section is at. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get a section: checklist, issues, sign-off, review points, evidence and next step
GET/api/v1/engagements/{id}/sections/{code}
- Scopes
engagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
coderequired | path | string | Section code (W/P ref), e.g. C. One of: |
Responses
200 OK, application/json
Body: object (SectionDetail).
| Field | Type | Description |
|---|---|---|
coderequired | string | example "C" |
namerequired | string | |
kindrequired | string (SectionKind) | One of: |
statusrequired | string (SectionStatus) | One of: |
issueCountrequired | integer | at least 0 |
openReviewPointsrequired | integer | at least 0 |
signOffsrequired | array of SignOff | |
signOffs[].stageIdrequired | string | |
signOffs[].stageNamerequired | string | |
signOffs[].signAsrequired | string (SignOffRole) | Who signs at this stage, for labels such as "Sign as reviewer". One of: |
signOffs[].userIdrequired | string (uuid)nullable | |
signOffs[].atrequired | string (date-time)nullable | |
signOffs[].currentrequired | boolean | Signed against the content as it is now. |
currentStageIdrequired | stringnullable | Stage the section is at. |
checklistrequired | array of ChecklistItem | |
checklist[].idrequired | string (uuid) | |
checklist[].positionrequired | integer | |
checklist[].textrequired | string | |
checklist[].originrequired | string (ChecklistOrigin) | One of: |
checklist[].statusrequired | string (ChecklistStatus) | "" is not yet done. One of: |
checklist[].autorequired | boolean | Ticked by Tolvi from the data (a person’s own tick always wins). |
checklist[].noterequired | string | |
checklist[].doneByrequired | string (uuid)nullable | |
checklist[].doneAtrequired | string (date-time)nullable | |
checklist[].evidencerequired | array of Evidence | |
checklist[].evidence[].idrequired | string (uuid) | |
checklist[].evidence[].sectionCoderequired | stringnullable | |
checklist[].evidence[].checklistItemIdrequired | string (uuid)nullable | |
checklist[].evidence[].reviewPointIdrequired | string (uuid)nullable | |
checklist[].evidence[].reviewPointMessageIdrequired | string (uuid)nullable | |
checklist[].evidence[].fileNamerequired | string | |
checklist[].evidence[].contentTyperequired | string | |
checklist[].evidence[].sizeBytesrequired | integer | at least 0 |
checklist[].evidence[].statusrequired | string (EvidenceStatus) | pending_upload until POST …/evidence/{evidenceId}/confirm checks the stored file; then pending_scan (malware scan) and available. Only available files can be downloaded or read by Tolvi AI. rejected: the file failed the checks or the scan found malware (see rejectedReason); it can never be downloaded. One of: |
checklist[].evidence[].rejectedReasonrequired | stringnullable | Why a rejected file was rejected, e.g. "malware detected"; null otherwise. |
checklist[].evidence[].uploadedByrequired | string (uuid)nullable | |
checklist[].evidence[].createdAtrequired | string (date-time) | |
issuesrequired | array of Issue | |
issues[].messagerequired | string | |
issues[].actionrequired | string (IssueAction) | One of: |
issues[].labelrequired | string | |
issues[].targetrequired | stringnullable | |
signOffrequired | object (SectionSignOffPanel) | |
signOff.stagesrequired | array of SignOff | Every stage that applies to sections, with its live sign-off. |
signOff.stages[].stageIdrequired | string | |
signOff.stages[].stageNamerequired | string | |
signOff.stages[].signAsrequired | string (SignOffRole) | Who signs at this stage, for labels such as "Sign as reviewer". One of: |
signOff.stages[].userIdrequired | string (uuid)nullable | |
signOff.stages[].atrequired | string (date-time)nullable | |
signOff.stages[].currentrequired | boolean | Signed against the content as it is now. |
signOff.currentStageIdrequired | string | |
signOff.signrequired | object (GuardResult) | Can the caller sign at the current stage now? |
signOff.sign.allowedrequired | boolean | |
signOff.sign.reasonsrequired | array of string | |
signOff.sign.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
signOff.ownSignOffStageIdrequired | stringnullable | Stage of the caller’s own live sign-off, which they can remove. |
signOff.signStageId | stringnullable | The stage signing now would record the caller’s sign-off at. Usually |
signOff.unsign | object (GuardResult) | Can the caller remove their own sign-off now? Refused while the file is signed off at every stage. |
signOff.unsign.allowedrequired | boolean | |
signOff.unsign.reasonsrequired | array of string | |
signOff.unsign.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
signOff.unsignRemoves | array of SignOff | Removing the caller’s own sign-off removes these later sign-offs too (other people’s). |
signOff.unsignRemoves[].stageIdrequired | string | |
signOff.unsignRemoves[].stageNamerequired | string | |
signOff.unsignRemoves[].signAsrequired | string (SignOffRole) | Who signs at this stage, for labels such as "Sign as reviewer". One of: |
signOff.unsignRemoves[].userIdrequired | string (uuid)nullable | |
signOff.unsignRemoves[].atrequired | string (date-time)nullable | |
signOff.unsignRemoves[].currentrequired | boolean | Signed against the content as it is now. |
signOff.fingerprintrequired | string | Content fingerprint now. |
nextSteprequired | object (NextStep) | The first outstanding item and the one action that deals with it. |
nextStep.messagerequired | string | |
nextStep.actionrequired | string (NextStepAction) | One of: |
nextStep.labelrequired | stringnullable | Button label, e.g. "Post tax journal". |
nextStep.targetrequired | stringnullable | Section code, journal id, account code or review point id the action is about. |
nextStep.donerequired | boolean | Nothing left for anyone to do here. |
nextStep.morerequired | integer | Further outstanding items after this one. at least 0 |
reviewPointsrequired | array of ReviewPoint | |
reviewPoints[].idrequired | string (uuid) | |
reviewPoints[].sectionCoderequired | stringnullable | Null for a point on the engagement as a whole. |
reviewPoints[].categoryrequired | string (ReviewPointCategory) | One of: |
reviewPoints[].statusrequired | string (ReviewPointStatus) | One of: |
reviewPoints[].raisedAtStageIdrequired | string | |
reviewPoints[].raisedInRoundrequired | integer | at least 1 |
reviewPoints[].raisedByrequired | string (uuid)nullable | |
reviewPoints[].raisedAtrequired | string (date-time) | |
reviewPoints[].clearedByrequired | string (uuid)nullable | |
reviewPoints[].clearedAtrequired | string (date-time)nullable | |
reviewPoints[].sourceMeetingId | string (uuid)nullable | The meeting whose next step this point came from (Create follow-ups in Tolvi Meetings), if any. |
reviewPoints[].messagesrequired | array of ReviewPointMessage | |
reviewPoints[].messages[].idrequired | string (uuid) | |
reviewPoints[].messages[].kindrequired | string (ReviewPointMessageKind) | One of: |
reviewPoints[].messages[].authorIdrequired | string (uuid)nullable | |
reviewPoints[].messages[].bodyrequired | string | |
reviewPoints[].messages[].mentionsrequired | array of string (uuid) | |
reviewPoints[].messages[].atrequired | string (date-time) | |
reviewPoints[].messages[].evidencerequired | array of Evidence | |
reviewPoints[].messages[].evidence[].idrequired | string (uuid) | |
reviewPoints[].messages[].evidence[].sectionCoderequired | stringnullable | |
reviewPoints[].messages[].evidence[].checklistItemIdrequired | string (uuid)nullable | |
reviewPoints[].messages[].evidence[].reviewPointIdrequired | string (uuid)nullable | |
reviewPoints[].messages[].evidence[].reviewPointMessageIdrequired | string (uuid)nullable | |
reviewPoints[].messages[].evidence[].fileNamerequired | string | |
reviewPoints[].messages[].evidence[].contentTyperequired | string | |
reviewPoints[].messages[].evidence[].sizeBytesrequired | integer | at least 0 |
reviewPoints[].messages[].evidence[].statusrequired | string (EvidenceStatus) | pending_upload until POST …/evidence/{evidenceId}/confirm checks the stored file; then pending_scan (malware scan) and available. Only available files can be downloaded or read by Tolvi AI. rejected: the file failed the checks or the scan found malware (see rejectedReason); it can never be downloaded. One of: |
reviewPoints[].messages[].evidence[].rejectedReasonrequired | stringnullable | Why a rejected file was rejected, e.g. "malware detected"; null otherwise. |
reviewPoints[].messages[].evidence[].uploadedByrequired | string (uuid)nullable | |
reviewPoints[].messages[].evidence[].createdAtrequired | string (date-time) | |
evidencerequired | array of Evidence | |
evidence[].idrequired | string (uuid) | |
evidence[].sectionCoderequired | stringnullable | |
evidence[].checklistItemIdrequired | string (uuid)nullable | |
evidence[].reviewPointIdrequired | string (uuid)nullable | |
evidence[].reviewPointMessageIdrequired | string (uuid)nullable | |
evidence[].fileNamerequired | string | |
evidence[].contentTyperequired | string | |
evidence[].sizeBytesrequired | integer | at least 0 |
evidence[].statusrequired | string (EvidenceStatus) | pending_upload until POST …/evidence/{evidenceId}/confirm checks the stored file; then pending_scan (malware scan) and available. Only available files can be downloaded or read by Tolvi AI. rejected: the file failed the checks or the scan found malware (see rejectedReason); it can never be downloaded. One of: |
evidence[].rejectedReasonrequired | stringnullable | Why a rejected file was rejected, e.g. "malware detected"; null otherwise. |
evidence[].uploadedByrequired | string (uuid)nullable | |
evidence[].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).
Get a lead schedule
GET/api/v1/engagements/{id}/sections/{code}/lead-schedule
- Scopes
engagements:read
Accounts by line with prior year, draft, journals and final balances, movements, variance notes and tickmarks. 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. |
coderequired | path | string | Section code (W/P ref), e.g. C. One of: |
Responses
200 OK, application/json
Body: object (LeadSchedule).
| Field | Type | Description |
|---|---|---|
sectionCoderequired | string | |
sectionNamerequired | string | |
thresholdsrequired | object (Thresholds) | Movements at or above both thresholds need an explanation. |
thresholds.absolutePencerequired | integer | Integer pence (GBP). |
thresholds.percentrequired | integer | at least 0 |
linesrequired | array of LeadLine | |
lines[].lineIdrequired | string (LeadLineId) | One of: |
lines[].labelrequired | string | |
lines[].statementrequired | string (Statement) | One of: |
lines[].accountsrequired | array of LeadAccount | |
lines[].accounts[].coderequired | string | |
lines[].accounts[].namerequired | string | |
lines[].accounts[].pyPencerequired | integer | Integer pence (GBP). |
lines[].accounts[].draftPencerequired | integer | Integer pence (GBP). Per the imported (draft) TB. |
lines[].accounts[].adjustmentsPencerequired | integer | Integer pence (GBP). Posted journals. |
lines[].accounts[].draftJournalsPencerequired | integer | Integer pence (GBP). Draft journals, not in the final figure. |
lines[].accounts[].finalPencerequired | integer | Integer pence (GBP). |
lines[].accounts[].movementPencerequired | integer | Integer pence (GBP). |
lines[].accounts[].movementPctrequired | numbernullable | Movement as a percentage of last year, 1 dp; null when last year was nil. |
lines[].accounts[].needsExplanationrequired | boolean | |
lines[].accounts[].noterequired | object (VarianceNote)nullable | |
lines[].accounts[].note.bodyrequired | string | |
lines[].accounts[].note.authorIdrequired | string (uuid)nullable | |
lines[].accounts[].note.updatedAtrequired | string (date-time) | |
lines[].accounts[].note.editablerequired | boolean | The caller wrote it, so can change it. |
lines[].accounts[].lastYearNoterequired | stringnullable | Last year’s explanation, brought forward as a hint: it never counts as this year’s. |
lines[].accounts[].tickmarkrequired | object (TickmarkEntry)nullable | |
lines[].accounts[].tickmark.markrequired | string (Tickmark) | One of: |
lines[].accounts[].tickmark.labelrequired | string | |
lines[].accounts[].tickmark.authorIdrequired | string (uuid)nullable | |
lines[].accounts[].tickmark.updatedAtrequired | string (date-time) | |
lines[].accounts[].tickmark.editablerequired | boolean | |
lines[].totalsrequired | object (LeadTotals) | |
lines[].totals.pyPencerequired | integer | Integer pence (GBP). |
lines[].totals.draftPencerequired | integer | Integer pence (GBP). |
lines[].totals.adjustmentsPencerequired | integer | Integer pence (GBP). |
lines[].totals.finalPencerequired | integer | Integer pence (GBP). |
totalsrequired | object (LeadTotals) | |
totals.pyPencerequired | integer | Integer pence (GBP). |
totals.draftPencerequired | integer | Integer pence (GBP). |
totals.adjustmentsPencerequired | integer | Integer pence (GBP). |
totals.finalPencerequired | integer | Integer pence (GBP). |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
List a section’s reconciliations (F bank, H VAT, I payroll)
GET/api/v1/engagements/{id}/sections/{code}/reconciliations
- Scopes
engagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
coderequired | path | string | Section code (W/P ref), e.g. C. One of: |
Responses
200 OK, application/json
Body: array of Reconciliation.
| Field | Type | Description |
|---|---|---|
[].coderequired | string | |
[].namerequired | string | |
[].sectionCoderequired | string | |
[].kindrequired | string (ReconciliationKind) | What the account is reconciled to: bank statement, VAT return, P32, pension schedule, CIS300, DLA or current account schedule, lender statement. One of: |
[].titlerequired | string | |
[].sourceLabelrequired | string | |
[].sourcePencerequired | integernullable | Integer pence (GBP). Per the source document, in its own sign (e.g. VAT payable is positive). |
[].itemsrequired | array of RecItem | |
[].items[].descriptionrequired | string | up to 200 characters |
[].items[].amountPencerequired | integer | Integer pence (GBP). |
[].noterequired | string | |
[].tbPencerequired | integer | Integer pence (GBP). Final TB balance in the source document’s sign. |
[].expectedPencerequired | integernullable | Integer pence (GBP). |
[].differencePencerequired | integernullable | Integer pence (GBP). |
[].agreesrequired | boolean | |
[].okrequired | boolean | Agrees, or the difference is explained in the note. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get your next step on the engagement and each section
GET/api/v1/engagements/{id}/next-steps
- Scopes
engagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Responses
200 OK, application/json
Body: object (NextSteps).
The engagement’s next step and each section’s, for the caller.
| Field | Type | Description |
|---|---|---|
engagementrequired | object (NextStep) | The first outstanding item and the one action that deals with it. |
engagement.messagerequired | string | |
engagement.actionrequired | string (NextStepAction) | One of: |
engagement.labelrequired | stringnullable | Button label, e.g. "Post tax journal". |
engagement.targetrequired | stringnullable | Section code, journal id, account code or review point id the action is about. |
engagement.donerequired | boolean | Nothing left for anyone to do here. |
engagement.morerequired | integer | Further outstanding items after this one. at least 0 |
sectionsrequired | array of SectionNextStep | |
sections[].coderequired | string | |
sections[].namerequired | string | |
sections[].statusrequired | string (SectionStatus) | One of: |
sections[].nextSteprequired | object (NextStep) | The first outstanding item and the one action that deals with it. |
sections[].nextStep.messagerequired | string | |
sections[].nextStep.actionrequired | string (NextStepAction) | One of: |
sections[].nextStep.labelrequired | stringnullable | Button label, e.g. "Post tax journal". |
sections[].nextStep.targetrequired | stringnullable | Section code, journal id, account code or review point id the action is about. |
sections[].nextStep.donerequired | boolean | Nothing left for anyone to do here. |
sections[].nextStep.morerequired | integer | Further outstanding items after this one. at least 0 |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Write or change a variance note
PUT/api/v1/engagements/{id}/accounts/{code}/note
- Scopes
engagements:writeandengagements:read
Only the note’s author can change it. 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. |
coderequired | path | string | Account code. |
Request body
Required. application/json: object (SetVarianceNote).
| Field | Type | Description |
|---|---|---|
bodyrequired | string | 1 to 4000 characters |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Remove your variance note
DELETE/api/v1/engagements/{id}/accounts/{code}/note
- Scopes
engagements:writeandengagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
coderequired | path | string | Account code. |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Set a tickmark
PUT/api/v1/engagements/{id}/accounts/{code}/tickmark
- Scopes
engagements:writeandengagements:read
Only the tickmark’s author can change it. 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. |
coderequired | path | string | Account code. |
Request body
Required. application/json: object (SetTickmark).
| Field | Type | Description |
|---|---|---|
markrequired | string (Tickmark) | One of: |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Remove your tickmark
DELETE/api/v1/engagements/{id}/accounts/{code}/tickmark
- Scopes
engagements:writeandengagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
coderequired | path | string | Account code. |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Set a reconciliation: source balance, reconciling items and note
PUT/api/v1/engagements/{id}/reconciliations/{code}
- Scopes
engagements:writeandengagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
coderequired | path | string | Account code of a bank, VAT or payroll control account. |
Request body
Required. application/json: object (SetReconciliation).
| Field | Type | Description |
|---|---|---|
sourcePencerequired | integernullable | Integer pence (GBP). |
itemsrequired | array of RecItem | up to 200 items |
items[].descriptionrequired | string | up to 200 characters |
items[].amountPencerequired | integer | Integer pence (GBP). |
note | string | up to 4000 characters |
Responses
200 OK, application/json
Body: object (Reconciliation).
| Field | Type | Description |
|---|---|---|
coderequired | string | |
namerequired | string | |
sectionCoderequired | string | |
kindrequired | string (ReconciliationKind) | What the account is reconciled to: bank statement, VAT return, P32, pension schedule, CIS300, DLA or current account schedule, lender statement. One of: |
titlerequired | string | |
sourceLabelrequired | string | |
sourcePencerequired | integernullable | Integer pence (GBP). Per the source document, in its own sign (e.g. VAT payable is positive). |
itemsrequired | array of RecItem | |
items[].descriptionrequired | string | up to 200 characters |
items[].amountPencerequired | integer | Integer pence (GBP). |
noterequired | string | |
tbPencerequired | integer | Integer pence (GBP). Final TB balance in the source document’s sign. |
expectedPencerequired | integernullable | Integer pence (GBP). |
differencePencerequired | integernullable | Integer pence (GBP). |
agreesrequired | boolean | |
okrequired | boolean | Agrees, or the difference is explained in the note. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Mark a checklist step done or N/A, or add a note
PATCH/api/v1/engagements/{id}/checklist/{itemId}
- Scopes
engagements:writeandengagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
itemIdrequired | path | string (uuid) |
Request body
Required. application/json: object (UpdateChecklistItem).
| Field | Type | Description |
|---|---|---|
status | string (ChecklistStatus) | "" is not yet done. One of: |
note | string | up to 4000 characters |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Attach evidence
POST/api/v1/engagements/{id}/evidence
- Scopes
engagements:writeandengagements:read
Records the file and returns a short-lived presigned URL to PUT it to storage. 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 (CreateEvidence).
Give a section (optionally a checklist step) or a review point. Accepted files: PDF (.pdf); images (.png, .jpg/.jpeg, .heic/.heif, .webp); Excel (.xlsx, .xls); Word (.docx, .doc); .csv; .txt; email (.eml, .msg). The extension decides the type: contentType must agree with it (or be application/octet-stream when unknown), and the upload headers returned carry the type to PUT with. The stored bytes are checked against the type when the upload is confirmed.
| Field | Type | Description |
|---|---|---|
sectionCode | string | |
checklistItemId | string (uuid) | |
reviewPointId | string (uuid) | |
fileNamerequired | string | 1 to 200 characters |
contentTyperequired | string | up to 100 characters; example "application/pdf" |
sizeBytesrequired | integer | 1 to 104857600 |
Responses
201 Created, application/json
Body: object (EvidenceUpload).
| Field | Type | Description |
|---|---|---|
evidencerequired | object (Evidence) | |
evidence.idrequired | string (uuid) | |
evidence.sectionCoderequired | stringnullable | |
evidence.checklistItemIdrequired | string (uuid)nullable | |
evidence.reviewPointIdrequired | string (uuid)nullable | |
evidence.reviewPointMessageIdrequired | string (uuid)nullable | |
evidence.fileNamerequired | string | |
evidence.contentTyperequired | string | |
evidence.sizeBytesrequired | integer | at least 0 |
evidence.statusrequired | string (EvidenceStatus) | pending_upload until POST …/evidence/{evidenceId}/confirm checks the stored file; then pending_scan (malware scan) and available. Only available files can be downloaded or read by Tolvi AI. rejected: the file failed the checks or the scan found malware (see rejectedReason); it can never be downloaded. One of: |
evidence.rejectedReasonrequired | stringnullable | Why a rejected file was rejected, e.g. "malware detected"; null otherwise. |
evidence.uploadedByrequired | string (uuid)nullable | |
evidence.createdAtrequired | string (date-time) | |
uploadrequired | object (PresignedUpload) | |
upload.urlrequired | string (uri) | |
upload.methodrequired | string (PresignedMethod) | One of: |
upload.headersrequired | object | Headers to send with the request. |
upload.expiresAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Confirm an evidence upload
POST/api/v1/engagements/{id}/evidence/{evidenceId}/confirm
- Scopes
engagements:writeandengagements:read
Call after the presigned PUT. Checks the stored file’s size, type and contents against what was declared, records its checksum (ETag) and starts the malware scan. Only confirmed, scanned files can be downloaded or read by Tolvi AI. 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. |
evidenceIdrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (Evidence).
| Field | Type | Description |
|---|---|---|
idrequired | string (uuid) | |
sectionCoderequired | stringnullable | |
checklistItemIdrequired | string (uuid)nullable | |
reviewPointIdrequired | string (uuid)nullable | |
reviewPointMessageIdrequired | string (uuid)nullable | |
fileNamerequired | string | |
contentTyperequired | string | |
sizeBytesrequired | integer | at least 0 |
statusrequired | string (EvidenceStatus) | pending_upload until POST …/evidence/{evidenceId}/confirm checks the stored file; then pending_scan (malware scan) and available. Only available files can be downloaded or read by Tolvi AI. rejected: the file failed the checks or the scan found malware (see rejectedReason); it can never be downloaded. One of: |
rejectedReasonrequired | stringnullable | Why a rejected file was rejected, e.g. "malware detected"; null otherwise. |
uploadedByrequired | string (uuid)nullable | |
createdAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get an evidence file’s status
GET/api/v1/engagements/{id}/evidence/{evidenceId}
- Scopes
engagements:read
For following an upload through the malware scan: pending_upload, pending_scan, available, or rejected with the reason. 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. |
evidenceIdrequired | 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).
Remove evidence you attached
DELETE/api/v1/engagements/{id}/evidence/{evidenceId}
- Scopes
engagements:writeandengagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
evidenceIdrequired | 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).
Presign an evidence upload again
POST/api/v1/engagements/{id}/evidence/{evidenceId}/upload
- Scopes
engagements:writeandengagements:read
A fresh presigned PUT for a file that is still pending_upload (the first PUT failed or its link expired), for the same size and type. Only the person who attached it; 409 once the file has been uploaded. 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. |
evidenceIdrequired | 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).
Get a short-lived download URL for evidence
GET/api/v1/engagements/{id}/evidence/{evidenceId}/download
- Scopes
engagements:read
Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
evidenceIdrequired | path | string (uuid) |
Responses
200 OK, application/json
Body: object (PresignedUpload).
| Field | Type | Description |
|---|---|---|
urlrequired | string (uri) | |
methodrequired | string (PresignedMethod) | One of: |
headersrequired | object | Headers to send with the request. |
expiresAtrequired | string (date-time) |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get what was brought forward from the prior year
GET/api/v1/engagements/{id}/roll-forward
- Scopes
engagements:read
The year-end roll forward: the source engagement, what was brought forward (final TB as comparatives, mappings, fixed asset register, pools and losses, variance notes as hints), whether the prior year changed since (content fingerprint) and whether it can be rolled 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. |
Responses
200 OK, application/json
Body: object (RollForward).
The year-end roll forward: what was brought forward from the prior year, and whether the prior year changed since.
| Field | Type | Description |
|---|---|---|
rolledForwardrequired | boolean | |
sourcerequired | object (RollForwardSource)nullable | The engagement brought forward from. |
source.engagementIdrequired | string (uuid)nullable | Null once that engagement was deleted. |
source.periodStartrequired | string (date) | example "2026-03-31" |
source.periodEndrequired | string (date) | example "2026-03-31" |
source.statusrequired | string (EngagementStatus)nullable | One of: |
rolledAtrequired | string (date-time)nullable | |
rolledByrequired | string (uuid)nullable | |
optionsrequired | object (RollForwardParts) | What was brought forward. |
options.priorYearFiguresrequired | boolean | |
options.mappingsrequired | boolean | |
options.fixedAssetsrequired | boolean | |
options.taxBroughtForwardrequired | boolean | |
options.notesrequired | boolean | |
summaryrequired | object (RollForwardSummary) | What was brought forward; null for a part that was not. |
summary.priorYearFiguresrequired | object (RolledPriorYearFigures)nullable | |
summary.priorYearFigures.accountsrequired | integer | at least 0 |
summary.priorYearFigures.profitPencerequired | integer | Integer pence (GBP). Last year’s profit after tax per its final TB (a loss negative). |
summary.mappingsrequired | integernullable | Mappings added. |
summary.notesrequired | integernullable | Variance notes brought forward as hints. |
summary.fixedAssetsrequired | object (RolledFixedAssets)nullable | |
summary.fixedAssets.classesrequired | integer | at least 0 |
summary.fixedAssets.assetsrequired | integer | at least 0 |
summary.fixedAssets.costPencerequired | integer | Integer pence (GBP). |
summary.fixedAssets.depreciationPencerequired | integer | Integer pence (GBP). |
summary.fixedAssets.nbvPencerequired | integer | Integer pence (GBP). |
summary.taxrequired | object (RolledTax)nullable | |
summary.tax.mainPoolPencerequired | integer | Integer pence (GBP). |
summary.tax.specialRatePoolPencerequired | integer | Integer pence (GBP). |
summary.tax.lossesPre2017Pencerequired | integer | Integer pence (GBP). |
summary.tax.lossesPost2017Pencerequired | integer | Integer pence (GBP). |
summary.tax.deferredTaxPencerequired | integernullable | Integer pence (GBP). Last year’s deferred tax provision per its computation (liability positive); null when it did not apply. |
summary.tax.selfAssessmentrequired | object (RolledSelfAssessment)nullable | Null for a company. |
summary.tax.selfAssessment.lossesPencerequired | integer | Integer pence (GBP). Trading losses carried forward (ITA 2007 s83). |
summary.tax.selfAssessment.overlapReliefPencerequired | integer | Integer pence (GBP). Overlap relief not yet used (nil after 2023/24, FA 2022 Sch 1). |
summary.tax.selfAssessment.transitionProfitPencerequired | integer | Integer pence (GBP). Transition profit still being spread. |
summary.tax.selfAssessment.partnersrequired | integer | at least 0 |
summary.warningsrequired | array of string | |
priorChangedrequired | boolean | The prior year changed after it was brought forward (its content fingerprint differs): re-roll to take the change. |
changedPartsrequired | array of RollForwardPart | The parts that changed. One of: |
changesrequired | array of string | |
availablerequired | object (RollForwardSource)nullable | The engagement a (re-)roll would bring forward from now. |
available.engagementIdrequired | string (uuid)nullable | Null once that engagement was deleted. |
available.periodStartrequired | string (date) | example "2026-03-31" |
available.periodEndrequired | string (date) | example "2026-03-31" |
available.statusrequired | string (EngagementStatus)nullable | One of: |
rerollrequired | object (GuardResult) | Re-roll from prior year: only while the file is Not started or Preparing. |
reroll.allowedrequired | boolean | |
reroll.reasonsrequired | array of string | |
reroll.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Re-roll from prior year
POST/api/v1/engagements/{id}/roll-forward
- Scopes
engagements:writeandengagements:read- Role
- Preparer or above
- Retries
- Send an
Idempotency-Keyto make retries safe.
Brings forward again from the client’s previous engagement, replacing what an earlier roll forward brought (this year’s additions and disposals are kept). Only while the file is Not started or Preparing (409 otherwise); 422 when there is no earlier engagement. Never copies sign-offs, review points or evidence; sign-offs on changed sections go stale.
Needs the Preparer role or above. 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 (RollForwardOptions).
What to bring forward from the prior year. Each defaults to true.
| Field | Type | Description |
|---|---|---|
priorYearFigures | boolean | Last year’s final TB (draft + posted journals) as this year’s comparatives, kept when this year’s TB is imported. |
mappings | boolean | Last year’s account mappings (codes not yet mapped here). |
fixedAssets | boolean | The fixed asset register: closing cost and depreciation as opening balances, disposed assets left out. |
taxBroughtForward | boolean | Main and special rate pool WDVs and trading losses carried forward. |
notes | boolean | Last year’s variance notes, as hints on the lead schedules (never as this year’s explanations). |
Responses
200 OK, application/json
Body: object (RollForward).
The year-end roll forward: what was brought forward from the prior year, and whether the prior year changed since.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 409 Conflict, 422 Unprocessable content, 429 Too many requests, as problem details (application/problem+json).
Get where the trial balance would be pulled from
GET/api/v1/engagements/{id}/tb/pull
- Scopes
engagements:read
The source a pull for this engagement reads, without reading it: an engagement keeps the source it is on (its Work queue software); a new one reads the client’s Tolvi Books when linked, else its OAuth connection. Switch an engagement’s source on the client’s Connection tab (PUT /clients/{clientId}/books-link/engagements). 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 (TbPullSource).
Which source a pull for this engagement reads: the client’s only source, or, when it has both an OAuth connection and a Tolvi Books link, the one a person chose. Until someone chooses, needsChoice is true and choices lists both: Tolvi asks rather than guessing.
| Field | Type | Description |
|---|---|---|
engagementIdrequired | string (uuid) | |
sourcerequired | string (BooksSource)nullable | Null when the client has no connection and no Tolvi Books link. One of: |
labelrequired | stringnullable | example "Tolvi Books" |
organisationNamerequired | stringnullable | |
availablerequired | boolean | A pull can be made now. |
reasonrequired | stringnullable | Why not, in words, when it can’t. |
needsChoicerequired | boolean | The client has both sources and nobody has chosen which its engagements use. |
choicesrequired | array of TbPullSourceChoice | The two to choose from when needsChoice; empty otherwise. |
choices[].userequired | string (BooksSourcePick) | What to send as One of: |
choices[].sourcerequired | string (BooksSource) | Null when the client has no connection and no Tolvi Books link. One of: |
choices[].labelrequired | string | example "Tolvi Books" |
choices[].organisationNamerequired | stringnullable |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Pull the trial balance from the bookkeeping software
POST/api/v1/engagements/{id}/tb/pull
- Scopes
engagements:writeandengagements:read- Retries
- Send an
Idempotency-Keyto make retries safe.
Pulls again and adds a new immutable trial balance version, exactly as a CSV import would (mappings remembered, journals on missing accounts removed only with confirmJournalRemoval). Send the preview’s hash as expectedHash to refuse (409) if the books changed in between. 409 also when the connection needs reconnecting. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
Idempotency-Key | header | string | A unique key (1-255 visible characters) to make retries safe. Replayed for 24 hours. 1 to 255 characters |
Request body
Required. application/json: object (TbPullRequest).
| Field | Type | Description |
|---|---|---|
expectedHash | string | The hash from the preview. If the books have changed since, nothing is imported (409) and you preview again. |
confirmJournalRemoval | boolean | Confirm removing the journals the preview listed. |
reopenFile | boolean | Required to pull into a file signed off at every stage: the import reopens it for re-approval (reviewer or above). See |
Responses
201 Created, application/json
Body: object (TbImportResult).
| Field | Type | Description |
|---|---|---|
versionrequired | object (TbVersion) | |
version.idrequired | string (uuid) | |
version.versionrequired | integer | at least 1 |
version.sourceKindrequired | string (TbSourceKind) | One of: |
version.sourceLabelrequired | stringnullable | |
version.providerrequired | stringnullable | |
version.asAtrequired | string (date)nullable | example "2026-03-31" |
version.booksThroughSeqrequired | integernullable | Pulled from Tolvi Books: the ledger point it was read at (the last entry included), so the same trial balance can be recomputed and later entries found exactly. Null for any other source. |
version.hashrequired | string | Content hash (sha256): the same balances give the same hash. |
version.accountCountrequired | integer | at least 0 |
version.cyTotalPencerequired | integer | Integer pence (GBP). Net of current year balances (nil when balanced). |
version.pyTotalPencerequired | integer | Integer pence (GBP). |
version.balancedrequired | boolean | |
version.removedJournalRefsrequired | array of string | |
version.createdByrequired | string (uuid)nullable | |
version.createdAtrequired | string (date-time) | |
removedJournalRefsrequired | array of string | |
warningsrequired | array of TbWarning | |
warnings[].coderequired | string | |
warnings[].messagerequired | string | |
unmappedCountrequired | integer |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 409 Conflict, 422 Unprocessable content, 429 Too many requests, as problem details (application/problem+json).
Preview pulling the trial balance from the bookkeeping software
POST/api/v1/engagements/{id}/tb/pull/preview
- Scopes
engagements:writeandengagements:read
Reads the TB at the period end (and at the prior period end for the prior year column) from the client’s connection, or from the client’s Tolvi Books through the firm’s practice link (both columns at one ledger point, already mapped by the books’ lead lines). Nothing is stored. Same shape as the CSV import preview, plus where it came from. 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 (TbPullPreview).
| Field | Type | Description |
|---|---|---|
accountCountrequired | integer | |
addedCodesrequired | array of string | |
removedCodesrequired | array of string | |
changedCountrequired | integer | Accounts whose balances differ from the current version. |
cyTotalPencerequired | integer | Integer pence (GBP). |
balancedrequired | boolean | |
hashrequired | string | |
sameAsCurrentrequired | boolean | Identical to the current version (an import would be refused). |
journalsToRemoverequired | array of JournalRef | Journals whose accounts are not in the new TB: confirm before they are removed. |
journalsToRemove[].idrequired | string (uuid) | |
journalsToRemove[].refrequired | string | |
journalsToRemove[].statusrequired | string (JournalStatus) | One of: |
unmappedCountrequired | integer | Accounts nothing could map. |
debitTotalPencerequired | integer | Current year debit balances added up, in integer pence. |
creditTotalPencerequired | integer | Current year credit balances added up, in integer pence (negative). |
accountsrequired | array of TbPreviewAccount | The accounts as read, in file order. |
accounts[].coderequired | string | |
accounts[].namerequired | string | |
accounts[].cyPencerequired | integer | Current year balance in integer pence: debits positive, credits negative. |
accounts[].pyPencerequired | integer | Prior year balance in integer pence: debits positive, credits negative. |
accounts[].lineIdrequired | string (LeadLineId)nullable | The mapping already held for this code, else the one found automatically; null when it needs mapping. One of: |
warningsrequired | array of TbWarning | |
warnings[].coderequired | string | |
warnings[].messagerequired | string | |
layoutrequired | object (TbColumns)nullable | The header row and columns read (detected, or as given in |
layout.headerRowrequired | integernullable | 1 to 1000000 |
layout.code | integer | 0 to 49 |
layout.name | integer | 0 to 49 |
layout.type | integer | Account type (asset, liability…), when the file has one. 0 to 49 |
layout.debit | integer | 0 to 49 |
layout.credit | integer | 0 to 49 |
layout.balance | integer | 0 to 49 |
layout.pyDebit | integer | 0 to 49 |
layout.pyCredit | integer | 0 to 49 |
layout.pyBalance | integer | 0 to 49 |
staleSignOffsrequired | array of TbStaleSignOff | Sections whose current sign-offs this import would put out of date. |
staleSignOffs[].sectionCoderequired | string | |
staleSignOffs[].sectionNamerequired | string | |
staleSignOffs[].stagesrequired | array of string | The stages whose sign-off would go out of date, e.g. "Preparer", "Reviewer". |
importLock | object (TbImportLock)nullable | Set when the file is signed off at every stage: the import is refused unless it reopens the file. |
importLock.coderequired | string (TbImportLockCode) | One of: |
importLock.reasonrequired | string | In plain words, for the import screen. |
importLock.canReopenrequired | boolean | The caller can import anyway by reopening the file (send |
providerrequired | string (BooksSource) | Where the trial balance was read: an OAuth provider, or tolvi_books (the client’s Tolvi Books, through the firm’s practice link). One of: |
sourceLabelrequired | string | example "Tolvi Books" |
organisationNamerequired | string | The organisation (OAuth) or business (Tolvi Books) read. |
booksThroughSeqrequired | integernullable | Tolvi Books: the ledger point read (the last entry included). Null for other sources. |
asAtrequired | string (date) | The period end: the current year column. example "2026-03-31" |
priorAsAtrequired | string (date)nullable | The prior period end: the prior year column (null for a first period). example "2026-03-31" |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Preview sending an engagement’s journals to the client’s Tolvi Books
POST/api/v1/engagements/{id}/books/post-back/preview
- Scopes
review:write- Access
- A signed-in Tolvi session only, never an API token.
For an engagement approved at every stage whose trial balance came from the client’s Tolvi Books: the year-end adjustments the books would get (and any earlier post-back reversed because its journal changed), the books’ trial balance at the period end before and after, which must equal the final trial balance to the penny, and the year-end lock it would set. problems says what stands in the way. Nothing is posted. Session only; reviewers and above. 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 (PostBackPreview).
What sending an approved engagement’s posted journals to the client’s Tolvi Books would do: the entries, the books’ trial balance at the period end before and after (which must equal the final trial balance to the penny), and the year-end lock it sets. Nothing is posted.
| Field | Type | Description |
|---|---|---|
engagementIdrequired | string (uuid) | |
businessIdrequired | string (uuid) | |
businessNamerequired | stringnullable | |
periodEndrequired | string (date) | example "2026-03-31" |
readyrequired | boolean | Nothing stands in the way: post-back can go ahead. |
problemsrequired | array of string | What stands in the way, in words. |
entriesrequired | array of PostBackEntry | |
entries[].actionrequired | string (PostBackAction) | reverse: undoes an earlier post-back of a journal that has changed or gone. One of: |
entries[].journalRefrequired | stringnullable | example "J4" |
entries[].daterequired | string (date) | example "2026-03-31" |
entries[].descriptionrequired | string | |
entries[].dateProblemrequired | stringnullable | Why the books would refuse this date (a lock), or null. |
entries[].linesrequired | array of PostBackLine | |
entries[].lines[].accountCoderequired | string | |
entries[].lines[].accountNamerequired | stringnullable | |
entries[].lines[].amountPencerequired | integer | Debit positive, in pence. |
unchangedrequired | integer | Journals the books already hold exactly, from an earlier post-back. at least 0 |
missingAccountsrequired | array of PostBackMissingAccount | Accounts the journals use that the books don’t have (add them in the client’s chart first; nothing is created silently). |
missingAccounts[].coderequired | string | |
missingAccounts[].namerequired | string | |
balancesrequired | array of PostBackBalance | |
balances[].coderequired | string | |
balances[].namerequired | string | |
balances[].booksBeforePencerequired | integer | Integer pence (GBP). The books at the period end now, debit positive. |
balances[].booksAfterPencerequired | integer | Integer pence (GBP). The books at the period end after post-back. |
balances[].finalPencerequired | integer | Integer pence (GBP). The engagement’s final balance (draft TB plus posted journals). |
balances[].differencePencerequired | integer | booksAfterPence less finalPence: nil on every account before post-back can go ahead. |
agreesrequired | boolean | After post-back the books agree to the final trial balance on every account. |
lockedThroughrequired | string (date)nullable | The year-end (accountant) lock in force now. example "2026-03-31" |
lockThroughrequired | string (date) | Post-back locks the books to this date (the period end), unless already locked to it or later. example "2026-03-31" |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Send an engagement’s journals to the client’s Tolvi Books and lock the year
POST/api/v1/engagements/{id}/books/post-back
- Scopes
review:write- Access
- A signed-in Tolvi session only, never an API token.
- Retries
- Send an
Idempotency-Keyto make retries safe.
Posts the preview’s entries to the client’s books as year-end adjustments, through the firm’s practice link (year-end or full bookkeeping access), and locks the books to the period end, in one transaction. 409 with the reasons when the preview isn’t ready (not approved, the TB didn’t come from these books, missing accounts, the books wouldn’t agree, a lock). Safe to repeat: journals the books already hold are not posted again. Never creates a trial balance version. Session only; reviewers and above; not available through the MCP server. 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 |
Responses
201 Created, application/json
Body: object (PostBackResult).
| Field | Type | Description |
|---|---|---|
engagementIdrequired | string (uuid) | |
postedrequired | integer | at least 0 |
reversedrequired | integer | at least 0 |
unchangedrequired | integer | at least 0 |
entrySeqsrequired | array of number | The ledger entries made, in order. |
lockedThroughrequired | string (date) | example "2026-03-31" |
lockedNowrequired | boolean | This call set the lock (false when it was already in place). |
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).