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
GET /api/pos/sales.php- If
salesis empty → sleep 60s → repeat - For each sale, print a receipt (staff name, every
items[]line, amount, guest, time). Ifvideo_addon.requestedis true, print the video add-on line and a QR ofvideo_addon.upload_url. - If print succeeded,
POST /api/pos/ack.phpwith thoseids - If print failed, do not ack — the next poll will retry (duplicate print is better than a lost ticket)
- When staff hand the drink over,
POST /api/pos/delivered.phpwith thoseids (optional — the venue app can do this instead) - 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:
GETonly (405otherwise) - 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:
GETonly (405otherwise) - 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:
POSTonly (405otherwise) - 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:
POSTonly (405otherwise) - Body: same as ack (
order_idsand/ororder_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.phpwhen 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.requestedis true, print that add-on on the same ticket and print a QR ofvideo_addon.upload_url(native ESC/POS QR, orqr_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_idsstill 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.localor your tunnel); production ishttps://barbooster.io.