BarBooster
Home Map How it works
Partners Log in

Developers

Machine-readable Markdown (for POS firmware and AI): https://barbooster.io/docs/pos-api.md

BarBooster POS poll API

Canonical HTML: https://barbooster.io/docs/pos-api Canonical Markdown (for POS firmware and AI): https://barbooster.io/docs/pos-api.md

Integration spec for an on-premise POS / receipt printer.

Paid guest boosts on BarBooster become print jobs. The till polls over HTTPS about once a minute, prints one receipt per staff member, then acknowledges. After the drink is handed over, the till can mark the boost delivered. The venue mobile app can still confirm delivery the same way — use either path, not both.

If the guest ticked Receive a short video from the girls, that add-on is included on the same staff ticket. Print the extra line and a QR from video_addon.upload_url. Staff scan it, upload the clip on BarBooster, and we email the guest a private watch link. The venue app can upload the same way.

Base URL (production): https://barbooster.io

Poll about once per minute. An empty sales array is normal. Do not put the secret in the query string (it ends up in access logs). Use HTTPS only.


Credentials

Created on the venue dashboard: Settings → POS printer.

Config on the POS Value Example
Bar / POS identifier key_id pos_7k2m9abc12def345
POS secret shown once at generate/rotate random URL-safe string

Store both on the till. Rotating credentials in BarBooster invalidates the old secret immediately. One active key per venue — use it on one printer so two tills do not double-print.

Auth (required on every request)

Prefer HTTP Basic (widely supported):

Authorization: Basic base64(key_id + ":" + secret)

Or custom headers:

X-Pos-Id: pos_7k2m9abc12def345
X-Pos-Secret: <secret>

CORS allows GET, POST, OPTIONS and headers Content-Type, Accept, Authorization, X-Pos-Id, X-Pos-Secret. JSON responses use Content-Type: application/json; charset=utf-8 and Cache-Control: no-store.


Recommended POS loop

  1. GET /api/pos/sales.php
  2. If sales is empty → sleep 60s → repeat
  3. For each sale, print a receipt (staff name, every items[] line, amount, guest, time). If video_addon.requested is true, print the video add-on line and a QR of video_addon.upload_url.
  4. If print succeeded, POST /api/pos/ack.php with those ids
  5. If print failed, do not ack — the next poll will retry (duplicate print is better than a lost ticket)
  6. When staff hand the drink over, POST /api/pos/delivered.php with those ids (optional — the venue app can do this instead)
  7. Sleep 60s → repeat

Ack is not sent on GET. Do not ack a sale until the printer confirms the ticket. Delivered is separate from ack: print first, mark delivered when the guest has the drink.

A “boost everyone” checkout is multiple sales (one per staff). Print a separate receipt for each. A video add-on is attached to the staff member it was bought for, not duplicated onto every ticket.


GET /api/pos/sales.php

Unprinted paid boosts for this bar from the last 24 hours, oldest first, max 50.

  • Method: GET only (405 otherwise)
  • Query string: none required
  • Auth: required

Success 200

{
  "ok": true,
  "sales": [
    {
      "id": 123,
      "order_number": "A1B2C3D4E5F6",
      "kind": "boost",
      "paid_at": "2026-08-25T07:12:00+07:00",
      "currency": "THB",
      "amount": 500.0,
      "guest_name": "James",
      "message": "Great night · Video add-on requested",
      "staff": {
        "id": 9,
        "name": "Anna",
        "job_title": "Bartender"
      },
      "items": [
        {
          "name": "Boost",
          "quantity": 1,
          "unit_price": 500.0
        },
        {
          "name": "Receive a short video from the girls",
          "quantity": 1,
          "unit_price": 500.0
        }
      ],
      "video_addon": {
        "requested": true,
        "label": "Receive a short video from the girls",
        "amount": 500.0,
        "order_id": 124,
        "order_number": "B2C3D4E5F6A1",
        "print_hint": "Scan QR to upload the thank-you video. Guest is emailed a watch link.",
        "upload_url": "https://barbooster.io/upload-thank-you.php?token=…",
        "qr_image_url": "https://api.qrserver.com/v1/create-qr-code/?data=…",
        "qr_caption": "Scan to upload video"
      },
      "related_ids": [124]
    }
  ]
}

Idle bar:

{ "ok": true, "sales": [] }

Sale fields

Field Type Notes
id integer BarBooster order id. Use this for ack and delivered.
order_number string 12-char hex, e.g. A1B2C3D4E5F6. Alternate ack/delivered key.
kind string boost (drink/tip) or video_addon (standalone video ticket if it could not be folded onto a boost).
paid_at string \ null ISO-8601 in the venue timezone, e.g. 2026-08-25T07:12:00+07:00
currency string ISO 4217, usually THB
amount number Boost subtotal for this staff member (not platform fee, not card surcharge). Does not include video_addon.amount.
guest_name string \ null Buyer first name / display name. Email is never sent.
message string \ null Optional guest note
staff.id integer Staff member id
staff.name string Name to print on the ticket
staff.job_title string \ null e.g. Bartender
items[].name string Product name, e.g. Boost, 10 Boosts, or the video add-on label
items[].quantity integer Usually 1
items[].unit_price number Unit price in currency
video_addon.requested boolean Guest ticked the video add-on for this staff member
video_addon.label string Present when requested is true
video_addon.amount number Video add-on price. Add to amount for a ticket total.
video_addon.upload_url string Staff upload page. Encode this as a QR on the ticket (preferred).
video_addon.qr_image_url string PNG of that QR if the printer draws bitmaps instead of native QR.
video_addon.qr_caption string Short line under the QR, e.g. Scan to upload video
video_addon.print_hint string Till copy: scan QR, guest is emailed a watch link
related_ids integer[] Extra order ids folded onto this ticket. Ack of id also acks these.

Receipt suggestion: staff name, every item name × qty, amount + currency, video add-on line when video_addon.requested, QR from upload_url, guest name, paid_at, order_number. Ticket total ≈ amount + (video_addon.amount if requested).

Staff scan the QR on a phone, record a short video on barbooster.io, then we email the guest a private watch link. The venue app can still upload the same way.


GET /api/pos/staff.php

Venue staff roster for POS mapping. New people added on the BarBooster website appear here without waiting for a boost.

  • Method: GET only (405 otherwise)
  • Query string: none required
  • Auth: required

Success 200

{
  "ok": true,
  "staff": [
    {
      "id": 9,
      "name": "Anna",
      "job_title": "Bartender",
      "active": true,
      "approved": true
    }
  ]
}

An empty venue:

{ "ok": true, "staff": [] }
Field Type Notes
id integer BarBooster staff id. Use this when mapping to a POS staff member.
name string Display name
job_title string \ null e.g. Bartender
active boolean false if the profile is switched off on BarBooster
approved boolean false if the profile is still waiting for venue approval

POST /api/pos/ack.php

Call after a successful print. Marks those orders so they are not returned again. Does not mark the boost delivered in BarBooster.

Acking a boost that includes a video add-on also acks the folded video order (related_ids) so it is not printed twice.

  • Method: POST only (405 otherwise)
  • Body: JSON (Content-Type: application/json) or form fields
  • Auth: required
  • Idempotent: ack-ing the same id twice still returns ok: true

Request

{ "order_ids": [123, 124] }

Or:

{ "order_numbers": ["A1B2C3D4E5F6"] }

You may send both. A single id/number is accepted (not only arrays).

Success 200

{
  "ok": true,
  "acked_ids": [123, 124]
}

acked_ids are the order ids that belong to this venue. Unknown ids are ignored (not an error). Empty acked_ids means none of the ids matched this bar.

Error 422

{
  "ok": false,
  "error": "invalid_request",
  "message": "Provide order_ids or order_numbers."
}

POST /api/pos/delivered.php

Call when the drink (or item) has been handed to the guest. Same effect as Confirm delivery in the venue mobile app or dashboard. Either the till or the app may do this; the second call is idempotent.

  • Method: POST only (405 otherwise)
  • Body: same as ack (order_ids and/or order_numbers)
  • Auth: required
  • Also sets print-ack so the sale leaves the poll queue

Boosts (kind: "boost") can be marked delivered from the POS even if a video add-on was requested. The thank-you video is still recorded in the venue app.

A dedicated video add-on order (kind: "video_addon", or video_addon.order_id) cannot be marked delivered until staff have uploaded the thank-you video in the app. The POS then receives thank_you_required for that id; retry after the upload, or let the app confirm it.

Request

{ "order_ids": [123] }

Success 200

{
  "ok": true,
  "delivered_ids": [123],
  "failed": []
}

delivered_ids includes orders that were already delivered (app got there first). Unknown ids are ignored.

Partial failure 200

{
  "ok": true,
  "delivered_ids": [123],
  "failed": [
    {
      "id": 124,
      "error": "thank_you_required",
      "message": "This order includes a video return. Staff must upload the thank-you video before delivery can be confirmed."
    }
  ]
}

Treat thank_you_required as “wait for the app upload”, not as a network retry loop.

Error 422

{
  "ok": false,
  "error": "invalid_request",
  "message": "Provide order_ids or order_numbers."
}

Errors (all endpoints)

Every error body is:

{
  "ok": false,
  "error": "unauthorized",
  "message": "Invalid POS identifier or secret."
}
HTTP error When
401 unauthorized Missing or wrong identifier/secret
403 venue_unavailable Venue inactive / not found
405 method_not_allowed Wrong HTTP method
422 invalid_request Ack body missing ids/numbers
503 db_unavailable Temporary; retry next minute

OPTIONS returns 204 (CORS preflight).

Treat network failures and 5xx as “try again next poll”. Do not ack unless you received 200 after a successful print.


curl

Replace host, identifier, and secret.

# Poll (Basic auth)
curl -sS -u 'pos_IDENTIFIER:SECRET' \
  'https://barbooster.io/api/pos/sales.php'

# Poll (headers)
curl -sS \
  -H 'X-Pos-Id: pos_IDENTIFIER' \
  -H 'X-Pos-Secret: SECRET' \
  'https://barbooster.io/api/pos/sales.php'

# Staff roster (for POS mapping)
curl -sS -u 'pos_IDENTIFIER:SECRET' \
  'https://barbooster.io/api/pos/staff.php'

# Ack after print
curl -sS -u 'pos_IDENTIFIER:SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"order_ids":[123]}' \
  'https://barbooster.io/api/pos/ack.php'

# Mark delivered (drink handed over) — same as the venue app
curl -sS -u 'pos_IDENTIFIER:SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"order_ids":[123]}' \
  'https://barbooster.io/api/pos/delivered.php'

POS implementation notes (for Cursor)

  • Persist key_id + secret per venue/till in POS config (not in source control).
  • Sync GET /api/pos/staff.php when opening Admin → Settings → BarBooster so commission staff can be mapped before the first boost.
  • Timer: every 60 seconds; skip overlapping polls if a previous request is still in flight.
  • Print synchronously per sale; collect successfully printed ids; one ack POST for the batch is fine.
  • When video_addon.requested is true, print that add-on on the same ticket and print a QR of video_addon.upload_url (native ESC/POS QR, or qr_image_url). Caption: Scan to upload video. After upload, BarBooster emails the guest a watch link — the till does not send email.
  • Optional: after hand-over, POST delivered with the same ids. The venue app may already have done this — delivered_ids still returns those ids.
  • Do not persist BarBooster sales as POS table sales unless you want them in Z-reports — this API is a print feed, not a payment settlement feed. Money already settled on Stripe.
  • Local staging host may differ (https://ladydrink.local or your tunnel); production is https://barbooster.io.

Download / raw Markdown · Home

© 2026 BarBooster. More customers, more boosts, more revenue.

Privacy Terms POS API Support