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

Getting started

Authentication

OAuth 2.0 client credentials: mint a token once a day, send it as a bearer on every call.

OAuth 2.0 client credentials, the same grant Guesty’s Open API uses. An integration written against one needs only a different token URL.

The flow

  1. Create credentials. Settings › Integrations issues a client ID and a client secret. The secret is shown once.
  2. Exchange them for a token. POST the form to /oauth2/token. The answer is a bearer token that lasts 24 hours.
  3. Send the bearer. Authorization: Bearer <token> on every call. Cache and re-use the token until a call answers 401, then mint another.

The token endpoint

Send the form as application/x-www-form-urlencoded. HTTP Basic with client_id:client_secret also works, as does a JSON body with the same three fields. The full contract is on the reference page.

Request

curl -X POST "https://go.quarters.live/api/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=qc_YOUR_CLIENT_ID" \
  -d "client_secret=qs_YOUR_CLIENT_SECRET"
const res = await fetch("https://go.quarters.live/api/v1/oauth2/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    "grant_type": "client_credentials",
    "client_id": "qc_YOUR_CLIENT_ID",
    "client_secret": "qs_YOUR_CLIENT_SECRET"
  })
});
const data = await res.json();
import requests

res = requests.post(
    "https://go.quarters.live/api/v1/oauth2/token",
    data={
      "grant_type": "client_credentials",
      "client_id": "qc_YOUR_CLIENT_ID",
      "client_secret": "qs_YOUR_CLIENT_SECRET",
    },
)
res.raise_for_status()
data = res.json()
$ch = curl_init("https://go.quarters.live/api/v1/oauth2/token");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => ["Content-Type: application/x-www-form-urlencoded"],
  CURLOPT_POSTFIELDS => http_build_query([
    "grant_type" => "client_credentials",
    "client_id" => "qc_YOUR_CLIENT_ID",
    "client_secret" => "qs_YOUR_CLIENT_SECRET",
  ]),
]);
$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/oauth2/token")
req = Net::HTTP::Post.new(uri)
req.set_form_data({
  "grant_type" => "client_credentials",
  "client_id" => "qc_YOUR_CLIENT_ID",
  "client_secret" => "qs_YOUR_CLIENT_SECRET",
})
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"
	"net/url"
	"os"
	"strings"
)

func main() {
	token := os.Getenv("ACCESS_TOKEN")
	form := url.Values{
		"grant_type": {"client_credentials"},
		"client_id": {"qc_YOUR_CLIENT_ID"},
		"client_secret": {"qs_YOUR_CLIENT_SECRET"},
	}
	req, _ := http.NewRequest("POST", "https://go.quarters.live/api/v1/oauth2/token", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	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)
}

Response · 200

{
  "token_type": "Bearer",
  "access_token": "qat_…",
  "expires_in": 86400
}

The token endpoint fails in the RFC 6749 shape, error plus error_description, which OAuth client libraries read. Every other endpoint fails as described under Errors.

StatuserrorWhen
400invalid_requestclient_id or client_secret missing, or a body that is neither form-encoded nor JSON.
400unsupported_grant_typeAnything but grant_type=client_credentials.
401invalid_clientUnknown client id, wrong secret, or credentials that were revoked.

Caching and expiry

A token is good for 24 hours from when it is issued, and expires_in says so in seconds. Keep one per credential set in your own process or cache, and re-use it for every call.

Store the token with the time you got it. Mint a new one when it is older than 23 hours, or when a call answers 401 unauthorized.

Without a token

A tool that cannot keep a token between runs, such as Zapier, Make or n8n, sends the credentials on every call as HTTP Basic, with the client ID as the username and the secret as the password. It gets the same data under the same limits. Zapier, Make and n8n walks through each tool.

In your own code, use the token. One that leaks from a log is dead within 24 hours.

Revocation

Revoking a credential set under Settings › Integrations invalidates every token issued from it, and the next call answers 401. Each set has its own tokens, so revoking one leaves the others working. Create one set per integration.

Server to server only

There are no CORS headers. Credentials and tokens belong on your server, never in a browser. A booking page calls your backend, and your backend calls Quarters.