Resort Buggy

Reporting API

A read-only HTTP API that returns ride times, pick-up and drop-off points, which buggy ran each ride, and the wait figures behind them. Three endpoints, one API key, JSON. Your property's admin creates the key in the console.

Version 1 · Last updated: 2026-08-27

The key, and where it goes

An administrator at the property creates a key in Settings → Reporting API, gives it a label ("Power BI — finance"), and chooses what it may read. The key is shown once. A lost key is rotated: a new one is issued and the old one stops working immediately.

A key carries only the permissions chosen when it is created: rides:read for reporting, and handoff:mint for opening the buggy screen from the property's own guest app (documented separately).

The key travels in the Authorization header, never in a query string. A request with the key in a query string is refused with its own error code.

curl -H "Authorization: Bearer rb_live_xxxxxxxx_..." \
  https://app.resortbuggy.com/api/partner/v1/meta

The three endpoints

GET /partner/v1/meta

Requires: any live key

The property this key is bound to, its timezone, its own wait target, and what the key is allowed to read. Confirms the key works.

GET /partner/v1/rides/daily

Requires: rides:read

One row per calendar day: rides requested, completed, cancelled and timed out, plus the median and 90th-percentile wait and how many rides met the property's own target.

GET /partner/v1/rides

Requires: rides:read

One row per ride: its times, where it started and finished, how long the guest waited, how long the ride took, how many people rode, and which buggy.

Both ride endpoints take ?from=YYYY-MM-DD&to=YYYY-MM-DD, inclusive at both ends, as calendar dates on the property's own clock. With no dates you get the last seven days. Ask for at most 31 days at a time and page through longer periods.

A day is not final until the day is over. A ride still running when you pull will be reported with the status it had at that moment; re-pull the day afterwards to settle it.

GET /partner/v1/meta

{
  "apiVersion": "1",
  "resort": {
    "name": "Kunaavashi",
    "timezone": "Indian/Maldives",
    "waitTargetMinutes": 15
  },
  "scopes": ["rides:read"],
  "keyPrefix": "a1b2c3d4",
  "deprecation": { "noticeMonths": 12, "sunsetAt": null }
}

GET /partner/v1/rides/daily

{
  "from": "2026-07-01",
  "to": "2026-07-03",
  "timezone": "Indian/Maldives",
  "waitTargetMinutes": 15,
  "backfilledExcluded": 0,
  "days": [
    {
      "date": "2026-07-01",
      "ridesRequested": 42, "ridesCompleted": 39,
      "ridesCancelled": 2, "ridesTimedOut": 1,
      "boardedRides": 39, "withinTargetRides": 34,
      "medianWaitMinutes": 8.5, "p90WaitMinutes": 19.9
    }
  ]
}
date
A calendar day on the property's own clock, not UTC.
ridesRequested
Guest rides asked for on that day.
ridesCompleted · ridesCancelled · ridesTimedOut
What became of them. These need not add up to ridesRequested: a ride asked for at 23:55 may still have been running at midnight.
boardedRides
Guest rides that actually boarded that day — the denominator.
withinTargetRides
How many of those met the property's own wait target — the numerator.
medianWaitMinutes · p90WaitMinutes
Null on a day nobody boarded.

GET /partner/v1/rides

rideId
Stable identifier for the ride.
status
COMPLETED, CANCELLED, TIMED_OUT, or a live status if the ride was still running when you pulled.
scheduledFor
The promised time, if the guest pre-booked. Null for an on-demand ride.
requestedAt
When the guest asked.
dispatchedAt · acceptedAt · arrivedAt · boardedAt · completedAt
The ride's stamps, in ISO 8601 UTC. Any of them can be null — a cancelled ride never reached most of them.
waitMinutes
Request to boarding, one decimal. Null for a pre-booked ride — the guest did not wait from booking time to pick-up.
rideMinutes
Boarding to drop-off, as recorded.
passengerCount
How many people rode.
pickup · dropoff
The names of the two points, as the property named them.
vehicleLabel
The buggy's call-sign. Null if no buggy ever took the ride.

Making your numbers agree with the property's

We give you the numerator and the denominator, never a percentage. A daily "% met target" cannot be averaged across a month — a Tuesday with four rides would count as much as a Saturday with ninety. Sum withinTargetRides and boardedRides over whatever period you want and divide once.

Two clocks in one row. The counts (requested, completed, cancelled, timed out) bucket by the minute the guest asked. The wait figures bucket by the minute the guest boarded — the same clock the property's own wait card uses.

Every ride-bearing response carries backfilledExcluded: rides entered after the fact from a radio log, excluded from published figures here and in the property's own reports.

Staff errands are not guest rides and are excluded.

What is not on this API

Version 1 of this API reports rides, hours and utilisation per buggy. Per-driver figures are not part of version 1.

Driver productivity is reported in the ops dashboard.

Any guest, in any form

No name, no room or villa number, no reservation reference, no contact detail, and no opaque identifier that would let rides be grouped by person. There is no guest field on any row, so there is nothing to join a guest onto.

Any member of staff, in any form

No driver name, no staff identifier, no shift, no sign-on or sign-off time — and no opaque driver id either.

Messages, notes and ratings

Guest and host messages, booking notes, cancellation reasons and star ratings do not cross this boundary at any granularity.

Location traces

A ride is reported as two named points and a set of timestamps — no GPS trace crosses this API.

A request for one of these returns the reason above at a machine-readable "code": "not_offered". A query parameter that asks either ride endpoint to group by a person is refused, not ignored.

What happens when a guest's data is deleted

We delete a departed guest's name, contact details and messages. We do not delete the ride. A ride record — its times, its pick-up and drop-off points, its duration and its outcome — is retained as operational history and, after deletion, is attached to nobody.

Nothing you have already pulled is affected, and nothing you pull afterwards changes.

Versioning, and what we promise

The version is in the path. Everything documented here is version 1, at /partner/v1.

If version 1 is ever withdrawn, we will publish the date at least twelve months in advance, and version 1 will keep working throughout those twelve months. The date is machine-readable at /partner/v1/meta as deprecation.sunsetAt, which is null while version 1 is current.

Within version 1 we may add a field. We will not remove one, rename one, or change what an existing one means — so a report that reads the fields on this page keeps working. Parse leniently: ignore fields you do not recognise rather than failing on them.

Excel and Power BI

Paste this into Power Query, with the key in the header. The same snippet with the key already filled in is shown in the console at the moment the key is created.

let
    Source = Json.Document(
        Web.Contents(
            "https://app.resortbuggy.com/api/partner/v1/rides/daily",
            [Headers = [#"Authorization" = "Bearer rb_live_xxxxxxxx_..."]]
        )
    ),
    Days = Table.FromRecords(Source[days])
in
    Days

Rate limits are per key, not per address: 60 reporting requests an hour, bursting to 10 a minute.

To request a field or an endpoint, contact us.