KW Knowledge base

Billing & Invoicing

The two billing surfaces: InvoiceNinja read-only sync, and the platform's own monthly recurring (PSA) billing engine.

Mitch Wigham
Updated 24 June 2026 · 6 views

Two Separate Billing Surfaces

The platform exposes billing in two distinct places, and they do different jobs:

  1. InvoiceNinja sync (/billing) — a read-only view of invoices and quotes pulled live from an external InvoiceNinja server, joined to portal customers and projects.
  2. 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:

  1. Go to Admin → Billing → Monthly run.
  2. Pick the period (usually the previous calendar month).
  3. 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

Still need help?

Log a support ticket and the team will pick it up from this page.