Admin API: reports
17 methods for reports: building and CSV, background exports, saved reports, custom metrics, the click log, the cloaking decision and lead data.
The methods on this page build the same reports as the report builder in the panel and return data on individual clicks. Authorization is described on the Admin API: overview and authorization page, and the request format and common error codes on the Request, response and error format page.
Methods and token scopes
- A Summary reports («Сводные отчёты») token (
reports:read) has access to three methods:POST /api/v1/reports,POST /api/v1/reports/exportandGET /api/v1/custom-metrics. - The other 14 methods need Full access («Полный доступ») (
full).
A reports:read token builds a report only with the allowed groupings and filters. Their list is in the Summary reports access limits section. For a report with other fields, the server responds 403 with code token_scope.
Background exports and saved reports belong to the user who created the token. The API also shows the ones this user created in the panel.
Report request
Three methods accept the same body: POST /reports, POST /reports/export and POST /report-exports.
| Field | Type | Required | Description |
|---|---|---|---|
from | string, RFC 3339 date | yes | Start of the period. Both boundaries are included in the period |
to | string, RFC 3339 date | yes | End of the period |
dimensions | array of string | no | Groupings, up to 12. Without groupings, the report consists of one row |
metrics | array of string | no | Metrics. The default: clicks, conversions, leads, sales, revenue, cost, profit, roi, cr, approval |
filters | array of object | no | Conditions combined with AND |
filter_groups | array of object | no | Groups of conditions, up to 16 |
timezone | string | no | Time zone in IANA format, for example Europe/Warsaw. The default is UTC. The value Local is not accepted |
attribution | string | no | click, conversion or default. The default is default — the tracker setting |
sort | object | no | {"field": "...", "direction": "asc"}. The default is clicks in descending order |
limit | integer | no | Rows per page, from 1 to 1000. 0 or no field — 1000 |
offset | integer | no | How many rows to skip, at least 0 |
compare | boolean | no | Comparison with the previous period |
The time zone sets the boundaries of hours, days, weeks and months in groupings, and the spend day. What is counted by the click date and what by the conversion date is explained on the Period, time zone and attribution page.
Groupings
| Group | dimensions values |
|---|---|
| Objects | campaign, stream, offer, lander, source, campaign_group |
| GEO and network | country, region, city, connection, isp, ip |
| Device | device, device_model, os, os_version, browser, browser_version, lang, user_agent |
| Time | hour, day, week, month, year, dow, hour_of_day |
| Flags | bot, unique, cloak, proxy, empty_referer |
| Click parameters | click_id, external_id, creative_id, keyword, adset_name, ad_name, referer, sub_id_1 … sub_id_30 |
- Grouping values in the response are always strings.
campaign,stream,offer,landerandsourcecontain IDs, not names.- Flags come as
1or0. - A week starts on Monday. In
dow, Monday is1and Sunday is7. - The
sub_id_12value is not revealed: the row contains***or nothing.
Metrics
Built-in metrics: clicks, unique_clicks, bots, lp_clicks, lp_ctr, leads, sales, rejected, conversions, cost, revenue, revenue_expected, profit, profit_expected, roi, roi_expected, cr, approval, cpa, epc, cpl. The formulas are on the Report metrics and formulas page.
You can also pass in metrics:
- the key of a custom metric — its
namefield; - a conversion type column —
cv_followed by the type ID without hyphens. The IDs are returned byGET /api/v1/conversion-types, see Admin API: conversions and postback.
Amounts come as numbers in USD, percentages as numbers without the % sign.
Filters
A condition is an object of three strings:
| Field | Type | Required | Description |
|---|---|---|---|
field | string | yes | Click field or built-in metric |
op | string | yes | Operator |
value | string | yes | Value, up to 2048 characters. A number is also passed as a string |
The operators depend on the kind of field:
| Field kind | Fields | Operators |
|---|---|---|
| Text | Groupings, except time, flags and click_id, plus subid and ad_campaign_id | eq, ne, contains, not_contains, starts, ends, regexp, not_regexp, has, not_has |
| Flag | bot, unique, cloak, proxy, empty_referer, ipv4, ipv6 | eq, ne |
| Metric | Built-in metrics | gt, gte, lt, lte, eq, ne |
hasandnot_hascheck whether the field is filled in. Thevalueis ignored.- For a flag, the values
0,false,no,off,нетand an empty string mean “no”; any other value means “yes”. - For a metric,
valueis a finite number. - You cannot filter by time groupings: the period is set by
fromandto.
A group in filter_groups is an object {"mode": "or", "filters": [...]}. The mode field takes and or or, and the filters array cannot be empty. Groups are combined with each other and with filters using AND. In an or group, you cannot mix click fields and metrics. A request can have no more than 64 conditions in total. The operator values and examples are on the Report filters page.
Sorting
sort.field is one of the selected groupings or a metric, built-in or custom. sort.direction is asc or desc. The other groupings follow in ascending order.
Building a report
Build a report
POST /api/v1/reportsReturns a page of the report and the totals for all rows. Token scope: reports:read or full. The body is a report request. A report is built in no more than 70 seconds.
Response — code 200:
| Field | Type | Description |
|---|---|---|
columns | array of string | Column keys: groupings first, then metrics in the order of the request |
rows | array of array | Rows. The values follow the order of columns |
totals | object or null | Totals for all rows of the report, not for the page. The keys are the requested metrics. If there are no rows — null |
total_rows | integer | How many rows the report has in total |
limit, offset, row_limit | integer | The applied page size and offset. row_limit equals limit |
truncated | boolean | true if not all rows of the report got into rows |
has_more | boolean | true if there are more rows after this page |
attribution | string | The applied attribution: click or conversion |
timezone | string | The time zone the report was calculated in |
Compare timezone in the response with the requested one: if the time zone is not available on the server, the report is calculated in UTC.
To get the next page, repeat the request with offset increased by limit while has_more is true.
Example request:
curl -X POST https://panel.example.com/api/v1/reports \
-H "Api-Key: <token>" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-07T23:59:59Z",
"timezone": "Europe/Warsaw",
"dimensions": ["campaign", "country"],
"metrics": ["clicks", "leads", "cost", "revenue", "roi"],
"filters": [
{"field": "bot", "op": "eq", "value": "0"},
{"field": "clicks", "op": "gte", "value": "100"}
],
"sort": {"field": "roi", "direction": "desc"},
"limit": 100
}'Example response:
{
"columns": ["campaign", "country", "clicks", "leads", "cost", "revenue", "roi"],
"rows": [
["c41a9e07-52bd-4f13-8a60-9d7e6f5a4b3c", "PL", 1520, 42, 310.5, 504, 62.318840579710144],
["7b0c1d52-3f4e-4a6b-9c8d-1e2f3a4b5c6d", "CZ", 830, 17, 190, 204, 7.368421052631579]
],
"totals": {"clicks": 2350, "cost": 500.5, "leads": 59, "revenue": 708, "roi": 41.45854145854146},
"truncated": false,
"row_limit": 100,
"total_rows": 2,
"limit": 100,
"offset": 0,
"has_more": false,
"attribution": "click",
"timezone": "Europe/Warsaw"
}Comparison with the previous period
With "compare": true, two fields are added to the response:
previous_rows— rows of the previous period. The row with the same index inrowsbelongs to the same group. If a group has no data in one of the periods, its metrics are0.previous_totals— totals of the previous period.
The previous period has the same length and ends before from. Conditions:
- the period is no longer than 366 days;
- there is no more than one of
hour,day,week,month,yearamong the groupings; - each of the two periods has no more than 100,000 rows.
A sort metric that is not in metrics is added to columns as the last column. Without sort, it is clicks. How to read the comparison is explained on the Period comparison and daily trend page.
Errors:
| Code | code | When |
|---|---|---|
403 | token_scope | A reports:read token, and the groupings or filters contain a field outside the allowed list |
422 | validation | from and to are not specified; an unknown grouping, metric, filter field or operator; more than 12 groupings; limit out of range; invalid sort, attribution or timezone; the comparison conditions are not met. The reason is in message |
503 | unavailable | Temporary failure. The response has the Retry-After: 5 header |
503 | report_unavailable | The report was not built: an analytics failure or the time limit was exceeded. Repeat the request later or narrow the report |
Get a report as a CSV file
POST /api/v1/reports/exportBuilds the same report and returns it as a file right away. Token scope: reports:read or full. The body is a report request. The limit and offset fields do not affect the contents of the file: all rows go into it.
Response — code 200, type text/csv; charset=utf-8, a file name like report-20261007-101500.csv. The first line contains the column keys; there is no totals row. The file contents are described in the What's inside the CSV section, and the columns of a comparison file in the Comparison CSV section.
Limits: 100,000 rows and 32 MiB.
The errors are the same as for POST /reports, plus two more:
| Code | code | When |
|---|---|---|
422 | validation | The report has more than 100,000 rows: export exceeds 100000 rows; narrow the report filters. Narrow the period or the filters |
500 | internal | The file is larger than 32 MiB. Narrow the report or remove some of the columns |
Background CSV exports
A background export builds the same file without waiting for a response: the request puts the report in a queue, and the finished file is kept for 24 hours from its creation. All methods need full access. The same feature in the panel is described on the Saved reports and CSV export page.
Export object:
| Field | Type | Description |
|---|---|---|
id | uuid | Export ID |
state | string | pending — in the queue, running — being built, ready — the file is ready, failed — error |
error_code | string | Reason for the error; an empty string in other states |
row_count | integer | Number of rows in the finished file |
compare | boolean | true for a period comparison export |
created_at | string, RFC 3339 date | Creation time |
expires_at | string, RFC 3339 date | Time after which the export is deleted |
Values of error_code
report_limit_or_validation— the report has more than 100,000 rows, or the report settings no longer pass validation;file_too_large— the file is larger than 32 MiB;scope_changed— the role or campaign access of the token's creator has changed;interrupted— the build was interrupted several times;report_failed— the report was not built.
Create an export
POST /api/v1/report-exportsPuts a report in the queue. The body is a report request. The offset field is ignored, and limit does not affect the contents of the file. The default attribution is fixed at the moment of the request.
Response — code 202 and an export object in the pending state.
| Code | code | When |
|---|---|---|
422 | validation | Укажите корректный период отчёта (specify a valid report period) — there is no from or to, or to is earlier than from. The other reasons are the same as for POST /reports |
429 | export_quota | The user already has 2 exports in the pending and running states, or the team has 20 unexpired exports |
List exports
GET /api/v1/report-exportsReturns the unexpired exports of the token's creator: an array of objects, up to 20, newest first. There are no parameters. Poll the list until the state of the export you need becomes ready or failed.
Download the file
GET /api/v1/report-exports/{id}/fileReturns the finished file: code 200, type text/csv; charset=utf-8, name report-<id>.csv. The id path parameter is the export ID.
| Code | code | When |
|---|---|---|
403 | scope_changed | The permissions of the export's owner changed after it was created. Create a new export |
404 | not_found | The token's creator has no export with this id: it has expired or was deleted |
409 | not_ready | The state is not ready: Выгрузка ещё не готова (the export is not ready yet) |
curl https://panel.example.com/api/v1/report-exports/5e2f8a10-9b3c-4d7e-8f21-0a1b2c3d4e5f/file \
-H "Api-Key: <token>" \
-o report.csvDelete an export
DELETE /api/v1/report-exports/{id}Deletes an export in any state and frees a place in the quota. Response — code 204 with no body. Error: 404, code not_found.
Saved reports
A saved report is a name and a set of report builder settings: what opens in the panel from the list of saved reports. All methods need full access. Object:
| Field | Type | Description |
|---|---|---|
id | uuid | ID |
name | string | Name, from 1 to 120 characters |
definition | object | Report settings |
created_at, updated_at | string, RFC 3339 date | Creation and last change time |
The definition fields. The object takes no more than 32 KiB, and a field outside this list gives a 422 error:
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | yes | Always 1 |
mode | string | yes | flat — a table, drilldown — drill-down |
dimensions | array of string | no | From 0 to 12 groupings without repeats |
metrics | array of string | yes | From 1 to 64 metrics without repeats |
filters | array of object | no | Up to 64 conditions, in the same format as in the report request |
filter_groups | array of object | no | Groups of conditions |
attribution | string | yes | default, click or conversion |
sort | object | yes | field and direction |
page_size | integer | yes | From 1 to 1000 |
date_range | object | yes | preset, tz, and for a range — from and to |
Period and mode in definition
date_range.preset:today,yesterday,this_week,last_7,this_month,prev_month,last_30,this_year,all,range,range_time.- For
range, thefromandtodates are written as2026-10-01, and forrange_timeas2026-10-01T09:30. For the other values,fromandtoare not saved. date_range.tzis an IANA time zone. An empty value is saved asUTC.- The
drilldownmode needs at least one grouping. Theyear,month,week,day,hour,dow,hour_of_dayandsub_id_12groupings are available only in theflatmode.
List saved reports
GET /api/v1/report-viewsReturns an array of the saved reports of the token's creator. Recently changed ones come first. There are no parameters.
Save a report
POST /api/v1/report-viewsCreates a saved report. The body has two required fields: name and definition. Response — code 201 and the object.
Error 422 with code validation: an invalid name, settings larger than 32 KiB, an unknown field in definition, an unknown grouping, metric or operator. The reason is in message.
Edit a saved report
PUT /api/v1/report-views/{id}Replaces the name and settings entirely. The body and checks are the same as when creating. Response — code 200 and the object. Errors: 422 with code validation, 404 with code not_found.
Delete a saved report
DELETE /api/v1/report-views/{id}Response — code 204 with no body. Error: 404, code not_found.
Custom metrics
A custom metric is a report column calculated by a formula from built-in metrics. Metrics are shared by the whole team. The list is available to reports:read and full tokens; creating, editing and deleting — only to full. Object:
| Field | Type | Required | Description |
|---|---|---|---|
id | uuid | — | ID, set by the tracker |
name | string | yes | Metric key for metrics and sort.field. Latin letters, digits and _. Does not match a built-in metric and does not start with cv_ |
formula | string | yes | Formula, for example revenue / clicks * 100. The rules are in the Formula section |
title | string | no | Column name in the panel |
format | string | no | number, currency or percent. The default is number |
decimals | integer | no | Decimal places. The default is 0 |
List custom metrics
GET /api/v1/custom-metricsReturns an array of metrics. There are no parameters.
Create a metric
POST /api/v1/custom-metricsThe body contains the object fields without id. Response — code 201 and the object.
Error 422 with code validation: name и formula обязательны (name and formula are required), имя совпадает с базовой метрикой (the name matches a base metric), имя — только латиница, цифры и _ (the name — only Latin letters, digits and _) or a message that starts with ошибка в формуле: (formula error:). The messages are explained in the Error messages section.
Edit a metric
PUT /api/v1/custom-metrics/{id}Pass all fields: name, formula, title and decimals are replaced with the values from the body. An empty format keeps the previous format. Response — code 200 and the object. Errors: 422 with code validation, 404 with code not_found.
Delete a metric
DELETE /api/v1/custom-metrics/{id}Response — code 204 with no body. Error: 404, code not_found.
Clicks
Three methods return data on individual clicks. All of them need full access. Searching for a click by IP, subid or external_id and the full click card are described on the Admin API: logs and diagnostics page.
Click log
GET /api/v1/reports/clicksReturns the latest clicks across all campaigns for the period, newest first.
| Parameter | Where | Type | Required | Description |
|---|---|---|---|---|
from | query | string, RFC 3339 date | no | Start of the period. The default is 24 hours ago |
to | query | string, RFC 3339 date | no | End of the period. The default is the current time |
limit | query | integer | no | Number of clicks, from 1 to 1000. By default and for a value out of range — 200 |
A date that could not be parsed is replaced with the default value. Encode the + sign in a time zone offset as %2B, or pass the time in UTC with the letter Z.
Response — code 200 and an object {"items": [...]}. The click fields: time, click_id, campaign_id, source_id, offer_id, country, city, device, os, browser, ip, is_bot, cost, external_id, sub_id_1.
Errors: 503 with code no_analytics — analytics is unavailable, 400 with code report_error — the query failed.
Cloaking decision for a click
GET /api/v1/reports/click-cloak?subid=<subid>Returns the cloaking decision for a click. The subid query parameter is required — it is the click_id from the click log.
Response — code 200. If no decision is saved for the click, you get {"found": false}. Otherwise — {"found": true, "verdict": {...}}:
verdict field | Type | Description |
|---|---|---|
result | string | money — passed to the target stream, white — filtered out, challenge — waiting for the JS check |
stage | string | The stage at which the decision was made: server or challenge |
score | integer | Score of the JS challenge |
human | boolean | Whether the visitor was recognized as a human at the challenge stage |
reasons | array of string | Reasons for the decision. They are explained on the Diagnostics: why a click went to “white” page |
details | string | Explanation as text |
ts | integer | Time of the decision, Unix time in milliseconds |
Errors: 400 with code bad_request — there is no subid parameter; 404 with code not_found and the message click not found — there is no such click.
Lead data for a click
GET /api/v1/reports/click-lead?subid=<subid>Returns the contact details that a landing page form bound to the click. The subid query parameter is required.
Response — code 200 and an object {"found": true, "lead": {...}}. The lead fields: email, phone, first_name, last_name. If there is no data for the click, found is false and the lead fields are empty.
The errors are the same as for the cloaking decision.