The day your product starts driving a device inside a customer's building, tenancy stops being a schema question. A tenant_id column on every table is necessary and not close to sufficient, because a device has a physical location, a person standing next to it, a set of credentials on disk, and a support ticket in your queue. Each of those is a place where one customer can see, or print, another customer's work.
Hardware drags tenancy out of your database
A row belongs to whoever your query says it belongs to. A device belongs to a place, the place belongs to a customer, and neither of those facts lives in your database.
So the device gets enrolled by a human who may work for the wrong customer: a managed service provider installs on behalf of twelve of them. It outlives your model of it, because re-imaging, renaming, and moving to a second site are all tenancy events. And it is the only part of your system a customer can physically touch, so it is the only part they can attack without an account.
This checklist comes from our own support tickets. It applies to print stations, and equally to POS terminals, scales, scanners, kiosks, and any agent a customer installs.
Every identifier should carry its tenant, and names are not identifiers
Scope every identifier to the tenant in the lookup path, not at query time. If device IDs are globally unique opaque strings, an authorization bug leaks one customer's device to another. If the tenant is part of the path, the same bug returns nothing.
And never use a human-assigned name as a key. Printer names and hostnames are set by whoever unboxed the hardware, they collide across sites, and they get renamed in the first week. RocketPrint printer IDs stay stable across renames, so a stored ID keeps working when the warehouse relabels "Front Desk" to "Pack Station 2".
Credentials belong to one tenant, and device credentials belong to one device
An API key should be readable in exactly one tenant's context, and a device token usable by exactly one device. Those are different properties and you need both: tenant scoping stops customer A's key from listing customer B's jobs, device binding stops a token copied off one machine from working on another.
RocketPrint is a remote printing API and desktop Station for software platforms: your backend POSTs a print job, and a small app beside the printer prints it, with no inbound networking, no drivers for raw label formats, and a durable job record. Each customer workspace has its own API keys, stations, printers, jobs, and users. Enrollment issues device-bound tokens, and a Station prints only jobs for printers it reported itself, on the account it signed in to. Keys are stored hashed and station credentials encrypted. More on those boundaries in our remote printing security review and the Station docs.
Enrollment is where tenancy is actually decided
Everything downstream inherits whatever enrollment got right or wrong, so spend the design effort here. A shared secret pasted into an installer is the common shortcut and the wrong one: it travels over email, it lands in a runbook, and it cannot be revoked for one machine.
The shape that holds up is a browser authorization. The installer shows a code, the operator approves it while signed in to the correct workspace, and the server issues a token bound to that device. The person proving which tenant owns the device is already authenticated as that tenant. That is the flow Station uses, and stations can be revoked individually afterwards.
Delegated parent access beats a shared login
Platforms need to see into their customers' workspaces. The temptation is an admin user in every tenant with the same email and password. Do not: that is one credential whose compromise is total, and it makes every parent action look like a customer action. Model the parent explicitly instead.
| Actor | Should see | Should not see |
|---|---|---|
| Workspace user | Their own printers, stations, jobs, users | Anything in a sibling workspace |
| Platform account | All child workspaces, under an identity recognizable as the parent | Anything after a workspace is terminated |
| Sibling workspace | Nothing | Each other, in any listing, search, or error message |
RocketPrint platform accounts get delegated access to the workspaces they provision, and sibling workspaces cannot see each other. The second half of that sentence is the important one, and it is worth testing with a real request rather than trusting.
Suspension has to be a state, not a delete
Customers stop paying, get acquired, fail a compliance check, or go dark for a season. If your only states are "active" and "gone", you will delete data you need. A suspended tenant should keep its records, refuse new work, stop billing, and come back with its identity intact. RocketPrint workspaces can be suspended and reactivated for that reason.
Also decide what a suspended tenant's hardware does: refuse, queue, or keep printing until the shift ends. There is no universally right answer, but there is a wrong one, which is finding out during the incident.
Support needs a door, and the door needs a log
Support will need to look at a customer's device state. Build that as a feature: an explicit "enter workspace" action, a visible banner while it is active, a bounded session, and a record of who entered, when, and why. The alternative is handing support the production database and hoping, which is a tenancy hole with a human in it.
The device view needs state that is not in your primary tables: is the agent connected right now, what version is it running, which printers did it report, what was the last job and what did it prove. Our job status reference exists mostly because support conversations kept turning on the difference between sent and completed.
A limit worth stating: we do not ship webhooks. A per-tenant event history inside your own product is something you build by polling the REST API, and that is work we have not done for you.
Billing attribution is a tenancy feature, not a finance chore
If you cannot attribute usage to a tenant at the moment it happens, you cannot invoice a reseller, cap a customer, or answer "why did this month cost more". Decide two things early: the billable event, and what makes a tenant active. RocketPrint bills completed print jobs and active customer workspaces, where a workspace is active if it had Station presence or a completed job that month. Failed jobs, canceled jobs, and idempotent replays are not billed, and copies on one job count once. There is no charge per printer, Station, API key, or user. Tiers are on the pricing page; the reasoning is in usage-based billing that only charges for success.
Note what that costs you as our customer: active workspace count is itself a billed unit. A workspace you provision for a prospect who never installs anything is free, but the moment a Station connects to it or it prints once in the month, it counts. So a workspace per pilot site is free only while the pilot has not started. Check how any vendor counts tenants before you design your provisioning around it.
Branding is per tenant because the device is in someone else's building
An agent on a warehouse PC shows up in the Start menu, the update dialog, and the tray. If it carries your vendor's name, your customer's staff learn your vendor's name and your support desk takes calls about software nobody recognizes. Branding is an isolation item, not a marketing one. Paid RocketPrint plans include one branded Station edition. One, not one per customer workspace, which is worth knowing before you promise a reseller their own logo.
Three anti-patterns that are cheap now and expensive later
One API key for all customers. It works until the first rotation, breach, or customer who asks for their own credential, and rotating it is an outage for everyone.
Copying admins into every tenant. One compromised password becomes full platform access, and your audit trail lies about who did what.
Treating the device as stateless. A redelivered instruction on a device with no memory of what it already did produces a duplicate physical object. Both ends need a record, which is the argument in how idempotency prevents duplicate labels.
The checklist
| # | Item | The question to ask |
|---|---|---|
| 1 | Tenant-scoped identifiers | Does an authorization bug leak a sibling's device, or return nothing? |
| 2 | Stable machine IDs | What breaks when a customer renames the hardware? |
| 3 | Per-tenant credentials | Can one customer's key be used in another's context, ever? |
| 4 | Device-bound tokens | Is a token copied off this machine usable from another? |
| 5 | Tenant-deciding enrollment | Who proved which customer this device belongs to, and how? |
| 6 | Delegated parent access | Is parent action distinguishable from customer action in the log? |
| 7 | Suspend without delete | What does a suspended tenant's hardware do right now? |
| 8 | Support entry with a trail | Who entered which workspace last week, and why? |
| 9 | Per-tenant usage attribution | Can you produce this month's usage for one customer today? |
| 10 | Per-tenant branding | Whose name is in the tray on the customer's PC? |
None of these is hard on its own. They are hard to retrofit, because each is a change to identifiers, credentials, or an installer already deployed in buildings you cannot walk into. If you are checking an existing setup rather than building from scratch, running two print paths side by side tests a tenancy model against real devices before you move anything.
END / multi-tenant-checklist-customer-hardware
More field notes →