Concepts
A company on your platform can work with an accounting firm that uses FinBaze. The firm opens the company in its normal FinBaze dashboard (/b/<firm>/…) with the same tools it has for its other clients, inside the features that are effective for that company. There are no firm services, no reselling and no marketplace: a company connects one firm, by name, and can end it at any time.
| Who pays | You keep paying for the company (active companies). The firm pays nothing for it: no client seat, no filings-only fee, no per-document or AI usage. Usage by firm staff on the company is yours, as for your own users. |
| What the firm sees | Whatever the company's effective features allow. A module you hide for the company is hidden for the firm too, for reads and for changes. The firm cannot switch features on. Firm-installed apps and MCP access do not reach your companies. |
| What the firm files | Nothing, unless the company chooses taxes for it. For the taxes the company hands over, the firm files with its BECON number and FinBaze no longer files those. All other taxes stay with FinBaze, which files as a software supplier without a BECON number (see filing consent). Requests only an intermediary may make, such as the corporate income tax extension, need a connected firm with a BECON number. |
| Whose users | The firm's staff are normal FinBaze accounts. They never become your platform users, and your users never get firm permissions. |
Connecting#
There are two ways. Both end in the same engagement, and both need the company to confirm a statement that names the firm, what it can see and do, the taxes it files, the billing rule and how to end.
1. The company invites its firm (the accountant-connect component).
- A company admin enters the firm's KvK number or its connect code.
lookupFirmForConnectreturns the firm for an exact match only: there is no directory to browse. Lookups are limited to 10 an hour per company. - The admin picks the taxes the firm may file (none means bookkeeping access only), reads the statement (
firmEngagementStatement) and confirms it. A firm without a BECON number can only get bookkeeping access (FIRM_HAS_NO_BECON). requestFirmConnectionstores the invitation. The firm sees it in its dashboard under Clients, Invites, Requests from companies, and gets an email. It accepts or declines; a request nobody answers expires after 14 days.
2. The firm asks for access. A firm admin enters the company's KvK number (and optionally a contact email and a message) in its dashboard. FinBaze sends a request to every platform company with that number whose platform has accountant connections on. The firm is never told whether a company matched, which platform it is on, or whether it was declined: requestCompanyAccess always answers true, and the firm's list says waiting, connected or closed.
You learn about a request when it arrives: the webhook firm.access_requested and the message firm.access_requested (routed like any notification, by default delivered by you). The company sees it in the notification-banner and approves or declines it in accountant-connect, choosing the taxes.
// Mount the component; the company does the rest.
<FinBazeAccountantConnect
onFirmConnected={({ firmId }) => refresh()}
onFirmDisconnected={({ firmId }) => refresh()}
/>
Create the session with components: { accountantConnect: { enabled: true } }. Its component features are invite, approve and end; switch one off and that button and its operation are gone for the session. The consent steps need an embed or headless token of a company admin; a backend-minted platform_user token gets FORBIDDEN_TOKEN_TYPE, and your server token cannot consent for a company.
Reading the state#
query { companyFirmEngagements(companyId: "…", includeEnded: false) {
id status origin taxes filingAuthority requestedAt expiresAt endsAt
firm { id name hasBecon }
} }
With the SDK: platform.firms.listEngagements(companyId). A server token needs the scope platform_firms:read.
firm_access and ending a connection#
Accountant connections are the feature key firm_access, which FinBaze puts in your allowance and you may limit per company or for the whole platform like any limitable key (see allowance and limits).
- Hide it: no new connections or requests. Companies with an active firm get a notice (default 30 days): you receive
firm.disconnect_scheduled, the company and the firm get a message, and the engagement ends at the set time (endedBySide: platform, reasonfirm_access_hidden). Show the key again before then and the notice is cancelled. - End it yourself with
platform.firms.end(engagementId, { reason })(scopeplatform_firms:write). A platform may only use the reasonsfirm_access_hidden,company_closed,company_transferredandplatform_request; the last is logged and both sides are told. - Either side can end it any time: the company admin in
accountant-connect, a firm admin in the dashboard, FinBaze staff.
Ending is one transaction: the firm loses access on the next request; its BECON is no longer used for the company; its HMRC client authorisations for the company are cancelled (HMRC's side cannot be removed through the API, so the firm and the company are told to remove the relationship there); its open document requests are closed; the company's drafts and books stay. From then on FinBaze files the taxes the firm used to file, once the company's consent exists. You receive firm.disconnected.
Events#
| Event | When |
|---|---|
firm.access_requested | A firm asked for access to a company. |
firm.connection_requested | The company invited a firm; the firm has not answered. |
firm.connected | The firm now works the company. |
firm.connection_declined | A request was declined or withdrawn. |
firm.connection_expired | A request expired after 14 days. |
firm.disconnect_scheduled | The engagement ends at endsAt because firm_access was hidden. |
firm.disconnected | The engagement ended. |
firm.authority_changed | The company changed which taxes the firm files. |
data.object is the engagement: id, companyId, firmId, firmName, origin, status, filingAuthority, taxes, expiresAt, and for some events endedBySide, reason, endsAt, previousTaxes. No email address is ever in an event.
Error codes#
| Code | Meaning |
|---|---|
FIRM_ACCESS_DISABLED | firm_access is hidden for the company. |
FIRM_NOT_FOUND | No such firm. |
FIRM_HAS_NO_BECON | Taxes chosen, but the firm has no BECON number. |
FIRM_ENGAGEMENT_STATEMENT_OUTDATED | The statement changed since it was shown; read it again. |
ENGAGEMENT_NOT_PENDING | The request was already answered or expired. |
FORBIDDEN_TOKEN_TYPE | Consent needs an embed or headless token. |
COMPANY_ADMIN_REQUIRED / FIRM_ADMIN_REQUIRED | The person lacks the role. |
PLATFORM_END_REASON_NOT_ALLOWED | A platform ended it with a reason that is not in its list. |
FIRM_LOOKUP_RATE_LIMITED | Too many lookups or requests. |
PLATFORM_COMPANY_USE_ENGAGEMENT | A firm tried to connect a platform company through an invite link, onboarding or createClient. |