Workflow
7 operations in the Tolvi API, version 1. Paths are relative to https://api.tolvi.co.uk. Every amount is integer pence.
Sign a section off at its current stage
POST/api/v1/engagements/{id}/sections/{code}/sign
- Scopes
engagements:writeandengagements:read
Preparer sign-off, or a review stage’s (which also needs review:write). Records the content fingerprint. Not available through MCP. 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).
Remove your own sign-off from a section
POST/api/v1/engagements/{id}/sections/{code}/unsign
- Scopes
engagements:writeandengagements:read
Later sign-offs are removed with it (the section detail’s signOff.unsignRemoves lists them). Refused (409) while the file is signed off at every stage: reopen it for re-approval first. Part of the working_papers product: the firm must have it and you must have access to it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | Engagement id. |
coderequired | path | string | Section code (W/P ref), e.g. C. One of: |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get the engagement workflow and its event history
GET/api/v1/engagements/{id}/workflow
- 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 (WorkflowHistory).
| Field | Type | Description |
|---|---|---|
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. |
eventsrequired | array of WorkflowEvent | |
events[].idrequired | string (uuid) | |
events[].subjectrequired | string | "engagement" or a section code. |
events[].typerequired | string | |
events[].stageIdrequired | string | |
events[].actorIdrequired | string (uuid)nullable | |
events[].atrequired | string (date-time) | |
events[].fromStatusrequired | string | |
events[].toStatusrequired | string | |
events[].roundrequired | integer | |
events[].openPointsrequired | integer | |
events[].fingerprintrequired | stringnullable | |
events[].note | stringnullable | The note given with a submission or approval. |
events[].declaration | object (ApprovalDeclaration)nullable | The declaration given with an approval. |
events[].declaration.initialsrequired | string | The approver’s initials, typed as a signature. 1 to 10 characters |
events[].declaration.statementrequired | string | What they declared, e.g. "I have reviewed the file and approve the accounts for issue to the client." 1 to 500 characters |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Submit the engagement for review
POST/api/v1/engagements/{id}/workflow/submit
- Scopes
engagements:writeandengagements:read
Every section prepared and unchanged, no unanswered review points. Optionally name who takes the next stage (reviewerId, saved on the team) and leave them a note. Not available through MCP. 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 (SubmitEngagement).
Optional: name who reviews next and leave them a note.
| Field | Type | Description |
|---|---|---|
reviewerId | string (uuid) | The person for the next stage (reviewer, manager or partner by the stage’s role). Saved on the engagement team, replacing whoever was there, and told about the submission. Must be a member of the firm. |
note | string | A cover note for the reviewer: kept with the submission in the history and sent with the notification. up to 2000 characters |
Responses
200 OK, application/json
Body: object (EngagementWorkflow).
| Field | Type | Description |
|---|---|---|
templateIdrequired | string | |
templateNamerequired | string | |
statusrequired | string (EngagementStatus) | One of: |
workflowStatusrequired | string (WorkflowStatus) | One of: |
currentStageIdrequired | string | |
currentStageNamerequired | string | |
currentStageIndexrequired | integer | at least 0 |
stagesrequired | array of WorkflowStage | |
stages[].idrequired | string | |
stages[].namerequired | string | |
stages[].staterequired | string (TimelineStepState) | One of: |
stages[].signedByrequired | string (uuid)nullable | |
stages[].signedAtrequired | string (date-time)nullable | |
stages[].assigneeIdrequired | string (uuid)nullable | Who the engagement team assigns to this stage (preparer, reviewer, manager or partner), or null when nobody is. |
stages[].sentBackByrequired | string (uuid)nullable | While the workflow is returned: who sent it back from this stage. |
stages[].sentBackAtrequired | string (date-time)nullable | While the workflow is returned: when it was sent back from this stage. |
roundrequired | integer | Amendment round (each return or reopen starts a new one). at least 1 |
returnCountrequired | integer | at least 0 |
reopenCountrequired | integer | at least 0 |
changedAfterApprovalrequired | boolean | |
returnTargetsrequired | array of string | Stages a return can go back to. |
actionsrequired | object (EngagementActions) | The engagement-level actions, checked for the caller. |
actions.submitrequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
actions.submit.allowedrequired | boolean | |
actions.submit.reasonsrequired | array of string | |
actions.submit.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
actions.approverequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
actions.approve.allowedrequired | boolean | |
actions.approve.reasonsrequired | array of string | |
actions.approve.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
actions.returnrequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
actions.return.allowedrequired | boolean | |
actions.return.reasonsrequired | array of string | |
actions.return.codesrequired | array of string | Machine-readable reason codes from @tolvi/workflow. |
actions.reopenrequired | object (GuardResult) | Whether the caller can take an action now, and every reason not. |
actions.reopen.allowedrequired | boolean | |
actions.reopen.reasonsrequired | array of string | |
actions.reopen.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).
Approve the engagement at its current stage
POST/api/v1/engagements/{id}/workflow/approve
- Scopes
review:writeandengagements:read- Role
- Reviewer or above
Segregation of duties and every workflow guard apply. Optionally name who takes the next stage (reviewerId, saved on the team as on submit, and told). An optional note and declaration (with the approver’s typed initials) are kept with the approval in the history and the audit trail. Not available through MCP.
Needs the Reviewer 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. |
Request body
Optional. application/json: object (ApproveEngagement).
| Field | Type | Description |
|---|---|---|
reviewerId | string (uuid) | The person for the next stage (manager or partner by the stage’s role), as on submit. Saved on the engagement team, replacing whoever was there, and told about the approval. Must be a member of the firm. Ignored at the last stage. |
note | string | A note kept with the approval in the history and sent with the notification. up to 2000 characters |
declaration | object (ApprovalDeclaration) | The approver’s declaration with their typed initials (the web app asks for one at partner sign-off). |
declaration.initialsrequired | string | The approver’s initials, typed as a signature. 1 to 10 characters |
declaration.statementrequired | string | What they declared, e.g. "I have reviewed the file and approve the accounts for issue to the client." 1 to 500 characters |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Return the engagement to an earlier stage
POST/api/v1/engagements/{id}/workflow/return
- Scopes
review:writeandengagements:read- Role
- Reviewer or above
Needs at least one unanswered review point; starts a new amendment round. An optional note for the preparer is kept in the history and sent with the notification.
Needs the Reviewer 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. |
Request body
Required. application/json: object (ReturnEngagement).
| Field | Type | Description |
|---|---|---|
toStageId | string | Default: the first stage. |
reviewPointIds | array of string (uuid) | Default: every unanswered review point. |
note | string | A note for the preparer: kept with the return in the history and sent with the notification. 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).
Reopen an approved engagement whose content changed
POST/api/v1/engagements/{id}/workflow/reopen
- Scopes
review:writeandengagements:read- Role
- Reviewer or above
Approvals from the target stage on go stale.
Needs the Reviewer 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. |
Request body
Required. application/json: object (ReopenEngagement).
| Field | Type | Description |
|---|---|---|
toStageId | string | Default: the first review stage. |
Responses
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).