Skip to content
Developers
esc
  • Type an endpoint, an object or a word from a guide.

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 booking requiresContract, 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

  • pricing is the money as agreed when the booking was made. A later rate change on the listing never rewrites it. rateBasis says whether rentCents is per month or per night. monthlyCents is rent, utilities and furniture added up, null for a nightly booking.
  • property is the flat’s identity as last published: name, slug, address and time zone, frozen by Publish like the listings. Only ownerName is live, the lessor the tenancy agreement names. property is null until the flat is first published.
  • agreement is the current contract, or null. Its status and document say whether it is signed and archived.
  • dashboardUrl opens the booking in the dashboard, for an alert email or a back-office record.