Facebook Tracking Pixel

Using the BetterSTR API without a PMS

October 30, 2026 18 views Barry Murphy
Push bookings, manage properties and control your TVs from your own software with a write-enabled API credential. Worked curl examples and the interactive console.

If your booking system is not one of the PMSs BetterSTR connects to, you can push bookings yourself through the BetterSTR API. Create a credential with Write access, send a few JSON calls, and your guests get their personal guidebook link, the TV greets them by name and your check-in automations fire, exactly as they would from a PMS sync. This article walks through the basics; the full reference and a “try it out” console are at api.betterstr.com/user-api/docs.

What you can do with a write key

  • Properties – create, update and delete the properties you manage without a PMS (address, timezone, check-in and check-out times, capacity, picture, tags).
  • Bookings – push a booking with the guest’s name, dates, contact details and door code; update it, cancel it, or import up to 50 at a time.
  • TVs – list your BetterSTR TVs, check power, switch them on or off, put a message on screen, change settings.
  • Everything else the API already does – guidebook entries, tags, map locations, SMS to guests, payment links, the WiFi guest list.

Bookings that come from a connected PMS stay read-only, because the PMS is their source of truth. The write calls only touch properties you added yourself.

Step 1: create a credential with Write access

  1. Log in and open Profile → API Credentials (gb.betterstr.com/members/profile/api-keys.php).
  2. Click Create, give it a name, and tick Write access.
  3. Copy the client_id (starts with bsc_) and the client_secret (starts with bss_). The secret is shown once.

Existing credentials stay read-only

Write access is set when a credential is created and cannot be added to an old one. If you made a credential before this feature existed, create a new one with the box ticked and revoke the old one when you no longer need it.

Every request uses HTTP Basic auth with the two values. The examples below use placeholders; put your own in.

Step 2: find your property

curl -u "bsc_your_client_id:bss_your_client_secret" \
     https://api.betterstr.com/user-api/v1/properties
{"data":{"properties":[{
   "uuid":"4f0c9a2e-6b1d-4c8e-9f3a-2d7e8b1c5a60",
   "name":"Harbour View Apartment",
   "city":"Auckland","country":"NZ",
   "timezone_iana":"Pacific/Auckland",
   "guide_url":"https://gb.betterstr.com/26/g/4f0c9a2e-6b1d-4c8e-9f3a-2d7e8b1c5a60"
 }],"total":1}}

The uuid is what every other call uses. If you have no property yet, add one in the dashboard first (see Add a Property Manually). Booking times you send are read in the property’s timezone_iana, with daylight saving handled for you.

Step 3: push a booking

reference is your own booking number. Sending the same reference again updates the booking instead of creating a second one, so re-sending your whole calendar every night is safe.

curl -u "bsc_your_client_id:bss_your_client_secret" \
     -X POST https://api.betterstr.com/user-api/v1/reservations \
     -H "Content-Type: application/json" \
     -d '{
       "property_uuid": "4f0c9a2e-6b1d-4c8e-9f3a-2d7e8b1c5a60",
       "reference": "BK-1001",
       "first_name": "Ana", "last_name": "Silva",
       "email": "ana@example.com", "phone": "+64211234567",
       "check_in": "2026-12-20 15:00", "check_out": "2026-12-23 10:00",
       "guests": 2, "door_code": "4821",
       "total_amount": 540, "currency": "NZD"
     }'
HTTP 201
{"data":{"reservation":{
   "id":"manual_u12345_bk-1001",
   "reservation_code":"BK-1001",
   "status":"accepted",
   "guide_url":"https://gb.betterstr.com/26/g/r/manual_u12345_bk-1001",
   "arrival_date":"2026-12-20 15:00:00","departure_date":"2026-12-23 10:00:00",
   "nights":3,"guest_name":"Ana Silva","guests":2,"door_code":"4821"
 },"created":true,"notified":true}}

Required: property_uuid, reference, first_name, check_in, check_out. Optional: last_name, email, phone, guests, status (accepted, pending or cancelled), door_code, notes, total_amount, currency, notify.

A brand-new booking triggers your guest automations (the guide link by SMS or email, if you have set them up), exactly like a booking from a channel. When you import old bookings add "notify": false so nobody gets a welcome message for a stay that has already happened. The guest shows on the TV automatically during their stay, and guide_url is their personal guide link.

Step 4: update or cancel

Address a booking by your reference or by our id:

curl -u "bsc_your_client_id:bss_your_client_secret" \
     -X PATCH https://api.betterstr.com/user-api/v1/reservations/BK-1001 \
     -H "Content-Type: application/json" \
     -d '{"guests": 3, "door_code": "5678"}'

curl -u "bsc_your_client_id:bss_your_client_secret" \
     -X POST https://api.betterstr.com/user-api/v1/reservations/BK-1001/cancel

curl -u "bsc_your_client_id:bss_your_client_secret" \
     "https://api.betterstr.com/user-api/v1/reservations?checkin_from=2026-12-01&checkin_to=2026-12-31"

Step 5: import many at once

Up to 50 bookings per call. Bulk calls never send notifications and report success or failure per row, so one bad date does not stop the rest.

curl -u "bsc_your_client_id:bss_your_client_secret" \
     -X POST https://api.betterstr.com/user-api/v1/reservations/bulk \
     -H "Content-Type: application/json" \
     -d '{
       "property_uuid": "4f0c9a2e-6b1d-4c8e-9f3a-2d7e8b1c5a60",
       "reservations": [
         {"reference":"BK-1002","first_name":"Marc","check_in":"2026-12-27 15:00","check_out":"2027-01-02 10:00"},
         {"reference":"BK-1003","first_name":"Yuki","check_in":"2027-01-05 15:00","check_out":"2027-01-08 10:00"}
       ]}'

Step 6: your TVs

curl -u "bsc_your_client_id:bss_your_client_secret" \
     "https://api.betterstr.com/user-api/v1/android-tv?include=power"

curl -u "bsc_your_client_id:bss_your_client_secret" \
     -X POST https://api.betterstr.com/user-api/v1/android-tv/<device_id>/message \
     -H "Content-Type: application/json" \
     -d '{"text":"Welcome! The ski bus leaves at 8:30."}'

curl -u "bsc_your_client_id:bss_your_client_secret" \
     -X POST https://api.betterstr.com/user-api/v1/android-tv/<device_id>/power \
     -H "Content-Type: application/json" \
     -d '{"state":"on"}'

Listing always works. Calls addressed to one TV need that TV to be on a plan; the free trial counts.

Update your property details

curl -u "bsc_your_client_id:bss_your_client_secret" \
     -X PATCH https://api.betterstr.com/user-api/v1/properties/4f0c9a2e-6b1d-4c8e-9f3a-2d7e8b1c5a60 \
     -H "Content-Type: application/json" \
     -d '{"timezone":"Pacific/Auckland","currency":"NZD","checkin":"15:00","checkout":"10:00"}'

Other fields: name, street, city, state, postcode, country, latitude, longitude, max_capacity, bedrooms, beds, bathrooms, picture, tags.

What needs a paid plan

  • Reading is free, as always.
  • Pushing bookings to a property, or changing its details, needs that property on Guidebook Premium or BetterSTR Simple. Without it the call returns 402 paid_property_required.
  • Creating properties through the API needs Premium with the flat-rate option covering all properties, because each new property joins your next bill automatically (the response tells you the monthly cost). On other plans, add properties in the dashboard.
  • Limits: 5,000 new bookings and 1,000 new properties per account per day. If your first sync is bigger, contact support and we will lift them for the day.

Errors you may see

CodeMeaning
401 invalid_credentialsWrong client_id or client_secret.
403 insufficient_scopeThe credential has no Write access. Create a new one with the box ticked.
402 paid_property_requiredThe property is not on a paid plan.
409 not_manual_propertyThat property is synced from a PMS and cannot be written to.
409 property_lockedA booking cannot move to another property. Cancel and re-create it.
422 invalid_reservationCheck errors in the response, for example “check_out must be after check_in”.
429 rate_limitedSlow down and retry after the Retry-After header.

All responses are {"data": ...} on success and {"error": {"code", "message"}} on failure.