An accountant client portal lets a bookkeeper or CPA open a client’s financial picture without ever holding the client’s password. In SpendifiAI I built it as a real account type, not a permission flag: accountants invite clients by email, the client accepts inside their own settings, and every time the accountant steps into a client’s account it happens on a separate, audited session. The same release also changed how the app talks about late subscription charges.
Everything below is taken from the public repository at github.com/parkktech/spendifiai: commit 04f7dab for the accountant system and commit 477bf05 for the subscription wording change. If a detail is not in the code, it is not in this post.
What Is an Accountant Client Portal?
It is the part of a finance product that lets one professional work across many clients’ records from a single login. The client keeps ownership of their data and decides who can see it. The accountant gets a list of clients, their status, and a way to do the work that normally requires screenshots, shared passwords or a spreadsheet emailed back and forth.
Before this release SpendifiAI had exactly one kind of user: a person managing their own money. That works for a freelancer or a household. It does not work for the person who does their taxes, who might have forty of them.
Why a separate user type instead of a permission flag
The commit adds a user_type column (personal or accountant) and a company_name to the users table, backed by a UserType enum. An EnsureAccountant middleware returns a 403 to anyone who is not an accountant, so the client-management routes are closed at the door rather than hidden in the interface.
A flag bolted onto the personal account would have leaked into every screen. A type lets the navigation, the dashboard and the data rules branch once, at login, and stay clean as the accountant side grows. That growth came three weeks later, in the v2.0 tax document vault and firm portal.
Signing up as an accountant, including with Google
The registration page now asks “personal or accountant” above the Google button, so the choice applies to both email sign-up and Google sign-in. For Google, the choice is stashed in the browser before the OAuth redirect and sent to a new PATCH /api/auth/user-type endpoint when the user comes back. Small detail, but without it every accountant who signed in with Google would have landed as a personal user.
Shared password
how most small practices still do it
- Who logs in
- The accountant, as the client
- Client control
- Change the password and hope
- Record of access
- None
- Two-factor
- Breaks, or gets turned off
- Ending access
- Awkward conversation
SpendifiAI accountant link
commit 04f7dab
- Who logs in
- The accountant, as themselves
- Client control
- Accept, decline or remove in Settings
- Record of access
- Every session in an audit table
- Two-factor
- Untouched; the client never shares it
- Ending access
- One click by either side
How Does an Accountant Get Access to a Client’s Account?
Access is a relationship with a status, stored in an accountant_clients table that records the accountant, the client, whether the link is pending or active, and who sent the invite. The sequence is the same every time:
- Invite The accountant enters the client’s email. SpendifiAI sends a branded invite email that lists the permissions being granted and links to the client’s Settings page.
- Decide The client accepts or declines from Settings. Nothing is visible to the accountant until they accept.
- Work Once active, the client appears in the accountant’s client table with a status badge, and the accountant can view the account, pull a tax summary, download tax exports or trigger a bank sync.
- Manage The accountant can resend or rescind a pending invite. Either side can remove the relationship later.
Clients can also go the other way: search for an accountant already on SpendifiAI and add them. The accountant’s “my accountants” list only returns active relationships; pending ones come from a separate invites endpoint. That split exists because the first version showed the same accountant twice, and the fix shipped in the same commit.
The client dashboard an accountant sees
The new Accountant/Clients page is the accountant’s home: stat cards across the top, search, an invite modal, and a table of clients with status badges. Tax downloads for a client carry the client’s name in the file (for example John_Smith_Tax_2026.xlsx) so forty downloads in a Downloads folder stay sortable. Those exports reuse the same tax export service individual users already had, so an accountant gets the Schedule C mapping clients see, not a second implementation that can drift.
Is Client Impersonation Safe?
It can be, if three things are true: the accountant is never actually logged in as the client, the session can be told apart from the client’s own, and every entry is written down. SpendifiAI’s ImpersonationController handles all three.
- Separate tokens. Starting impersonation issues a new Sanctum token named
impersonate:{accountant_id}. The client’s own tokens are never touched, and stopping impersonation throws the temporary one away. - Unmissable interface. An
ImpersonationBarsits across the app while it is active, and the shared page data carriesisImpersonatingand the accountant’s ID so every screen knows the context. - An audit table. Starts and stops are written to
accountant_activity_logs, a table created for exactly this purpose.
Letting a professional see what the client sees saves hours of screenshots. Doing it without a record would turn a convenience into a liability. The audit table shipped in the same commit as the feature, not as a follow-up.
The route-ordering bug worth knowing about
One line in the commit message is the kind of thing that bites every Laravel team once: /impersonate/stop had to be declared before /impersonate/{client}, or the router treats the word “stop” as a client ID. If you build impersonation in Laravel, put the literal routes first.

What Else Shipped: Consent, Activity and the Sync Digest
Lines added in commit 04f7dab, grouped by feature
- 1,774 accountant, clients, impersonation
- 1,572 cookie consent and admin
- 1,090 settings, auth, routes, types
- 650 activity tracking and sync digest
Cookie consent by region, with Google Consent Mode
A finance app asks people to connect their bank. It should not then drop analytics cookies without asking. The consent system stores each choice in a cookie_consents table, knows three regions (European Union, California, and everywhere else), and drives Google Consent Mode from a dedicated component so tags only fire after the visitor agrees. Admins get a consent page with statistics, search, per-user history and a revoke action.
Activity tracking and an AI sync digest
A TrackUserActivity middleware stamps last_active_at on each user. That timestamp feeds a new sync digest: a queued job that emails users a summary of their recent bank syncs, written by a SyncSummaryService that uses the AI layer to turn a list of new transactions into a short account of what changed. The accountant audit trail is a separate table; activity tracking is about knowing who is active, not what an accountant did.
Why “Overdue” Became “Expected Any Day”
The second commit, 477bf05 on 9 March 2026, is three files and 46 lines. It may matter more to users than the rest put together.
The dashboard’s upcoming-payments carousel used to show a red Overdue badge whenever a subscription’s expected date passed without a charge. But the user is not overdue on anything. Merchants bill late all the time. The badge was accurate about the calendar and wrong about the situation, and it made people think they had missed a payment.
| Situation | Before | After 477bf05 |
|---|---|---|
| Active subscription, expected date passed, no charge yet | Red “Overdue” | Violet “Expected any day” |
| Cancelled subscription, still inside its billing window | Disappeared from upcoming payments | Shown with a “Cancelled” badge |
| Cancelled, 14 days past expected date with no charge | Already gone | Drops off: no recharge confirmed |
| Payment has cleared | Could linger | Filtered out once the last charge date passes the expected date |
Showing cancelled subscriptions on purpose
Hiding a cancelled subscription the moment you cancel it looks tidy, but cancellation is exactly when people worry about one more charge. Keeping it visible with a “Cancelled” badge, and dropping it only after 14 days with no recharge, answers the question the user actually has: did it really stop?
In March the accountant was a single person, not a firm, and there was no document exchange. Firms, branded invite links, document requests and annotations arrived in v2.0 on 31 March. The consent system does not replace legal review of your own cookie policy.
What It Takes to Build an Accountant Portal
If you are adding professional access to your own product, the checklist from this release is short and none of it is optional:
- A real user type and a middleware gate, so professional routes are closed by default.
- A relationship table with a status, so access is invited and accepted, never assumed.
- Impersonation on its own token, with a visible banner and an audit row for each session.
- Exports that reuse the individual user’s code path, so the accountant and client never see different numbers.
The code for all of it is public. Read commit 04f7dab if you want to see the whole thing in one diff.
SpendifiAI
Source code for SpendifiAI, the AI-powered personal finance platform.
Frequently Asked Questions
What does "Expected any day" mean on a subscription?
The subscription's usual billing date has passed and the charge has not appeared yet. It does not mean you missed a payment; merchants often bill a few days late. It replaced the old "Overdue" label in March 2026.
What is an accountant client portal?
It is a login for accountants and bookkeepers that shows every client they work with in one place. The client grants access and can remove it; the accountant never needs the client’s password. In SpendifiAI it includes a client table, invites, tax exports and audited account viewing.
How do I give my accountant access to my SpendifiAI account?
Your accountant invites you by email, or you search for them and add them. You accept the invite in Settings, and from then on they can view your account and download your tax exports. You can remove the connection at any time from the same page.
Can my accountant see my bank password?
No. The accountant uses their own login and a separate temporary session to view your account. Your bank connection runs through Plaid, and SpendifiAI never shows bank credentials to anyone.
What is client impersonation in accounting software?
It lets a professional see the client’s account exactly as the client sees it, to answer questions or check categories. Done safely, it uses a separate session, shows a visible banner, and writes each session to an audit log, which is how SpendifiAI implements it.
What does “Expected any day” mean on a subscription?
The subscription’s usual billing date has passed and the charge has not appeared yet. It does not mean you missed a payment; merchants often bill a few days late. It replaced the old “Overdue” label in March 2026.
Why do cancelled subscriptions still show in upcoming payments?
So you can confirm the charge really stopped. A cancelled subscription stays visible with a “Cancelled” badge until 14 days past its expected date with no new charge, then it drops off.
Need professional access in your product?
I build multi-role platforms like SpendifiAI end to end: the data model, the audit trail and the interface. Tell me what your users need to share, and I will tell you honestly what it takes.