Concepts
A company on your platform is active, suspended, closed or archived. You move it with your server key; FinBaze staff can move any company. Books, filings, documents and Digipoort or HMRC history are never deleted by a lifecycle change.
| State | What it means |
|---|---|
active | Normal. |
suspended | Read-only. Mutations fail with COMPANY_SUSPENDED (ending an authorisation or engagement, answering a support request and exports still work). Cron work (recurring invoices, payment batches, reminders, bank sync) skips the company. A suspended company does not count as active for usage. |
closed | The company stopped trading on closeEffectiveDate. Final returns can still be filed and exported; everything else fails with COMPANY_CLOSED. |
archived | Detached from your platform. Only exports work (COMPANY_ARCHIVED). The books are kept until retentionUntil (per country, 7 years in the Netherlands by default, counted from the later of the closing date and the last financial year end). Archived companies are hidden from platformCompanies unless you pass includeArchived. |
Moving a company#
await finbaze.platform.companies.suspend(id, { reason: "non-payment", displayMessage: "Your account is on hold." });
await finbaze.platform.companies.reactivate(id);
const checklist = await finbaze.platform.companies.closingChecklist(id, "2026-10-31");
await finbaze.platform.companies.close(id, { effectiveDate: "2026-10-31" });
await finbaze.platform.companies.reopen(id); // within the reopen window (90 days by default)
await finbaze.platform.companies.archive(id); // needs every blocking checklist item done
The checklist has one blocking item per registered return (final VAT, ICP, OSS, corporate income tax) and informational items (open receivables and payables, deregistration with the KVK and the Belastingdienst). archive fails with ARCHIVE_BLOCKED while a blocking item is open. A company FinBaze suspended cannot be reactivated by you (SUSPENDED_BY_FINBAZE).
Errors: INVALID_LIFECYCLE_TRANSITION, INVALID_EFFECTIVE_DATE, REOPEN_WINDOW_PASSED, ARCHIVE_BLOCKED, SUSPENDED_BY_FINBAZE, NOT_A_PLATFORM_COMPANY.
Events: company.suspended, company.reactivated, company.closed, company.reopened, company.archived.
Transfers#
A company can move to another platform or to direct FinBaze. The company's admin consents in the embedded company-transfer element, which shows a versioned statement and sends back its hash; the consent is audited with the statement version and hash.
- To another platform. The element shows a one-time 12-character code, once. Hand it to the receiving platform, whose backend calls
platform.transfers.accept(code, externalId). The code works once and expires (14 days by default); every wrong, used or expired code fails the same way (TRANSFER_CODE_INVALID). - To direct FinBaze. The company admin enters an email address and FinBaze emails a claim link to
/claim-company/<code>. A signed-in FinBaze account claims the company and chooses a plan, or keeps its accountant when it has one.
On completion the sending platform loses access: its users, limits, data sources and embed sessions are detached, and you receive company.transferred_out. The receiving side gets company.transferred_in. The former owner never sees the new owner's externalId. Cancel a pending transfer with platform.transfers.cancel; company.transfer.created and company.transfer.cancelled report the rest.
Errors: TRANSFER_CODE_INVALID, TRANSFER_CODE_EXPIRED, TRANSFER_ALREADY_PENDING, TRANSFER_TO_SAME_PLATFORM, TRANSFER_WRONG_TARGET, TRANSFER_STATEMENT_OUTDATED, TRANSFER_EMAIL_REQUIRED, TRANSFER_EMAIL_MISMATCH, EXTERNAL_ID_TAKEN.
Ending a user#
platform.users.deprovision(id, { anonymise }) revokes every token and embed session of the hidden user at once and removes the user from all companies. Tokens issued before the call fail with UNAUTHENTICATED. platform.users.upsert with the same externalUserId brings the user back with a new identity; old tokens stay dead. Event: platform_user.deprovisioned.
Audit log#
platform.audit.list({ filter, limit, starting_after }) returns what happened to your companies, newest first, in keyset pages (nextCursor). It needs the audit:read scope and an admin role, and only ever shows your own companies; another platform's entries do not exist for you. Each entry carries the actor (actorKind: user, platform, support, ...), platformUserId and platformUserExternalId, tokenType, ip, userAgent, requestId (also returned as the X-Request-Id response header), stepUpAssertionId and supportGrantId. Secrets, tokens, transfer codes and bank details are redacted. platform.audit.export(filter) starts an NDJSON export that appears on platform.exports.