Resort Buggy

Opening the guest app from your app

A guest taps a tile in your app. Your server asks ours for a fresh link; the tile opens it; she lands on the map already signed in, having told us nothing about herself and never having seen a code. One call, one clause.

Version 1 · Last updated: 2026-08-11

Clause one

Open our link in the phone’s own browser, never in an in-app webview.

On iOS that means SFSafariViewController. On Android it means Custom Tabs. Not a WKWebView, not an embedded browser component, not a frame.

When the guest first arrives, her phone stores a small credential that is how we recognise her device on night two without a code, a token or a login screen. Inside a webview that storage belongs to your app: it dies when your app is cleared, reinstalled or updated in a way that drops its data. The next time she taps the tile we have never seen that device before, so we create a brand-new guest with an empty history — her rides gone, her chat gone, her scheduled pick-up gone. No error is raised, and nothing in either system flags it: on our side it looks exactly like a guest arriving for the first time.

Open it in the system browser and none of that happens. She can reinstall your app and we still resume her, because the credential was never yours to lose.

We do not support being embedded in a frame

The same storage boundary decides this one. Inside a frame the browsers our guests actually use either partition our storage into a per-site box we cannot reach again or withhold our cookie altogether: the credential that remembers this guest never comes back, and every launch looks like a new guest. There is no allow-list to ask for — our pages carry Content-Security-Policy: frame-ancestors 'none'.

The tile is the supported shape: it looks like part of your app, it opens on top of your app, and the guest returns to your app when she closes it.

The call your server makes

One request, from your server, not the phone — the key must never reach a device. The key travels in the Authorization header and never in a query string — a request with the key in the URL is refused. It needs the handoff:mint permission, which a property’s administrator chooses when creating the key in Settings → Reporting API.

POST /partner/v1/handoff
Authorization: Bearer rb_live_xxxxxxxx_...
Content-Type: application/json

{
  "villaId": "ckv8h2p9x0001qz",
  "reservationRef": "ABC-9931",
  "checkoutAt": "2026-08-15T06:00:00Z"
}
villaId
Required. Our id for the villa, from GET /partner/v1/handoff/villas. Room numbers and villa names are not accepted.
reservationRef
Optional. Your own reservation reference for the party currently in that villa. We store a one-way hash of it, never the reference itself. An empty string is rejected.
checkoutAt
Optional. An ISO 8601 instant — the checkout time of the stay.
langHint
Optional. A hint, never an authority — see Languages below.
{
  "url": "https://app.resortbuggy.com/e/?t=eyJhbGciOi...",
  "token": "eyJhbGciOi...",
  "expiresAt": "2026-08-11T16:35:00.000Z",
  "villa": { "id": "ckv8h2p9x0001qz", "name": "Hill Villa 214" },
  "warnings": []
}

Mint on the tap, not in advance. The link is good for five minutes and it is not single-use, so it is a thing to fetch when the guest presses the tile — not something to cache against a stay, print, or email. Open url as it is given. token is the same credential on its own, for a caller who would rather build the URL themselves.

warnings is an array of plain sentences about things that are not errors and did not stop the link working — a language we cannot serve, for instance. Log it somewhere a person will read at setup. It is empty in normal operation.

Mapping your rooms to our villas, once

You hold room numbers; we hold villa ids. They are joined once, at setup, from this endpoint.

GET /partner/v1/handoff/villas
{
  "villas": [
    { "id": "ckv8h2p9x0001qz", "name": "Hill Villa 214" },
    { "id": "ckv8h2p9x0002ab", "name": "Beach Villa 07" }
  ]
}

We match on the id only. Two villas at one property can share a name, and names are edited by administrators — a match on a name or a room number can send a guest to the wrong door.

A retired villa is simply absent from the list. Re-read it after the property changes its map.

What the link carries, and what it does not

The token asserts a stay: the property, the villa, a one-way hash of your reservation reference, the checkout instant, and the language hint if you sent one. It carries no name, no email, no phone number, and no reservation reference in the clear.

The mint writes nothing; the first write happens at redemption, on the guest’s own request from her own phone. What she gets is a device — not an account and not a person. Two phones in one villa become two devices on the same stay and both see the villa’s rides, which is what already happens when a couple both scan the villa card.

A guest who arrives this way falls under the same retention as one who scanned the villa card: whatever name she gave, her contact details and her messages are deleted thirty days after the stay closes, and the ride records survive attached to nobody.

Checkout, and the next guest in that villa

When a link arrives for a villa whose open stay carries a different reservationRef, we close that stay and open a fresh one. Sunday’s arrival gets a clean app: none of Saturday’s rides, none of Saturday’s chat. Without a reference we fall back to closing a stay on silence, which in a continuously occupied villa can leave one party’s history in front of the next.

The stay closes six hours after the checkoutAt you send, so a late departure is not cut off mid-transfer.

If your system sends a wrong or stale reference and closes a stay while the guest is still in the villa, she loses the session and taps the tile again. She never loses her data — the deletion clock cannot start before the stated checkout has actually passed.

One case answers “no”: if the departing party is physically in a buggy at that moment, the turnover is refused rather than performed, and the tile is tapped again a few minutes later.

Languages, stated exactly

A langHint this deployment cannot serve is dropped, and the mint tells you so in warnings[].

Warnings are the only place this surfaces: a property sending "vi" gets a working link, a guest who reads English, and a 200, not an error. This is the sentence you will receive, word for word:

{
  "url": "https://app.resortbuggy.com/e/?t=eyJhbGciOi...",
  "warnings": [
    "langHint "vi" is not a language this deployment can serve, so it will be ignored and the guest will get English. This is not an error and the link works."
  ]
}

The hint applies on a first arrival only; a guest who has since chosen a language keeps her choice.

The screens run in nineteen languages. The guest picks hers and the app’s own buttons and labels follow. Messaging is a separate setting, translated live in fourteen — a message needs a translation model behind it, a button only needs the words written once. The entry screen the guest sees while the link redeems shows your property’s name, logo and colours.

One limit. If the guest adds our app to her home screen, the icon says Resort Buggy rather than your property’s name. A per-property icon is not built.

What can come back

400 · invalid_request
A field is missing or the wrong shape. The message says which one and what it wanted.
400 · key_in_query_string
The key was sent in the URL instead of the Authorization header. It is now in access logs you do not control — rotate it.
401
The key is missing, unknown, or revoked.
403 · insufficient_scope
The key is live but does not carry handoff:mint. Scopes are chosen when a key is created and are never added to a key that already exists.
404 · unknown_villa
No villa with that id at this property. One answer covers "no such id", "not a villa", "retired" and "belongs to somewhere else" — re-read the villa list rather than guessing which.
409 · villa_mode_off
The property is not running villa mode, so a villa handoff cannot be redeemed there.
429
Rate limit: 300 mints an hour, bursting to 30 a minute, per key.
503 · unavailable
A fault at our end; retry shortly.

A separate limit applies at redemption, on the guest’s request rather than on your call: ten new devices per villa per hour, counted across all of that villa’s stays — the same limit the villa’s own QR card is subject to. A guest who reaches it is asked to try again shortly.

Revocation takes effect on the next request, with no cache and no grace period. If a vendor’s contract ends on a Friday, the key stops working on the Friday.

What is not built

Single sign-on

There is no SSO, in any protocol. The handoff is how a guest arrives already signed in, and it is the only such path.

Webhooks

Nothing is pushed to you. Ride data is pulled, on your schedule, from the reporting API.

Writing to us

A partner never writes to our database. There is no endpoint that creates, edits or cancels a ride, a guest or a villa.

Looking a guest up

There is no endpoint that answers a question about one identified party — no lookup by reservation reference, no "is this guest active".

Ride data is available read-only from the reporting API. Guest support runs inside our app — nothing for your app to build or route.

Something here that does not fit how your app works? Tell us what the tile needs to do.