Analytics
Real-time traffic analytics for every domain routed through the Oblien edge — request counts, bandwidth, geo breakdown, raw request logs, and live SSE streaming.
Open Dashboard → Routes, then Analytics on any
hostname. For a static Page, Pages → a Page → Analytics also lets you select
its managed or custom hostname. Each registered hostname, including www
aliases and disabled routes, has its own analytics. Disabling a route preserves
access to its recorded history; deleted routes no longer authorize access.
Analytics are collected automatically for all domains registered via Pages, Edge Proxy, Edge Tunnel, and Public Access (exposed ports). No configuration needed.
How it works
- Every request hitting the edge (OpenResty) is logged asynchronously — zero impact on request latency
- Per-minute metrics are buffered in Redis, then flushed to the database every 60 seconds
- The API serves recent data (last 24h) from Redis and older data from DB rollups, auto-stitching both
- Live streaming delivers request logs in real-time via SSE from the edge
Namespace isolation
Analytics requires an account/dashboard session, an admin key, or a namespace-scoped key. Workspace-only and billing-only credentials cannot read route traffic or obtain live-stream tokens.
When using a namespace-scoped API key, only domains within that namespace
are visible in the home summary and per-domain endpoints. A query parameter
cannot widen that scope. Another account's domain and an unknown domain both
return an opaque 404; an owned domain in another namespace returns
403 namespace_mismatch.
Home summary
Recorded cumulative counters across your registered hostnames, including disabled routes. Optionally filtered by namespace. These counters are not a billing-period allowance or a selected-range total; use the unified endpoint for a time range and Usage for billing-period traffic.
// All domains
const home = await client.analytics.home();
// Filtered by namespace
const ns = await client.analytics.home({ namespace: 'production' });GET /analytics/home/summary
GET /analytics/home/summary?ns=productioncurl "https://api.oblien.com/analytics/home/summary?ns=production" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Query parameters
| Parameter | Type | Description |
|---|---|---|
ns | string | Filter by namespace slug. Auto-applied for namespace-scoped API keys. |
Response
{
"success": true,
"data": {
"domains": [
{
"domain": "myapp.oblien.app",
"requests": 14820,
"bandwidth_in": 5242880,
"bandwidth_out": 104857600
},
{
"domain": "docs.example.com",
"requests": 3200,
"bandwidth_in": 1048576,
"bandwidth_out": 20971520
}
],
"totals": {
"requests": 18020,
"bandwidth_in": 6291456,
"bandwidth_out": 125829120,
"domain_count": 2
}
}
}Unified (single domain)
All analytics for a single domain in one call — overview, timeseries, geo, and request logs. This is the recommended endpoint for dashboards.
const { data } = await client.analytics.get('myapp.oblien.app');
console.log(data.overview); // { requests, bandwidth_in, bandwidth_out }
console.log(data.timeseries); // [{ timestamp, requests, bandwidth_in, ... }]
console.log(data.geo); // { total, countries: [{ code, count, pct }] }
console.log(data.requests); // [{ ip, method, status, uri, ... }]GET /analytics/:domain
GET /analytics/:domain?from=1713398400000&to=1713484800000&interval=hour&limit=100curl "https://api.oblien.com/analytics/myapp.oblien.app?interval=hour" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from | number | now - 24h | Start of range (epoch ms) |
to | number | now | End of range (epoch ms) |
interval | string | auto | Bucket size: minute, hour, or day. Auto-detected from range if omitted. |
limit | number | 50 | Max request log entries (1–200) |
Response
{
"success": true,
"data": {
"overview": {
"requests": 14820,
"bandwidth_in": 5242880,
"bandwidth_out": 104857600
},
"timeseries": [
{
"timestamp": 1713398400,
"requests": 620,
"bandwidth_in": 204800,
"bandwidth_out": 4194304,
"response_time_sum": 3100,
"unique_visitors": 45,
"countries": { "US": 310, "DE": 180, "JP": 130 }
}
],
"geo": {
"total": 14820,
"countries": [
{ "code": "US", "count": 7200, "pct": 48.6 },
{ "code": "DE", "count": 3800, "pct": 25.6 }
]
},
"requests": [
{
"ip": "203.0.113.42",
"timestamp": "2026-04-18T12:30:15Z",
"date": "2026-04-18",
"method": "GET",
"status": "200",
"uri": "/api/data",
"ua": "Mozilla/5.0 ...",
"req_size": "256",
"res_size": "8192",
"req_time": "0.012"
}
]
},
"meta": {
"domains": ["myapp.oblien.app"],
"from": 1713398400000,
"to": 1713484800000,
"interval": "hour",
"timeseries_count": 24,
"requests_count": 50
}
}The overview is derived from the timeseries data — it always matches the chart. Bandwidth values are in bytes.
Country data covers at most the last 24 hours of the range. requests contains
the latest captured log entries, independently of from and to.
Timeseries
Time-bucketed metrics for charts. Last 24h from Redis (minute-level granularity), older from DB rollups (hour/day).
const ts = await client.analytics.timeseries('myapp.oblien.app', {
from: Date.now() - 7 * 86400000,
interval: 'day',
});GET /analytics/:domain/timeseries?from=1712793600000&interval=daycurl "https://api.oblien.com/analytics/myapp.oblien.app/timeseries?interval=hour" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from | number | now - 24h | Start of range (epoch ms) |
to | number | now | End of range (epoch ms) |
interval | string | auto | minute, hour, or day |
Response
{
"success": true,
"data": [
{
"timestamp": 1713398400,
"requests": 620,
"bandwidth_in": 204800,
"bandwidth_out": 4194304,
"response_time_sum": 3100,
"unique_visitors": 45,
"countries": { "US": 310, "DE": 180 }
}
],
"meta": {
"from": 1712793600000,
"to": 1713484800000,
"interval": "day",
"count": 8
}
}Geo breakdown
Country breakdown of traffic. Capped to the last 24 hours (Redis-only).
const geo = await client.analytics.geo('myapp.oblien.app');
// geo.data.countries → [{ code: 'US', count: 7200, pct: 48.6 }, ...]GET /analytics/:domain/geo
GET /analytics/:domain/geo?from=1713398400000&to=1713484800000curl "https://api.oblien.com/analytics/myapp.oblien.app/geo" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response
{
"success": true,
"data": {
"total": 14820,
"countries": [
{ "code": "US", "count": 7200, "pct": 48.6 },
{ "code": "DE", "count": 3800, "pct": 25.6 },
{ "code": "JP", "count": 2100, "pct": 14.2 },
{ "code": "GB", "count": 1720, "pct": 11.6 }
]
}
}Request logs
Recent raw request logs. Served from the Redis request list — most recent first.
const logs = await client.analytics.requests('myapp.oblien.app', { limit: 100 });GET /analytics/:domain/requests?limit=100curl "https://api.oblien.com/analytics/myapp.oblien.app/requests?limit=100" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Max entries (1–200) |
Response
{
"success": true,
"data": [
{
"ip": "203.0.113.42",
"timestamp": "2026-04-18T12:30:15Z",
"date": "2026-04-18",
"method": "GET",
"status": "200",
"uri": "/api/data",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"req_size": "256",
"res_size": "8192",
"req_time": "0.012"
}
],
"meta": { "count": 1 }
}Fields
| Field | Description |
|---|---|
ip | Client IP address |
timestamp | ISO 8601 timestamp |
method | HTTP method (GET, POST, etc.) |
status | HTTP status code |
uri | Request path |
ua | User agent string |
req_size | Request body size in bytes |
res_size | Response body size in bytes |
req_time | Server-side processing time in seconds |
Live stream
Real-time request log streaming via Server-Sent Events (SSE). The stream is served directly by the edge (OpenResty) — not proxied through the API.
Step 1: Get a stream token
const { data } = await client.analytics.streamToken('myapp.oblien.app');POST /analytics/:domain/live/tokencurl -X POST "https://api.oblien.com/analytics/myapp.oblien.app/live/token" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 300,
"stream_url": "https://edge.oblien.com/stream",
"usage": "Authorization: Bearer <token>"
}
}Step 2: Connect to the SSE stream
const es = new EventSource(
`${data.stream_url}?token=${data.token}`
);
es.onmessage = (event) => {
const entry = JSON.parse(event.data);
console.log(`${entry.method} ${entry.uri} → ${entry.status}`);
};
es.onerror = () => {
// Token expired or connection lost — request a new token and reconnect
es.close();
};Stream tokens expire after 5 minutes. The SSE connection must be established before expiry. If the connection drops, request a new token and reconnect.
Each SSE message is a JSON object with the same fields as request logs.
Endpoint reference
| Endpoint | Method | Description |
|---|---|---|
/analytics/home/summary | GET | Aggregated stats across all domains |
/analytics/:domain | GET | Unified: overview + timeseries + geo + requests |
/analytics/:domain/timeseries | GET | Time-bucketed metrics |
/analytics/:domain/geo | GET | Country breakdown |
/analytics/:domain/requests | GET | Raw request logs |
/analytics/:domain/live/token | POST | Issue SSE stream token |
Data retention
| Source | Granularity | Retention |
|---|---|---|
| Redis (live) | Per-minute | ~24 hours |
| DB rollups | Hourly | 90 days |
| DB rollups | Daily | 1 year |
| Request logs | Individual | ~1 hour (last 200 per domain) |