Appearance
Printers
Overview
A printer is one type of device managed under Device Management. A printer device represents a network receipt printer that polls Upvendo for work: it is configured with an Upvendo URL carrying its own device token, and it asks the server for jobs on a fixed interval.
The direction is deliberate — Upvendo never dials out to a printer. The only poll protocol shipped today is Epson Server Direct Print: the printer POSTs to POST /api/print/epson/sdp, authenticated by the device token minted when the device was created (routes/api/print.php:39-48). Star CloudPRNT is planned as a sibling route ("Wave 2") and is not shipped (routes/api/print.php:32-36).
There is also a second, newer delivery path — the LAN print bridge, where an on-site POS, Kitchen Display or kiosk claims queued jobs and drives the printer over the local network. See The LAN print bridge.
Printers are not a standalone page. They are created and managed through the same Devices screen as Kiosk and Kitchen Display devices, by choosing the device type "Printer".
Key Purpose: Register a network receipt printer as a device so kitchen-ticket routing can point at it.
Purpose
This area lets you add a printer device: give it a name and a location, and pick the type "Printer". That is the whole create. The server mints the printer's poll token and the device is created already activated, so there is no activation code to type in and no URL shown on the create response.
Key Concepts
- Printer is a device type: Devices have a
typefield (DeviceTypesenum) with the valuesKiosk,Kitchen Display,POS, andPrinter. "Printer" is the value used for receipt printers. There is no separate receipt/kitchen/label printer classification in the device model. - Selectable by every merchant:
Printeris a live entry in the back office'sDATA_TYPE_OPTION_DEVICElist (src/constants.ts:41), listed last on purpose — after POS, Kiosk and Kitchen Display — because a printer is a peripheral rather than a terminal an operator works at. It appears in neitherPOS_REQUIRED_DEVICE_TYPESnorFIRST_PARTY_POS_REQUIRED_DEVICE_TYPES, so unlike Kiosk / POS / Kitchen Display the tile is enabled for every merchant (SelectDeviceTypeDialog.vue:47-50is the only path that disables a tile). - Adding one is open; provisioning one is not: creating a
Printerdevice is open to every merchant, but everything that turns it into a working printer —GET /devices/unconfigured-printersand the whole/devices/{id}/printer/*group (configure, pairing, test print, rotate, retire) — carries thefirst-party-posmiddleware, which 403s unless the tenant merchant haspos_provider === 'upvendo'(routes/api/backoffice/devices.php:40-41,:135,EnsureFirstPartyPos.php:47-53). That marker is written in exactly one place,FirstPartyPosProvisioningService::selectFirstPartyPos()(:83), but it has two callers:POST /back-office/pos/select-provider(PosProviderController.php:28) andPosDemoSeederService.php:254, which thepos:seed-demoandcreate:test-accountartisan commands drive. Only the first is gated for live merchants: while the provider is staged'test_only' => true(config/pos-providers.php:52),selectFirstPartyPos()403s a non-is_testmerchant in production (FirstPartyPosProvisioningService.php:70-77). So only test-flagged merchants can pass this gate self-serve — and a merchant seeded from the console holds the marker and reaches the whole provisioning surface, which is why this is staged rather than absent. - Create is name + location + type:
StoreDeviceRequestadds nothing for a printer (StoreDeviceRequest.php:75-91).mac_addressandprinter_profile_idwere both removed as inputs by Kitchen Routing v2. - The create response is
{id}only:CreatedDeviceResourcereturns just the device id for a printer — noactivation_code, no URL, and deliberately no token (CreatedDeviceResource.php:30-42). Revealing the URL + token to an operator is a separate, permission-gated provisioning surface. - Born activated with a server-minted token:
DeviceService::genDevice()mints a 64-character hex token (32 random bytes) and setsis_activated = truefor printers, because a printer has no app and therefore no activation-code screen (DeviceService.php:195-209). - Poll authentication: the token rides either as
?token=<token>or as the headerX-Printer-Token(AuthenticatePrinterToken.php:71,:153-159). The poll route is mounted outside every session/JWT/tenant group — the token itself selects the tenant — behindthrottle:printer-poll+printer.auth. - Configuration lives in config, not per device:
print.printer_base_url(PRINTER_BASE_URL, falling back toapp.url) is the origin written into the hardware at pairing, andprint.sdp_interval_seconds(PRINTER_SDP_INTERVAL_SECONDS, default 5) is how often the printer asks for work (config/upvendo.php:46-75). The old fleet-wideUPVENDO_PRINTER_URLwas removed (config/upvendo.php:41-44). - LAN discovery lives in the POS app, not the back office:
upvendo-posships@upvendo/capacitor-printer-discovery, which browses mDNS/DNS-SD over OS-native APIs to find LAN printers. It is discovery-only — no print, connect or configuration-write methods. Nothing in the back office performs discovery. - The MAC and IP arrive from a discovery report, never from a form — as a server contract; no shipped client sends one yet. The route is real and enforced, but nothing in
upvendo-posimports@upvendo/capacitor-printer-discovery(it is apackage.jsondependency only) andupvendo-kioskhas no printer-discovery code at all, so every statement in this bullet describes what the server accepts, not something a merchant's hardware does today. A register or kiosk on the LAN would POST what it found toPOST /pos/printers/discovery-reportorPOST /kiosk/printers/discovery-report(routes/api/pos.php:99-100,routes/api.php:234-235). A report entry may carry only seven keys —mac,ip,model,device_name,serial,paper_width_mm,mac_source(DiscoveryReportRequest.php:54) — and any eighth key fails the whole batch with a 422 naming the accepted list (:72,:106-108).macis the only required one (:73);paper_width_mmmust be58or80(:90); a batch may carry at most 16 printers (PrinterProvisioningService.php:171). The reports are rate-limited bythrottle:printer-discovery— 4/minute and 60/hour per device credential, plus 120/minute per client address (AppServiceProvider.php:291-303). - MAC provenance —
mac_source: each sighting says how it learned the MAC↔address binding. Three values (PrinterMacSource.php:44-53):ipp-uuid(the_ipp._tcpTXTUUIDtail, cross-checked against the mDNS hostname — how an Epson is found),star-http(Star's unauthenticated identity page, reached after a TCP 9100 sweep — how a Star would be found, because a Star advertises nothing on mDNS), andmanual(an operator typed the address). Onlyipp-uuidhas a client behind it. The POS discovery plugin browses mDNS/DNS-SD only, its exportedMacSourceunion is literally'ipp-uuid'(packages/capacitor-printer-discovery/src/definitions.ts:45) andcorrelate.ts:210emits'ipp-uuid'or nothing — no shipped code producesstar-httpormanual. Both are values the server accepts, not mechanisms that run. The field is optional on the wire; an omitted value is written asipp-uuidbecause that is what every build predating the field actually does (:56-73,PrinterProvisioningService.php:581-584). A row that has no provenance stored reads back asnull— the read side never invents one (Device.php:582). - Why provenance is tracked: the stored IP used to be inert (a printer polls us, so nothing dialled it). It is now a delivery-control field, so a lower-ranked sighting may not change an address a higher-ranked one established:
ipp-uuidandmanualrank 2,star-httpranks 1 (PrinterMacSource.php:97-116). It may still confirm an address, and it may still supply one where nothing is recorded — that is how a Star gets its first address. On top of that, a configured printer takes an address change only from a device standing at that printer's own location; an unconfigured one still takes one from any site of the merchant, deliberately (DeviceRepository::refreshPrinterAddress,DeviceRepository.php:483-535). - A refused address claim is visible, not silent: when a change is refused, the claim (
ip,mac_source, reporting device, reporting location, timestamp) is written onto the printer asnetwork_info.address_conflictand surfaced on the printer resource (DeviceRepository::recordAddressConflict,DeviceRepository.php:576-604;PrinterResource.php:59-67), and logged atwarninglevel as "Printer address change refused" (PrinterProvisioningService.php:600-613). The two readings are both actionable: this printer is homed to the wrong location, or a device at another site is claiming it. - No subscription required: unlike Kiosk and Kitchen Display devices, a printer device does not open a Stripe subscription (
DeviceService.php:769-775).
Actions
Add a Printer
Create a device of type Printer: provide a name and a location. There is no profile step and no MAC-address step — the naming dialog goes straight to create (NewDeviceDialogs.vue:192-197). On success the back office shows PrinterAddedDialog (src/views/devices/components/dialogs/PrinterAddedDialog.vue), which confirms the printer exists and offers Close plus — only for a first-party-POS merchant — Manage stations (PrinterAddedDialog.vue:42-43, :103). The hand-off is hidden otherwise, because the Stations tab it leads to is itself first-party-pos-gated. It deliberately shows no poll URL and no token.
The old Printer Activation / Printer Configuration dialog (
PrinterActivationDialog.vue, with a URL box, Copy and a stubbed Send button) was deleted. Do not point a merchant at it.
Route the printer to a station
A newly created printer does nothing until a KDS station routes tickets to it — that is the primary action on the post-create dialog, and it is the printer's real configuration in this program. Station routing lives on the Devices page's Stations tab (src/views/devices/components/stations/StationsTab.vue).
Manage Printer Profiles
Printer profiles now have their own back-office page: Device Management → Printer Profiles (/device-management/printer-profiles). It is gated on the device-profile CASL subject — the same VIEW/CREATE/EDIT/DELETE_DEVICE_PROFILE permissions the kiosk and KDS profile families use, with no new permission slug — plus firstPartyPosOnly, because the route group also carries the first-party-pos middleware and the merchant-owner wildcard grants the profile permissions to every merchant (so the permissions alone are not a first-party check).
Full CRUD at routes/api/backoffice/printer-profiles.php:37-53: GET / (list), POST / (create), GET /options, GET /{id}, PUT /{id}, DELETE /{id}.
A location is a precondition, not a filter. The list endpoint requires location_id, so with no location chosen the page shows a hint and never calls the API.
The table lists Name, what it prints, paper width, column mode, assigned device count and last-updated.
What a profile describes
A profile describes a printer — what it prints, its paper, its column mode, its cut. It does not route tickets (that is a station's sinks).
| Setting | Values | Default |
|---|---|---|
Prints (prints) | kitchen_ticket, receipt — a profile may hold both | — |
Paper width (paper_width_mm) | 58, 80 | 80 |
| Column mode | 48/35, 42/32 | 42/32 |
| Cut mode | per_ticket (the only mode shipped) | per_ticket |
- There is deliberately no
fiscal_receiptcapability. Whether a customer receipt is a signed VAT receipt is a property of the location's regime, decided at render time — a per-printer choice would be a second source of truth and, at a mandated site, an ungated receipt channel bypassing the fiscal device. One label, every country. 48/35is the hardware's factory setting;42/32is the platform default. The default is the budget that fits under both modes, so an undeclared profile keeps printing exactly what it printed yesterday.- Dual-role printers are supported. A profile carrying both capabilities stores
roleas the array['kitchen', 'receipt'], so both legacy single-role queries still match it by containment.roleis derived at the persistence gate, never caller-supplied — the repository throws if a caller supplies one, so the stored pair can never disagree withprints. A request may still sayrole: receipt_and_kitchenas a compatibility alias; that word is mapped server-side and never stored. - Receipt routing is ambiguous by design when more than one printer can print receipts. The page scans the whole location (not just the visible page) and says so: the paying drawer's
kick_binding.printer_device_idwins, otherwise the oldest candidate. Two tills with two drawers is a legitimate layout, so this is an explanation, never a constraint.
Binding a profile to a printer
Assignment is not done from this page, and not through the Devices profile dialog. DeviceService::assignProfile() has no printer branch — it writes only for Kiosk and Kitchen Display — and the back office agrees: deviceTypeHasProfile() returns true only for Kiosk and Kitchen Display (src/utils/deviceProfileFamily.ts), so a printer device has no "Assigned profile" card in its drawer at all.
A printer's profile is bound during pairing/provisioning: PrinterPairingOrchestrator::configure() binds the chosen printer_profile_id when one is supplied, and otherwise derives a default from the paper width the hardware reports at configure time (app/Services/Orchestrators/PrinterPairingOrchestrator.php:169-180).
Print a test page
There is now a merchant-facing test print — the first print path outside the pairing flow and the dev/e2e route (PrinterProvisioningController.php:144-167). It exists on both mounts:
- back office:
POST /back-office/devices/{id}/printer/test-print, permissionEDIT_DEVICES, inside thefirst-party-pos-gated/printergroup (routes/api/backoffice/devices.php:135,:147-148); - register:
POST /pos/printers/{id}/test-print, staff PIN session only (pos.staff) — no manager escalation (routes/api/pos.php:108). Configure and pairing-session demand a manager approval; the test print does not, because it mints nothing and discloses nothing, and it is how an operator tells two identical units on one counter apart (routes/api/pos.php:89-97,PrinterPairingOrchestrator.php:268-284).
Either way a human principal is required — a bare device token is refused, and on the register the device must be a POS one, so a kiosk or KDS token cannot queue a test print (PrinterPairingOrchestrator.php:440-486). Each press enqueues a fresh job (a new idempotency key per call), so pressing twice yields two pieces of paper (:276-284). The printer must already be configured: discovered, pre_registered and retired printers are unroutable and answer 404 (DeviceRepository::retrievePrinterAtLocation, DeviceRepository.php:950-972, filtering on Device::UNROUTABLE_PROVISIONING_STATES; Device.php:225-229).
No back-office button ships for this. Nothing in
upvendo-backofficeat the production pin references a test print, andupvendo-poshas no printer screens at all — the whole discover → configure → pair → test-print surface is API-only today.
Rename the Printer
Update the device name via PUT /back-office/devices/{id} (the update request only permits changing name).
Location
- Backoffice Route:
/device-management/devices(route namedevice-management-devices; nav: "Device Management" > "Devices"). There is no dedicated/devices/printersroute — printer devices are managed within the Devices page by device type. - Printer Profiles Route:
/device-management/printer-profiles(route namedevice-management-printer-profiles; nav: "Device Management" > "Printer Profiles"). First-party-POS merchants only. Components:src/views/printer-profiles/PrinterProfiles.vue(list) andPrinterProfileForm.vue(editor), page entrysrc/pages/device-management/printer-profiles/index.vue. - Backend Routes: prefixed
/back-office/devices, handled byapp/Http/Controllers/Api/DeviceController.php(there is noPrinterController). The printer's own poll route isPOST /api/print/epson/sdp(routes/api/print.php), and printer provisioning/pairing lives under/back-office/devices/{id}/printer/*andPOST /pos/printers/*. - Vue Component:
src/views/devices/Devices.vue(page entrysrc/pages/device-management/devices/index.vue). Printer-specific dialogs live undersrc/views/devices/components/dialogs/—PrinterAddedDialog.vueis the post-create screen.
Fields
These are the fields actually accepted when creating a printer device. The model is a MongoDB document (App\RawModels\Device), not a SQL table.
Device Name
| Property | Value |
|---|---|
| Field ID | name |
| Label | Device / Printer name |
| Type | Text |
| Required | Yes |
| Validation | required, string, unique per merchant (UniqueInConnectionWithModel) |
Description: Identifier for this printer device. (not-verified-here: any max-length limit — none is set in StoreDeviceRequest.)
Device Type
| Property | Value |
|---|---|
| Field ID | type |
| Label | Device type |
| Type | Select |
| Options | POS, Kiosk, Kitchen Display, Printer (enum DeviceTypes) |
| Required | Yes |
| Validation | required, enum DeviceTypes |
Description: For a printer this is set to Printer. There is no receipt/kitchen/label sub-type.
Assigned Location
| Property | Value |
|---|---|
| Field ID | location_id |
| Label | Location |
| Type | Select |
| Required | Yes |
| Validation | required, string, exists in locations |
Description: Which location this device belongs to.
There are no other printer create fields.
mac_addressandprinter_profile_idwere deleted fromStoreDeviceRequestby Kitchen Routing v2 and are no longer accepted.SelectProfileDialog.vue:112-114still renders a required MAC field behinddeviceForm.type === 'Printer', but printers never reach that dialog — the naming step calls create directly (NewDeviceDialogs.vue:192-197) — so it is still dead UI and must not be documented as a step. A printer's MAC now arrives from a discovery report posted by a register or kiosk on the same LAN, never from a person typing it.The following fields appeared in earlier documentation but do not exist in the printer device model, request validation, or UI:
connection_type,ip_address,port,paper_width,auto_cut,print_logo,copies,category_ids, and anenabledtoggle. There is no per-printer category routing and no receipt/kitchen/label type. Connection details such as IP are surfaced read-only via device connectivity, not as editable form fields.
What a printer record reports back (read-only)
PrinterResource is the one shape both the register and the back office read (PrinterResource.php:31-71). It carries id, provisioning_state, location_id, name (null, not "", when nobody has named it yet) and a network_info block:
| Key | Meaning |
|---|---|
mac | The printer's MAC — the identity a report is matched on |
mac_source | How the sighting that supplied the address knew the printer: ipp-uuid, star-http, manual, or null for a record written before provenance was tracked |
ip | Last reported address |
model, serial, paper_width_mm | Hardware description; written at birth, and afterwards only while the record is still unconfigured |
last_discovered_at | ISO-8601 timestamp of the last sighting |
address_conflict | The last refused address claim (ip, mac_source, reported_by_device_id, location_id, at), or null |
The poll token is never in this shape — it appears in exactly one response anywhere, the sdp.url of a pairing session (PrinterProvisioningController.php:28-32, PrinterResource.php:15-19).
How a printer's status reads in the back office
A printer speaks its own status vocabulary, and it is deliberately not the tablet one. seen and unknown are not synonyms for online and offline: nothing polls a printer, so both words say only when Upvendo last looked.
seenis intentionally not the same green asonline— reachability is not readiness.unknownis intentionally not an alarm colour.- The last ticket's outcome outranks reachability. A printer that answers discovery but failed its last ticket reads as broken, not "Seen recently".
The device drawer renders a printer-specific panel (PrinterSection) against ShowDeviceResource.printer / .print_activity, and omits any field it has no value for rather than printing "Unknown". The Connectivity, tablet-schema and device-logs cards are off for printers: a printer carries a disjoint network_info schema on the same field and writes to no device-log collection, so those cards resolved to all-null.
One reading of the status lives in src/utils/deviceStatus.ts and is shared by the fleet-table chip, its colour and the status filter — those three drifting apart was the defect itself.
Print Job Output
The device model and back-office code do not define receipt/kitchen/label print-job templates or their contents. Receipt rendering and "when to print" timing for online orders are governed elsewhere — see Print Settings (PrintSettingOptions: pickup_time, when_order_is_placed) and the kiosk/POS receipt flows. (not-verified-here: exact on-paper layout and content, which is determined by the printer profile and device firmware, not by this feature's code.)
The LAN print bridge
There is a second delivery path alongside the printer's own Server Direct Print poll: the LAN print bridge. Whichever on-site screen is awake — a POS register, a Kitchen Display or a kiosk — acts as the site's printing agent, claims queued jobs and puts them on the LAN printer itself.
One bridge per site at a time. A lease is held per (merchant, location): the holder renews it on a ~20-second heartbeat and it retires after a 90-second TTL. Only the holder's claims are honoured. A client that is backgrounded or locked proactively releases the lease, which is the difference between a site that fails over cleanly and one that is silently asleep. The release is compare-and-set on the lease id, so it is a no-op for anyone who is not the holder — otherwise any screen at the site could run a release loop and stop the kitchen printing.
It is device-level, not staff-level, on purpose. Tickets must print when nobody is signed in — a kitchen printer that stops at the end of a shift because the last operator logged out is exactly the silent failure this exists to remove. (A Kitchen Display has no staff-PIN session at all, so a staff gate would exclude the highest-priority bridge class outright.) The compensating controls are all server-side: no lease, no claim; the printer set is derived from the device's own location; and a result is accepted only from the recorded claimant of that exact attempt.
A lapsed KDS stops bridging. The KDS mount additionally enforces an active subscription (402) — a device Upvendo will not serve orders to should not be the site's printing agent either.
First-party POS only, including the kiosk mount. The gate sits once on the shared route file rather than three times at the mount sites, and it resolves the merchant from the bound tenant database, so it works identically under a device JWT.
Routes (routes/api/print-bridge.php, mounted inside the POS, KDS and kiosk device-JWT groups, throttled by throttle:print-bridge keyed on the bearer credential, never the proxy egress address):
| Method | Path | Purpose |
|---|---|---|
| POST | /print-bridge/lease | Take or renew the site's lease |
| DELETE | /print-bridge/lease | Proactively release it (compare-and-set on lease id) |
| POST | /print-bridge/claim | Claim queued jobs for this location |
| POST | /print-bridge/jobs/{id}/result | Report the outcome of one attempt |
Business Logic
Device creation (token vs activation code)
Create device (DeviceService::genDevice)
│
├── type = Printer?
│ └── token = Device::generateSecureToken() // 64-char hex
│ is_activated = true // born active
│ API returns { id } // nothing else
│
└── otherwise (Kiosk / Kitchen Display / POS)
└── activation_code = Device::generateActivationCode()
API returns { id, activation_code }The poll loop
Printer ──POST /api/print/epson/sdp?token=<device token>──▶ Upvendo
◀──────────── job (or "nothing to print") ──────────Mounted outside every auth group; throttle:printer-poll then printer.auth resolves the device, binds its merchant's tenant database, and answers every failure with one indistinguishable 404.
No order still produces a print job — PrintJob::SOURCE_ORDER is declared and never emitted (PrintJob.php:102); order-driven ticket printing is Wave 2. But "nothing produces a print job" is no longer true. PrintJobService::enqueueKitchenTicket() has three callers (PrintDevController.php:64, PrinterPairingOrchestrator.php:342), and all three enqueue SOURCE_TEST (PrintJob.php:99):
1. dev/e2e POST /dev/print/test-ticket isTestEnv + e2e.auth — 403 in production
2. pairing test ticket …/pairing-session/confirm-write the ACK that gates pairing
3. merchant test print …/printer/test-print NEW — an operator pressing a buttonThe test ticket is rendered by the same price-free renderer and sized by the same printer profile a real kitchen ticket will use, so it proves the path that ships (PrinterPairingOrchestrator.php:331-349).
Customer Impact
- A connected printer is the target a KDS station's routing can point at.
- No subscription is required to add a printer device (unlike Kiosk/KDS).
Relations
Depends On
- Locations: Each device is assigned to exactly one location (
location_id, required).
Affects
- Devices: Printers are part of the shared Devices management surface.
Related Features
- Print Settings — controls when online-ordering tickets print (separate setting on the Online Ordering page).
- Locations
- KDS
- Kiosk
Business Rules
- A printer is a device of
type = Printer; the device type enum values areKiosk,Kitchen Display,POS,Printer. There is no receipt/kitchen/label printer type. - The
Printertile is selectable by every merchant — it carries neither the third-party-POS gate nor the first-party-POS gate. - Printer devices do not open a Stripe subscription; only Kiosk and Kitchen Display do.
- A printer create accepts
name,location_idandtypeand nothing else. - The create response contains only the device id. The poll token is minted server-side and is not returned.
- A printer device is created already activated (
is_activated = true), so there is no activation code and no code-entry step. - Updating a device only allows changing
name. - Printer-profile CRUD is first-party-POS-only: the whole
/back-office/printer-profilesgroup carriesfirst-party-poson top of the*_DEVICE_PROFILEpermissions. - A printer's MAC and IP are only ever written by a discovery report from a device on the same LAN. There is no field anywhere that lets a person type them into the create flow.
- A discovery-report entry may carry only
mac,ip,model,device_name,serial,paper_width_mm,mac_source; any other key rejects the entire batch (422), as does a batch of more than 16 printers. mac_sourceis optional; an omitted value is stored asipp-uuid. A record with no stored provenance reads back asnulland may be given an address by any source.- A
star-httpsighting may not change an address that anipp-uuidormanualsighting established. It may confirm one, and it may supply one where none is recorded. - Once a printer is configured, only a device at that printer's own location may change its address. While it is still unconfigured, any site of the merchant may.
- A refused address change is recorded on the printer as
address_conflictand logged; it is evidence for an operator and is never used to route a ticket. - The merchant-facing test print requires a human principal — a device token alone is refused — and on the register a
POSdevice specifically. The back-office mount needsEDIT_DEVICES; the register mount needs a staff PIN session but no manager escalation. - A test print only reaches a configured printer:
discovered,pre_registeredandretiredprinters are unroutable and answer 404. - Every printer provisioning route on the back office (
/devices/{id}/printer/*) andGET /devices/unconfigured-printerscarriesfirst-party-pos, so they 403 for any merchant whosepos_provideris notupvendo.
FAQs
- "What is the difference between a printer and a kiosk/KDS device?" They are all device types. A printer is created already activated with a server-minted poll token and its create response carries only the device id; kiosk and KDS devices return an
activation_codeyou enter on the device, and they require a Stripe subscription. Printers do not. - "How do I connect the printer?" Create the printer device, then have the printer's Server Direct Print settings pointed at Upvendo. The URL + token is revealed only through the permission-gated pairing flow — there is no back-office screen that displays it today, so this step is handled during on-site provisioning rather than by copying a URL out of the Devices page.
- "Do I set an IP address or port for the printer in Upvendo?" No. There are no IP/port/connection-type fields. Connectivity details are shown read-only on the device.
- "Do I need the printer's MAC address?" No. It used to be required and is not accepted any more — a printer create is name + location + type. The MAC is designed to be learned automatically, from a register or kiosk on the same network reporting the printers it can see. No shipped app sends that report yet, so on a current build nothing populates the MAC at all.
- "What does 'found by network scan' mean next to my printer?" That is the
mac_sourceon the record, and today you will only ever seeipp-uuidor a blank.ipp-uuidmeans the printer announced itself over mDNS, which is how an Epson is found — the only method any shipped client implements.star-http(scanning port 9100 and reading a Star's identity page) andmanual(somebody typed the address) are accepted by the server but no app produces them yet. The field exists so you can tell an announcement apart from a scan — a scan is easier for the wrong device to imitate. - "Why did my printer's IP not update?" Because a weaker sighting cannot move an address a stronger one set, and because once a printer is configured only a device at that printer's own location may change its address. The refused claim is kept on the printer (
address_conflict) with which device claimed it, from which site and when. The usual causes are a printer homed to the wrong location, or one flat network shared by two sites. - "Can I print a test page?" Yes — that is new. A printer that is already configured can be sent a test ticket from the back office (
EDIT_DEVICES) or from the register with just a staff PIN — no manager approval needed. Pressing it twice prints twice. Note that no button ships for it yet in either app; today it is an API-only capability. - "Can I route specific menu categories to a kitchen printer?" Not on the printer device — there is no
category_idsor category-routing field on a printer. Routing is a KDS station concern, and no order fires a printed ticket yet in any case (Wave 2).
Troubleshooting
Problem: Printer not printing
Possible causes:
- The printer's Server Direct Print settings do not point at Upvendo, or carry the wrong token.
- Printer offline / not powered.
- Paper out.
- Nothing was queued: no order produces a print job yet — only test tickets do (pairing, and the merchant test print).
Checks:
- Re-run the pairing/provisioning flow so the printer is written with the correct poll URL and token.
- Verify the printer's power and network connection.
- Refill paper.
- Send a test print to confirm the poll loop works end to end.
(not-verified-here: device-side diagnostics and firmware behavior.)
Problem: A test print returns "Printer not found at this location"
Possible causes:
- The printer is still
discoveredorpre_registered— seen on the network but never configured, so it holds no credential and cannot be a print target. - The printer was retired; a retired record keeps its name and location but its credential is revoked.
- The printer belongs to a different location than the register asking for the test.
Checks:
- Configure and pair the printer first; a discovered record is an invitation to set one up, not a working printer.
- If it was retired, the same hardware has to be discovered and configured again.
- Confirm the printer's location matches the register's.
Problem: The printer screen shows a refused address claim
Possible causes:
- The printer is homed to the wrong location, so the device that can actually see it stands somewhere else.
- Two sites share one flat network and a device at the other site is reporting this printer.
- A weaker source (a Star identity page) is trying to move an address that mDNS established.
Checks:
- Re-home the printer to the location it physically stands at.
- Read the conflict record — it names the claiming device, its location and the time.
- The corroboration a refused
star-httpchange waits for is amanualsighting — an operator typing the address. No shipped surface produces one today:configureaccepts no address field (ConfigurePrinterRequest.php:68-81) and the POS discovery plugin only ever emitsipp-uuid(packages/capacitor-printer-discovery/src/correlate.ts:210). Until that lands, clear a stuck claim by re-homing the printer to the reporting device's location.
Examples
Create a printer device (request)
json
{
"name": "Receipt Printer - Counter",
"type": "Printer",
"location_id": "507f1f77bcf86cd799439014"
}Create-device response (printer)
json
{
"id": "507f1f77bcf86cd799439310"
}(For non-printer device types the response instead contains activation_code.)