---
title: "Analytics reports via API"
description: "Pull reports over the API — the report types the REST path serves, formats, date ranges, group-by, eqStr/eqLong filter maps, pagination, exports, column ids, saved reports and DSP spend, with the refusals and the daily quota."
type: "reference"
audience: [network-manager, admin]
keywords: [api, analytics, report, eqStr, eqLong, groupBy, range, csv, xlsx, json, jsonKeys, column id, export, statisticType, curl, dsp spend, invoicing, header bidding]
---

Fetch the complete documentation index at: https://epom.com/ad-server/docs/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Analytics reports via API

Reports you run in **Account → Analytics** can also be pulled over the API, in five output
formats. This page documents the report endpoints, their filter model, and which report types
the REST path serves. Examples authenticate with an `X-Api-Key` header or with the hash in the
path; see the [API overview](https://epom.com/ad-server/docs/api.md) for both. All report parameters, with descriptions and
an example request:
[`GET /rest-api/analytics/{format}.do`](https://epom.com/ad-server/docs/api/analytics.md#get-rest-apianalyticsformatdo).

## Endpoints

Four operations of the Analytics group — two paths, each on GET and POST — are
the report engine itself, in two flavors that differ only in how you authenticate:

| Method | Path | Auth |
|---|---|---|
| GET/POST | `/rest-api/analytics/{format}.do` | Your API key in the `X-Api-Key` header (or `Authorization: Bearer <key>`). A call with no key, or a key that matches no one, is a `401` |
| GET/POST | `/rest-api/analytics/{format}/{login}/{hash}/{timestamp}.do` | Hash auth **in the path** |

A client that cannot set a header may pass the key as an `apiKey` query parameter instead; this
endpoint is the only one that accepts that fallback, and a header wins when both are sent. A
deactivated account is refused on both flavors — `401`, `Account is not active` under
`rest_error`.

`{format}` is `JSON`, `CSV`, `XLSX`, `PDF` or `HTML`. Both flavors accept exactly the same
report parameters, on both GET and POST; nothing is available on one and not the other.

For the hash flavor, `hash` = `MD5( MD5(password) + timestamp )` and `timestamp` is Unix time
**in milliseconds**, valid within ±5 minutes — same rules as everywhere else, just placed in
the path. See the [worked example](https://epom.com/ad-server/docs/api.md#hash-authentication-recommended).

```bash
# API-key flavor, JSON, last 7 days grouped by day
curl -H "X-Api-Key: $APIKEY" "https://<your-network-domain>/rest-api/analytics/JSON.do?range=LAST_7_DAYS&groupRange=DAY"

# Hash flavor, CSV download
curl -o report.csv \
  "https://<your-network-domain>/rest-api/analytics/CSV/john.doe/$HASH/$TS.do?range=LAST_MONTH&groupBy=SITE"
```

The rest of the Analytics group is the column-id list and the layout-settings reader, the DSP
spend report and saved reports — each covered in its own section below — plus
two diagnostics, `GET /rest-api/analytics/report-check.do` and
`GET /rest-api/analytics/raw-log-analyze.do`, documented in the
[generated reference](https://epom.com/ad-server/docs/api/analytics.md).

## Report types

`statisticType` selects the report. The REST path serves every value below, and **omitting
the parameter is itself a report type** — the default is the **General** report, not the
Advanced one.

| `statisticType` | Report | What it returns | Over REST |
|---|---|---|---|
| *(omitted)* | General | Daily and hourly totals | Works — this is the default |
| `HADOOP` | Advanced | Totals broken down by what each ad request carried | Works |
| `IMPRESSIONS_LOG` | Impressions Log | One row per impression | Works |
| `CLICKS_LOG` | Clicks Log | One row per click | Works |
| `CONVERSIONS_LOG` | Conversions Log | One row per conversion | Works |
| `ACTIONS_LOG` | Actions Log | One row per tracked action | Works — but see the note below |
| `TARGETING_COOKIE` | Targeting Cookie | Daily totals | Works |
| `HEADER_BIDDING` | Header Bidding | Daily and hourly totals | Works — see the note below |

Three legacy names are accepted and silently mapped to `HADOOP`: `HADOOP_ADREQUESTS`,
`BROWSER_OS` and `HADOOP_SECURITY`. They exist so bookmarked URLs and saved widgets keep
working; use `HADOOP` in new code.

> **Note:** the Header Bidding report has its own columns — `fills`, `unfilled`, `hbFillRate`,
> `gross` and `ecpmGross` (the money columns need the Gross permissions) — and its own
> dimensions: group by `FILL_SOURCE`, `COUNTRY`, `SITE`, `ZONE` and `PLACEMENT`, and filter
> with `eqStr[FILL_SOURCE]`, `eqStr[COUNTRY]` and `eqLong` on `SITE`, `ZONE` or `PLACEMENT`.
> It returns the same figures as the report in **Account → Analytics**. A publisher sees the
> fills on their own inventory only.

> **Note:** `ACTIONS_LOG` is the one report type the API serves that the UI's report-type
> picker never offers.

Each report type is also gated by its own reporting permission — the *Analytics Report Type …*
group in the role editor (`ACTIONS_LOG` uses the Conversions Log one) — and the report is
refused with `403` naming it, for example `Your role lacks the RPT_TYPE_HADOOP permission.`
A report with no `statisticType` needs the General report permission. A type that works in
principle can therefore still be refused for the calling user.

## Report parameters

The report parameters are identical on all four report operations. Time and scope first:

| Parameter | Values / format | Notes |
|---|---|---|
| `statisticType` | See [Report types](#report-types) | Omit for the General report |
| `range` | `TODAY` (default), `YESTERDAY`, `CURRENT_WEEK`, `LAST_WEEK`, `LAST_2_WEEKS`, `CURRENT_MONTH`, `LAST_7_DAYS`, `LAST_30_DAYS`, `LAST_MONTH`, `LAST_3_MONTH`, `LAST_6_MONTH`, `CURRENT_YEAR`, `LAST_YEAR`, `CUSTOM` | 14 values. `CUSTOM` reads the period from `customFrom` / `customTo`. Each range has its own reporting permission |
| `customFrom`, `customTo` | `yyyy-MM-dd` | Only with `range=CUSTOM`; both default to today, so `CUSTOM` with only `customFrom` reports up to today. `customTo` before `customFrom` is a `customFrom-customTo` error |
| `groupRange` | `NONE` (default), `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR`, `HOUR_24` | Time bucketing; `HOUR_24` groups by hour-of-day across days |
| `hourFrom`, `hourTo` | 0–23 | Only applied when `groupRange=HOUR`; `hourFrom` must not exceed `hourTo`. A value outside 0–24 is a `400` on that field. Send the two together |
| `usersTimeZoneId` | A fixed-offset zone name: `UTC_0`, `UTC_PLUS_2`, `UTC_MINUS_5` … (`UTC_MINUS_12` to `UTC_PLUS_14`) | Advanced (`HADOOP`) reports only; default UTC. Not an IANA ID — an unknown value silently means UTC, and there is no daylight-saving switch. See [Which time zone do reports use?](https://epom.com/ad-server/docs/concepts/analytics-pipelines-explained.md#which-time-zone-do-reports-use) |
| `groupBy` | Repeatable or comma-separated — see [Grouping](#grouping) | |
| `eqStr`, `eqLong` | Filter maps — see [Filters](#filters-eqstr-and-eqlong) | |
| `archive` | boolean | Zips the download: the response is `export.<format>.zip` holding `export.<format>`. It does not select older or archived data |
| `offset`, `limit` | integers | Pagination; `offset` defaults to **0**. Omit `limit` and you get up to the network's **Records per request limit** (10,000 by default); a larger `limit` is a `400`. See the corner case below |
| `hadoopRefDomain` | text | Advanced (`HADOOP`) only: count only requests whose referrer URL contains this text |
| `hadoopAppPackage` | text | Advanced only: count only in-app requests whose app package / bundle id contains this text |
| `hadoopCustomParmName`, `hadoopCustomParmValue` | text | Advanced only: count only requests carrying this custom parameter (the name the ad tag passes), and with a value, only where it has that value |

Then the ones that shape the columns rather than the rows:

| Parameter | Values / format | Notes |
|---|---|---|
| `displayIds` | boolean | Adds entity IDs next to names |
| `showUnique` | boolean | Adds unique visitor/click columns (Advanced reports, Unique view). Omitted, a unique column listed in `reportsLayoutSettings` asks for them as well — `columns.do` lists them for the Unique view; `showUnique=false` still leaves them out |
| `useReportsLayoutSettings` | boolean, default `false` | When `true`, output only the metric columns listed in `reportsLayoutSettings` (grouping and date columns always stay). It does **not** load the layout saved in the UI. When `false`, every metric column your role may see is returned |
| `reportsLayoutSettings` | Repeatable column ids, e.g. `impressions`, `ctr`, `fill_rate` — see [Report column layout](#report-column-layout) | Applied only with `useReportsLayoutSettings=true`; an id that is no column of the report type is dropped, whether or not it would be applied, and named in the `X-Ignored-Report-Columns` response header — the report still runs |
| `splitColumnsBy` | `NONE`, `CONVERSION_ACTION`, `CUSTOM_PARAMETER`, `ATTRIBUTION_TYPE` | The single dimension spread across columns. Not available on the Advanced report |
| `splitByAttribution` | boolean | Nests a click-through / view-through pair under each conversion-action column. Only meaningful with `splitColumnsBy=CONVERSION_ACTION` |
| `actionIds` | Repeatable or comma-separated conversion action ids | Restricts the conversion columns — and the per-action columns of `splitColumnsBy=CONVERSION_ACTION` — to these actions. Omitted: every action you may see |
| `breakdownByActions` | boolean | One conversions column per conversion action. Ignored when `splitColumnsBy` is set |
| `breakdownByCustomParameter` | boolean | Break rows down by custom parameter values |
| `actionCustomParameterFragment`, `actionCustomValueFragment` | strings | Filter by custom parameter name / value fragment |
| `jsonKeys` | `name` (default), `id` | JSON only: how each row names its fields — see [JSON output](#json-output). Other formats ignore it |

> **Corner case:** `splitColumnsBy` takes precedence over `breakdownByActions` /
> `breakdownByCustomParameter`, which it derives, so a report cannot ask for two column splits
> at once. Money columns are rounded to the network's analytics decimal precision; a request
> cannot change it.

> **Corner case:** on the report endpoints `limit=50` is read as "no limit given". 50 is the
> default when you omit the parameter, and the endpoint treats it as the omitted case: a request with `limit=50` returns up to the network's records-per-request limit,
> not 50 rows. Ask for 49 or 51 when you need a page of about that size.

> **Corner case:** `groupBy` is checked on submit, not silently trimmed. A dimension your role
> lacks, or one the report type does not have, refuses the whole request instead of returning a
> report with those columns quietly missing — see [Grouping](#grouping) for the two answers.

## Grouping

`groupBy` is repeatable. Its schema accepts 68 dimension values, but **being in that list does
not mean the report accepts it**: the same value list serves group-by, both filter maps and the
search endpoint's `entities` parameter, so it is a superset of what any single report offers.
Which dimension belongs to which report type is the subject of
[Analytics dimensions](https://epom.com/ad-server/docs/reference/analytics-dimensions.md#per-report-type); the generated reference lists
the dimensions per report type next to the parameter:
[`GET /rest-api/analytics/{format}.do`](https://epom.com/ad-server/docs/api/analytics.md#get-rest-apianalyticsformatdo).

Group by the entity's name dimension (`CAMPAIGN`, not `CAMPAIGN_ID` — no report groups by the
`_ID` values) and add `displayIds=true` for the ids. Repeat the parameter for multi-level grouping:

```bash
curl -H "X-Api-Key: $APIKEY" "https://<your-network-domain>/rest-api/analytics/JSON.do?\
range=LAST_30_DAYS&groupBy=CAMPAIGN&groupBy=COUNTRY&groupRange=DAY&limit=1000"
```

> **Corner case:** grouping by the name dimension (`CAMPAIGN`) merges entities that share a
> name. If you post-process results programmatically, add `displayIds=true` so rows are
> unambiguous.

> **Corner case:** a `groupBy` value is refused for one of two reasons, and the answer says
> which. A grouping your role lacks the permission for answers `403` with
> `permissionDenied: true` and `errors.forbidden` naming the permission — `Your role lacks the
> RPT_GRP_FILTER_ZONE_PLACEMENT permission needed to group by ZONE.` A grouping the report type
> does not have answers `400` with `errors.groupBy` — `The General report does not group by
> DEVICE_FORMAT; the Advanced report (statisticType=HADOOP) does.` — and no permission will
> make it work: send the report type that offers it. Neither is counted against the daily limit.

## Filters: `eqStr` and `eqLong`

Filters are maps passed in bracket notation. The two maps are **not interchangeable** — each
takes its own set of fields:

| Map | Field type | Fields it accepts |
|---|---|---|
| `eqLong[FIELD]=<id>` | Numeric entity IDs | `BANNER`, `CAMPAIGN`, `ADVERTISER`, `PLACEMENT`, `ZONE`, `SITE` |
| `eqStr[FIELD]=<string>` | String values | `CHANNEL`, `COUNTRY`, `PLACEMENT_TYPE`, `BANNER_LABEL`, `PLACEMENT_LABEL`, `ACTION`, `BROWSER`, `OPERATING_SYSTEM`, `UNIVERSAL_AD_ID` |

Both maps accept the full 68-value list as their keys, for the same reason as
`groupBy`; the fields above are the ones the General report filters on. Two notes:

- `UNIVERSAL_AD_ID` filters by the ISCI / Ad-ID code set on video creatives, and needs the
  **Banner ISCI / Ad-ID Code** permission. The raw-log reports resolve it to the creatives
  carrying that code.
- The Advanced report (`statisticType=HADOOP`) reads further `eqStr` fields — its own device,
  browser, risk, language, connection-type and traffic-source dimensions, for example
  `DEVICE_VENDOR`, `RISK_LEVEL`, `CONNECTION_TYPE` and `LANGUAGE` — each behind its own
  reporting permission. [Analytics dimensions](https://epom.com/ad-server/docs/reference/analytics-dimensions.md) says which
  dimension belongs to which report.

Repeat a key for OR-matching within the same field:

```bash
# US or Germany traffic on two specific sites
curl -H "X-Api-Key: $APIKEY" "https://<your-network-domain>/rest-api/analytics/JSON.do?\
range=YESTERDAY&groupBy=SITE&groupBy=COUNTRY\
&eqStr\[COUNTRY\]=US&eqStr\[COUNTRY\]=DE\
&eqLong\[SITE\]=100&eqLong\[SITE\]=101"
```

Different fields are combined with AND: the example above reads "(country US **or** DE)
**and** (site 100 **or** 101)".

> **Note:** remember to escape (or quote) the square brackets in shells; curl also accepts
> `--data-urlencode 'eqStr[COUNTRY]=US'` with `-G` for GET requests.

## Raw event logs

Set `statisticType` to `IMPRESSIONS_LOG`, `CLICKS_LOG`, `CONVERSIONS_LOG` or `ACTIONS_LOG` to
receive raw event rows instead of aggregates — one row per impression, click or conversion,
with the same `range`, filter and pagination parameters. These are significantly heavier than
aggregated reports; keep date ranges short and always paginate.

```bash
curl -H "X-Api-Key: $APIKEY" "https://<your-network-domain>/rest-api/analytics/CSV.do?\
statisticType=CLICKS_LOG&range=YESTERDAY&eqLong\[CAMPAIGN\]=2001&limit=5000"
```

Raw-log results are cached per query on the server. A report
whose numbers look frozen is usually a cache that has not been invalidated because no new log
flush has landed — see [Analytics discrepancies](https://epom.com/ad-server/docs/troubleshooting/analytics-discrepancies.md).

## Report column layout

The metric columns a report can show are named by short ids (`impressions`, `ctr`, `fill_rate`,
`sumGross`…). Two reads tell you which ids you may use:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/analytics/columns.do?statisticType=…` | The columns you may use for a report type — `id`, display `name`, and the picker `group` (`BASIC`, `PUBLISHER`, `ADVERTISER`) with its `groupName` — exactly what the Analytics column picker offers your role. Omit `statisticType` for the General report. Standard REST authentication (hash or `X-Api-Key`). `403` naming the report-type permission when your role cannot run that type |
| GET/POST | `/rest-api/analytics/reports-layout-settings/{login}/{hash}/{timestamp}.do` | All available analytics columns grouped by report type, plus your current selection (legacy path-hash authentication). Optional `statsType` narrows it to one report type |

To get a subset of columns, send `useReportsLayoutSettings=true` together with the column ids in
`reportsLayoutSettings` on the report call. The call does not read the layout you saved in the
UI — the ids you send are the whole selection. The same column ids are what saved reports take
in `reportsLayoutSettings` and dashboard widgets in `data.reportsLayoutSettings` /
`data.choosenMetric`.

> **Corner case:** the layout is read-only over the API. `saveReportsLayoutSettings` is accepted
> but has no effect — writing the layout is only supported from the UI.

> **Corner case:** an id that names no column of the report type for your role — a display
> name such as `Revenue`, a typo, or a retired id — is handled differently per endpoint. The
> report endpoint (`/rest-api/analytics/{format}…`) drops it, runs the report and names the dropped ids in the `X-Ignored-Report-Columns` response header
> (comma-separated, URL-encoded). Dashboard widgets and saved reports, which store the list,
> refuse it with `400`, `success:false` and the unknown ids under `errors` — `reportsLayoutSettings`
> on a saved report, `data.reportsLayoutSettings` (and `data.choosenMetric`) on a widget.

## JSON output

A JSON report is an array with one object per row. By default each field is named by the
column's **display name in your interface language** — `"Impressions"` for an English-speaking
user, `"Impressionen"` for a German one — so the same script reads different keys depending on
whose account runs it.

Several columns share a display name in every language: the Base, Publisher and Advertiser
`Impressions`, `Clicks`, `CTR`, `Requests`, `Fill rate`, `Conversions`, `CCR` and their
post-click / post-view variants, and `Gross` and `Net`, which two column ids each carry. A row may only
carry each name once, so when your role sees more than one of them the **last** keeps the display
name and each earlier one is named by its **column id** instead:

```json
[{"impressions":"1820","publisher_impressions":"1700","Impressions":"1650",
  "clicks":"40","publisher_clicks":"38","Clicks":"37"}]
```

Here `Impressions` is the Advertiser figure — the value a JSON parser read under that name before
release 153, when the three were all sent as `Impressions` and only the last survived parsing.

For scripts, send **`jsonKeys=id`**: every field is then named by its column id (`impressions`,
`publisher_impressions`, `advertiser_impressions`, `sumGross`, `site_name`…), the same in every
language and never repeated. The ids are the ones [Report column layout](#report-column-layout)
lists. A saved report keeps `jsonKeys` like any other parameter, and its scheduled JSON e-mail
names its fields the same way.

```bash
curl -H "X-Api-Key: $APIKEY" "https://<your-network-domain>/rest-api/analytics/JSON.do?\
range=YESTERDAY&groupBy=SITE&displayIds=true&jsonKeys=id"
```

> **Note:** CSV, XLSX, PDF and HTML show display names as column headers, and repeat a shared
> name, as the report page does. A CSV parser that keys rows by header name keeps only one of
> them — read CSV by column position, or use JSON with `jsonKeys=id`.

## DSP endpoint traffic and spend

What a demand partner's endpoint was billed, in the shape invoicing needs — one row per
endpoint instead of one per creative:

```
GET /rest-api/rtb/dsp-analytics/search.do
```

Standard REST authentication — the hash in the query string, or an `X-Api-Key` header. The
caller needs the **DSP Partners Analytics** permission; without it the call is `403` naming
`RTB_DSP_PARTNERS_ANALYTICS`, not an empty result.

| Parameter | Required | Purpose |
|---|---|---|
| `from`, `to` | yes | Range in `yyyy-MM-dd`, both days inclusive. `to` before `from` is `400`. |
| `groupBy` | no | Comma-separated dimensions: `date`, `dspPartnerName`, `placementId`, `countryCode`. An unknown name is `400` rather than a silently ignored breakdown. Omit it for a single total row over the range. |
| `sort` | no | Any dimension you grouped by, or `requests`, `bids`, `wins`, `impressions`, `spend`. |
| `dir` | no | `asc` (default) or `desc`. |
| `start` | no | Row offset, default 0. Negative is `400`. |
| `limit` | no | Default 1000, must be between 1 and 10000; outside that is `400`. |

Rows carry the dimensions you asked for and always carry the metrics — `requests`, `bids`,
`wins`, `impressions`, `clicks`, `spend`. Dimensions you did not group by are aggregated over
and come back `null`.

`countryCode` is `null` for a second reason too — when the ad request's country could not be
resolved. Under `groupBy=countryCode` a `null` therefore means *unknown country*, not
*aggregated*.

```bash
curl -G 'https://<your-network-domain>/rest-api/rtb/dsp-analytics/search.do' \
  --data-urlencode 'from=2026-08-01' \
  --data-urlencode 'to=2026-08-31' \
  --data-urlencode 'groupBy=dspPartnerName' \
  --data-urlencode 'sort=spend' --data-urlencode 'dir=desc' \
  --data-urlencode "username=$USER_NAME" \
  --data-urlencode "timestamp=$TS" --data-urlencode "hash=$HASH"
```

`spend` is the gross booked on the delivery that endpoint won, so it matches the **Gross** a
standard report shows for the partner's OpenRTB creatives. It follows the partner's pricing
model — impressions for CPM and CPI, clicks for CPC — which is why both counts come back with
it, and it counts delivery rather than auction wins. See
[What Spend counts](https://epom.com/ad-server/docs/guides/admin/dsp-partners.md#what-spend-counts).

## Saved reports

Report templates, their schedules and their recipients are managed under
`/rest-api/saved-reports` — list and create, read, update
and delete one, add and remove recipients, and set and clear the schedule. The operations,
their parameters and the refusals are described in
[Saved and scheduled reports](https://epom.com/ad-server/docs/guides/analytics/saved-and-scheduled.md#over-the-api).

## Self-serve dashboard analytics

Self-serve advertiser dashboards have a dedicated aggregate endpoint (hash auth in path,
`Dashboard` tag rather than `Analytics`):

```
GET/POST /rest-api/self-serv/dashboard/{login}/{hash}/{timestamp}.do
```

It takes the report parameters except `archive`, `displayIds` and `showUnique`, and returns current-period KPIs (impressions, clicks, revenue, conversions,
fill rate, CTR, eCPM) against the equivalent previous period. Dashboard **widget** management
endpoints are covered in
[Network services and miscellaneous APIs](https://epom.com/ad-server/docs/api/network-and-misc.md#dashboard-widgets).

## Limits and performance

- Two ceilings apply, both network settings that Epom support changes on request: **Daily
  requests per user limit** (default 100) and
  **Records per request limit** (default 10,000). The daily limit answers `429` with
  `errors["Too Many Requests"]: Daily requests limit reached.`; a `limit` above the
  record-count limit answers `400` with `errors.limit` naming the ceiling. When you hit them, back
  off and batch — don't poll per entity.
- The daily count is kept **per user** and starts again each day by the server's date, not the
  network's reporting time zone. Your own count and limit are in `GET
  /rest-api/network/settings.do` under `analyticsApi` (`dailyRequestLimit`, `requestsUsedToday`,
  `recordsPerRequestLimit`); the AI assistant's `whoami` carries the same block and its
  `analytics_report` answer says how many requests are left.
- A report the role may not run — a report type, date range, group-by or filter whose
  reporting permission it lacks — answers `403` naming the permission, e.g.
  `Your role lacks the RPT_GRP_FILTER_CAMPAIGN_BANNER permission.` A group-by the report
  type does not have is a `400` under `errors.groupBy`, not a `403` (see [Grouping](#grouping)).
  Other validation errors answer `400` with the field in `errors`, and a failed signature or key `401`.
- A report is counted only once it is known to run: a call refused with `400`, `401` or `403`
  does not spend the daily quota, because the checks happen first. A call that is accepted and
  then fails on the server (`500`) has already been counted.
- Without `limit` a report returns up to the records-per-request limit (`recordsPerRequestLimit` in `GET /rest-api/network/settings.do`); page with
  `offset`/`limit` rather than requesting everything.
- Reports reflect processed log data, not the live counter — very recent traffic (typically
  the last minutes) may not appear yet. For discrepancy questions see
  [Analytics discrepancies](https://epom.com/ad-server/docs/troubleshooting/analytics-discrepancies.md).

> **Corner case:** timestamps in the hash-auth path are validated within ±5 minutes. Long-running
> export scripts must compute the hash **per request**, not once at startup, or requests start
> failing with `Request is obsolete` after five minutes.

## Related

- [API overview](https://epom.com/ad-server/docs/api.md)
- [Analytics endpoint reference (generated)](https://epom.com/ad-server/docs/api/analytics.md)
- [Run a report (UI guide)](https://epom.com/ad-server/docs/guides/analytics/run-report.md)
- [Report types](https://epom.com/ad-server/docs/guides/analytics/report-types.md)
- [Exports and AI](https://epom.com/ad-server/docs/guides/analytics/exports-and-ai.md)
- [Network services and miscellaneous APIs](https://epom.com/ad-server/docs/api/network-and-misc.md)
- [Analytics metrics reference](https://epom.com/ad-server/docs/reference/metrics.md)
- [Analytics dimensions reference](https://epom.com/ad-server/docs/reference/analytics-dimensions.md)