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

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"])
}
  • description fields are the editor’s HTML; title is plain text. translations carries the same six fields per language. Fall back to description for anything missing.
  • Photos come in four renditions with absolute URLs. Use regular in a grid, large on the detail page, thumbnail in a list.
  • bedrooms, beds and bathrooms are counted from rooms for 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"])
}
  • available is bookable. reserved is held but not firm; show it as unavailable. booked and unavailable are taken.
  • Check-out is exclusive. A stay ending on the 30th leaves the 30th free, so two stays can meet on one day.
  • minNights is 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.