Get network map
curl --request GET \
--url https://api.offlineprotocol.com/api/v1/stats/nodesimport requests
url = "https://api.offlineprotocol.com/api/v1/stats/nodes"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.offlineprotocol.com/api/v1/stats/nodes', 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.offlineprotocol.com/api/v1/stats/nodes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$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.offlineprotocol.com/api/v1/stats/nodes"
req, _ := http.NewRequest("GET", url, nil)
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.offlineprotocol.com/api/v1/stats/nodes")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.offlineprotocol.com/api/v1/stats/nodes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"message": "<string>",
"data": {
"devices_total": 123,
"devices_published": 123,
"devices_coarse": 123,
"nodes_total": 123,
"cells_total": 123,
"grid_deg": 123,
"parent_grid_deg": 123,
"coarse_grid_deg": 123,
"min_devices_per_cell": 123,
"page": 123,
"limit": 123,
"returned": 123,
"nodes": [
{
"lat": 123,
"lon": 123
}
],
"cells": [
{
"lat": 123,
"lon": 123,
"count": 123,
"size_deg": 123,
"coarse": true
}
]
}
}{
"message": "<string>",
"data": {},
"error": {
"message": "<string>",
"code": "<string>",
"retryAfter": 123,
"currentHourCount": 123,
"limit": 123
}
}{
"message": "<string>",
"data": {},
"error": {
"message": "<string>",
"code": "<string>",
"retryAfter": 123,
"currentHourCount": 123,
"limit": 123
}
}Network
Get network map
Public and unauthenticated. Returns device counts per area for the network map, never a device’s or profile’s location. Responses are paginated: page defaults to 1 and limit to 50. Each client IP can make 60 requests a minute.
GET
/
stats
/
nodes
Get network map
curl --request GET \
--url https://api.offlineprotocol.com/api/v1/stats/nodesimport requests
url = "https://api.offlineprotocol.com/api/v1/stats/nodes"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.offlineprotocol.com/api/v1/stats/nodes', 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.offlineprotocol.com/api/v1/stats/nodes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$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.offlineprotocol.com/api/v1/stats/nodes"
req, _ := http.NewRequest("GET", url, nil)
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.offlineprotocol.com/api/v1/stats/nodes")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.offlineprotocol.com/api/v1/stats/nodes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"message": "<string>",
"data": {
"devices_total": 123,
"devices_published": 123,
"devices_coarse": 123,
"nodes_total": 123,
"cells_total": 123,
"grid_deg": 123,
"parent_grid_deg": 123,
"coarse_grid_deg": 123,
"min_devices_per_cell": 123,
"page": 123,
"limit": 123,
"returned": 123,
"nodes": [
{
"lat": 123,
"lon": 123
}
],
"cells": [
{
"lat": 123,
"lon": 123,
"count": 123,
"size_deg": 123,
"coarse": true
}
]
}
}{
"message": "<string>",
"data": {},
"error": {
"message": "<string>",
"code": "<string>",
"retryAfter": 123,
"currentHourCount": 123,
"limit": 123
}
}{
"message": "<string>",
"data": {},
"error": {
"message": "<string>",
"code": "<string>",
"retryAfter": 123,
"currentHourCount": 123,
"limit": 123
}
}This public endpoint needs no API key or user token. It returns device counts per area, in three tiers, and never returns a device’s or profile’s location:
Responses are cached for 60 seconds. Each client IP can make 60 requests a minute; the
- Areas of 0.1 degree (about 11 km) with at least three devices.
- Devices left over are rolled up into areas of 1 degree (about 111 km), published with at least three devices.
- Devices still left over go into wider regions of 5 degrees (about 550 km), published with any count and marked
"coarse": true. A wider region places a device only within a region hundreds of kilometers across.
devices_published equals devices_total. format=cells returns every tier as {lat, lon, count, size_deg} (plus coarse on wider regions). The default point list (one {lat, lon} per device at its area’s center) leaves wider regions out; nodes_total gives its length.
Pagination
Both formats are paginated.page starts at 1 and limit defaults to 50, so a request without them returns the first 50 points. Each response reports the page and limit it applied and how many items it returned. Request further pages until returned is less than limit, or size one request from nodes_total or cells_total:
curl "https://api.offlineprotocol.com/api/v1/stats/nodes?format=cells&limit=500&page=1"
ratelimit-remaining header shows what is left, and a 429 carries Retry-After.Query Parameters
Page number, starting at 1. Defaults to 1.
Required range:
x >= 1Items per page: points in the default format, areas with format=cells. Defaults to 50; larger values are capped by the service.
Required range:
x >= 1Set to cells to return every area with its count instead of one point per device.
Available options:
cells Set to true to page from the end of the list.

