Skip to content

Statistics ​

Methods for reports: the same statistics as on the "Dashboard" and the "Statistics" page, only in JSON - for your own dashboards, exports to spreadsheets and automated checks. What the metrics themselves mean is covered in detail in the "Dashboard" and "Statistics" sections.

MethodAddressWhat it does
GET/stats/dashboardSummary for a period: totals, charts, GEO, devices, top PWAs
GET/stats/funnelDaily funnel and split results
GET/stats/subsBreakdown by one sub tag (sub_1 … sub_20)

Replace YOUR_KEY and panel.example.com with your own.

Period ​

All three methods require a period - the date_from and date_to parameters:

ParameterRequiredWhat it is
date_fromyesFirst day of the period, YYYY-MM-DD
date_toyesLast day of the period inclusive, not earlier than date_from
  • Days are taken whole: date_from=2026-10-01&date_to=2026-10-01 is the whole of October 1.
  • Time is UTC: a day starts at 00:00 UTC, not in your time zone.
  • The period is no longer than a month: date_to can't be later than date_from plus one month. Need statistics for a quarter - request it month by month and add up (except uniq and percentages - those can't be added).

Without dates or with a period longer than a month you get 422:

json
{
  "data": null,
  "message": "The date to field must be a date before or equal to 2026-02-01.",
  "errors": { "date_to": ["The date to field must be a date before or equal to 2026-02-01."] }
}

Visits by visit date, events by event date

Visits, uniques and whites are counted by the day of the visit. Installs, opens, registrations and deposits - by the day the event arrived. That's why on a short period a funnel step can be more than 100% of the previous one: the events belong to visits that happened earlier.

The numbers are refreshed every few minutes: a fresh visit or deposit shows up in reports with a delay of up to 5 minutes. Statistics cover only the PWAs of your teams, for admin - all of them.

Summary ​

GET/stats/dashboard

Everything the "Dashboard" shows, in one request. The only parameter is the period.

bash
curl "https://panel.example.com/api/stats/dashboard?date_from=2026-09-29&date_to=2026-10-05" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
    'timeout' => 30,
]);

$response = $api->get('stats/dashboard', ['query' => [
    'date_from' => '2026-09-29',
    'date_to' => '2026-10-05',
]]);
$metrics = json_decode($response->getBody(), true)['data']['metrics'];

echo "Installs: {$metrics['total_installs']['value']} ({$metrics['total_installs']['trend']}%)\n";
echo "Deposits: {$metrics['total_deposits']['value']}, CR {$metrics['avg_cr']['value']}%\n";
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

response = requests.get(f"{API}/stats/dashboard", headers=HEADERS, params={
    "date_from": "2026-09-29",
    "date_to": "2026-10-05",
}, timeout=30)
metrics = response.json()["data"]["metrics"]

print("Installs:", metrics["total_installs"]["value"], f'({metrics["total_installs"]["trend"]}%)')
print("Deposits:", metrics["total_deposits"]["value"], f'CR {metrics["avg_cr"]["value"]}%')
js
// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };

const params = new URLSearchParams({ date_from: '2026-09-29', date_to: '2026-10-05' });
const response = await fetch(`${API}/stats/dashboard?${params}`, {
  method: 'GET',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

const { metrics } = data;
console.log('Installs:', metrics.total_installs.value, `(${metrics.total_installs.trend}%)`);
console.log('Deposits:', metrics.total_deposits.value, `CR ${metrics.avg_cr.value}%`);
Response (shortened)
json
{
  "data": {
    "metrics": {
      "previous_period": { "from": "2026-09-22", "to": "2026-09-29" },
      "total_visits": { "value": 12480, "trend": 14 },
      "total_uniques": { "value": 9870, "trend": 12.2 },
      "total_repeats": { "value": 2610, "trend": 21.4 },
      "total_whites": { "value": 1310, "trend": -7.7 },
      "total_installs": { "value": 2140, "trend": 8.1 },
      "total_opens": { "value": 1890, "trend": 11 },
      "total_registrations": { "value": 412, "trend": 8.4 },
      "total_deposits": {
        "value": 96,
        "trend": 18.5,
        "amounts": [
          { "currency": "USD", "amount": 4820.5 },
          { "currency": "EUR", "amount": 310 }
        ]
      },
      "avg_cr": { "value": 4.49, "trend": 6.1 }
    },
    "chart": [
      { "date": "2026-10-04", "visits": 610, "whites": 60, "installs": 110, "deposits": 4 },
      { "date": "2026-10-05", "visits": 640, "whites": 70, "installs": 120, "deposits": 6 }
    ],
    "geo": [
      { "code": "US", "installs": 1200, "cr": 5.1, "pct": 100 },
      { "code": "DE", "installs": 400, "cr": 3.5, "pct": 33 }
    ],
    "devices": [
      { "name": "Android", "installs": 1900, "pct": 89 },
      { "name": "iOS", "installs": 240, "pct": 11 }
    ],
    "pwas": [
      {
        "id": "WZAX", "name": "Promo EN", "icon_url": null,
        "domains": [{ "id": "K3LP", "domain": "promo-en.example.com" }],
        "visits": 12480, "installs": 2140, "cr": 4.5, "status": "active"
      }
    ],
    "activity": [
      { "id": "WZAX", "type": "deploy", "msg": "Promo EN is running", "icon_url": null, "time": "2 hours ago" }
    ]
  }
}

metrics - totals for the period. Each metric has value - the value, and trend - the change in percent against the equally long period before it (its dates are in previous_period). If the previous period was zero and now there is something, trend is 100.

FieldWhat it is
total_visitsAll visits
total_uniquesUniques - a visitor's first visits per flow
total_repeatsRepeats: visits minus uniques
total_whitesVisits the filter sent to the white page
total_installsInstalls
total_opensOffer opens from an installed PWA
total_registrationsRegistrations from postbacks
total_depositsDeposits; amounts - payout totals for each currency separately
avg_crConversion: deposits ÷ installs × 100%. trend - the change against the previous period, same rules as the other trends

chart - the same visits, whites, installs and deposits by day. A day that had only installs or deposits and no visits is there too - with zeros in visits and whites.

geo - top 10 countries by installs: code - country code (XX - not detected), cr - deposits ÷ installs, pct - installs as a percentage of the first country in the list.

devices - installs by device type (Android, iOS, Desktop), pct - share of all installs.

pwas - top 10 PWAs by installs for the period. status: active - enabled, paused - disabled, archived - archived. domains - the PWA's domains (an empty list when it has none).

activity - the ten most recently changed PWAs, regardless of the period. It's for the feed in the panel, scripts rarely need it.

Errors
CodemessageWhen
422The date from field is required. (and 1 more error)No period passed
422The date from field must be a valid date.The date isn't recognised
422The date to field must be a date after or equal to date from.date_to is earlier than date_from
422The date to field must be a date before or equal to 2026-02-01.The period is longer than a month (the text gives the last allowed day)

Funnel and splits ​

GET/stats/funnel

The Visits → Uniq → Installs → Opens → Reg → Dep funnel by day and in total, plus split results.

ParameterRequiredWhat it is
date_from, date_toyesPeriod, see above
pwa_idnoOnly one PWA. Someone else's or a non-existent one - 404
bash
curl "https://panel.example.com/api/stats/funnel?date_from=2026-09-29&date_to=2026-10-05" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
    'timeout' => 30,
]);

$response = $api->get('stats/funnel', ['query' => [
    'date_from' => '2026-09-29',
    'date_to' => '2026-10-05',
]]);
$funnel = json_decode($response->getBody(), true)['data']['funnel'];

foreach ($funnel['rows'] as $day) {
    echo "{$day['date']}: {$day['installs']} installs, {$day['deps']} deposits, CR {$day['cr']}%\n";
}
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

response = requests.get(f"{API}/stats/funnel", headers=HEADERS, params={
    "date_from": "2026-09-29",
    "date_to": "2026-10-05",
}, timeout=30)
funnel = response.json()["data"]["funnel"]

for day in funnel["rows"]:
    print(day["date"], day["installs"], "installs,", day["deps"], "deposits, CR", day["cr"])
js
// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };

const params = new URLSearchParams({ date_from: '2026-09-29', date_to: '2026-10-05' });
const response = await fetch(`${API}/stats/funnel?${params}`, {
  method: 'GET',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

for (const day of data.funnel.rows) {
  console.log(day.date, day.installs, 'installs,', day.deps, 'deposits, CR', day.cr);
}
Response (shortened)
json
{
  "data": {
    "funnel": {
      "rows": [
        {
          "date": "2026-10-05",
          "visits": 640, "uniq": 590, "uniques": 520, "repeats": 120, "whites": 70,
          "installs": 120, "opens": 105, "regs": 24, "deps": 6,
          "inst_pct": 20.3, "open_pct": 87.5, "reg_pct": 20, "dep_pct": 25, "cr": 5
        }
      ],
      "totals": {
        "date": null,
        "visits": 1250, "uniq": 1100, "uniques": 1020, "repeats": 230, "whites": 130,
        "installs": 230, "opens": 203, "regs": 44, "deps": 10,
        "inst_pct": 20.9, "open_pct": 88.3, "reg_pct": 19.1, "dep_pct": 22.7, "cr": 4.3
      }
    },
    "splits": [
      {
        "id": "Lm3T",
        "name": "Split US",
        "status": "active",
        "visits": 6100, "installs": 1030, "conversions": 44, "cr": 4.3,
        "flows": [
          {
            "flow_id": "Qp8R", "pwa_id": "WZAX", "pwa_name": "Promo EN", "weight": 50,
            "visits": 6100, "installs": 1030, "conversions": 44, "cr": 4.3
          }
        ]
      }
    ],
    "pwa": null
  }
}

funnel.rows - one row per day, newest first. funnel.totals - the total for the whole period (date is null).

FieldWhat it is
visitsAll visits
uniqUnique people - how many different visitors came
uniquesUnique visits - a visitor's first visits per flow. One person who went from the main PWA to a similar one is two uniques but one uniq
repeatsRepeats: visits − uniques
whitesWent to the white page
installs, opens, regs, depsInstalls, offer opens, registrations, deposits
inst_pctInstalls ÷ uniq
open_pctOpens ÷ installs
reg_pctRegistrations ÷ installs
dep_pctDeposits ÷ registrations
crDeposits ÷ installs - the main conversion

All percentages are numbers from 0 to 100 (on a short period they can be higher, see above).

uniq can't be added up

totals.uniq is recounted over the whole period: a person who came three days in a row is in it once, but three times in the sum of days. So totals.uniq is less than the sum of daily uniq, and that's correct. The other counters add up as usual.

splits - all splits of your teams with results for the period: the total per split and per flow inside it (flows) - which PWA the flow leads to (pwa_id, pwa_name) and its weight (weight). conversions are deposits, cr - deposits ÷ installs. With the pwa_id parameter the split list is empty: a split combines several PWAs and isn't counted for a single app.

pwa - the selected PWA (id and name) if pwa_id was passed, otherwise null.

Errors
CodemessageWhen
422The date from field is required. (and 1 more error)No period passed
422The date from field must be a valid date.The date isn't recognised
422The date to field must be a date after or equal to date from.date_to is earlier than date_from
422The date to field must be a date before or equal to 2026-02-01.The period is longer than a month (the text gives the last allowed day)
404Not foundpwa_id of someone else's or a non-existent PWA

Sub tags ​

GET/stats/subs

Which values of one sub tag bring installs and deposits: for example, which campaigns, creatives or ad sets in sub_1 … sub_20 really convert.

ParameterRequiredWhat it is
date_from, date_toyesPeriod, see above
slotyesSub tag number, from 1 to 20
limitnoHow many values to return, from 1 to 100, 20 by default
searchnoOnly values containing this substring (case-insensitive)
filters[sub_N]noNarrow by other tags: filters[sub_1]=fb-ua-creo1 - only visits with this sub_1. Several are allowed
pwa_idnoOnly one PWA: its visits and its conversions. Someone else's or a non-existent one - 404

Example: which ad sets (sub_2) work inside the creative fb-ua-creo1 (sub_1).

bash
curl -G "https://panel.example.com/api/stats/subs" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json" \
  --data-urlencode "date_from=2026-09-29" \
  --data-urlencode "date_to=2026-10-05" \
  --data-urlencode "slot=2" \
  --data-urlencode "filters[sub_1]=fb-ua-creo1"
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
    'timeout' => 30,
]);

$response = $api->get('stats/subs', ['query' => [
    'date_from' => '2026-09-29',
    'date_to' => '2026-10-05',
    'slot' => 2,
    'filters' => ['sub_1' => 'fb-ua-creo1'],
]]);
$subs = json_decode($response->getBody(), true)['data']['subs'];

foreach ($subs as $sub) {
    $value = $sub['value'] ?? '(empty)';
    echo "{$value}: {$sub['installs']} installs, CR {$sub['cr']}%\n";
}
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

response = requests.get(f"{API}/stats/subs", headers=HEADERS, params={
    "date_from": "2026-09-29",
    "date_to": "2026-10-05",
    "slot": 2,
    "filters[sub_1]": "fb-ua-creo1",
}, timeout=30)
subs = response.json()["data"]["subs"]

for sub in subs:
    print(sub["value"] or "(empty)", sub["installs"], "installs, CR", sub["cr"])
js
// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };

const params = new URLSearchParams({
  date_from: '2026-09-29',
  date_to: '2026-10-05',
  slot: 2,
  'filters[sub_1]': 'fb-ua-creo1',
});
const response = await fetch(`${API}/stats/subs?${params}`, {
  method: 'GET',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

for (const sub of data.subs) {
  console.log(sub.value || '(empty)', sub.installs, 'installs, CR', sub.cr);
}
Response
json
{
  "data": {
    "slot": 2,
    "pwa": null,
    "subs": [
      { "value": "adset-7", "visits": 5210, "installs": 980, "conversions": 51, "cr": 5.2 },
      { "value": null, "visits": 1200, "installs": 140, "conversions": 3, "cr": 2.1 }
    ]
  }
}

Values are sorted by installs, best first. value: null - visits where this tag is empty. conversions are deposits, cr - deposits ÷ installs.

A key in filters can only be sub_1 … sub_20.

Errors
CodemessageWhen
422The date from field is required. (and 1 more error)No period passed
422The date from field must be a valid date.The date isn't recognised
422The date to field must be a date after or equal to date from.date_to is earlier than date_from
422The date to field must be a date before or equal to 2026-02-01.The period is longer than a month (the text gives the last allowed day)
422The slot field is required.slot not passed
422The slot field must not be greater than 20.slot is greater than 20
422The limit field must not be greater than 100.limit is greater than 100
422Filter key must be sub_1..sub_20.A key in filters is not sub_1 … sub_20
404Not foundpwa_id of someone else's or a non-existent PWA