dotsplatform / analytics-api-sdk
Library for the Dots Platform analytics service Read API client
Requires
- php: ^8.3
- ext-json: *
- dotsplatform/utils: ^2.3
- guzzlehttp/guzzle: ^7.8
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- eduarguz/shift-php-cs: ^3.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpstan/phpstan: ^2.1
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.5|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-08 23:45:55 UTC
README
PHP SDK for the Dots analytics service Read API v1 (GET api/v1/accounts/{account}/...). The Read API answers
from the daily MySQL rollups of the analytics service: overview, active users, events, platforms, items, pages and
hourly visits.
Install
composer require dotsplatform/analytics-api-sdk php artisan vendor:publish --tag=analytics-api-sdk-config
config/analytics-api.php:
| Key | Env | Meaning |
|---|---|---|
host |
ANALYTICS_API_URL |
Base URL of the analytics service, e.g. https://e.dots.live. The SDK adds api/v1/...; a path prefix in the host is kept |
token |
ANALYTICS_API_GATEWAY_TOKEN |
The internal gateway token, sent as X-Internal-Gateway-Token |
stub |
ANALYTICS_API_STUB |
Empty: the HTTP client. success: sample data, no HTTP. fail: every call throws AnalyticsServerException (503) |
connect_timeout |
– | Seconds to connect, 2 by default |
timeout |
– | Seconds for a whole request, 8 by default |
Every request sends X-Internal-Gateway-Token: <token>. The analytics service answers 401 without it, and also
when its own token is not configured.
Usage
use Dotsplatform\AnalyticsApi\AnalyticsClient; use Dotsplatform\AnalyticsApi\DTO\Requests\StatsRequestDTO; $analyticsClient = app(AnalyticsClient::class); $overview = $analyticsClient->stats()->getOverview(StatsRequestDTO::forAccount($accountId, '2026-11-01', '2026-11-07')); $overview->getTotals()->getPurchases();
The request
Every method takes one StatsRequestDTO:
| Field | Query | Meaning |
|---|---|---|
accountId |
path {account} |
An account id (1 or more; a lower id throws InvalidArgumentException), or null for all accounts (the path segment all; includes events with no resolved account) |
dateFrom, dateTo |
date_from, date_to |
Y-m-d, both days included, at most 366 days |
platform |
platform |
Platform enum: WEB, IOS, ANDROID, SERVER, UNKNOWN |
limit |
limit |
Size of the top lists (events, platforms, items, pages), 1–500 |
dimension |
dimension |
BreakdownDimension enum, sent only by getPlatformBreakdown(): PLATFORM, OS, OS_VERSION, BROWSER, BROWSER_VERSION, APP_VERSION |
A field left null is not sent, so the service default applies: the last 30 days in Kyiv time, every platform,
limit 50 and dimension os. The response getFilters() shows the account, period, platform and limit the service used.
use Dotsplatform\AnalyticsApi\Enums\BreakdownDimension; use Dotsplatform\AnalyticsApi\Enums\Platform; $request = StatsRequestDTO::forAllAccounts('2026-10-01', '2026-10-31')->copy([ 'platform' => Platform::IOS, 'limit' => 10, 'dimension' => BreakdownDimension::APP_VERSION, ]); // or StatsRequestDTO::fromArray(['accountId' => 42, 'dateFrom' => ..., 'platform' => Platform::WEB, ...])
Methods
All are on $analyticsClient->stats():
| Method | Endpoint | Returns |
|---|---|---|
getOverview($request) |
overview |
AccountOverviewDTO: getTotals() (CountersDTO), getPlatforms() (list of PlatformTotalsDTO: platform + counters), getDaily() (list of DailyTotalsDTO: date + counters) |
getActiveUsers($request) |
active-users |
ActiveUsersDTO: getDaily(), a list of ActiveUsersDayDTO (getDate(), getDau(), getWau(), getMau()) |
getEventStats($request) |
events |
EventStatsDTO: getEvents(), a list of EventStatDTO (getEvent(), getEvents(), getDeviceDays()), most frequent first |
getPlatformBreakdown($request) |
platforms |
PlatformBreakdownDTO: getDimension() (?BreakdownDimension), getDimensionValue() (raw string), getRows(), a list of PlatformBreakdownRowDTO (getValue(), getEvents(), getDeviceDays()), most device-days first |
getTopItems($request) |
items |
TopItemsDTO: getItems(), a list of TopItemDTO (getItemId(), getViews(), getAddToCart(), getDeviceDays(), getAddToCartRate()), most viewed first |
getTopPages($request) |
pages |
TopPagesDTO: getPages(), a list of TopPageDTO (getPage(), getViews(), getDeviceDays()), most viewed first |
getHourlyVisits($request) |
hourly-visits |
HourlyVisitsDTO: getHours() (24 HourVisitsDTO: getHour() 0–23, getEvents(), getDeviceHours()), getWeekdays() (7 WeekdayVisitsDTO: getWeekday() 1 = Monday … 7 = Sunday, getEvents(), getDeviceHours()) |
Every response DTO also has getFilters() (StatsFiltersDTO: getAccountId(), getDateFrom(), getDateTo(),
getPlatform(), getLimit()).
CountersDTO has getEvents(), getSessions(), getPageViews(), getItemViews(), getAddToCart(),
getCheckouts(), getPurchases() and getDeviceDays().
Reading the numbers
- Device-days (
getDeviceDays()) sum daily unique devices: a device active on 3 days counts 3. Compare it between periods; do not show it as a user count. - Device-hours (
getDeviceHours(), hourly visits only) sum hourly unique devices: a device active in two hours of a day counts twice. Use it to compare hours and weekdays. - Active users for all accounts are per-account numbers summed: a device used in two accounts counts twice.
- Gap-free series: overview
getDaily()and active usersgetDaily()have one row per day of the period,getHours()always has 24 rows andgetWeekdays()7; days and hours without data have zeros. - Missing values are null: a breakdown row of devices that did not report the dimension (no OS version, no app
version) has
getValue() === null; likewiseEventStatDTO::getEvent(),TopItemDTO::getItemId()andTopPageDTO::getPage()return null when the events carried no value. - Add-to-cart rate is
add_to_cart / viewsrounded to 4 decimals,0.0when there were no views. - Platforms and dimensions in responses are kept as raw strings, so a value this SDK version does not know never
breaks a response (
PlatformBreakdownDTO::getDimension()returns null for it). Requests take enums only.
Errors
Every call may throw a subclass of Dotsplatform\AnalyticsApi\Exceptions\AnalyticsApiException:
| Exception | When |
|---|---|
AnalyticsValidationException |
422: getErrors() / getErrorsFor('date_to'), Laravel style (bad date format, date_to before date_from, range over 366 days, unknown platform, limit out of 1–500) |
AnalyticsUnauthorizedException |
401: wrong or missing token |
AnalyticsNotFoundException |
404: the path does not exist (the service also answers 404 for account 0) |
AnalyticsRateLimitException |
429: getRetryAfterSeconds() |
AnalyticsServerException |
any other error status |
AnalyticsConnectionException |
no response (DNS, timeout, refused) |
AnalyticsUnexpectedResponseException |
the body does not have the documented shape |
AnalyticsConfigurationException |
analytics-api.host or analytics-api.token is empty |
Tests of consumers
Set ANALYTICS_API_STUB=success (or fail), or bind a stub in a test:
use Dotsplatform\AnalyticsApi\AnalyticsClient; use Dotsplatform\AnalyticsApi\Clients\FailAnalyticsStubClient; use Dotsplatform\AnalyticsApi\Clients\SuccessAnalyticsStubClient; $this->app->bind(AnalyticsClient::class, SuccessAnalyticsStubClient::class); // sample data, no HTTP $this->app->bind(AnalyticsClient::class, FailAnalyticsStubClient::class); // every call throws AnalyticsServerException (503)
The sample data is in Clients\Stubs\StubResponses. Its filters echo the request.
Development
composer install composer init-pre-commit # fixer + phpstan + phpunit before each commit composer test # PHPUnit, HTTP mocked with Guzzle MockHandler composer analyse # PHPStan, level max composer fix # PHP CS Fixer (Laravel Shift style + strict rules)