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.
TURN analytics provides rich data that you can query and aggregate in various ways.
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.
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
You can filter the data in TURN analytics on:
- Datetime range
- TURN Key ID
- TURN Username
- Custom identifier
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.
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.
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
}
},
]
}
]
}
]
}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
}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
}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
}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
}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
}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
}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 intervalsdatetimeHour: 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
}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
}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
}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: