API Reference

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

  1. Every request hitting the edge (OpenResty) is logged asynchronously — zero impact on request latency
  2. Per-minute metrics are buffered in Redis, then flushed to the database every 60 seconds
  3. The API serves recent data (last 24h) from Redis and older data from DB rollups, auto-stitching both
  4. 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=production
curl "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

ParameterTypeDescription
nsstringFilter 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=100
curl "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

ParameterTypeDefaultDescription
fromnumbernow - 24hStart of range (epoch ms)
tonumbernowEnd of range (epoch ms)
intervalstringautoBucket size: minute, hour, or day. Auto-detected from range if omitted.
limitnumber50Max 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=day
curl "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

ParameterTypeDefaultDescription
fromnumbernow - 24hStart of range (epoch ms)
tonumbernowEnd of range (epoch ms)
intervalstringautominute, 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=1713484800000
curl "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=100
curl "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

ParameterTypeDefaultDescription
limitnumber50Max 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

FieldDescription
ipClient IP address
timestampISO 8601 timestamp
methodHTTP method (GET, POST, etc.)
statusHTTP status code
uriRequest path
uaUser agent string
req_sizeRequest body size in bytes
res_sizeResponse body size in bytes
req_timeServer-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/token
curl -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

EndpointMethodDescription
/analytics/home/summaryGETAggregated stats across all domains
/analytics/:domainGETUnified: overview + timeseries + geo + requests
/analytics/:domain/timeseriesGETTime-bucketed metrics
/analytics/:domain/geoGETCountry breakdown
/analytics/:domain/requestsGETRaw request logs
/analytics/:domain/live/tokenPOSTIssue SSE stream token

Data retention

SourceGranularityRetention
Redis (live)Per-minute~24 hours
DB rollupsHourly90 days
DB rollupsDaily1 year
Request logsIndividual~1 hour (last 200 per domain)