FurnFlowDocs
Open the app →
Docs › Admin › Admin, billing & security

Admin, billing & security

The control room for your whole workspace — locations, users, modules, billing, branding, operational settings, and the audit/security layer that keeps it all accountable.

🏢 Tenant admin

Left navAdminTenant admin

A quick-action grid jumps you to everything that configures how FurnFlow runs:

🏬 Branches

Your physical locations / showrooms.

📦 Warehouses

Stock locations that can serve one, several or all branches.

👤 Users

Invite people, set role, branch and access.

🔑 Roles & permissions

Groups & per-user grants — see Roles.

📦 Modules

Turn modules on/off and start 14-day trials.

🔌 API & webhooks

Issue API keys and subscribe to events.

🏬

Warehouses serve branches. This mapping is what lets FurnFlow know which stock can fulfil which branch's orders — set it up before you expect availability and transfers to behave.

💳 Billing, caps & trials

AdminTenant adminBilling
  • Live seats / branches / warehouses usage with at-capacity warnings; going over a cap is blocked until you upgrade.
  • Monthly spend history — click a month for a per-line breakdown.
  • Upgrade/downgrade your plan (it syncs your modules & caps); manage invoices, payments and dunning in the billing portal.
  • Trial higher-tier modules free for 14 days, then upgrade to keep them.
PlanUsersBranchesWarehouses
Starter511
Core2532
Operations7583
EnterpriseUnlimitedUnlimitedUnlimited

Card-free trial flow (default)

New tenants sign up without a credit card and get a 14-day free trial. The card is captured later — either by the operator before the trial ends or via a self-serve "Add payment method" CTA. Three things track this transition:

FieldWhereMeaning
tenant.has_card_on_filedb.tenantstrue once a paymentMethod is attached + confirmed (signup or in-app or first successful charge)
tenant.requires_card_bydb.tenantssame value as trial_ends_at until a card is added; cleared to null thereafter
tenant.lifecycle_statusdb.tenants"trial" → "active" (card added or trial converted) → "past_due" (failed invoice) → "suspended" (canceled)

Operator-facing: Global Admin → Licensing shows tier counts so you can spot at-risk trials. Tenant-facing: the Tenant admin → Overview shows a colour-coded countdown banner (🎁 blue → ⏰ amber → ⚠ red → ⛔ red) escalating as the trial winds down, plus a one-click "💳 Add payment method" CTA that opens an inline Stripe Elements modal.

Trial reminder emails (5-stage cadence)

Best-effort reminders fire at five stages relative to trial_ends_at. Each stage debounces via tenant_events so each tenant gets each stage at most once:

StageTriggerTone
T-77 days before expiryGentle heads-up
T-33 days beforeWarmer — "to keep things running"
T-11 day beforeUrgent — "last reminder"
T+0Day of expiry"Workspace is read-only until you add a card"
T+11 day past expiryFinal notice — "Reactivate now"

Triggered opportunistically on every GET /api/billing/overview read (so tenants who open Billing see them) and via POST /api/admin/run-trial-reminders for hands-off operation (wire to Azure Functions / GitHub Actions cron once a day).

Read-only mode on trial expiry without card

If a tenant's trial ends without a card on file, the enforceTrialActive middleware returns 402 trial_expired_no_card on write endpoints (POST / PUT / PATCH / DELETE). Reads stay open — the customer can always view their data. The SPA catches the 402 globally and shows a persistent red top banner with an "Add payment method" CTA that jumps to Billing where the Stripe Elements modal handles the recovery.

Exempt paths (must always work for self-recovery): /api/billing/add-payment-method, /api/billing/portal-session, /api/billing/overview, /api/me/*, /api/auth/*, /api/bootstrap, /api/tenant/license, /api/logout. Platform admins bypass.

Operators who prefer the legacy "card required at signup" flow can set SIGNUP_REQUIRE_CARD=true. The signup wizard then shows the 4-step flow with the Payment step, and the trial gate never fires (every tenant has a card from day 1).

🎨 Domain & branding

AdminTenant adminDomain & branding

Everything customers see can carry your identity, not FurnFlow's:

SettingEffect
White-label nameThe brand name shown to customers.
Logo & faviconYour marks across the portal & emails.
Primary & accent coloursTheme the customer experience — with a live preview.
Custom domainsServe the portal on your own domain.
Portal welcome & trackingThe customer-portal greeting & delivery-tracking copy.
"Powered by" toggleShow or hide the FurnFlow attribution.
Support, legal & social linksContact details and footer links customers see.

⚙️ Settings

Left navAdminSettings

Operational configuration — the rules the whole app follows. Sections start collapsed; expand only what you need.

💲 Currency, tax & locale

Currency (defaults to CAD for Canada), tax rates and tax display.

🏷 Pricing & discount policy

The discount caps that drive quote & order approvals.

🚚 Delivery & install fees

Base + per-km + per-item fee engine.

🗓 Scheduling defaults

Default windows & lead times.

📄 Quote & payment terms

Validity windows, deposit %, payment terms.

📣 Customer communication

What customers get notified about.

🔧

Changing a discount cap here instantly changes who needs approval in Quotes and Orders — Settings is the policy layer the operational screens obey.

🛡 Audit, security & access

AdminTenant adminSecurity / Audit log

FurnFlow is multi-tenant and audited by design, so your data stays yours and every sensitive change is traceable.

📜 Audit log

Money, permission & status changes record who, what, before/after and when.

🔐 Authentication

Email + password (securely hashed) issues a session token; integrations can use an X-API-Key.

🏢 Tenant isolation

Every record carries a tenant id; other companies' data is invisible.

🏬 Branch scope

Field roles see their branch; owners/admins/executives/finance see across all.

📚

That's the whole platform. Need a hand with something not covered here? In the app, use Send feedback in the account menu — it reaches the FurnFlow team and is tracked. Open the app →