Recipes
Build a booking website
Mirror the listings, draw the calendar, land the form as an inquiry.
A website needs three things from Quarters: the flats, the days they are free, and somewhere to send the form. Each is one endpoint, and webhooks tell you when to look again. All of it runs on your server. The browser talks to you, never to the API.
Mirror the listings
List every published listing and store the result. It is the complete set, not a delta. A listing missing from the response was unpublished, so drop it from your mirror. Render from the mirror, not from the API.
Request
curl "https://go.quarters.live/api/v1/listings" \
-H "Authorization: Bearer $ACCESS_TOKEN"const res = await fetch("https://go.quarters.live/api/v1/listings", {
headers: { Authorization: `Bearer ${token}` }
});
const { listings } = await res.json();import requests
res = requests.get(
"https://go.quarters.live/api/v1/listings",
headers={"Authorization": f"Bearer {token}"},
)
res.raise_for_status()
listings = res.json()["listings"]$ch = curl_init("https://go.quarters.live/api/v1/listings");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
]);
$res = curl_exec($ch);
curl_close($ch);
$listings = json_decode($res, true)["listings"];require "net/http"
require "json"
uri = URI("https://go.quarters.live/api/v1/listings")
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) }
listings = JSON.parse(res.body)["listings"]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/listings", 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["listings"])
}descriptionfields are the editor’s HTML;titleis plain text.translationscarries the same six fields per language. Fall back todescriptionfor anything missing.- Photos come in four renditions with absolute URLs. Use
regularin a grid,largeon the detail page,thumbnailin a list. bedrooms,bedsandbathroomsare counted fromroomsfor you. Money is integer euro-cents. A null price is unset, not free.
Draw the calendar
Don’t mirror availability. It is computed on request from bookings and blocks, so a tenancy taken thirty seconds ago already blocks its days. Ask for the window you are drawing, up to 1100 days, and ask again when the visitor moves.
Request
curl "https://go.quarters.live/api/v1/listings/0d6e1c2a-…/calendar?start=2026-10-01&end=2026-10-04" \
-H "Authorization: Bearer $ACCESS_TOKEN"const res = await fetch("https://go.quarters.live/api/v1/listings/0d6e1c2a-…/calendar?start=2026-10-01&end=2026-10-04", {
headers: { Authorization: `Bearer ${token}` }
});
const { days } = await res.json();import requests
res = requests.get(
"https://go.quarters.live/api/v1/listings/0d6e1c2a-…/calendar",
params={
"start": "2026-10-01",
"end": "2026-10-04",
},
headers={"Authorization": f"Bearer {token}"},
)
res.raise_for_status()
days = res.json()["days"]$ch = curl_init("https://go.quarters.live/api/v1/listings/0d6e1c2a-…/calendar?start=2026-10-01&end=2026-10-04");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
]);
$res = curl_exec($ch);
curl_close($ch);
$days = json_decode($res, true)["days"];require "net/http"
require "json"
uri = URI("https://go.quarters.live/api/v1/listings/0d6e1c2a-…/calendar?start=2026-10-01&end=2026-10-04")
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) }
days = JSON.parse(res.body)["days"]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/listings/0d6e1c2a-…/calendar?start=2026-10-01&end=2026-10-04", 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["days"])
}availableis bookable.reservedis held but not firm; show it as unavailable.bookedandunavailableare taken.- Check-out is exclusive. A stay ending on the 30th leaves the 30th free, so two stays can meet on one day.
minNightsis the listing’s shortest stay. Enforce it in the date picker.
Land the form as an inquiry
Post the form to Create an inquiry. It lands as a row,
linked to a guest record, that staff convert to a reservation in one click. Only lastName and email are required. Anything else you collect that has no
field of its own goes in metadata.
Request
curl -X POST "https://go.quarters.live/api/v1/inquiries" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"propertyId": "0d6e1c2a-…",
"firstName": "Sofia",
"lastName": "Rossi",
"email": "sofia@example.com",
"phone": "+39 333 000 0000",
"startDate": "2026-10-01",
"endDate": "2027-04-01",
"adults": 2,
"children": 0,
"infants": 0,
"pets": false,
"message": "Two of us, relocating for a project.",
"locale": "en",
"form": "booking",
"metadata": {
"page": "https://www.example.com/house/…"
}
}'const res = await fetch("https://go.quarters.live/api/v1/inquiries", {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({
"propertyId": "0d6e1c2a-…",
"firstName": "Sofia",
"lastName": "Rossi",
"email": "sofia@example.com",
"phone": "+39 333 000 0000",
"startDate": "2026-10-01",
"endDate": "2027-04-01",
"adults": 2,
"children": 0,
"infants": 0,
"pets": false,
"message": "Two of us, relocating for a project.",
"locale": "en",
"form": "booking",
"metadata": {
"page": "https://www.example.com/house/…"
}
})
});
const { id } = await res.json();import requests
res = requests.post(
"https://go.quarters.live/api/v1/inquiries",
headers={"Authorization": f"Bearer {token}"},
json={
"propertyId": "0d6e1c2a-…",
"firstName": "Sofia",
"lastName": "Rossi",
"email": "sofia@example.com",
"phone": "+39 333 000 0000",
"startDate": "2026-10-01",
"endDate": "2027-04-01",
"adults": 2,
"children": 0,
"infants": 0,
"pets": False,
"message": "Two of us, relocating for a project.",
"locale": "en",
"form": "booking",
"metadata": {
"page": "https://www.example.com/house/…",
},
},
)
res.raise_for_status()
id = res.json()["id"]$ch = curl_init("https://go.quarters.live/api/v1/inquiries");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"propertyId" => "0d6e1c2a-…",
"firstName" => "Sofia",
"lastName" => "Rossi",
"email" => "sofia@example.com",
"phone" => "+39 333 000 0000",
"startDate" => "2026-10-01",
"endDate" => "2027-04-01",
"adults" => 2,
"children" => 0,
"infants" => 0,
"pets" => false,
"message" => "Two of us, relocating for a project.",
"locale" => "en",
"form" => "booking",
"metadata" => [
"page" => "https://www.example.com/house/…",
],
]),
]);
$res = curl_exec($ch);
curl_close($ch);
$id = json_decode($res, true)["id"];require "net/http"
require "json"
uri = URI("https://go.quarters.live/api/v1/inquiries")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"propertyId" => "0d6e1c2a-…",
"firstName" => "Sofia",
"lastName" => "Rossi",
"email" => "sofia@example.com",
"phone" => "+39 333 000 0000",
"startDate" => "2026-10-01",
"endDate" => "2027-04-01",
"adults" => 2,
"children" => 0,
"infants" => 0,
"pets" => false,
"message" => "Two of us, relocating for a project.",
"locale" => "en",
"form" => "booking",
"metadata" => {
"page" => "https://www.example.com/house/…",
},
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
id = JSON.parse(res.body)["id"]package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
func main() {
token := os.Getenv("ACCESS_TOKEN")
body := `{
"propertyId": "0d6e1c2a-…",
"firstName": "Sofia",
"lastName": "Rossi",
"email": "sofia@example.com",
"phone": "+39 333 000 0000",
"startDate": "2026-10-01",
"endDate": "2027-04-01",
"adults": 2,
"children": 0,
"infants": 0,
"pets": false,
"message": "Two of us, relocating for a project.",
"locale": "en",
"form": "booking",
"metadata": {
"page": "https://www.example.com/house/…"
}
}`
req, _ := http.NewRequest("POST", "https://go.quarters.live/api/v1/inquiries", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
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["id"])
}Inquiries share a daily allowance with the organisation’s built-in website forms, 500 by default.
Past it, the API answers 429. Put a captcha or a rate limit in front of any form a
bot can submit.
Stay current
Subscribe to three webhooks:
listing.updated: re-fetch that listing, or the whole set, and re-render.listing.deleted: drop it from the mirror.calendar.updated: if you cache calendar windows for a few minutes, evict that listing’s.
Also re-fetch the full set on a schedule, say hourly. A delivery is attempted once, and for listings the full read is one call.