curl --request GET \
--url https://api.closebot.com/leaderboard/board \
--header 'X-CB-KEY: <api-key>'import requests
url = "https://api.closebot.com/leaderboard/board"
headers = {"X-CB-KEY": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-CB-KEY': '<api-key>'}};
fetch('https://api.closebot.com/leaderboard/board', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.closebot.com/leaderboard/board",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-CB-KEY: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.closebot.com/leaderboard/board"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-CB-KEY", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.closebot.com/leaderboard/board")
.header("X-CB-KEY", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.closebot.com/leaderboard/board")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-CB-KEY"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"period": {
"key": "<string>",
"start": "2023-11-07T05:31:56Z"
},
"cohort": {
"kind": "<string>"
},
"industryAvailable": true,
"top": [
{
"direction": "<string>",
"displayName": "<string>",
"value": 123,
"isMine": true,
"cells": {},
"rank": 123,
"imageUrl": "<string>",
"entityId": "<string>",
"ownName": "<string>"
}
],
"around": [
{
"direction": "<string>",
"displayName": "<string>",
"value": 123,
"isMine": true,
"cells": {},
"rank": 123,
"imageUrl": "<string>",
"entityId": "<string>",
"ownName": "<string>"
}
],
"eligibility": {
"isEligible": true,
"minimum": 123,
"current": 123,
"reason": "<string>"
},
"industryLockReason": "<string>",
"industryMessagesCurrent": 123,
"industryMessagesRequired": 123,
"me": {
"percentile": 123,
"value": 123,
"entityId": "<string>"
}
}One leaderboard: the leaders, the caller's neighbourhood, and where the caller stands.
curl --request GET \
--url https://api.closebot.com/leaderboard/board \
--header 'X-CB-KEY: <api-key>'import requests
url = "https://api.closebot.com/leaderboard/board"
headers = {"X-CB-KEY": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-CB-KEY': '<api-key>'}};
fetch('https://api.closebot.com/leaderboard/board', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.closebot.com/leaderboard/board",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-CB-KEY: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.closebot.com/leaderboard/board"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-CB-KEY", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.closebot.com/leaderboard/board")
.header("X-CB-KEY", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.closebot.com/leaderboard/board")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-CB-KEY"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"period": {
"key": "<string>",
"start": "2023-11-07T05:31:56Z"
},
"cohort": {
"kind": "<string>"
},
"industryAvailable": true,
"top": [
{
"direction": "<string>",
"displayName": "<string>",
"value": 123,
"isMine": true,
"cells": {},
"rank": 123,
"imageUrl": "<string>",
"entityId": "<string>",
"ownName": "<string>"
}
],
"around": [
{
"direction": "<string>",
"displayName": "<string>",
"value": 123,
"isMine": true,
"cells": {},
"rank": 123,
"imageUrl": "<string>",
"entityId": "<string>",
"ownName": "<string>"
}
],
"eligibility": {
"isEligible": true,
"minimum": 123,
"current": 123,
"reason": "<string>"
},
"industryLockReason": "<string>",
"industryMessagesCurrent": 123,
"industryMessagesRequired": 123,
"me": {
"percentile": 123,
"value": 123,
"entityId": "<string>"
}
}Authorizations
CloseBot API Key Authorization
Query Parameters
"source" ranks individual sources, "agency" ranks whole accounts.
A key from GET /leaderboard/metrics.
Who to rank against: "all" (default), or "industry" for the subject's own vertical.
A mode, not an industry key. The server resolves which vertical the subject belongs to, so a caller can ask to be compared within it without ever learning — or naming — which one it is. Not valid with scope=agency: industry is a property of a source. Falls back to "all" when no board was published for the subject's vertical this period, which Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.IndustryAvailable reports.
"current" (default) or "previous".
Which of the caller's own entities Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.Me, Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.Around and the eligibility describe. Defaults to their best-placed one. Ignored at agency scope, where the caller has exactly one entity. A source belonging to another agency is rejected.
How many leaders to return. 1-10, default 10.
How many neighbours either side of the caller. 0-5, default 5.
Response
OK
The calendar month a board covers.
Start and key only. The period type was always "month", the end is always one month past the start, and the client formats its own month label in the user's locale — a server-side "September 2026" is English for everybody.
Show child attributes
Show child attributes
The peer group a rank is relative to.
Show child attributes
Show child attributes
Whether a same-industry board exists for this subject, metric and period — and so whether that comparison can be chosen.
Deliberately a boolean. Answering "is there one" is the whole of what a client needs; answering "which one" is the disclosure this endpoint will not make.
The leading entities, for the podium and the top-of-board view.
Show child attributes
Show child attributes
The window around the caller's best-placed entity. Empty when the caller is not on this board. This, not Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.Top, is the view most users should land on — the next rank up has to look reachable.
Show child attributes
Show child attributes
Why the caller is or is not on a board.
Show child attributes
Show child attributes
Why the comparison cannot be chosen, when it cannot. Null when it can.
"needsVolume" — the source has not been seen enough for us to classify it yet,
and Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.IndustryMessagesCurrent counts toward
Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.IndustryMessagesRequired.
"notYet" — anything else, and deliberately incurious. A source can be past the
threshold but waiting on the next batch, examined and found unclassifiable, classified
too tentatively to publish, or classified fine but in a vertical with too few ranked
participants to build a board from. The client is told the comparison is unavailable
and not invited to guess which — the last of those cases in particular would hint at
how many participants a vertical has.
Inbound messages this source has, when Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.IndustryLockReason is
"needsVolume". The caller's own figure for their own source.
Inbound messages needed before a first classification is attempted, when
Closebot.Api.Controllers.LeaderboardController.GetBoardResponse.IndustryLockReason is "needsVolume".
Where the caller sits on this board.
A percentile and a movement, never an absolute rank — a rank alongside the percentile solves for the cohort size, and a rank on its own still tells the worst-placed tenant roughly how many there are.
Show child attributes
Show child attributes

