Documentation

Admin manual

Last updated · February 2026

1. Admin overview

This guide is for workspace owners and admins. It covers the controls that affect every user in the tenant — billing, branding, invites, and bank-connect setup.

  • Owner — root account, manages billing, can demote / delete admins.
  • Admin — manages users, branding, categories, and integrations.
  • Accountant — full read/write on financial data, no admin controls.
  • Viewer — read-only access to dashboards and reports.

2. Workspace & branding

The workspace is your tenant — the isolation boundary for every other resource. Branding carries through to every exported report.

  1. 01
    Open Settings → Workspace
    Update the workspace name. This appears as the title on every report PDF / Excel cover.
  2. 02
    Pick a brand color
    Hex color — used for emphasis blocks (Net Income, Total Liabilities + Equity, Net Change in Cash) and the reconciliation header.
  3. 03
    Upload a logo
    Up to 2 MB, base64-encoded and stored alongside your tenant record. The logo appears at the top of every PDF cover.
Reset to defaults
Clear both the brand color and logo to restore Financials Ready's default navy palette across all exported reports.

3. Users, roles & invites

Add accountants, bookkeepers, and stakeholders by invite. Every invite is single-use and expires.

  1. 01
    Open Team
    Sidebar → Team.
  2. 02
    Click 'Invite user'
    Pick a role (Admin, Accountant, Viewer). Owners cannot be invited — they are created at workspace signup.
  3. 03
    Share the invite link
    Copy the generated URL and send it to the recipient. The link works once and self-expires after the configured TTL.
  4. 04
    Manage existing users
    Toggle role or deactivate from the team table. The owner cannot be deactivated.
Seat limits
Your plan caps the number of active seats. If you hit the cap, deactivate an inactive user or upgrade your plan before issuing a new invite.

4. Billing & plan limits

Billing is handled via Stripe Checkout. Plans differ in bank account count, seat count, and statement history depth.

  • Starter — $29/mo: 1 bank account, 3 months history, 1 seat.
  • Pro — $79/mo: 5 bank accounts, 60 months history, 5 seats.
  • Business — $199/mo: unlimited accounts, 60 months, 50 seats.
  1. 01
    Open Billing
    Sidebar → Billing.
  2. 02
    Click 'Upgrade plan'
    Stripe Checkout opens in a new tab. After payment, your plan and limits update automatically.
  3. 03
    Download invoices
    Past payment_transactions are listed in the billing screen — click any row to view.
Failed payments
If Stripe declines a charge, your plan stays active until the end of the current period. We mark the workspace 'past_due' and email the owner.
Webhook signature validation
Every incoming Stripe webhook is signature-verified using STRIPE_WEBHOOK_SECRETfrom backend/.env. In production (with a real sk_live_* key), any request missing the Stripe-Signature header is rejected with a 400, and any bad signature is caught by the SDK's construct_event. In dev mode (sk_test_emergent) signatures are accepted permissively — this is intentional so local proxy replays work. Set the real secret in .env and restart the backend before pointing production traffic at the webhook endpoint.

5. Custom categories

The default category list covers most general-purpose businesses. Add domain-specific categories per workspace to match your chart of accounts.

  1. 01
    Open Settings → Categories
    Review the default list. Defaults cannot be removed but you can add custom ones below.
  2. 02
    Add a new category (Settings)
    Name and pick a group (Income, COGS, Expense, Other). The group decides which report bucket the category contributes to (e.g. an 'Income' custom category feeds Revenue on the P&L).
  3. 03
    Add a new category (Transactions page)
    Users can also create categories inline: on the Transactions page, click the + button next to the category filter, name the category, pick a group, save. It appears in every dropdown immediately and is tenant-scoped (every teammate sees it).
  4. 04
    Bulk re-categorize legacy data
    Use the bulk re-categorize action on the Transactions screen. Select rows via checkboxes, pick the new category from the bulk-action bar, click Apply.
  5. 05
    Bulk import via CSV
    The Transactions page 'Import CSV' button lets users paste or upload up to 500 rows with per-row category or a bulk 'Assign category' bar in the preview. Duplicates on (bank, date, description, amount) are auto-skipped. Column mapping is saved per bank in browser localStorage.
  6. 06
    Delete or bulk-delete rows
    The trash icon on any Transactions row (or the bulk-action bar's Delete button) removes the row(s) permanently. Reconciled rows are protected — un-reconcile first. Deleting also decrements the parent statement's counter.

6. Bank Connect setup (Teller MTLS)

Teller is the integration that lets your users link real bank accounts. It uses mutual TLS: your backend presents a client certificate and private key issued by Teller on every API call.

  1. 01
    Create a Teller application
    Visit teller.io → dashboard → create an application. Note the Application ID (looks like app_...) and download the certificate.pem + private_key.pem pair.
  2. 02
    Place the certs
    Drop the two .pem files into /app/backend/teller_certs/. The backend reads them at startup; restart after replacing them.
  3. 03
    Set environment variables
    In backend/.env, set TELLER_APP_ID, TELLER_ENVIRONMENT (sandbox/development/production), TELLER_CERT_PATH, TELLER_KEY_PATH, and (for webhooks) TELLER_SIGNING_SECRET.
  4. 04
    Verify the config endpoint
    Hit GET /api/bank-connect/config — it should return configured: true. If false, the message field tells you what is missing.
  5. 05
    Connect a bank from the UI
    Open Bank Accounts and click 'Connect a bank'. In sandbox mode the password is always 'password'.
Keep your private key secret
The private_key.pem grants access to every linked user's financial data. Treat it like a production secret — restrict file permissions, never check it into git, and rotate it via the Teller dashboard if you suspect compromise.
Sandbox vs production
Sandbox simulates banks without MTLS being strictly required. Development and production environments mandate MTLS — switch only after you have certificates issued for the target env.

7. Platform admin (cross-tenant)

Platform admin is a SaaS-wide superuser role for Financials Ready staff. Anyone whose email is listed in PLATFORM_ADMIN_EMAILS (server env, comma-separated) gets is_platform_admin=true on startup, plus a "Platform admin" link in the sidebar.

  • 8-KPI control room — total tenants, users, active subs, MRR, trial subs, bank accounts, statements, txns (last 30d).
  • Searchable tenant table with inline editors for plan (starter/pro/business) and subscription status (active/trial/comped/suspended/canceled). The comped status gives a tenant Business-plan privileges without billing — useful for partner accounts and giveaways.
  • Tenant detail drawer — click any row to open 4 tabs: Overview (KV + counts), Users (with platform-admin badges), Billing (Stripe transactions), Audit log (timeline of plan changes, impersonations, flag events).
  • Login as tenant — mints a 15-minute JWT marked impersonated=true and switches you into the tenant's owner session. An amber banner persists at the top of the app while active; "Exit support session" pops you back to your platform-admin login.
  • Export tenant data — generates an AES-256 password-protected ZIP of every record scoped to the tenant (12 collections + manifest + README). Password returned in X-Export-Password response header AND persisted in the audit log so a future admin can re-derive it. Used for GDPR Art. 15 / CCPA §1798.110 data-subject access requests.
Everything is audited
Every patch, impersonation, and export is appended to the admin_auditcollection with actor email + payload diff. The tenant detail drawer's Audit tab surfaces this timeline.

8. Demo tenants & background crons

Financials Ready exposes a public zero-signup demo endpoint used by the landing-page "Try the live demo" button, plus two APScheduler cron jobs that keep the database healthy. This section is for platform admins who need to reason about these background writes.

  • POST /api/auth/demo — unauthenticated. Mints a tenant with id = demo-<12-hex> , an owner user (email demo-<12-hex>@financialsready.app, random 32-byte password), one bank account, one statement, and ~240 seeded transactions over 90 days. Returns the same { token, user, tenant } payload as /auth/login. The tenant is placed on the business plan so all features are unlocked for the trial.
  • Rate limit: 5 demos per source IP per hour (sliding window, in-memory). Exceeding the limit returns HTTP 429 with a friendly retry-after message. Because the counter is in-process, the effective per-host limit is 5 × N-workers — fine for a preview button, but consider a Redis-backed limiter before a serious marketing launch.
  • Daily 3am UTC — dormant-user deactivation. Any non-owner, non-platform-admin user whose last_login_at is older than DORMANT_USER_DAYS (default 90) is flipped to is_active=false. An admin_audit row with action user.auto_deactivate is written per user. Owners are exempt so a tenant is never accidentally locked out.
  • Daily 4am UTC — demo tenant cleanup. Every tenant whose id starts with demo- and whose created_atis older than 24 hours is purged along with ALL child rows: users, bank accounts, statements, transactions, memberships, custom categories, bank connections, and prior audit entries. A final demo_tenant.cleanup audit row is written for each purge.
  • Daily 5am UTC — recurring rules materialisation. Every active row in recurring_rules whose next_run is today or earlier is materialised into a real transactions row (with is_manual=true) and the schedule is advanced by its cadence. If the current next_run's YYYY-MMappears in the rule's skip_periods, the row is advanced without creating a transaction (pause window). Duplicate protection: if a matching (bank, date, description, amount) row already exists (e.g. a statement upload created it first), the schedule still advances but no duplicate is inserted. Backfill loop caps at 24 iterations per rule to avoid runaway loops on malformed dates.
  • Weekly Monday 9am UTC — upload reminder push. Sends a web-push nudge to any owner whose tenant hasn't uploaded a statement in the last 30 days. Respects each user's push-subscription opt-in.
Manually cleaning up mid-day
Need to purge demos before the 4am cron? Run python -c "from routes_demo import cleanup_expired_demo_tenants; import asyncio; asyncio.run(cleanup_expired_demo_tenants())"from /app/backend to fire it on demand.
Demo users can't log in
The demo user's password is secrets.token_urlsafe(32) — effectively unguessable. Demos are strictly session-bound: once the returned JWT expires or the tenant is purged, that user is inaccessible. This is intentional.

9. Data lifecycle & deletes

Cascade rules to know about before you delete anything:

  • Deleting a bank account deletes all related statements and transactions.
  • Deleting a statement deletes all transactions that originated from it.
  • Deleting a transaction is now surfaced directly on the Transactions screen — via a per-row trash icon or a bulk-action bar "Delete" button. The parent statement's transaction_count is decremented automatically so exports and dashboards stay in sync.
  • Reconciled transactions are protected from deletion — the endpoint returns HTTP 409 and the UI disables the trash icon with a "Un-reconcile first" tooltip. This is a safety guard so a closed period never silently changes underneath you.
  • Bulk delete silently skips reconciled rows in the selection and reports them via skipped_reconciled — the un-reconciled rows are still deleted.
  • Deleting a user revokes their token and removes them from the team table; transactions they edited remain.
  • Deleting a tenant (workspace) is a support-only action and cannot be self-served.
  • Demo tenants (id prefixed demo-) are auto-purged after 24h by the daily 4am cron — see section 8.
Backups
Deletes are immediate. We retain MongoDB backups for 7 days — email support if you need a restore.

10. Security & compliance

  • Passwords are hashed with bcrypt at cost factor 12; we never store plaintext.
  • JWTs are signed with HS256 using the per-deployment JWT_SECRET. Tokens expire after 24h of inactivity.
  • Stripe API keys, Teller MTLS keys, and the LLM key live in backend/.env — never in client code.
  • Tenant isolation is enforced at the database query layer: every read and write is filtered by tenant_id.
  • Webhook signatures (Teller, Stripe) are verified before any state mutation occurs.

11. Troubleshooting

  • Statements stuck in 'parsing' — check the backend supervisor logs for parser errors. Restart with sudo supervisorctl restart backend if needed.
  • AI categorization returning 'Uncategorized' — your Emergent LLM key may be out of credit. Check Settings → Universal Key in the Emergent admin console.
  • Teller Connect button disabled — usually a missing TELLER_APP_ID or missing cert files. Use GET /api/bank-connect/config to confirm.
  • Stripe checkout opens but plan never updates — webhook signature failing. Check the webhook URL in Stripe matches your deployed backend AND that STRIPE_WEBHOOK_SECRET in .env matches Stripe's dashboard value. See section 4.
  • Statement preview says "No date column detected" — your CSV headers don't include a recognized date keyword. Cancel the preview, rename the column to Date or Posted Date, re-export from your bank, and try again.
  • Trash icon disabled on a transaction — the row is reconciled. Toggle the reconciled checkbox off first, then the trash icon becomes active.
  • "Try the live demo" returns 429 — you've exceeded the per-IP demo rate limit (5/hour). Wait, or bump DEMO_RATE_LIMIT in routes_demo.py. See section 8.
  • PDF export looks wrong — try clearing branding (Settings → reset color & logo) to isolate whether the issue is brand-color or layout-related.
Still stuck?
Email support@financialsready.app with the workspace ID (Settings → Workspace) and a short description. A finance-literate engineer triages every report within hours.