Not legal advice
The register supports recordkeeping. It does not decide whether your clinic is compliant, whether a loss is “significant,” or which state rules apply.
The write path
Balances are decided on the server. Every change goes through one authenticated function, and the audit trail is append-only. This page shows exactly how that works — and what it does not claim.
The page you are reading can read the register. It cannot decide a balance or write a row.
Firestore security rules let clinic members read the register and deny every client write. There is no “save” path in the browser that can change a bottle balance, a log row, or an audit entry.
The only write path is a callable Cloud Function. It checks authentication, membership, the role’s capability, the staff PIN session, a rate limit, and an idempotency key before it loads the affected rows and hands them to the shared business-rule engine. The engine returns the mutated register; only the documents that actually changed are committed.
That architecture is the reason a balance can be trusted: it is computed from the register on the server, not typed into a field on a web page.
Rows are never hard-deleted. Voiding an entry writes a reversing correction row and flags the original. The original stays visible in the trail, and the correction explains what changed. This is how a register should handle a mistake — not by making the mistake disappear.
Why it matters: an auditor can see both the entry and the correction. A system that silently overwrites a row gives you a cleaner screen and a weaker record.
PIN hashes, staff sessions, idempotency records, and rate-limit records are denied to browser clients. The client never receives a PIN hash or a session secret; it receives the result of an authorized action.
The browser may hide a button, but the matrix is what the function enforces. A viewer cannot log a dispense by calling the API directly.
Source: shared/authz.ts · enforced by the Cloud Function
| Role | Read | Log | Void | Receive | File | Members | Settings | Owner |
|---|---|---|---|---|---|---|---|---|
| Owner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Clinic admin | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| Pharmacist | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | — |
| Technician | ✓ | ✓ | — | — | — | — | — | — |
| Read-only | ✓ | — | — | — | — | — | — | — |
“Log” covers dispense, waste, counts, and incident drafts. “File” covers PMP packets and Form 106 paperwork. Only an owner can change another member’s role or transfer ownership.
A trust page that only lists strengths is a sales page. These are the boundaries.
The register supports recordkeeping. It does not decide whether your clinic is compliant, whether a loss is “significant,” or which state rules apply.
DEA does not approve recordkeeping software. We do not display an approval badge, and no page on this site claims one.
We do not claim SOC 2, HIPAA, or “bank-level” security. We describe the actual controls: server writes, roles, PIN sessions, audit, and export.
The register can be filtered and downloaded as a portable record. That matters for two reasons: an inspection may ask for a period, and a clinic should never be trapped in a vendor to read its own history.
Attachments — invoices, Form 222 copies, PMP files, incident evidence — are stored server-side and readable by clinic members. Storage writes also go through the function, not the browser.
If the architecture, subprocessors, or access model changes materially, this page and the privacy policy will be updated before the change is described anywhere else. A security page that quietly goes stale is worse than no page.
The FAQ covers DEA approval, binder replacement, PMP filing, retention, and access — in plain language.
Read the FAQTwo minutes, sample data only, no account and nothing saved.
Already have an account? Clinic sign inPlans from $39/mo