Two Separate Billing Surfaces
The platform exposes billing in two distinct places, and they do different jobs:
- InvoiceNinja sync (
/billing) — a read-only view of invoices and quotes pulled live from an external InvoiceNinja server, joined to portal customers and projects. - Recurring (PSA) billing (
/admin/billing) — the platform's own monthly billing engine that raises a bill per customer for managed RMM devices and billable ticket time, then emails a statement.
These are independent. InvoiceNinja is an external integration; the recurring billing engine is built into the platform.
InvoiceNinja Sync (/billing)
Where to find it: Sidebar → Billing or /billing. Requires the projects feature. If InvoiceNinja is not configured the page shows a "Connect InvoiceNinja" prompt linking to the integration page.
The platform does not host the InvoiceNinja accounting — InvoiceNinja remains the system of record. The portal is a read-only viewer: it lists invoices and quotes, and links out to InvoiceNinja for any edit.
Layout
The page shows KPI cards (invoice count + total, outstanding balance, quote count + total) and an Invoices / Quotes tab toggle. Each row shows:
- Number (links out to the InvoiceNinja edit page)
- Customer — links to the portal customer record if the InvoiceNinja client is mapped to a
PortalCustomer - Reference — the
po_number; links to the source project if it matches a project code - Status — Draft / Sent / Partial / Paid / Cancelled for invoices; Draft / Sent / Approved / Converted / Cancelled for quotes
- Amount and Balance
- An Open in IN ↗ button
There is no in-portal invoice editor, no "+ New invoice" button on this page, no per-customer invoices tab, no subscriptions view, and no status polling — the list is fetched live each time the page loads.
How the Link Works
Each portal customer maps to one InvoiceNinja client; the first invoice push creates the client if it doesn't exist. A billable project pushes an invoice or quote that references the project ID in the InvoiceNinja po_number field. A billable ticket pushes an invoice referencing the ticket ID.
Configuring InvoiceNinja
The integration is configured by env, not the UI:
| Env key | Purpose |
|---|---|
INVOICENINJA_URL |
Your InvoiceNinja install URL |
INVOICENINJA_TOKEN |
API token from InvoiceNinja → Settings → Account Management → API Tokens |
Set these in /opt/.env and restart the portal. The admin page is a connection tester — it calls InvoiceNinja and reports one of: not configured, connected (with the company name), or configured but unreachable (with the error). A Re-test connection button re-runs the check.
Recurring (PSA) Billing (/admin/billing)
The platform's own billing engine is what most MSPs use day-to-day. It runs a monthly billing job that aggregates, per customer:
- Per-device fees — every RMM device linked to the customer, charged at the customer's per-device rate.
- Billable ticket time — every time entry on a ticket belonging to the customer, that is marked
billable = true, and that is not already covered by a support contract's included hours. - Project time — billable time entries logged against a project, billed at the project's hourly rate.
The result is a single monthly statement per customer, emailed automatically if the customer has autoSend = true on their billing settings.
Customer Billing Settings
Per customer (/customers/[id] → Billing tab):
- Per-device rate — monthly charge per RMM device.
- Default hourly rate — fallback rate for time entries without a contract.
- Tax percentage — applied to the line-item subtotal.
- Currency — the customer's billing currency.
- Auto-send — whether the statement is emailed automatically or held for manual review.
- Billing email — the address the statement is sent to.
Running the Monthly Billing
The billing job runs on the 1st of each month by default. To run it manually:
- Go to Admin → Billing → Monthly run.
- Pick the period (usually the previous calendar month).
- Click Run now.
The page shows each customer's generated statement: subtotal, tax, total, line items, and a download link for the PDF. Statements with autoSend = true are emailed immediately; the rest are queued for manual review.
Refunding or Adjusting a Bill
Open a statement from the run list and click Adjust. You can:
- Edit individual line items.
- Add a credit note (negative line item).
- Re-send the corrected statement.
Original line items are never deleted — adjustments are appended, preserving the audit trail.
API Access (for external accounting integrations)
External systems (your accounting platform, a BI tool, a custom invoice export) can pull billing data through the platform API.
# Authenticated. Must be logged in as a user with billing access (ADMIN or above).
TOKEN=$(curl -s -X POST https://portal.kwgroup.org.uk/api/auth/login \
-H 'content-type: application/json' \
-d '{"email":"you@example.com","password":"...","mfaCode":"123456"}' \
| jq -r .accessToken)
# List InvoiceNinja-synced invoices (read-only, live from InvoiceNinja).
curl -s -H "authorization: Bearer $TOKEN" \
https://portal.kwgroup.org.uk/api/billing/invoices
# List platform-generated monthly statements.
curl -s -H "authorization: Bearer $TOKEN" \
https://portal.kwgroup.org.uk/api/billing/statements
# Get line items for a single statement.
curl -s -H "authorization: Bearer $TOKEN" \
https://portal.kwgroup.org.uk/api/billing/statements/<id>
The recurring billing engine runs in-tenant — a statement is scoped to its customer's
orgId. The API returns only statements for tenants the authenticated user is a member of.
See Also
- Helpdesk Time Tracking — the source of billable ticket time
- Email Configuration — required for statement delivery