It works at the first location and returns an auth error at the other four
In short
A POS integration that works at one restaurant and returns authorisation errors at the others is almost never a code fault: POS platforms grant access per merchant location, by the person who controls that location, so 4 stores can be unauthorised while your code is identical everywhere. The repair is a credential inventory with an expiry column, and an alert that fires when a location goes quiet.
Key takeaways
- POS authorisation is granted per merchant location, by whoever administers that location — not once per brand.
- A 401 and a 403 are different diagnoses: credential not accepted versus credential accepted and not permitted here.
- The artefact that makes this visible in seconds is a credential inventory with grant date, scopes, expiry and last successful call.
- Rotating refresh tokens are single-use. Losing the replacement bricks a location silently until someone re-authorises on the terminal.
- Alert on a location going quiet, not on error volume. A store that stopped calling produces no errors at all.
The code is the same at every restaurant, so the difference is not in the code. POS platforms almost always issue access per merchant location rather than per brand or per company, and the grant is performed by whoever administers that location — often the individual owner or manager, not the group's head office. One store working and 4 failing usually means the authorisation flow was completed once, in a meeting, on a laptop, at 1 restaurant.
This is a commercial and operational shape rather than an engineering one, which is why it survives good code review. The platform is protecting a merchant's data from an integrator the merchant did not personally approve. That is the right behaviour, and the scope of what a platform will let you do once approved is a separate question, covered in what a POS partner programme actually gates.
The credential inventory almost nobody has built
Before diagnosing anything, build the table. It takes an afternoon, it is the artefact that makes every future instance of this a 30-second question, and its absence is why teams debug this by reading application logs at 8pm on a Friday.
| Column | What it holds | What it tells you when this happens |
|---|---|---|
| Location id and the platform's merchant id | Both identifiers, kept separately | Whether you are sending a location id where a merchant id belongs |
| Granted at, and by whom | Timestamp plus the person or account that completed the flow | Which stores never completed authorisation at all |
| Scopes granted | The exact list returned by the platform, not the list you requested | Why writes fail at 1 store while reads succeed |
| Access token expiry, refresh token expiry | 2 separate clocks | Which locations are about to go dark, before they do |
| Last successful call, last error code | Timestamp and status | Which stores have stopped calling rather than started failing |
Four stores that were never actually authorised
The commonest cause is the dullest. Somebody clicked through the authorisation flow at the pilot restaurant, the integration went live, and the remaining locations were listed in a project plan as "same setup". Nobody performed the grant, because the grant is a human action on a screen in the merchant's own admin, and it produces no artefact anywhere in your system when it does not happen.
The tell is in the inventory: a row with no grant timestamp. If you have not built the inventory yet, the equivalent check is to ask the platform which locations your application currently holds a grant for, rather than asking your own database what you think you were given. The full sequence from developer account to a live restaurant is set out in from a developer account to a live restaurant.
Scopes granted unevenly, and the error that will not say so
Authorisation screens change. A location authorised in March may hold a scope set that no longer matches what your application requests today, and a location authorised last week may hold more. Where a merchant admin can approve a subset of what you asked for, they will sometimes do so — usually declining the write permissions, which are the ones that matter.
- Record the granted scopes, not the requested ones. The 2 lists differ silently, and only the first is true.
- Test each scope at go-live rather than trusting the grant. A read that succeeds proves nothing about the write you need during service.
- Expect reads to outlive writes. Menu sync working while order injection fails at 1 store is the classic signature, and it is why the symptom often surfaces during the first real order rather than in testing — the same asymmetry that makes the full menu sync that never finishes a separate problem from writing checks.
- Do not paper over a missing scope with a fallback. Silently degrading to a mode the restaurant did not ask for is worse than failing loudly at 3pm.
A location id in the slot where a merchant id belongs
Multi-location platforms carry at least 2 identifiers: something that identifies the business and something that identifies the individual restaurant. They are frequently similar-looking opaque strings, and the pilot store is often the one where both resolve to the same thing, which is precisely why the bug survives the pilot.
Store both, in named columns, and never derive one from the other. The same discipline applies to every identifier you hold on the platform's behalf — the argument in the mapping table between your ids and the POS — and it is the reason your test orders sometimes land in a real restaurant's figures, as in your test orders are now in the owner's sales report.
The refresh token you rotated and did not persist
Many platforms rotate refresh tokens: each refresh returns a new refresh token and invalidates the old one. If your process crashes between receiving the new token and committing it, or if 2 workers refresh the same credential concurrently, one of them writes a token the platform has already retired. Nothing errors at that moment. The location simply stops working at the next refresh, hours later.
- Refresh proactively on a schedule, well before expiry, rather than reactively on the first 401 during a dinner service.
- Serialise refreshes per location with a lock, so 2 workers cannot both spend the same single-use refresh token.
- Commit the new token in the same transaction that marks the refresh complete, and keep the previous value for 1 cycle so a failed write is recoverable.
- Log every refresh with its outcome. A rotation failure is invisible in application logs that only record request errors.
- Treat re-authorisation as a supported operational path, not an incident: a one-click flow the restaurant's manager can complete without you, because eventually one of them will have to.
The owner revoked you from a screen you have never seen
Merchant admin consoles list connected applications with a disconnect button next to each, and restaurants use it: during a POS upgrade, when a franchisee changes hands, when a manager is tidying up, when a different vendor is being trialled. You will usually not be told, and there is often no callback to your system when it happens.
This is also why credential ownership should be settled in writing before launch — whose developer account, whose grant, what happens when the person who clicked through leaves — the subject of whose POS credentials are these. And it is a live risk during any POS change, which is why switching POS without taking ordering down treats re-authorisation as a scheduled step rather than a surprise.
Alert on silence, because a dead location produces no errors
Error-rate alerting misses this entirely. A location whose grant was revoked, or whose token expired between services, stops making calls — and zero calls generates zero errors. The monitor that works is a per-location heartbeat: expected activity in a window, measured against what the location actually did.
A restaurant that has stopped calling you looks identical to a restaurant that is quiet. Only the calendar knows the difference, which is why the alert has to be built on expected traffic rather than on failures.
Set the window from each location's own trading pattern rather than a global default — a site open 7 days is not a site open 5 — and route the alert to whoever can ask the restaurant to re-authorise, which is usually account management rather than engineering. That per-location asymmetry is the same reason configuration drifts across a group, as it does with tables as sellable inventory, and building the inventory, the heartbeat and the re-authorisation flow is ordinary internal tools and ops work.
One thing this page deliberately does not cover: how to roll an integration out across dozens of sites, in what order, with what sign-off. That is governance rather than diagnosis. Missed status changes during service are their own failure, handled in the POS says it sent the event and your system never moved. The rest of this silo sits under integrating with the POS on the counter, inside our restaurants and food service practice.
Frequently asked questions
Short answers to the follow-ups this page tends to raise.
Why does a POS API token work at one restaurant and not at another in the same group?
Because the grant is per merchant location, not per company. Most POS platforms require whoever administers each location to complete the authorisation flow for your application, so a group of 5 restaurants needs 5 grants. Identical code failing at 4 of them almost always means the flow was only ever completed at the pilot site.
What is the difference between a 401 and a 403 from a POS API?
A 401 means the credential was not accepted at all — missing, expired, revoked, or belonging to a different merchant. A 403 means the credential was accepted but is not permitted to perform that action at that location, which usually points at a scope you were never granted. Logging them as one category is the main reason this class of fault takes days to isolate.
How should refresh tokens be handled across many locations?
Refresh proactively on a schedule, hold a lock per location so 2 workers cannot spend the same single-use token, and commit the new token in the same transaction that records the refresh. Where a platform rotates refresh tokens, losing the replacement disconnects that location silently, and the only recovery is a human re-authorising on the merchant's own admin screen.
How do we find out that a restaurant disconnected our integration?
By monitoring for silence rather than for errors. A revoked grant produces no traffic and therefore no error rate, so the signal is a location that has not made an expected call inside its own trading window. Build the threshold from each site's opening pattern and send the alert to whoever can ask the manager to re-authorise.
- pos
- oauth
- multi-location
- diagnostics
The work behind this page
Builds from our portfolio that this page draws on.
Read next
- External IDs: the mapping table nobody designs until it breaksThe identity map between your catalogue and the till is the contract that makes every order possible, and 4 routine events break it silently. Design it as a versioned artefact.definition
- What a POS partner programme gates, and what it does notA partner programme is a commercial gate wearing technical clothing. It controls scopes, production credentials and listing — and the wait is somebody else's decision, not engineering.definition
- Order source and dining option: two fields your reporting rests onTwo small POS fields decide whether an injected order is taxed, routed and attributed correctly — and a wrong value is invisible until the first report nobody can answer.definition
- The full menu sync hits the limit before it finishesA sync that works on a 40-item menu and dies on a 400-item one is not too big. It is making one request per item, at the same minute as every other location.diagnostic
- The order lands in the POS and nothing prints at the stationThe POS accepted your order and the line never saw it. Routing is configuration — dining option, revenue centre, station map — and no field in your payload can override it.diagnostic
- The POS says it sent the event and your system never movedA status event that arrives after service is worth nothing. Five places it dies before your handler, and why the durable fix is a polling sweep over open orders rather than better webhook code.diagnostic
Working on something in this space?
Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.
Start the conversation