Overview Setup Guide Installation Usage CLI Reference API Architecture Use Cases Comparisons Benchmarks Troubleshooting FAQ
neuralmind.uk / Docs / Billing Runbook — quote to cash, manually Edit this page on GitHub ↗

Billing Runbook — quote to cash, manually

How a Team deal actually gets from an inbound email to a signed license file on the customer’s machine.

There is no payment processor integrated, and that is deliberate. Team is $29/user/mo across 5–50 seats on an annual contract — $1,740 to $17,400 a year per deal, sold by email. That is a signed-contract motion, not a self-serve one, and a checkout page would be machinery serving nobody until inbound volume justifies it. The pluggable payment broker is on ROADMAP.md; the licence portal is deferred until 3+ customers (see the honest-scope table in Tier2-Operator-Guide).

Until then this page is the billing system. Every step below is either a command in this repo or a click in a tool you already have.

Canonical terms — entity, price, contact — live in commercial-terms.json and are CI-gated. If a number here ever disagrees with that file, the file wins and this page is the bug.


Prerequisites (once, before the first deal)

What Where Check
Issuer private key NEURALMIND_ISSUER_PRIVATE_KEY_HEX in your shell, never in the repo The snippet below prints MATCH
Its public half Already embedded in the shipped package (neuralmind/tier2/license.py, fingerprint d23aeb5ae460fede)
An invoicing tool Stripe Invoicing, Wave, or your accountant’s system — dashboard only, no integration You can send an invoice from the entity’s name
Sales-tax position Confirmed with your accountant, not guessed See Tax

Confirm your private key is the one the shipped package will verify against — a mismatch here means every licence you issue fails activation:

python -c "
import os
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from neuralmind.tier2.license import _ISSUER_PUBLIC_KEY_HEX
pub = Ed25519PrivateKey.from_private_bytes(
    bytes.fromhex(os.environ['NEURALMIND_ISSUER_PRIVATE_KEY_HEX'])
).public_key().public_bytes_raw().hex()
print('MATCH' if pub == _ISSUER_PUBLIC_KEY_HEX else f'MISMATCH: {pub}')
"

The private key is the single point of failure for every paid customer: lose it and nobody can be renewed, leak it and anyone can mint licences against a public key that is already inside every installed copy. Keep it offline, keep a backup, and treat rotation as a planned exercise — LicenseValidator accepts a list of public keys precisely so a rotation can overlap.


The path

1. Qualify

Team buys seats beyond one (5–50), priority support, and an annual invoice. It does not unlock features — governance, audit and self-hosted all run on the free 1-seat licence. So the honest opener is: evaluate everything first, on the free tier, then buy seats when you have more than one person.

Under 5 seats, there is nothing to sell. Say so.

2. Quote

Prices come from the pricing table, not from memory:

python -c "
from neuralmind.tier2.pricing import load_pricing, calculate_price
p = load_pricing()
print(calculate_price(p, 'team', seats=12, term_months=12))
"

calculate_price returns the total for all seats over the whole term. Twelve seats on an annual term is 29 × 12 × 12 = $4,176. There are no volume discounts in the table and none should be invented in an email — if a discount is genuinely warranted, change the table and say why.

3. Agreement

Send LICENSE-COMMERCIAL.md as the template. It has to be executed before a licence is issued — the licence file records an agreement_version and an accepted_at timestamp on the assumption that it was.

4. Invoice

Raise the invoice in your invoicing tool, from Cheval-Volant LLC (d/b/a NeuralMind), Texas. Net terms are yours to set; annual-in-advance is what the tier is built around.

Nothing in this repo generates, sends, or reconciles an invoice. Do not issue the licence yet.

5. Issue — only after payment clears

export NEURALMIND_ISSUER_PRIVATE_KEY_HEX=...   # from your password manager

neuralmind license issue \
  --customer "Acme Corp" \
  --seats 12 \
  --term 12

The licence is written to ~/.neuralmind/<sanitized-customer-name>.json — for the above, ~/.neuralmind/acmecorp.json. Licences are only ever written inside ~/.neuralmind. --output can rename or relocate the file within that directory; a path outside it is refused, because the same guard stops a hostile customer name from escaping storage. Copy the file out afterwards if you want it elsewhere.

Terms accepted: 1, 3, 6, 12, 24, 36 months. Expiry lands on the calendar — a 12-month term issued on 28 August expires on 28 August the following year.

This also appends an issue record to the audit log and records the customer in ~/.neuralmind/customers.yaml with total_paid. That YAML file is your entire customer database right now; it lives on one machine and nothing backs it up. Copy it somewhere durable.

6. Deliver

Send the customer that JSON file and the two commands that consume it:

neuralmind team license activate ./acme-corp.json
neuralmind team license status          # confirms tier, seats, expiry

Then seats, which they administer themselves:

neuralmind team seats add someone@acme.com
neuralmind team seats list

If activation reports anything other than the expected tier and seat count, the file was altered in transit — reissue rather than debug it.

7. File

Keep, alongside the invoice: the executed agreement, the license_id from step 5, and the seat count. ~/.neuralmind/audit_log.jsonl is the append-only record of every issue, renew and revoke, and it is the thing you would hand an auditor.


Renewals

A lapsed licence is revenue lost quietly, so this is the one part of the path that does not rely on you remembering:

neuralmind license expiring              # anything due inside 60 days
neuralmind license expiring --within 90  # widen the window

It is read-only and needs no issuer key, so a scheduler can run it without holding anything sensitive.

Wiring it to a scheduler

The command is built to be consumed rather than read: the exit code alone says whether anyone needs to act, so a caller never has to parse output to decide whether to raise an alert.

Exit Meaning What a scheduler should do
0 Nothing due inside the window Nothing
6 Renewals due Notify — a conversation needs starting
7 Something already expired, or an expiry that cannot be read Escalate — revenue or data is already broken

--quiet prints nothing on exit 0, which is what makes it well behaved in cron (silence unless there is news). --json emits the full report — expired, expiring and unknown buckets, each sorted most-urgent first, with days_remaining per customer — for anything that wants to route by severity or render its own message.

Cron already mails whatever a job writes to stdout, so --quiet is the whole recipe — no output on a clean week, a report when there is one:

MAILTO=you@example.com
0 9 * * 1  neuralmind license expiring --quiet

If you would rather call mail yourself, capture the report first. Piping straight off || sends an empty message, because the report went to stdout and mail inherits cron’s empty stdin:

0 9 * * 1  report=$(neuralmind license expiring --quiet) || printf '%s\n' "$report" | mail -s "NeuralMind renewals" you@example.com

Exit 7 also covers a record whose expires_at cannot be parsed. That is deliberate: an expiry you cannot read is not a licence you can trust to alert on, so it escalates rather than passing silently.

Doing the renewal

Anything the report names gets a renewal conversation; invoice and collect exactly as above, then:

neuralmind license renew --customer "Acme Corp" --term 12

Renewal extends from the old expiry, not from today, so a customer who pays late does not silently gain free months — and one who pays early does not lose any.

A revoked licence cannot be renewed. Issue a new one.


Non-payment

neuralmind license revoke --customer "Acme Corp" --reason "non-payment, 60d past due"

This sets expiry to now and writes the reason to the audit log. It is not reversible by renewal, so use it when the relationship is actually over, not as a dunning nudge — for a late invoice, chase the invoice.


Tax — confirm before the first invoice

A Texas LLC invoicing software subscriptions, potentially to customers in the EU and UK, has a sales-tax and VAT position to establish. This is an accountant question, not a code question, and it is much cheaper to answer before the first invoice than to unwind afterwards. Nothing in this repo computes or collects tax.


What not to promise

From the do_not_market list in commercial-terms.json:


Known gaps

Gap Consequence today
No payment processor Invoicing and reconciliation happen entirely outside this repo
No alert delivery license expiring reports and signals via exit code, but nothing here sends the mail — a scheduler or autopilot owns delivery
Customer record is one YAML file on one machine No backup, no history, no second operator
No receipts or dunning Both are the invoicing tool’s job
No self-serve seat purchase Seat count changes mid-term mean a reissue and a conversation

Renewal alerting used to head this list, because it was the one that lost revenue silently; license expiring closes it. Of what remains, the single-file customer record is the most exposed — one machine, no backup, no second operator.


See also