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.
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 respect namespace boundaries. When using a namespace-scoped API key, only domains within that namespace are visible — both in the home summary and per-domain endpoints. Domains outside the namespace return 404 with no information leakage.
Home summary
Aggregated stats across all your domains. Optionally filtered by namespace.
// 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.
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) |