Suite
9 operations in the Tolvi API, version 1. Paths are relative to https://api.tolvi.co.uk. Every amount is integer pence.
Search clients, engagements, sections, meetings and people
GET/api/v1/search
- Scopes
clients:read
Matches part of a name (or a Companies House number for clients and engagements), a few per group, best matches first. A group is searched only when you may read it: engagements need engagements:read and Working Papers; sections need an engagementId as well; meetings need meetings:read and Meetings; people need settings:read. Read-only, and limited like every other read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Most results per group. 1 to 20; default 5 |
engagementId | query | string (uuid) | Also search the sections of this engagement. |
qrequired | query | string | What to look for. 1 to 100 characters |
Responses
200 OK, application/json
Body: object (SearchResults).
Matches grouped by kind. Each group is searched only when you may read it: engagements and sections need engagements:read and Working Papers, meetings need meetings:read and Meetings, people need settings:read. searched lists the groups that were.
| Field | Type | Description |
|---|---|---|
queryrequired | string | |
searchedrequired | array of SearchGroup | The groups searched for this caller. One of: |
clientsrequired | array of SearchClient | |
clients[].idrequired | string (uuid) | |
clients[].namerequired | string | |
clients[].entityTyperequired | string (EntityType) | One of: |
clients[].companyNumberrequired | stringnullable | |
engagementsrequired | array of SearchEngagement | |
engagements[].idrequired | string (uuid) | |
engagements[].clientIdrequired | string (uuid) | |
engagements[].clientNamerequired | string | |
engagements[].periodEndrequired | string (date) | example "2026-03-31" |
engagements[].stageNamerequired | string | The current workflow stage, e.g. "Preparation". |
engagements[].statusrequired | string (EngagementStatus) | One of: |
sectionsrequired | array of SearchSection | |
sections[].engagementIdrequired | string (uuid) | |
sections[].coderequired | string | example "F" |
sections[].namerequired | string | example "Bank & cash" |
meetingsrequired | array of SearchMeeting | |
meetings[].idrequired | string (uuid) | |
meetings[].titlerequired | string | |
meetings[].clientNamerequired | stringnullable | |
meetings[].joinAtrequired | string (date-time) | |
meetings[].statusrequired | string (MeetingStatus) | One of: |
peoplerequired | array of SearchPerson | |
people[].idrequired | string (uuid) | |
people[].namerequired | string | |
people[].emailrequired | string | |
people[].staffRolerequired | string (StaffRole)nullable | One of: |
taxReturns | array of SearchWorkItem | Needs self_assessment:read and Self Assessment. |
taxReturns[].idrequired | string (uuid) | |
taxReturns[].clientIdrequired | string (uuid) | |
taxReturns[].clientNamerequired | string | |
taxReturns[].periodLabelrequired | string | example "2025/26" |
taxReturns[].statusrequired | string | The product’s own status. |
taxReturns[].stageLabelrequired | string | example "In review" |
taxReturns[].dueDaterequired | string (date) | example "2026-03-31" |
companyTaxReturns | array of SearchWorkItem | Needs corporation_tax:read and Corporation Tax. |
companyTaxReturns[].idrequired | string (uuid) | |
companyTaxReturns[].clientIdrequired | string (uuid) | |
companyTaxReturns[].clientNamerequired | string | |
companyTaxReturns[].periodLabelrequired | string | example "2025/26" |
companyTaxReturns[].statusrequired | string | The product’s own status. |
companyTaxReturns[].stageLabelrequired | string | example "In review" |
companyTaxReturns[].dueDaterequired | string (date) | example "2026-03-31" |
accountsSets | array of SearchWorkItem | Needs accounts:read and Accounts. |
accountsSets[].idrequired | string (uuid) | |
accountsSets[].clientIdrequired | string (uuid) | |
accountsSets[].clientNamerequired | string | |
accountsSets[].periodLabelrequired | string | example "2025/26" |
accountsSets[].statusrequired | string | The product’s own status. |
accountsSets[].stageLabelrequired | string | example "In review" |
accountsSets[].dueDaterequired | string (date) | example "2026-03-31" |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
List the products in the Tolvi suite
GET/api/v1/products
- Scopes
work:read
The catalogue, whether or not your firm has each one.
Responses
200 OK, application/json
Body: array of Product.
A product in the Tolvi suite.
| Field | Type | Description |
|---|---|---|
[].keyrequired | string | Stable id, e.g. working_papers or meetings. pattern ^[a-z][a-z0-9_]{1,39}$; example "meetings" |
[].namerequired | string | example "Meetings" |
[].descriptionrequired | string | |
[].pathrequired | string | Where the product opens in the web app. example "/meetings" |
[].iconrequired | string | A lucide icon name. example "lucideVideo" |
[].statusrequired | string (ProductStatus) | beta: available, still changing. retired: no longer offered. One of: |
[].selfServeTrialrequired | boolean | A firm admin can start a trial without asking Tolvi. |
[].trialDaysrequired | integer | Length of a self-serve trial. at least 0 |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
List the products you can open in this firm
GET/api/v1/me/products
- Scopes
work:read
What the app switcher shows. A product the firm has only for people given access is listed only if you have been.
Responses
200 OK, application/json
Body: array of MyProduct.
A product you can open in this firm.
| Field | Type | Description |
|---|---|---|
[].keyrequired | string | Stable id, e.g. working_papers or meetings. pattern ^[a-z][a-z0-9_]{1,39}$; example "meetings" |
[].namerequired | string | example "Meetings" |
[].descriptionrequired | string | |
[].pathrequired | string | Where the product opens in the web app. example "/meetings" |
[].iconrequired | string | A lucide icon name. example "lucideVideo" |
[].statusrequired | string (ProductStatus) | beta: available, still changing. retired: no longer offered. One of: |
[].selfServeTrialrequired | boolean | A firm admin can start a trial without asking Tolvi. |
[].trialDaysrequired | integer | Length of a self-serve trial. at least 0 |
[].firmStatusrequired | string (FirmProductStatus) | The firm’s hold on it. suspended: read-only until billing is sorted out. One of: |
[].userequired | string (ProductUse) | read_only while the product is suspended. One of: |
[].trialEndsAtrequired | string (date-time)nullable |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
List the products your firm has
GET/api/v1/firm/products
- Scopes
work:read
With status, access mode, trial end and how many people can use each.
Responses
200 OK, application/json
Body: array of FirmProduct.
A product the firm has, and who can use it.
| Field | Type | Description |
|---|---|---|
[].keyrequired | string | pattern ^[a-z][a-z0-9_]{1,39}$; example "meetings" |
[].namerequired | string | |
[].pathrequired | string | |
[].iconrequired | string | |
[].productStatusrequired | string (ProductStatus) | beta: available, still changing. retired: no longer offered. One of: |
[].statusrequired | string (FirmProductStatus) | Null: the firm doesn’t have Meetings. One of: |
[].accessModerequired | string (ProductAccessMode) | everyone: every member of the firm. assigned: only people given access. One of: |
[].sourcerequired | string (FirmProductSource) | included: part of every firm. trial, subscription, or manual (set by Tolvi). One of: |
[].startedAtrequired | string (date-time) | |
[].trialEndsAtrequired | string (date-time)nullable | |
[].peopleWithAccessrequired | integer | at least 0 |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Start a trial of a product
POST/api/v1/firm/products/{key}
- Scopes
settings:adminandwork:read- Role
- Firm admin
- Access
- A signed-in Tolvi session only, never an API token.
- Retries
- Send an
Idempotency-Keyto make retries safe.
Only for products the catalogue marks self-serve. You are given access to it straight away.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
keyrequired | path | string | pattern ^[a-z][a-z0-9_]{1,39}$ |
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 (FirmProduct).
A product the firm has, and who can use it.
| Field | Type | Description |
|---|---|---|
keyrequired | string | pattern ^[a-z][a-z0-9_]{1,39}$; example "meetings" |
namerequired | string | |
pathrequired | string | |
iconrequired | string | |
productStatusrequired | string (ProductStatus) | beta: available, still changing. retired: no longer offered. One of: |
statusrequired | string (FirmProductStatus) | Null: the firm doesn’t have Meetings. One of: |
accessModerequired | string (ProductAccessMode) | everyone: every member of the firm. assigned: only people given access. One of: |
sourcerequired | string (FirmProductSource) | included: part of every firm. trial, subscription, or manual (set by Tolvi). One of: |
startedAtrequired | string (date-time) | |
trialEndsAtrequired | string (date-time)nullable | |
peopleWithAccessrequired | integer | at least 0 |
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).
Change who can use a product
PATCH/api/v1/firm/products/{key}
- Scopes
settings:adminandwork:read- Role
- Firm admin
- Access
- A signed-in Tolvi session only, never an API token.
everyone: every member of the firm. assigned: only people given access (their grants are kept either way).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
keyrequired | path | string | pattern ^[a-z][a-z0-9_]{1,39}$ |
Request body
Required. application/json: object (FirmProductPatch).
| Field | Type | Description |
|---|---|---|
accessModerequired | string (ProductAccessMode) | everyone: every member of the firm. assigned: only people given access. One of: |
Responses
200 OK, application/json
Body: object (FirmProduct).
A product the firm has, and who can use it.
The fields are as shown above.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Get the people × products view
GET/api/v1/product-access
- Scopes
settings:admin- Role
- Firm admin
The firm’s products, and for each person which they can use and which they have been given.
Responses
200 OK, application/json
Body: object (ProductAccessMatrix).
The firm’s products and, for each person, which they can use.
| Field | Type | Description |
|---|---|---|
productsrequired | array of FirmProduct | |
products[].keyrequired | string | pattern ^[a-z][a-z0-9_]{1,39}$; example "meetings" |
products[].namerequired | string | |
products[].pathrequired | string | |
products[].iconrequired | string | |
products[].productStatusrequired | string (ProductStatus) | beta: available, still changing. retired: no longer offered. One of: |
products[].statusrequired | string (FirmProductStatus) | Null: the firm doesn’t have Meetings. One of: |
products[].accessModerequired | string (ProductAccessMode) | everyone: every member of the firm. assigned: only people given access. One of: |
products[].sourcerequired | string (FirmProductSource) | included: part of every firm. trial, subscription, or manual (set by Tolvi). One of: |
products[].startedAtrequired | string (date-time) | |
products[].trialEndsAtrequired | string (date-time)nullable | |
products[].peopleWithAccessrequired | integer | at least 0 |
membersrequired | array of ProductAccessMember | |
members[].userIdrequired | string (uuid) | |
members[].namerequired | string | |
members[].emailrequired | string (email) | |
members[].initialsrequired | string | example "GL" |
members[].rolerequired | string (MembershipRole) | One of: |
members[].staffRolerequired | string (StaffRole)nullable | One of: |
members[].isAdminrequired | boolean | |
members[].productsrequired | array of string | Product keys they can open now. |
members[].assignedrequired | array of string | Product keys they have been given, whatever the access mode. |
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 429 Too many requests, as problem details (application/problem+json).
Give someone access to a product
PUT/api/v1/product-access/{key}/members/{userId}
- Scopes
settings:adminandsettings:admin- Role
- Firm admin
- Access
- A signed-in Tolvi session only, never an API token.
Idempotent. Takes effect at once.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
keyrequired | path | string | pattern ^[a-z][a-z0-9_]{1,39}$ |
userIdrequired | path | string (uuid) |
Responses
204 No content
They have access.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).
Take away someone’s access to a product
DELETE/api/v1/product-access/{key}/members/{userId}
- Scopes
settings:adminandsettings:admin- Role
- Firm admin
- Access
- A signed-in Tolvi session only, never an API token.
Idempotent. In everyone mode they keep using it until the mode is assigned.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
keyrequired | path | string | pattern ^[a-z][a-z0-9_]{1,39}$ |
userIdrequired | path | string (uuid) |
Responses
204 No content
They no longer have it.
Errors: 400 Bad request, 401 Unauthorised, 403 Forbidden, 404 Not found, 429 Too many requests, as problem details (application/problem+json).