The REST API for product locations and the map: the marker data every map is drawn from, a distance search over your rentals, and the map’s own query with its result cards. This page also covers the messaging webhook, the address an SMS or WhatsApp provider calls with delivery receipts and customer replies. The setup and the conventions shared by every endpoint (keys, errors, pagination) are in REST API overview and authentication.
Base URLs and sign-in #
| Namespace | What lives there | Needs |
|---|---|---|
/wp-json/ | locations: product markers, availability for dates, the distance search | Rentals & Bookings Regular 1.0.74 or newer |
/wp-json/ | map: the map’s query with its result cards | Rentals & Bookings Regular 1.0.74 or newer |
/wp-json/ | messaging/: the provider’s callback | Rentals & Bookings Regular 1.0.66 or newer |
wc-sibooking/v1takes a WooCommerce REST API key (WooCommerce > Settings > Advanced > REST API) sent as HTTP Basic, like the other rental endpoints. An Application Password works there too.sibooking/v1/mapdoes not start withwc-, so WooCommerce keys don’t apply there: a key sent as HTTP Basic answers401. Use a WordPress Application Password (Users > Profile > Application Passwords) as HTTP Basic (username:application password), or, inside wp-admin, the logged-in cookie with anX-WP-Nonceheader.- The messaging webhook takes no login. The provider’s signature on each request is the credential (see below).
locationsandmaphave been in Rentals & Bookings since 1.0.74, the messaging webhook since 1.0.66.- Errors use the WordPress shape
{"code", "message", "data": {"status"}}. Thelocationscodes start withsibooking_rest_, themapcodes withsibooking_map_. The webhook answers with its own short JSON, listed with it.
STORE="https://example.com/wp-json"
curl -u "ck_your_key:cs_your_secret" "$STORE/wc-sibooking/v1/locations?category=kayaks"
curl -u "username:YOUR_APP_PASSWORD" "$STORE/sibooking/v1/map?bounds=44.85,-93.40,45.00,-93.20"
Who may do what #
| Ability | Who has it | Opens |
|---|---|---|
manage_woocommerce | Shop managers and administrators | GET /wc-sibooking/ and GET /sibooking/v1/map |
| No login | Your SMS or WhatsApp provider | The messaging webhook. Every request must carry the provider’s valid signature |
A user without manage_woocommerce gets 401 (not signed in) or 403: code sibooking_ on locations, rest_forbidden on map.
Product locations #
A product goes on a map when it is published, visible in the catalogue (not “Hidden” or “Search results only”), not password protected, and has coordinates on its Location tab. The maps themselves are described in Maps.
GET /wc-sibooking/v1/locations #
Lists the products as map markers, optionally with availability for dates and narrowed to a distance from a point.
| Parameter | Notes |
|---|---|
category | Product category slugs, comma separated. Child categories are included. Slugs that match no category are skipped; when none match, the list is empty |
ids | Product ids, comma separated. A variation’s id means its parent product |
limit | Integer, 1 to 500. Left out: up to 500 (filter sibooking_). The limit is applied before the distance search, so with both, only the first limit products are measured |
from, to | Y-m-d. Send both. Each rental then carries available and total for those dates. The dates are checked for format and order only; past dates are not refused |
qty | Integer, at least 1, default 1. The units the availability and the total are for |
lat, lng | Decimal degrees (-90 to 90, -180 to 180): the center of a distance search |
radius | Number above 0, in unit. Send it with lat and lng |
unit | km (default) or mi |
Without a distance search the markers come in menu order, then by name. With one they come nearest first, and each has distance (in unit, two decimals) and unit. The list is not paged: X-WP-Total is the number of markers and X-WP-TotalPages is always 1.
Each marker:
| Field | Notes |
|---|---|
id, name, url | The product id, its name as plain text, its permalink |
image | The catalogue thumbnail, or WooCommerce’s placeholder |
images | Up to five {src, srcset, alt}: the product image, then the gallery (filter sibooking_, 1 to 10) |
price | One plain-text line: From $45.00 / day for a rental, the product’s own price otherwise |
lat, lng | Six-decimal strings: the public point (see Approximate locations below) |
address | The address on one line; for an approximate location, the area only (Minneapolis, MN) |
location_name | The location’s name from the Location tab |
location | The whole public location: name, address_1, address_2, city, state, postcode, country (two-letter code), lat, lng, show_map, formatted, description, privacy (exact or approximate), approximate, radius |
area | The location’s name and its town: Lake Nokomis, Minneapolis |
rental | true for a rental product |
available | With dates: true or false for a rental, judged as the shop grid judges the same dates. null without dates, and for a product that is not a rental |
unavailable_label | The shop’s “Not available for your dates” wording (Rentals > Settings > Search) when available is false, else empty |
approximate, radius | Whether the location is approximate, and its circle in meters (0 when exact) |
rating | {average, count}, or null when ratings are off or there are no reviews |
facts | One short line from the sibooking_ filter; empty by default |
from | The lowest rate, {amount, text, template, period}, or null. template has %s where the price goes: From %s / day |
total | With dates, the price for those dates, as the cart would charge it, times qty: {amount, text, per_amount, per_text, per_template, count, period, label, quantity, total_template}. null without dates, for a product that is not a rental, or when the rental is not priced for that length (a monthly rental in a two-night search shows its from instead) |
pin, pin_label | The short price for a price pin ($45: the total when there is one, else the from amount) and the same in words |
distance, unit | Only on a distance search |
Approximate locations. A product set to Approximate (on its Location tab, or by the store default in Rentals > Settings > Maps) never gives out its real place. Its lat and lng are a fixed point inside its circle, not the real one (the same point on every request), address_1, address_2 and postcode are empty, formatted and address are the area only, and approximate is true. A distance search measures from that public point too, so repeated searches cannot narrow down the real address. The real address is shown only on the customer’s order after booking.
| Error | When |
|---|---|
400 sibooking_ | from or to is missing or not Y-m-d, or to is before from |
400 sibooking_ | A distance search without all three of lat, lng and a radius above 0 |
400 rest_invalid_param | A value out of range: lat, lng, a negative radius, unit other than km or mi, limit outside 1 to 500, qty below 1 |
401 / 403 sibooking_ | Not signed in, or the user lacks manage_woocommerce |
Kayaks in one category, with availability for two units over a weekend:
curl -u "ck_your_key:cs_your_secret" \
"$STORE/wc-sibooking/v1/locations?category=kayaks&from=2026-12-04&to=2026-12-06&qty=2"
Everything within 5 miles of a point, nearest first:
curl -u "ck_your_key:cs_your_secret" \
"$STORE/wc-sibooking/v1/locations?lat=44.9778&lng=-93.2650&radius=5&unit=mi"
[
{
"id": 456,
"name": "Lakeside cabin",
"url": "https://example.com/product/lakeside-cabin/",
"image": "https://example.com/wp-content/uploads/lakeside-cabin-300x300.jpg",
"price": "From $180.00 / day",
"lat": "44.920700",
"lng": "-93.301900",
"address": "Minneapolis, MN",
"location_name": "Lake Harriet",
"location": {
"name": "Lake Harriet", "address_1": "", "address_2": "",
"city": "Minneapolis", "state": "MN", "postcode": "", "country": "US",
"lat": "44.920700", "lng": "-93.301900", "show_map": true,
"formatted": "Minneapolis, MN", "description": "",
"privacy": "approximate", "approximate": true, "radius": 500
},
"rental": true,
"available": null,
"unavailable_label": "",
"approximate": true,
"radius": 500,
"area": "Lake Harriet, Minneapolis",
"images": [{"src": "https://example.com/wp-content/uploads/lakeside-cabin-600x600.jpg", "srcset": "...", "alt": "Cabin by the lake"}],
"rating": null,
"facts": "",
"from": {"amount": 180, "text": "$180.00", "template": "From %s / day", "period": "day"},
"total": null,
"pin": "$180",
"pin_label": "From $180.00 / day",
"distance": 4.34,
"unit": "mi"
},
{"id": 123, "name": "Two-person kayak", "lat": "44.909700", "lng": "-93.241000", "approximate": false, "radius": 0, "distance": 4.85, "unit": "mi", "...": "..."}
]
With dates, a rental’s total looks like this:
"total": {"amount": 360, "text": "$360.00", "per_amount": 180, "per_text": "$180",
"per_template": "%s / day", "count": 2, "period": "day", "label": "for 2 days",
"quantity": 1, "total_template": "%s total"}
The map query #
GET /sibooking/v1/map #
The map’s own query: the markers and the result cards for one part of the map, the same answer a split-layout [sibooking_map] gets when a visitor moves the map. It returns only what the shortcode would print on a public page. The storefront map does not call this route (it asks through admin-ajax and gets the same answer), so use it from your own code with a staff login.
| Parameter | Notes |
|---|---|
bounds | south,west,north,east in decimal degrees. Only markers whose public point is inside. A west value greater than the east value is a box across the 180th meridian |
category, ids, limit | As on locations; limit is 1 to 500 |
search | 1 (default) or 0. With 0, the dates, len and the guests are ignored |
from, to | Y-m-d. Checked for format and order only; past dates are not refused |
from_time, to_time | H:i, for rentals by the hour |
len | A length from from, instead of to: a number and h, d, w, m or y (4h, 3d, 2w, 1m). At most 720 hours, 730 days, 104 weeks, 24 months or 2 years. A length in hours without from_time starts at the store’s opening time |
qty | Integer, at least 1, default 1 |
period | Empty (default: each product’s own wording), day or night: words the prices per day or per night |
pins | price (default) or marker. Accepted; it does not change the answer |
guests | 0 to 99: the party size, adults plus children. Rentals that can never hold the party are left out, and the dates are judged for the party |
children, infants, pets | 0 to 99. Read only when guests is sent |
When the shop’s “Filter products by the visitor’s dates” setting (Rentals > Settings > Search) is set to hide unavailable products, rentals that are not available for the dates are left out of the answer. Otherwise they come back with available: false.
| Field | Notes |
|---|---|
count | The number of markers |
count_label | 2 rentals, in the store’s language |
region_label | Map of 2 rental locations: the map’s label for screen readers |
markers | The markers, as on locations (without distance) |
html | The result cards as <li class="sib-card"> items, one per marker: the markup the split layout prints |
The response carries X-Robots-Tag: noindex and no paging headers.
| Error | When |
|---|---|
400 sibooking_ | bounds is not four numbers, a value is out of range, or south is above north |
400 sibooking_ | With search=1: from or to is missing or not Y-m-d, to is before from, or len is not a length |
400 rest_invalid_param | A value outside its list or range: search, period, pins, limit, qty, guests, children, infants, pets |
401 / 403 rest_forbidden | Not signed in, or the user lacks manage_woocommerce. A WooCommerce key answers 401 here as well (invalid_username) |
The rentals inside part of the map that are free for a weekend for two people:
curl -u "username:YOUR_APP_PASSWORD" \
"$STORE/sibooking/v1/map?bounds=44.85,-93.40,45.00,-93.20&from=2026-12-04&to=2026-12-06&guests=2"
{
"count": 2,
"count_label": "2 rentals",
"region_label": "Map of 2 rental locations",
"markers": [
{"id": 456, "name": "Lakeside cabin", "lat": "44.920700", "lng": "-93.301900",
"approximate": true, "radius": 500, "rental": true, "available": true, "...": "..."},
{"id": 123, "name": "Two-person kayak", "lat": "44.909700", "lng": "-93.241000",
"approximate": false, "radius": 0, "rental": true, "available": false,
"unavailable_label": "Not available for your dates", "...": "..."}
],
"html": "<li class=\"sib-card\" data-marker=\"456\">...</li><li class=\"sib-card is-unavailable\" data-marker=\"123\">...</li>"
}
The messaging webhook #
The address your SMS or WhatsApp provider calls to report what happened to a reminder (delivered or failed) and to pass on a customer’s reply (STOP, START, HELP). You don’t call it yourself: you paste it into the provider’s console. Setting up text reminders is in SMS and WhatsApp rental reminders.
https://example.com/wp-json/sibooking/v1/messaging/webhook/{provider}
| Provider | What it sends | How it is checked |
|---|---|---|
twilio | Delivery receipts, and incoming SMS and WhatsApp messages (POST, form-encoded) | X-Twilio-Signature: Twilio’s signature over the full URL and the posted fields, made with your Auth Token. There is no GET handshake |
meta_whatsapp | WhatsApp delivery statuses, customer messages, and WhatsApp’s own stop and resume choice (POST, JSON) | X-Hub-Signature-256: Meta’s sha256= signature over the raw body, made with your App secret. Plus the GET handshake below |
log | Nothing: the test provider only writes to the WooCommerce log | It takes no webhooks, so every call is refused |
A provider added with the sibooking_ filter | What its class handles | Its own signature check. A provider without webhooks refuses every call |
Where to paste it.
- Twilio. Delivery receipts need no setup: each message the plugin sends carries its own status callback (
..), as long as the site has a public address. For replies, set the Twilio phone number’s A message comes in webhook (or your Messaging Service’s incoming-message webhook) to. / webhook/ twilio ?message_ id= 123 https://example., method HTTP POST.com/ wp-json/ sibooking/ v1/ messaging/ webhook/ twilio - WhatsApp from Meta. Rentals > Settings > Text messages shows the Callback URL (
..) and a Verify token, each with a Copy button. In your Meta app, open WhatsApp > Configuration > Webhook, paste both, and subscribe to the. / webhook/ meta_ whatsapp messagesanduser_preferencesfields. The full steps are in WhatsApp via Meta.
Signatures. A POST without a signature, with a wrong one, or sent before the provider’s Auth Token or App secret is saved under Text messages, is refused with 403. Twilio signs the exact URL it called, so the site must see the request with the same scheme and host: behind a proxy that ends HTTPS, WordPress has to recognize the request as HTTPS, or every Twilio call is refused.
The handshake (Meta). Before Meta sends anything, it checks the URL with a GET carrying hub.mode=subscribe, hub.challenge and hub.verify_token. When the token matches the Verify token on the settings screen and the challenge is letters, digits, _ or - (up to 255 characters), the route answers 200 with the challenge as plain text and nothing else. Anything else answers 403, without saying which part was wrong. Twilio needs no handshake.
What a signed request does.
- A delivery status updates the reminder’s record (delivered or failed, with the provider’s reason). For a WhatsApp reminder, a status code that means the customer blocked the business records an opt-out, and on a reminder set to WhatsApp then SMS, a failure that means the number is not on WhatsApp sends the SMS instead.
- STOP, or another stop word (QUIT, END, CANCEL, UNSUBSCRIBE and their equivalents in the plugin’s languages), records an opt-out for every channel and cancels the texts still queued for that number. A carrier-level opt-out reported by the provider does the same.
- START (or UNSTOP, YES) records consent again.
- HELP or INFO is recorded and answered with the help text, unless text messages are in dry-run mode.
- Any other reply is kept for staff to read (the last 50) and fires
sibooking_.messaging_ inbound_ unmatched
| Answer | When |
|---|---|
200 {"ok": true} | A signed POST was accepted |
200, plain text | The GET handshake passed: the body is the challenge |
403 {"error": "signature"} | A POST without a valid signature, or to a provider that takes no webhooks |
403 {"error": "verification failed"} | The GET handshake failed, or the provider has none |
404 {"error": "unknown provider"} | No provider has that id |
404 rest_no_route | Any method other than GET and POST |
These answers are not the WordPress error shape: the body is {"error": "..."} or {"ok": true}. If your provider’s console shows 403 for every call, check the Auth Token or App secret saved under Text messages and that the URL matches the one the provider calls exactly.
Hooks #
sibooking_map_markersfilters the markers; its context’ssourceisrestforlocationsandmapformap.sibooking_(the 500 ceiling),map_ max_ markers sibooking_(turnmap_ price_ totals totaloff),sibooking_andmap_ card_ facts sibooking_shape the markers.map_ card_ image_ count sibooking_is the location every caller sees;product_ location sibooking_,location_ privacy sibooking_andlocation_ privacy_ default sibooking_decide exact or approximate and the circle.location_ privacy_ radius sibooking_registers a provider (its id becomes themessaging_ providers {provider}part of the webhook URL);sibooking_fires for a reply that matched no keyword, with the number, the text and the channel.messaging_ inbound_ unmatched
