SpotOnPOS DataStream (SpotOn Restaurant)
Written by AI, not reviewed by a human. Please apply the written-by-ai-not-reviewed label. The contract below comes from SpotOn's published documentation — no live SpotOn location has been exercised.
Integrates SpotOn Restaurant as a NexSigns DataStream source.
Source name |
|
Vendor API | SpotOn Restaurant POS Export API |
Docs | |
Status | CODE COMPLETE, UNVERIFIED LIVE |
Read this before selling it
SpotOn is the flattest of the fourteen POS sources by a wide margin. A menu item has a name, one price and a report category — and nothing else.
No images. No descriptions. No sizes or variants. No calories.
A SpotOn-driven menu board will be text and prices. That is a property of SpotOn's export API, not a gap in this integration, and it should be said to a customer before a design is promised.
The /modifiers and /modifier-options routes do exist — but neither carries a price, and neither carries any link back to the menu items it applies to. So there is genuinely nothing to fold in, and joining them would not produce a size dropdown. This is not an omission to be "fixed" later without SpotOn extending the API.
Configuration
{
"DataSource": "SpotOnPOS",
"Version": "1.0",
"ApiKey": "<Replace SpotOn API Key>",
"LocationID": "<Replace SpotOn Location ID>",
"Environment": "Production",
"IncludeOpenItems": false,
"IncludeUnavailableItems": true
}Field | Meaning |
|---|---|
| Sent as a single |
| The store. See the permission note below. |
|
|
| An "open item" is a placeholder the operator names and prices at the till ("Misc Food"). It has no price to show and no name worth showing, so it is dropped by default rather than rendering a blank row. |
| Applied after normalization. |
A connection-check endpoint reports the location a stream is pointed at:
GET /api/nexsigns/DataStreams/GetSpotOnPOSLocationInfo?DataStreamGUID=...Unlike the other five sources there is no "list my locations" call — every SpotOn route is location-centric and the key is granted per location — so this is a connection check rather than a picker feed.
API keys are granted per location
SpotOn grants an API key per location. A key that works everywhere else answers HTTP 403 for a location it has not been granted, and only SpotOn can grant it. So onboarding a second store is a support request to SpotOn, not a configuration change on our side.
Because that distinction matters operationally, 403 gets its own error message separate from 401 ("the key is wrong") and 404 ("the location does not exist").
API contract
Call | Purpose |
|---|---|
| Proves the key works and is granted this location; supplies the name and organization ID. Answers a single JSON object. |
| The board's categories. Hierarchical via |
| The menu items. |
The collection routes answer a BARE JSON ARRAY — no envelope object at all. That is the single biggest shape difference from every other source in this family, all of which wrap their data. The integration also accepts a data[]/items[] wrapper purely so a future enveloped version does not silently normalize to an empty menu.
There is no pagination on any reference route. SpotOn documents none, and its own best-practice guidance is to "retrieve all entities daily" — each call returns the location's whole collection in one response. So the sync is three plain GETs bounded only by wall clock and a per-request timeout.
Prices are decimal STRINGS
SpotOn documents standardPriceAmount as "a decimal number string representing US dollars" — so $1.40 arrives as the four characters "1.4". It is parsed once at sync time into a real JSON number, so the field resolver and the picker JS both read a number and can never disagree about how to read it. There is no divide-by-100 anywhere.
How the menu is normalized
{
"source": "SpotOnPOS", "schema_version": 1,
"location_id": "...", "location_name": "...", "organization_id": "...",
"environment": "Production", "currency": "USD",
"categories": [ { "id", "name", "parent_id", "parent_name", "sort_order" } ],
"items": [ { "id", "name", "plu", "category_ids": [], "open_item",
"sold_out",
"variations": [ { "id", "name", "plu", "pricing_type",
"price_amount", "sold_out",
"is_default" } ] } ]
}One synthetic variation per item
Every item gets exactly one unnamed variation carrying its price, and the picker hides the Variation row (it appears only below two entries). That is deliberate: the single-variation shape is what lets the Smart Menu Editor keep the same one-item-plus-size-dropdown UX across all fourteen sources — the same treatment a standalone CloverPOS item gets.
The variation's id deliberately equals the item id. There is no separate variant identity in SpotOn's export to borrow, and inventing one would put a value in a saved binding that nothing on the SpotOn side could ever be matched back to.
Categories
Report categories form a tree through parentId, flattened into a list carrying parent_name so the picker labels a subcategory "Parent / Child" — which also keeps two same-named children under different parents distinguishable. They carry no ordinal, so ordering is parent name then own name, which conveniently also groups a parent's children together.
Open items
An open item is projected with pricing_type: "VARIABLE" and a null amount, so if IncludeOpenItems is switched on the price binding renders blank rather than an error token — the same treatment as Clover's VARIABLE and OLO's OPEN.
Availability
Availability needs no opt-in flag here, unlike LoyversePOS, CloPOS, LightspeedPOS and Eats365POS. SpotOn exports an explicit available boolean on every menu item, set by the operator when they 86 something — and an explicit operator flag is safe to trust unconditionally. That is CloverPOS's treatment of its own available field, for the same reason. Absent information still means available.
Data fields available in the Smart Menu Editor
Name, Price, PLU, Inventory Status, Item ID, Location Name, Currency.
That is the complete list — seven fields, the shortest of the fourteen sources, and a direct consequence of what the export carries. Image, description and calorie bindings carried over from another POS source resolve to a blank rather than an error token, so a cross-source design degrades quietly.
Multi-location designs
Every SpotOn location exports its own menu item ids, so a design authored against location A would resolve to nothing against location B on the id alone. The picker records the item's plu and name into the binding's fallback key: PLU leads because it is the operator-authored product lookup code and the value a chain is most likely to keep identical across its stores; name is the last resort because a rename breaks it. There is no SKU or barcode in the export to sit between them.
Troubleshooting
Symptom | Likely cause |
|---|---|
HTTP 401 | The key is wrong, or |
HTTP 403 | The key is valid but has not been granted this location. Contact SpotOn — this cannot be fixed on our side. |
HTTP 404 | Wrong |
"response was neither a JSON array nor an object carrying data[]/items[]" | SpotOn has changed the response envelope. Worth reporting — the integration deliberately accepts both shapes. |
Board has no images or descriptions | Expected. SpotOn's export does not carry them — see the panel at the top. |
Prices all resolve |
|
Some items missing from the picker | They are open items (dropped by default) or 86'd with |
Known gaps
Everything in the panel at the top — no images, descriptions, sizes or calories.
Modifiers and modifier options are not projected, because they carry no price and no item link.
No webhook — hourly polling only, so a 86'd item takes up to an hour to disappear from a board. SpotOn's own guidance is daily reference-data refresh, so an hour is already more current than they suggest.
Currency is hard-coded to
USD. SpotOn Restaurant is a US product and the export documents its amounts in US dollars, so this is a statement of fact rather than a guess — but it is projected as a field so a future multi-currency export has somewhere obvious to land.
Verification status
Done: builds clean (0 errors); the editor partial parsed with the real Razor engine (0 parser errors) and verified pure ASCII.
Not done: no live SpotOn call; no offline assertion harness; no end-to-end walkthrough.
Because the shape is so simple, SpotOn is one of the quicker sources to validate — the whole contract is three GETs and nine fields. The one thing genuinely worth confirming on a live location is the decimal-string price format, since everything else is structurally trivial.