spirekeeper is the operator side of a bitSpire ATM: it pairs machines, publishes their fee and cassette configuration, receives every cash-out's outcome, and distributes each settlement. Everything between you and a machine travels over Nostr relays — there is no HTTP endpoint on the machine and none it needs on you.
spire-seed QR. Show it to the machine's camera (an unpaired machine boots into the pairing wizard). Pairing gives the machine its signing identity through the bunker; no key is ever typed into the machine.The machine owns its bay layout and its running counts. How many bays it has is hardware; which denomination sits in each and how many notes are there is something only the person at the machine knows. So:
| Fact | Where it comes from | How you change it |
|---|---|---|
| Number of bays | The machine's installation (VITE_BITSPIRE_CASSETTES in its .env on first boot, then its own database). spirekeeper adopts whatever the machine reports and deletes bays it stops reporting. | Re-provision the machine. There is no dashboard control for bay count. |
| Denomination per bay | You. | Set denomination op. |
| Count per bay | The machine's running total: refills add, dispenses subtract. | You publish operations, never counts: Refill +N, Empty, Recount. A recount is the only absolute — it is what you do when you open the bay and count it. |
Never set a count from a form you loaded before a dispense happened — that is exactly why counts are not editable. If the number on screen is wrong, open the bay, count, and record a Recount.
Each operation carries an id the machine deduplicates on, and the machine echoes the ids it has applied back in its state report; an op shows as acknowledged only when the machine says so. The machine republishes its state on every change and on a heartbeat, so a dashboard that disagrees with a machine heals itself within minutes.
Payment is the authorization; the machine's dispense report is the capture. A customer's payment lands as awaiting_dispense and nothing is distributed until the machine reports what physically came out:
| Machine reports | Settlement | What happens |
|---|---|---|
| Everything dispensed | processed | Distribution runs — platform fee, your splits, DCA. |
| Some notes out, value short | partial_pending | Held whole until you record how the shortfall was resolved. |
| Nothing out | cash_owed | Nothing moves. The customer is owed. You are alerted. |
| No report within 30 min | dispense_unreported | The machine never said. Check it. |
Those last three are the first thing on the Worklist. Every one means a human is owed money or a machine needs eyes.
A terminal dispenser fault — a jam, a motor or sensor error, a dispense that never answered — makes the machine refuse further cash-out and show the customer that it is temporarily unavailable. The machine's card in spirekeeper shows a red Cash-out is held banner with the code and the reason. It stays held until:
Restarting the machine does not clear it: re-initialising a dispenser does not move a stuck note. See the error glossary for what each code means and what you will find inside.
Two ways, both audited, both close the machine's ledger as well as this one:
For a partial, Record the resolution opens the partial-dispense dialog pre-filled from the machine's own count of what came out: confirm it if you are writing the shortfall off (the settlement distributes the scaled amount), or use one of the two routes above if you made the customer whole.
When a settlement lands in cash_owed or partial_pending you receive a Nostr direct message (NIP-17) addressed to the pubkey on your LNbits account — a note to self, readable in any client that speaks NIP-46 (the aiolabs webapp, Amber, nsec.app). You do not need your private key for this. To receive alerts on a different identity, set Alerts pubkey in the platform settings.
On the machine, atm-reconcile re-derives each bay from its last recount plus refills minus dispenses and reports any gap. A bay that has never been recounted cannot be reconciled — its starting number is unrecorded. Recount every bay once after installation; after that, any gap is real.
Design record: bitspire docs/adr/004-cassette-state-synchronization.md and docs/adr/005-cash-out-dispense-outcome.md.