CoinOp

Booking feed

CoinOp is a till on a server inside the venue. It has no address on the internet and is not going to get one — it holds every customer record and takes payments, and a booking form is not a good enough reason to put that machine where the world can reach it.

So the traffic goes the other way. CoinOp asks the website, once a minute, over HTTPS. The website exposes one read-only endpoint and nothing else changes. That is the whole integration.

1 · The endpoint

GET https://<your-site>/api/coinop/bookings?since=2026-08-03T09:00:00.000Z&sinceMs=1785542400000
Authorization: Bearer <token>
  • HTTPS only. The response carries customer names and phone numbers.
  • Bearer token that you generate and give to the venue once. Any long random string; rotate it by changing it in both places.
  • A missing or wrong token is 401. Never fall back to serving anything.

2 · since

The same instant, sent twice, in two formats. Use whichever you prefer and ignore the other.

ParameterLooks likeFor
since2026-08-03T09:00:00.000Zanything that date-parses a string
sinceMs1785542400000anything that would rather have an integer

Return every booking whose updatedAt is greater than or equal to it.

Filter on updatedAt, never on startsAt. A booking for next Friday that was paid for this morning has to arrive today, and one for tonight that was cancelled a minute ago has to arrive now. Filtering on startsAt loses the first and delays the second.

CoinOp always sends it, and asks for the last seven days on its first run. Overlapping is fine — CoinOp keys on id and applies the same booking twice with no effect. Returning too little is the failure that matters, not too much.

A since you cannot parse is a 400. Never an empty list.

3 · The response

200 OK, content-type: application/json.

{
  "bookings": [
    {
      "id": "bk_1042",
      "status": "confirmed",
      "resource": { "kind": "billiards", "name": "3" },
      "startsAt": "2026-08-10T19:00:00+04:00",
      "minutes": 60,
      "customer": { "name": "Rami Haddad", "phone": "+971501234567" },
      "fee": { "amount": 3000, "paid": true, "method": "stripe", "reference": "pi_3Q7xKl2eZvKY" },
      "note": "birthday, 6 people",
      "updatedAt": "2026-08-09T12:58:04+04:00"
    }
  ]
}
Return {"bookings": []} when there is nothing. Never a bare null, never a 404, never a 500 for “no results” — an empty list is a successful answer, and CoinOp treats anything else as the website being broken.

4 · Fields

FieldTypeRequiredNotes
idstringyesStable and unique forever. Never reused, never changed — not on reschedule.
status"confirmed" | "cancelled"yesCancellations must be sent, not deleted.
resource.kind"billiards" | "ps5" | "cinema"yesLower case, exactly these.
resource.namestring | nullnoA specific table if the customer chose one, otherwise null.
startsAtISO 8601 with offsetyes2026-08-10T19:00:00+04:00
minutesinteger > 0yesHow long they booked.
customer.namestringyesAs given.
customer.phonestringyesE.164 preferred: +971501234567
fee.amountintegeryesFils. 3000 is AED 30.00.
fee.paidbooleanyesTrue once the fee is settled, however it was settled.
fee.method"stripe" | "manual"wantedDecides whether the money enters CoinOp’s books.
fee.referencestringfor a real paymentPayment intent or charge id — what ties a figure to a payout.
fee.refundedintegernoFils refunded, if any.
notestring | nullnoAnything staff should see. Shown on the floor.
updatedAtISO 8601 with offsetyesChanges whenever anything above changes.

Anything else you send is ignored, so extra fields are safe to add. Do not send card numbers, addresses, or anything CoinOp has no use for.

5 · Which bookings to include

Only bookings that are paid and confirmed, or that were and are now cancelled. A half-finished checkout — the customer opened Stripe and wandered off — should never appear. If it later completes, send it then, with updatedAt set to the moment it completed.

6 · Cancellations

Send them, with "status": "cancelled" and a bumped updatedAt. This is not bookkeeping: if a cancellation never arrives, staff hold a table at 8pm for somebody who cancelled at noon, and the table sits empty on a Friday. Deleting the row on your side and saying nothing is the one failure mode that costs the venue money.

Keep cancelled bookings in the feed for at least 7 days, so a CoinOp that was switched off over a weekend still learns about them.

7 · When something is wrong, say so

400 for a request you cannot parse, 401 for a bad token, 5xx if you are broken. Never answer an error with 200 and an empty list: that is indistinguishable from a quiet night, and it is how a booking somebody had made and paid for became invisible on every screen in the building.