Skip to content
Cloudflare Docs

Analytics

Cloudflare Realtime TURN service counts ingress and egress usage in bytes. You can access this real-time and historical data using the TURN analytics API. You can see TURN usage data in a time series or aggregate that shows traffic in bytes over time.

Cloudflare TURN analytics is available over the GraphQL API only.

Available metrics and dimensions

TURN analytics provides rich data that you can query and aggregate in various ways.

Metrics

You can query the following metrics:

  • egressBytes: Total bytes sent from TURN servers to clients
  • ingressBytes: Total bytes received by TURN servers from clients
  • concurrentConnections: Average number of concurrent connections

These metrics support aggregations using sum and avg functions.

Dimensions

You can break down your data by the following dimensions:

  • Time aggregations: datetime, datetimeMinute, datetimeFiveMinutes, datetimeFifteenMinutes, datetimeHour
  • Geographic: datacenterCity, datacenterCountry, datacenterRegion (Cloudflare data center location)
  • Identity: keyId, customIdentifier, username

Filters

You can filter the data in TURN analytics on:

  • Datetime range
  • TURN Key ID
  • TURN Username
  • Custom identifier

GraphQL clients

GraphQL is a self-documenting protocol. You can use any GraphQL client to explore the schema and available fields. Popular options include:

  • Altair: A feature-rich GraphQL client with schema documentation explorer
  • GraphiQL: The original GraphQL IDE
  • Postman: Supports GraphQL queries with schema introspection

To explore the full schema, configure your client to connect to https://api.cloudflare.com/client/v4/graphql with your API credentials. Refer to Explore the GraphQL schema for detailed instructions.

Useful TURN analytics queries

Below are some example queries for common usecases. You can modify them to adapt your use case and get different views to the analytics data.

Concurrent connections with data usage over time

This comprehensive query shows how to retrieve multiple metrics simultaneously, including concurrent connections, egress, and ingress bytes in 5-minute intervals. This is useful for building dashboards and monitoring real-time usage.

query concurrentConnections {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
limit: 10000
filter: { date_geq: $dateFrom, date_leq: $dateTo }
) {
dimensions {
datetimeFiveMinutes
}
avg {
concurrentConnectionsFiveMinutes
}
sum {
egressBytes
ingressBytes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"avg": {
"concurrentConnectionsFiveMinutes": 816
},
"dimensions": {
"datetimeFiveMinutes": "2025-12-02T03:45:00Z"
},
"sum": {
"egressBytes": 207314144,
"ingressBytes": 8534200
}
},
{
"avg": {
"concurrentConnectionsFiveMinutes": 1945
},
"dimensions": {
"datetimeFiveMinutes": "2025-12-02T16:00:00Z"
},
"sum": {
"egressBytes": 462909020,
"ingressBytes": 128434592
}
},
]
}
]
}
]
}

Top TURN keys by egress

query egressByTurnKey{
viewer {
usage: accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
filter: {
date_geq: $dateFrom,
date_leq: $dateTo
}
limit: 2
orderBy: [sum_egressBytes_DESC]
) {
dimensions {
keyId
}
sum {
egressBytes
}
}
}
},
"errors": null
}

Example response:

{
"data": {
"viewer": {
"usage": [
{
"callsTurnUsageAdaptiveGroups": [
{
"dimensions": {
"keyId": "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
},
"sum": {
"egressBytes": 160040068147
}
}
]
}
]
}
},
"errors": null
}

Top TURN custom identifiers

query topTurnCustomIdentifiers {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
filter: { date_geq: $dateFrom, date_leq: $dateTo }
limit: 1
orderBy: [sum_egressBytes_DESC]
) {
dimensions {
customIdentifier
}
sum {
egressBytes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"dimensions": {
"customIdentifier": "some identifier"
},
"sum": {
"egressBytes": 160040068147
}
}
]
}
]
}
},
"errors": null
}

Usage for a specific custom identifier

query {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
filter: {
date_geq: $dateFrom
date_leq: $dateTo
customIdentifier: "tango"
}
limit: 100
orderBy: []
) {
dimensions {
keyId
customIdentifier
}
sum {
egressBytes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"usage": [
{
"callsTurnUsageAdaptiveGroups": [
{
"dimensions": {
"customIdentifier": "tango",
"keyId": "74007022d80d7ebac4815fb776b9d3ed"
},
"sum": {
"egressBytes": 162641324
}
}
]
}
]
}
},
"errors": null
}

Usage as a timeseries (for graphs)

query {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
filter: { date_geq: $dateFrom, date_leq: $dateTo }
limit: 100
orderBy: [datetimeMinute_ASC]
) {
dimensions {
datetimeMinute
}
sum {
egressBytes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"dimensions": {
"datetimeMinute": "2025-12-01T00:00:00Z"
},
"sum": {
"egressBytes": 159512
}
},
{
"dimensions": {
"datetimeMinute": "2025-12-01T00:01:00Z"
},
"sum": {
"egressBytes": 133818
}
},
... (more data here)
]
}
]
}
},
"errors": null
}

Usage breakdown by geographic location

You can break down usage data by Cloudflare data center location to understand where your TURN traffic is being served. This is useful for optimizing regional capacity and understanding geographic distribution of your users.

query {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
limit: 100
filter: { date_geq: $dateFrom, date_leq: $dateTo }
orderBy: [sum_egressBytes_DESC]
) {
dimensions {
datacenterCity
datacenterCode
datacenterRegion
datacenterCountry
}
sum {
egressBytes
ingressBytes
}
avg {
concurrentConnectionsFiveMinutes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"avg": {
"concurrentConnectionsFiveMinutes": 3135
},
"dimensions": {
"datacenterCity": "Columbus",
"datacenterCode": "CMH",
"datacenterCountry": "US",
"datacenterRegion": "ENAM"
},
"sum": {
"egressBytes": 47720931316,
"ingressBytes": 19351966366
}
},
...
]
}
]
}
},
"errors": null
}

Filter by specific key or identifier

You can filter data to analyze a specific TURN key or custom identifier. This is useful for debugging specific connections or analyzing usage patterns for particular clients.

query {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
limit: 1000
filter: {
keyId: "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
date_geq: $dateFrom
date_leq: $dateTo
}
orderBy: [datetimeFiveMinutes_ASC]
) {
dimensions {
datetimeFiveMinutes
keyId
}
sum {
egressBytes
ingressBytes
}
avg {
concurrentConnectionsFiveMinutes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"avg": {
"concurrentConnectionsFiveMinutes": 130
},
"dimensions": {
"datetimeFiveMinutes": "2025-12-01T00:00:00Z",
"keyId": "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
},
"sum": {
"egressBytes": 609156,
"ingressBytes": 464326
}
},
{
"avg": {
"concurrentConnectionsFiveMinutes": 118
},
"dimensions": {
"datetimeFiveMinutes": "2025-12-01T00:05:00Z",
"keyId": "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
},
"sum": {
"egressBytes": 534948,
"ingressBytes": 401286
}
},
...
]
}
]
}
},
"errors": null
}

Time aggregation options

You can choose different time aggregation intervals depending on your analysis needs:

  • datetimeMinute: 1-minute intervals (most granular)
  • datetimeFiveMinutes: 5-minute intervals (recommended for dashboards)
  • datetimeFifteenMinutes: 15-minute intervals
  • datetimeHour: Hourly intervals (best for long-term trends)

Example query with hourly aggregation:

query {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
limit: 1000
filter: {
keyId: "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
date_geq: $dateFrom
date_leq: $dateTo
}
orderBy: [datetimeFiveMinutes_ASC]
) {
dimensions {
datetimeFiveMinutes
keyId
}
sum {
egressBytes
ingressBytes
}
avg {
concurrentConnectionsFiveMinutes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"avg": {
"concurrentConnectionsFiveMinutes": 130
},
"dimensions": {
"datetimeFiveMinutes": "2025-12-01T00:00:00Z",
"keyId": "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
},
"sum": {
"egressBytes": 609156,
"ingressBytes": 464326
}
},
{
"avg": {
"concurrentConnectionsFiveMinutes": 118
},
"dimensions": {
"datetimeFiveMinutes": "2025-12-01T00:05:00Z",
"keyId": "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
},
"sum": {
"egressBytes": 534948,
"ingressBytes": 401286
}
},
...
]
}
]
}
},
"errors": null
}

Advanced use cases

Combining multiple dimensions

You can combine multiple dimensions in a single query to get more detailed breakdowns. For example, to see usage by both time and location:

query {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
limit: 10000
filter: { date_geq: $dateFrom, date_leq: $dateTo }
orderBy: [datetimeHour_ASC, sum_egressBytes_DESC]
) {
dimensions {
datetimeHour
datacenterCity
datacenterCountry
}
sum {
egressBytes
ingressBytes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"dimensions": {
"datacenterCity": "Chennai",
"datacenterCountry": "IN",
"datetimeHour": "2025-12-01T00:00:00Z"
},
"sum": {
"egressBytes": 3416216,
"ingressBytes": 498927214
}
},
{
"dimensions": {
"datacenterCity": "Mumbai",
"datacenterCountry": "IN",
"datetimeHour": "2025-12-01T00:00:00Z"
},
"sum": {
"egressBytes": 1267076,
"ingressBytes": 1140140
}
},
...
]
}
]
}
},
"errors": null
}

Identifying top consumers

To find which keys or custom identifiers are using the most bandwidth:

query {
viewer {
accounts(filter: { accountTag: $accountId }) {
callsTurnUsageAdaptiveGroups(
limit: 10
filter: { date_geq: $dateFrom, date_leq: $dateTo }
orderBy: [sum_egressBytes_DESC, sum_ingressBytes_DESC]
) {
dimensions {
keyId
customIdentifier
}
sum {
egressBytes
ingressBytes
}
avg {
concurrentConnectionsFiveMinutes
}
}
}
}
}

Example response:

{
"data": {
"viewer": {
"accounts": [
{
"callsTurnUsageAdaptiveGroups": [
{
"avg": {
"concurrentConnectionsFiveMinutes": 837305
},
"dimensions": {
"customIdentifier": "",
"keyId": "82a58d0aeabfa8f4a4e0c4a9efc9cda5"
},
"sum": {
"egressBytes": 160040068147,
"ingressBytes": 154955460564
}
}
]
}
]
}
},
"errors": null
}

Schema exploration

The GraphQL Analytics API is self-documenting. You can use introspection to discover all available fields, filters, and capabilities for callsTurnUsageAdaptiveGroups. Using a GraphQL client like Altair or GraphiQL, you can browse the schema interactively to find additional dimensions and metrics that may be useful for your specific use case.

For more information on GraphQL introspection and schema exploration, refer to: