# Troubleshoot Real-Time Metrics

This page lists the symptoms that [Real-Time Metrics](/en/documentation/platform/real-time-metrics/) shows on a dashboard in Azion Console or in a GraphQL API response, each with its cause and its fix. The chart symptoms come first: low or missing points, empty and failed charts, the variation tag, totals that differ from Billing, the tooltip, the legend, copied queries, and the query input. The errors the GraphQL API returns close the page.

---

## The newest points of a chart read lower than the rest

The last points of a line fall below the traffic you expect, then rise when you refresh the dashboard a few minutes later.

The Console does not plot the last bucket when the range ends at the current minute. The buckets before it may still be aggregating, for up to 10 minutes, so they can read low, as [Aggregation and delay](/en/documentation/platform/real-time-metrics/how-it-works/#aggregation-and-delay) explains.

- **End the range 10 minutes back**: in the **Absolute** tab of the time range picker, set **End date** to a time slot at least 10 minutes in the past, then select **Apply**.
- **Refresh after the delay**: select **Refresh** once the newest minutes have finished aggregating.
- **In a GraphQL query**: set the `end` of `tsRange` at least 10 minutes before the query runs. The same query sent twice within those 10 minutes returns different values for its newest buckets, for the same reason.

Every point of a range that ended 10 minutes or more in the past is final, and returns the same value on each refresh.

---

## A chart shows No data available

A chart card shows `No data available` in place of the chart, on one chart or on every chart of a product tab.

The dataset holds no metrics for the selected range and filters. Three cases cause it: the product that records the metrics is not active in your account, no traffic reached that product in the range, or an applied filter matches no traffic.

- **Activate the product behind the chart**: Real-Time Metrics reads only what these products record.

| Tab or chart                                                              | Requirement                                                                                                                                |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Edge Cache** chart, **Build** › **Applications** › **Data Transferred** | [Cache](/en/documentation/platform/applications/#cache) active in your account                                                             |
| **Build** › **Tiered Cache**                                              | [Tiered Cache](/en/documentation/platform/applications/cache/tiered-cache/) active in your account                                         |
| **Build** › **Functions**                                                 | [Functions](/en/documentation/platform/functions/) active in your account                                                                  |
| **Build** › **Image Processor**                                           | [Image Processor](/en/documentation/platform/applications/#image-processor) active in your account                                         |
| **Secure** › **Edge DNS**                                                 | [Edge DNS](/en/documentation/platform/edge-dns/) active in your account                                                                    |
| **Secure** › **Bot Manager**                                              | A subscription to [Bot Manager](/en/documentation/platform/firewall/#bot-manager), through [Technical Support](/en/documentation/support/) |
| **Observe** › **Data Stream**                                             | [Data Stream](/en/documentation/platform/data-stream/) active, with at least one stream configured                                         |

- **Widen the time range**: the initial range, **Last 5 minutes**, is empty when no request arrived in those minutes. Select a preset such as **Last 24 hours**.
- **Remove a filter**: select the remove icon on each applied-filter chip until the chart plots.
- **Read an empty array as no data**: through the API, a dataset with no metrics for the range returns `200` and an empty array, not an error. A `tieredCacheMetrics` query on an account with no Tiered Cache traffic returns:

```json
{
  "data": {
    "tieredCacheMetrics": []
  }
}
```

Once the product records traffic in the range, the chart plots it, and a bucket with no events inside the range plots as zero.

---

## A query for an older range returns an empty array

A GraphQL query for an old range returns `200` and an empty array, while the same query for a recent range returns rows.

Real-Time Metrics keeps each dataset for a fixed period, and past that period it returns no rows and no error. The period differs by dataset, so a breakdown dataset can return nothing for a range that another dataset still answers.

A query for a range in 2023 returns:

```json
{
  "data": {
    "httpMetrics": []
  }
}
```

- **Start the range inside the retention period** of the dataset, which [Data retention](/en/documentation/platform/real-time-metrics/limits/#data-retention) lists.
- **Expect partial ranges to return what is kept**: a range that starts before the retention period still returns the rows inside it, with no error.
- **Store what you need to keep longer**: query a period once it is complete and save the result, as [Best practices for Real-Time Metrics](/en/documentation/platform/real-time-metrics/best-practices/#end-every-range-you-compare-or-store-at-least-10-minutes-in-the-past) describes.

Inside the retention period, the query returns the rows that hold data.

---

## A chart shows The chart can't be plotted

A chart card shows `The chart can't be plotted. There was an issue loading the data.` in place of its aggregation tag row.

Each chart sends its own query to the GraphQL API, and this chart's query returned an error instead of data. The other charts of the dashboard can still plot.

- **Send the query again**: select **Refresh**.
- **Narrow the range or add a filter**: the API refuses a query past its request rate or the rows it reads, as [Real-Time Metrics limits](/en/documentation/platform/real-time-metrics/limits/#graphql-api) shows.
- **Read the error yourself**: in the chart's **More options** menu, select **Copy query**, then run the query in [GraphiQL Playground](/en/documentation/devtools/graphql/graphql-playground/). The response carries the message the chart does not show.

After the fix, the chart plots its points, or the response names one of the errors under [The GraphQL API refuses a query](/en/documentation/platform/real-time-metrics/troubleshooting/#the-graphql-api-refuses-a-query).

---

## The variation tag reads Can't compare

The variation tag of a chart reads **Can't compare**, in a warning color with a triangle icon, instead of a percentage.

The tag compares the selected range with the window of the same length immediately before it. It reads **Can't compare** when the change is between –0.01% and +0.01%, when either window has no value, or when the earlier window is 0.

- **Read a change within ±0.01% as no change**: the totals of the two windows differ by less than 0.01%.
- **Choose a range whose earlier window had traffic**: for example, if an application started serving traffic 30 minutes ago, **Last 1 hour** compares with an hour that held no requests.
- **Compare complete windows**: end the range at least 10 minutes in the past, so neither window holds buckets that are still aggregating.

When both windows hold a value and the change exceeds 0.01%, the tag shows the change as a percentage with two decimals, as [Variation tag](/en/documentation/platform/real-time-metrics/filters-and-time-range/#variation-tag) describes.

---

## Real-Time Metrics totals differ from Billing

The total of a dashboard or a query for a period differs from the usage that Azion Billing reports for the same period.

Real-Time Metrics counts each event at most once, and Billing counts each event exactly once, so Real-Time Metrics can miss an event that Billing counts. On average, the two differ by less than 1%, as [Counting and Billing](/en/documentation/platform/real-time-metrics/how-it-works/#counting-and-billing) explains.

- **Use the Billing figure for charges**: when the two differ, Billing is the reference, as [Real-Time Info and Precise Billing](/en/documentation/fundamentals/billing-and-subscriptions/#real-time-info-and-precise-billing) describes.
- **Use Real-Time Metrics for operations**: read the dashboards to see a traffic change within minutes, not to settle a charge.
- **Compare complete periods**: end the range at least 10 minutes in the past, so no bucket of the total is still aggregating.

A gap of about 1% between the two is the expected difference, not a fault in either one.

---

## A chart shows no tooltip

A chart plots, but shows no values when you hover over a series.

Azion Console shows the tooltip of a chart only in a browser window wider than 540 px. At 540 px and below, no chart shows a tooltip.

- **Widen the browser window** past 540 px.
- **Read the totals in the legend**: each entry shows the series name and its total over the range.
- **Export the points**: in the chart's **More options** menu, select **Export CSV** to download the points as plotted.

In a window wider than 540 px, the tooltip lists the name and value of each series at the point under the cursor.

---

## A chart legend stops at 16 series

A chart that splits its data into many series, such as one per domain, draws 16 of them, and its legend lists 16 entries.

A chart plots at most 16 series. Any series after the 16th is not added to the chart or to its legend.

- **Filter to the series you need**: add a filter on **Domain** or **Workload**, whichever label your account shows, with the **In** operator and the values you compare.
- **Query every series through the API**: select **Copy query** in the chart's **More options** menu, and run the query with a `limit` high enough for every row, up to 10,000. The copied query keeps the chart's own `limit`.

With the filter applied, the chart draws each series the filter keeps, up to 16, and the API returns one row for each series.

---

## A copied query does not run in GraphiQL

A query pasted from **Copy query** into GraphiQL Playground does not run as pasted.

**Copy query** copies a text block, not a request: the line `# QUERY`, the query, the line `# VARIABLES`, and the variables as a JSON object. The query reads its filter values from those variables, so the JSON belongs in the variables pane, not in the query editor.

To run the copied query in GraphiQL Playground:

1. **Paste the copied text into the query editor**

2. **Cut the JSON object that follows the VARIABLES comment**

   The object starts after the `# VARIABLES` line.

3. **Paste the JSON object into the variables pane**

4. **Run the query**

The response holds a `data` object named after the dataset, with the rows behind the chart. For the clipboard format, refer to [Copy query](/en/documentation/platform/real-time-metrics/filters-and-time-range/#copy-query), and for the playground, to [GraphiQL Playground](/en/documentation/devtools/graphql/graphql-playground/).

---

## The query input refuses a filter

A message appears under the Azion Query Language input in the filter row, and **Refresh** stays disabled.

The expression breaks a syntax rule of the input, or names a field that the dataset of the current dashboard does not have. The fields depend on the dashboard, so an expression that works on one dashboard can fail on another.

- **Space the operator**: write `status = 200`, not `status=200`.
- **Quote names of more than one word**: write `"Upstream Status"`.
- **Close lists in parentheses**: write `domain in (domain1, domain2)`, with no comma after the last value.
- **Give between two different values**: write `status between (200, 300)`.
- **Pick fields from the suggestions**: `Ctrl` + `Space`, or `Cmd` + `Space`, lists only the fields of the current dashboard.

When the expression is valid, the message clears and `Enter` applies it to the dashboard. Each message, verbatim, is listed in [Validation messages](/en/documentation/platform/real-time-metrics/filters-and-time-range/#validation-messages).

---

## The GraphQL API refuses a query

The GraphQL API answers at `https://api.azion.com/v4/metrics/graphql`. When it refuses a query, it returns a JSON body whose `detail` field holds the message. Each entry quotes the body the API returns. For every status code and message of the API, refer to [GraphQL API error responses](/en/documentation/devtools/graphql/error-responses/).

### A query is refused with Authentication credentials were not provided

The API answers `401` with this body:

```json
{
  "detail": "Authentication credentials were not provided."
}
```

The request carries no `Authorization` header, and every query to the API needs a personal token.

- **Send a personal token** in the `Authorization: Token [TOKEN VALUE]` header. To create one, refer to [How to manage a personal token](/en/documentation/guides/platform/account-and-billing/personal-tokens/). Test it with a minimal query:

```bash
curl -X POST 'https://api.azion.com/v4/metrics/graphql' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Token [TOKEN VALUE]' \
  -d '{"query":"{ __typename }"}'
```

- **Replace an invalid or expired token**: the API answers `401` with other messages, listed in [GraphQL API error responses](/en/documentation/devtools/graphql/error-responses/).

With a valid token, the API answers `200`:

```json
{
  "data": {
    "__typename": "Query"
  }
}
```

### A query is refused because it has no time range

The API answers `400` with this body:

```json
{
  "detail": "To execute queries it is mandatory to provide the desired time interval."
}
```

Every query must set a time range in its `filter`, and this one sets none.

- **Add `tsRange` to the filter**: for example, `filter: { tsRange: { begin: "2026-01-01T12:00:00", end: "2026-01-02T12:00:00" } }`.
- **Or set `tsGt` and `tsLt`** for the start and the end of the range.

With a time range, the query returns `200` and the rows of that range.

### A query is refused with You have exceeded the limit amount allowed for selected fields

The API answers `400` with this body:

```json
{
  "detail": "You have exceeded the limit amount allowed for selected fields (37 fields)."
}
```

The query selects more fields than one query accepts. The `ts` field counts toward the limit, and an aggregate output such as `sum` does not, as [Real-Time Metrics limits](/en/documentation/platform/real-time-metrics/limits/#graphql-api) shows.

- **Drop the fields you do not read**, including `ts` when you do not group by time.
- **Split the selection into two queries** over the same range and filter.

Within the limit, the query returns `200` with every selected field.

### A query is refused with The value for the query limit is invalid

The API answers `400` with this body:

```json
{
  "detail": "The value for the query limit is invalid (must be between 0 to 10000 rows)."
}
```

The `limit` argument is above 10,000 or below 0.

- **Set `limit` between 0 and 10,000.**
- **For more rows, shorten the range** or page through the rows with `offset`, as [GraphQL features](/en/documentation/devtools/graphql/features/) describes.
- **Do not drop `limit` to avoid the error**: a query without it is not refused, but it returns 10 rows.

With a valid `limit`, the query returns up to that many rows.

### A query is refused with Cannot query field

The API answers `400` when a dataset or a field name does not exist. For a dataset, the message suggests the closest names:

```json
{
  "detail": "Cannot query field \"imageProcessedMetrics\" on type \"Query\". Did you mean \"imagesProcessedMetrics\", \"edgeStorageMetrics\", \"ingestMetrics\" or \"dataStreamedMetrics\"?"
}
```

The query names a dataset or a field that the API does not have, such as `imageProcessedMetrics` for the `imagesProcessedMetrics` dataset. For a field, the message names the type it was looked up in, such as `Cannot query field "wafThreatFamilies" on type "HttpMetricsAggregatedFieldsLogType".`

- **Take the dataset name the message suggests**, such as `imagesProcessedMetrics`.
- **Check the field in its dataset**: [Real-Time Metrics GraphQL fields](/en/documentation/devtools/graphql/gql-real-time-metrics-fields/) lists the fields of each dataset.

With names the API knows, the query returns `200`.

### A query is refused with Argument has invalid value

The API answers `400` when `groupBy` or `aggregate` names a field it does not accept on that dataset:

```json
{
  "detail": "Argument \"groupBy\" has invalid value [remoteAddress].\nIn element #0: Expected type \"HttpMetricsGroupByFields\", found remoteAddress."
}
```

`groupBy` accepts only the dimensions of its own dataset, and `remoteAddress` is a dimension of `httpBreakdownMetrics`, not of `httpMetrics`. A computed field needs no `aggregate`, so `sum: uniqueSessions` on `connectedUsersMetrics` returns a message that starts with `Argument "aggregate" has invalid value {sum: uniqueSessions}.`

- **Query the dataset that has the dimension**: group by `remoteAddress` on `httpBreakdownMetrics`, as [Find the top sources of WAF threats](/en/documentation/guides/platform/observability/find-top-waf-threat-sources/) does.
- **Select a computed field directly**: remove `aggregate`, and list the field, such as `uniqueSessions`, among the selected fields.

With fields the dataset accepts, the query returns `200`.

### A query is refused with You have reached the request rate limit

The API answers `429` with the message `You have reached the request rate limit!`.

More requests reached the API in one minute than it accepts, as [Real-Time Metrics limits](/en/documentation/platform/real-time-metrics/limits/#graphql-api) shows.

- **Wait, then send the request again.**
- **Send fewer requests**: query a complete period once and keep the result, instead of querying the same period again.
- **Select several fields in one query** instead of one query per field.

Below the rate limit, each request returns its data again.

### A call to the legacy API host answers 403 Forbidden

A query sent to `https://api.azionapi.net/metrics/graphql` answers `403` with an HTML page titled `Azion - Default error page` that reads `Forbidden`, not with JSON.

`api.azionapi.net` is the legacy host of the API. Real-Time Metrics queries go to the v4 endpoint.

- **Send the query to `https://api.azion.com/v4/metrics/graphql`**, with the `Authorization: Token [TOKEN VALUE]` header.
- **Update a Grafana data source that uses the legacy URL**, as [Import the Data Transferred dashboard](/en/documentation/guides/platform/observability/data-transferred-dash/) shows.

On the v4 endpoint with a valid token, the query returns `200` and a JSON body.

---

## Related resources

- [Real-Time Metrics limits](/en/documentation/platform/real-time-metrics/limits.md): The retention of each dataset and every bound the fixes on this page refer to.
- [How Real-Time Metrics works](/en/documentation/platform/real-time-metrics/how-it-works.md): How aggregation, resolution, and counting shape the value of each point.
- [Filters and time range](/en/documentation/platform/real-time-metrics/filters-and-time-range.md): The time range picker, the filter operators, the chart states, and the chart menu.
- [GraphQL API error responses](/en/documentation/devtools/graphql/error-responses.md): Every status code and message the GraphQL API returns, with its cause.
