Recipes
Sync reservations
Load every booking once, then only what changed, for contracts, accounting or a guest portal.
A contract system, an accounting package or a guest portal wants every booking once and then only what changed. List reservations does both, and a webhook says when to ask.
The initial load
Page through everything, sorted by updatedAt, a hundred at a time. The order is
stable (the sort key, then id), and total counts the same filters, so you know when you
are done.
Request
curl "https://go.quarters.live/api/v1/reservations?sort=updatedAt&limit=100" \
-H "Authorization: Bearer $ACCESS_TOKEN"const res = await fetch("https://go.quarters.live/api/v1/reservations?sort=updatedAt&limit=100", {
headers: { Authorization: `Bearer ${token}` }
});
const data = await res.json();import requests
res = requests.get(
"https://go.quarters.live/api/v1/reservations",
params={
"sort": "updatedAt",
"limit": "100",
},
headers={"Authorization": f"Bearer {token}"},
)
res.raise_for_status()
data = res.json()$ch = curl_init("https://go.quarters.live/api/v1/reservations?sort=updatedAt&limit=100");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
]);
$res = curl_exec($ch);
curl_close($ch);
$data = json_decode($res, true);require "net/http"
require "json"
uri = URI("https://go.quarters.live/api/v1/reservations?sort=updatedAt&limit=100")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
token := os.Getenv("ACCESS_TOKEN")
req, _ := http.NewRequest("GET", "https://go.quarters.live/api/v1/reservations?sort=updatedAt&limit=100", nil)
req.Header.Set("Authorization", "Bearer "+token)
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
json.NewDecoder(res.Body).Decode(&data)
fmt.Println(data)
}Keep the updatedAt of the last row you stored. That is your cursor.
Incremental sync
From then on, ask for bookings changed at or after the cursor. The bound is inclusive, so you see
the last row again. Upsert on id and it does no harm.
Request
curl "https://go.quarters.live/api/v1/reservations?updatedSince=2026-09-13T06:00:00.000Z&sort=updatedAt&limit=100" \
-H "Authorization: Bearer $ACCESS_TOKEN"const res = await fetch("https://go.quarters.live/api/v1/reservations?updatedSince=2026-09-13T06:00:00.000Z&sort=updatedAt&limit=100", {
headers: { Authorization: `Bearer ${token}` }
});
const data = await res.json();import requests
res = requests.get(
"https://go.quarters.live/api/v1/reservations",
params={
"updatedSince": "2026-09-13T06:00:00.000Z",
"sort": "updatedAt",
"limit": "100",
},
headers={"Authorization": f"Bearer {token}"},
)
res.raise_for_status()
data = res.json()$ch = curl_init("https://go.quarters.live/api/v1/reservations?updatedSince=2026-09-13T06:00:00.000Z&sort=updatedAt&limit=100");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
]);
$res = curl_exec($ch);
curl_close($ch);
$data = json_decode($res, true);require "net/http"
require "json"
uri = URI("https://go.quarters.live/api/v1/reservations?updatedSince=2026-09-13T06:00:00.000Z&sort=updatedAt&limit=100")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
token := os.Getenv("ACCESS_TOKEN")
req, _ := http.NewRequest("GET", "https://go.quarters.live/api/v1/reservations?updatedSince=2026-09-13T06:00:00.000Z&sort=updatedAt&limit=100", nil)
req.Header.Set("Authorization", "Bearer "+token)
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
json.NewDecoder(res.Body).Decode(&data)
fmt.Println(data)
}Run it on a schedule, and when a reservation.created, reservation.updated or reservation.deleted webhook arrives. The webhook carries the id, so for a single change retrieving that one booking is enough. The scheduled sync catches
a delivery that did not land.
What the statuses mean
option: provisional. Blocks nothing; may become a booking or be dropped.reserved: held. The days are blocked; the contract is usually not signed yet.confirmed: firm. When the bookingrequiresContract, the agreement has been completed.cancelled: released. The days are free again.
The lifecycle stamps (optionAt, reservedAt, confirmedAt, cancelledAt) record when each status was entered and are never cleared, so a
cancelled booking still says when it was confirmed.
What the record carries
pricingis the money as agreed when the booking was made. A later rate change on the listing never rewrites it.rateBasissays whetherrentCentsis per month or per night.monthlyCentsis rent, utilities and furniture added up, null for a nightly booking.propertyis the flat’s identity as last published: name, slug, address and time zone, frozen by Publish like the listings. OnlyownerNameis live, the lessor the tenancy agreement names.propertyis null until the flat is first published.agreementis the current contract, or null. Itsstatusanddocumentsay whether it is signed and archived.dashboardUrlopens the booking in the dashboard, for an alert email or a back-office record.