curl --request GET \
--url https://api.apsio.io/v1/projects/{projectId}/cohorts/compare \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.apsio.io/v1/projects/{projectId}/cohorts/compare"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.apsio.io/v1/projects/{projectId}/cohorts/compare', 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.apsio.io/v1/projects/{projectId}/cohorts/compare",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$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.apsio.io/v1/projects/{projectId}/cohorts/compare"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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.apsio.io/v1/projects/{projectId}/cohorts/compare")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apsio.io/v1/projects/{projectId}/cohorts/compare")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"from": "2026-10-04T10:15:00.431000000Z",
"to": "2026-10-04T10:15:00.431000000Z",
"cohort": {
"sessions": 123
},
"rest": {
"sessions": 123
},
"comparable": true,
"note": "<string>",
"dimensions": [
{
"dimension": "device_model",
"values": [
{
"value": "<string>",
"cohort_sessions": 123,
"cohort_share": 123,
"rest_sessions": 123,
"rest_share": 123,
"difference": 123,
"lift": 123
}
]
}
]
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}Compare a cohort with the rest
The sessions that match the cohort filters, against every other session of the same app (or project) that started in the time range (default: the last 7 days, at most 30). For each dimension, the values most over-represented in the cohort, ranked by the difference in share. At least one cohort filter is required; app_id only narrows the population. Shares computed from a few sessions are noisy: min_sessions (default 2) leaves out rarer values, and a small cohort deserves a higher one.
curl --request GET \
--url https://api.apsio.io/v1/projects/{projectId}/cohorts/compare \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.apsio.io/v1/projects/{projectId}/cohorts/compare"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.apsio.io/v1/projects/{projectId}/cohorts/compare', 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.apsio.io/v1/projects/{projectId}/cohorts/compare",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$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.apsio.io/v1/projects/{projectId}/cohorts/compare"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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.apsio.io/v1/projects/{projectId}/cohorts/compare")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apsio.io/v1/projects/{projectId}/cohorts/compare")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"from": "2026-10-04T10:15:00.431000000Z",
"to": "2026-10-04T10:15:00.431000000Z",
"cohort": {
"sessions": 123
},
"rest": {
"sessions": 123
},
"comparable": true,
"note": "<string>",
"dimensions": [
{
"dimension": "device_model",
"values": [
{
"value": "<string>",
"cohort_sessions": 123,
"cohort_share": 123,
"rest_sessions": 123,
"rest_share": 123,
"difference": 123,
"lift": 123
}
]
}
]
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}Authorizations
A project token (apsio_pt_v0...), which reads exactly one project, or an OAuth access token issued for the API, which reads what its user can. Access tokens issued for Apsio's MCP server are accepted only from the MCP server itself.
Path Parameters
^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"0192f3a4-0000-7000-8000-00000000a101"
Query Parameters
Start of the time range, inclusive (RFC 3339). Defaults to the range end minus the default length.
"2026-10-01T00:00:00Z"
End of the time range, exclusive (RFC 3339). Defaults to now.
"2026-10-08T00:00:00Z"
Only this app.
^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$Only this release.
128crashed, abnormal_exit, or ok (every session that neither crashed nor ended abnormally).
crashed, abnormal_exit, ok device.model.identifier, exactly.
128"iPhone17,1"
os.version, exactly.
64Only sessions with (true) or without (false) handled errors.
true, false Only sessions with an occurrence of this issue in the time range.
^[A-Za-z0-9._:-]{1,128}$With endpoint_host and endpoint_template: one network endpoint.
^[A-Z_]{1,16}$255Only sessions with a request to this endpoint in the time range (/network/endpoints lists them).
256With the endpoint: only sessions where a request to it failed (5xx or no response).
true, false Only sessions that loaded this screen (app.screen.name) in the time range.
1 - 256With screen: only loads of it with a TTID of at least this many milliseconds.
0 <= x <= 600000Comma-separated, from device_model, os_version, release, country, feature_flag, network_type. Default: all.
200"device_model,os_version"
Values per dimension, 1 to 20.
1 <= x <= 20Leave out values with fewer cohort sessions than this.
1 <= x <= 1000Response
The over-represented values per dimension.
RFC 3339 time in UTC with up to nanosecond precision.
"2026-10-04T10:15:00.431000000Z"
RFC 3339 time in UTC with up to nanosecond precision.
"2026-10-04T10:15:00.431000000Z"
Show child attributes
Show child attributes
Show child attributes
Show child attributes
False when the cohort or the rest is empty: no values are listed.
Why nothing could be compared.
Show child attributes
Show child attributes