Skip to main content

Developers

Developer documentation for the Tolvi API

Everything in Tolvi is available through a versioned REST API. The Tolvi web app uses the same API, so there are no private endpoints. This page covers access, limits, webhooks and versioning. The reference lists every operation.

Last updated

What are the Tolvi API and the Tolvi MCP server?

The Tolvi API is a REST API over HTTPS with JSON bodies, version 1, at /api/v1. It has 606 operations covering the work queue, clients, engagements, trial balances, journals, lead schedules, review points, workflow, tax, fixed assets, sync health, Tolvi Meetings (transcripts and their write-ups), Tolvi AI, settings and webhooks. Its OpenAPI 3.1 specification is generated from the API's own code, so the reference can't drift from what the API does.

The Tolvi MCP server lets an AI assistant read your work queue, engagements, review points, meeting write-ups and your clients' Tolvi Books, and raise review points once you confirm. Every tool calls the same API with the same permissions. Connecting an assistant is coming next: the MCP page lists the tools and what's left to do.

Tolvi is in early access, so API credentials come with an early access account. Email us if you want to build against Tolvi.

Base URLs

Use https://api.tolvi.co.uk/api/v1 for your own scripts and integrations. Every request and response uses UTF-8 JSON unless the reference says otherwise.

Tolvi base URLs
WhatURLUse it for
APIhttps://api.tolvi.co.uk/api/v1Your scripts, integrations and apps.
The app's own APIhttps://app.tolvi.co.uk/api/v1The same API under the web app's address, for the Tolvi web app itself. Don't build against it.
MCP serverhttps://mcp.tolvi.co.uk/mcpComing AI assistants, once MCP sign-in is set up. Connecting is coming next.
OpenAPI specificationtolvi.co.uk/openapi.jsonGenerating a client or importing into your API tools. The API also serves it at /api/v1/openapi.json.

How do I authenticate?

Send Authorization: Bearer <token> on every request. Tolvi accepts three kinds of token. Each one acts with the permissions of a person or an API client in your firm, never more. If you belong to several firms, choose one with the x-firm-id header.

The three kinds of API token
TokenForHow you get it
Personal access token
twp_…
Scripts and tools acting as you.Create it in Settings → API credentials. Choose its scopes and expiry (90 days by default, up to 366). Tolvi shows it once and stores only a hash.
Client credentials
twat_…
Server-to-server integrations, such as a practice management system.A firm admin creates an API client in Settings → API credentials, with scopes and a role ceiling. Exchange its id and secret at POST /api/v1/oauth/token for a token that lasts an hour.
OAuth 2.1 authorisation code with PKCEApps acting for a signed-in person.Tolvi's identity provider issues the token. The API's /.well-known/oauth-protected-resource document names it and lists the scopes. To register an app, email support@tolvi.co.uk.

A request with a personal access token

curl "https://api.tolvi.co.uk/api/v1/clients?limit=20" \
  -H "Authorization: Bearer twp_…"

Exchanging client credentials for a token

curl -X POST "https://api.tolvi.co.uk/api/v1/oauth/token" \
  -u "<client id>:<client secret>" \
  -d grant_type=client_credentials \
  -d scope="work:read clients:read"

The token endpoint answers with access_token, token_type, expires_in and scope, and its errors use the OAuth shape (error, error_description). Creating, listing and revoking credentials is only possible from a signed-in Tolvi session, never with an API token.

How do scopes and roles work together?

A token can do what both its scopes and its role allow. Tolvi works this out on every request: the effective permission is the role's permissions intersected with the token's scopes. A personal access token is capped by its owner's current role, so it loses access the moment their role changes. An API client is capped by the role ceiling a firm admin set.

Each operation in the reference lists the scopes it needs. A missing scope gets a 403 with WWW-Authenticate: Bearer error="insufficient_scope". Operations with no scope, such as creating credentials or signing up, are for a signed-in Tolvi session only.

API scopes
ScopeAllows
work:readRead the work queue
clients:readRead clients
clients:writeCreate and edit clients
engagements:readRead year-end files and working papers
engagements:writePrepare working papers, sign sections off and assign work
review:writeRaise, clear and reopen review points; approve, return and reopen year-end files
integrations:manageConnect and disconnect clients’ bookkeeping software
settings:readRead firm settings and the team (names, emails, roles and capacity)
settings:adminChange everyday firm settings and see invitations (security, firm details, workflows, AI and people need a signed-in person)
webhooks:manageManage webhook subscriptions and see their deliveries
meetings:readRead meetings: transcripts and their write-ups (summary, takeaways, next steps, open questions, draft follow-up email)
meetings:writeSend note-takers to meetings and file them against clients
client_books:readSee which businesses have linked their Tolvi Books to the firm, and the state of each link
books:readRead a business’s books in Tolvi Books (as the business, or as its linked accountant)
books:writeKeep a business’s books: categorise, reconcile, invoices, receipts and journals (an accountant also needs the link’s access)
books:adminChange a business’s Tolvi Books set-up: its details, rules that post without asking, connections and who has access (some of it needs a signed-in person)
self_assessment:readRead clients’ self assessment returns and calculations, without their National Insurance numbers or dates of birth
self_assessment:writePrepare clients’ self assessment returns. Approving and filing stay with signed-in people
accounts:readRead clients’ statutory year end accounts: figures, notes and PDFs
accounts:writePrepare clients’ statutory year end accounts. Approving them stays with signed-in people
corporation_tax:readRead clients’ Company Tax Returns: boxes, computations and PDFs
corporation_tax:writePrepare clients’ Company Tax Returns. Approving and filing them stay with signed-in people

Sign-off and approval need a person: the API checks the same workflow guards as the app, and the MCP server never offers them.

What are the rate limits?

Each API credential or connected app in a firm (a personal access token, an API client, an app signed in with OAuth or the MCP server) gets a fixed number of requests a minute, set by the firm's plan. People using the Tolvi web app have limits of their own, so an integration can't use up anyone's allowance and the other way round: each person gets 600 requests a minute, and the whole firm a higher cap across everyone signed in, which grows with the plan.

API rate limits by plan
PlansTierRequests a minute, per credential or appRequests a minute, whole firm in the web app
Starter, Solo, Practice, Growth, Firm, ScaleStandard6003,000
Group, Group PlusHigher3,0006,000
EnterpriseAgreed per firm10,00015,000

Every response carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy, for the limit closest to running out. Over a limit, the API answers 429 with a Retry-After header, and the problem detail's rateLimitScope says which limit was hit: client (this credential or app), person, firm or ip.

Requests without a valid token are also limited per address: 600 a minute, and 20 a minute on the token, sign-up and invitation endpoints. Signed-in requests don't count towards the per-address limit, so an office sharing one address isn't slowed down together. See pricing for what each plan includes.

How do webhooks work?

Webhooks tell your systems when something happens in Tolvi. A firm admin subscribes an HTTPS URL to event types in Settings → Webhooks, or through the API with the webhooks:manage scope. Each delivery is a signed POST with a JSON body. The body carries ids only, so fetch the full record through the API.

Webhook event types
Event typeWhen
engagement.stage_changedAn engagement moved stage: submitted, approved, returned for amendments or reopened.
section.signed_offA section (W/P ref) was signed off by its preparer or a reviewer.
review_point.raisedA review point was raised on a section or the engagement.
review_point.clearedA review point was cleared.
tb.importedA new trial balance version was imported.
sync.change_detectedThe bookkeeping software changed after sign-off: the figures in Tolvi are out of date.
self_assessment.filedHMRC accepted a client’s self assessment return, filed live.
accounts.approvedPeople recorded the directors’ approval of a client’s statutory accounts.
corporation_tax.filedHMRC accepted a company’s Company Tax Return, filed live.

A delivery body

{
  "id": "6b0c…",
  "type": "review_point.raised",
  "apiVersion": "v1",
  "createdAt": "2026-10-18T09:12:44.120Z",
  "firmId": "3e57…",
  "data": { "engagementId": "…", "clientId": "…", "reviewPointId": "…", "sectionCode": "C", "category": "query", "raisedBy": "…" }
}

Each delivery has the headers Tolvi-Signature, Tolvi-Event-Id, Tolvi-Event-Type and Tolvi-Delivery-Id. The event id stays the same on every retry and replay.

How to verify the signature

Tolvi-Signature is t=<unix seconds>,v1=<hex>. The v1 value is HMAC-SHA256, keyed with the subscription's whsec_… secret, of <t>.<raw body>. To accept a delivery:

  1. Compute the HMAC over the raw body, before parsing the JSON.
  2. Compare it with each v1 value in constant time.
  3. Reject the delivery if t is more than 5 minutes from your clock.
  4. Ignore an event id you have already processed.

Node.js

import crypto from 'node:crypto';

// header: the Tolvi-Signature value. rawBody: the request body exactly as received.
export function verifyTolvi(secret, header, rawBody, toleranceSeconds = 300) {
  const parts = header.split(',').map((p) => p.split('='));
  const t = Number(parts.find(([k]) => k === 't')?.[1]);
  const sigs = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return sigs.some((s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}

Python

import hashlib, hmac, time

def verify_tolvi(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
    items = [p.split("=", 1) for p in header.split(",") if "=" in p]
    t = next((v for k, v in items if k == "t"), None)
    sigs = [v for k, v in items if k == "v1"]
    if not t or not t.isdigit() or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in sigs)

Retries and the delivery log

  • A delivery succeeds when your endpoint answers 2xx within 10 seconds. Redirects aren't followed and count as a failure.
  • A failed attempt is retried after 30 seconds, then the wait doubles up to 6 hours. After 12 attempts the delivery is marked failed.
  • After 20 failed attempts in a row, the subscription is disabled and the firm's admins are told. Re-enable it in Settings → Webhooks or with PATCH /api/v1/webhooks/{id}.
  • Every attempt is logged with its status code. Any delivery can be replayed from the log.
  • Destinations must be HTTPS URLs on public addresses. Private, loopback and link-local addresses are refused when the URL is saved and again on every connection.
  • Order isn't guaranteed. Use createdAt, and fetch the current state from the API when order matters.

Webhook endpoints in the API reference

How is the API versioned?

The version is in the path: /api/v1. Changes within version 1 are additive only, such as new endpoints, new optional fields and new event types. Your code should ignore fields it doesn't know.

Removing or renaming anything is a breaking change. Before anything is removed, the affected operations carry deprecation headers for six months' notice. Every change to the API is checked by an automated breaking-change test against the current specification, which fails the build if version 1 would break.

Where do I get help with the API?

Email support@tolvi.co.uk. You'll be emailing the people who build Tolvi. Include the request path, the time and the response's instance and detail, but never your token or secret. To report a security issue, put "Security" in the subject line.