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

Authentication

Exchange client credentials for an access token

POST /api/v1/oauth2/token

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 answer is a bearer token that lasts 24 hours. Cache and re-use it until a call answers 401, then mint another. Revoking the credentials under Settings › Integrations invalidates every token issued from them.

Request body

application/x-www-form-urlencoded, shaped as TokenRequest.

FieldTypeDescription
grant_type required"client_credentials"Always client_credentials.
client_id requiredstringThe client ID from Settings › Integrations, e.g. qc_…
client_secret requiredstringThe secret shown once when the credentials were created, e.g. qs_…

Response

200 { }

Errors

Every failure is { error, message }; see Errors. A missing or expired bearer is a 401 on every endpoint but the token exchange.

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.

Notes

  • The response carries Cache-Control: no-store and Pragma: no-cache, as the RFC asks. Cache the token in your own process, not in an HTTP cache.
  • Tokens are per credential set. Revoking one set leaves the others working.

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
}