Skip to content

API analytics

Requires a license with the apianalytics feature. See pricing.

API analytics counts every request to both APIs and rolls the numbers up by hour. You can read totals, break them down by endpoint, tenant, method or client, chart them over time, and list the hours that stand out from the rest.

Requests are held in memory and written to the database every 60 seconds by default, so numbers can lag by that much. Paths are grouped before they are counted, so /api/v1/content/post/42 and /api/v1/content/post/43 count as /api/v1/content/post/{id}:

A path segment that isCounts as
A UUID, with or without dashes{uuid}
A ULID (26 characters){ulid}
A number{id}
A hex digest of 8 to 64 characters{hash}
16 or more letters, digits, dashes or underscores{token}

The last rule also catches long readable words, so /api/admin/synthetic-monitoring/probes counts as /api/admin/{token}/probes.

A tenant admin always sees their own tenant, and a tenant_id they pass is ignored. A super admin sees every tenant, or one when they pass tenant_id.

This needs a license with apianalytics. You need an admin token in TOKEN. The quickstart shows how to get one.

  1. Read the totals for the last 24 hours:

    Terminal window
    curl http://localhost:3001/api/admin/apianalytics/metrics/summary \
    -H "Authorization: Bearer $TOKEN"
    {
    "from": "2026-09-30T09:05:43Z",
    "to": "",
    "total_requests": 1739,
    "total_2xx": 673,
    "total_4xx": 1063,
    "total_5xx": 3,
    "error_rate": 0.613,
    "avg_latency_p50_ms": 21.8,
    "avg_latency_p95_ms": 22.2,
    "avg_latency_p99_ms": 22.3,
    "max_latency_p99_ms": 10754.9,
    "avg_request_size_bytes": 4.2,
    "max_request_size_bytes": 222,
    "unique_endpoints": 955,
    "unique_tenants": 2,
    "unique_methods": 4,
    "unique_user_agents": 2
    }

    from echoes the start the query used. to stays empty when you did not set one, which means now.

  2. Rank the endpoints:

    Terminal window
    curl "http://localhost:3001/api/admin/apianalytics/metrics/endpoints?limit=3" \
    -H "Authorization: Bearer $TOKEN"
    {
    "items": [
    { "key": "/api/admin/search", "request_count": 15, "error_rate": 0.467, "avg_latency_p95_ms": 12.8, "max_latency_p99_ms": 42.1, "request_size_avg_bytes": 8.7 }
    ],
    "total": 1
    }

    total is the number of rows in items.

  3. Chart the hours:

    Terminal window
    curl http://localhost:3001/api/admin/apianalytics/metrics/trend \
    -H "Authorization: Bearer $TOKEN"
    { "points": [ { "hour": "2026-10-01T05:00:00Z", "request_count": 1701, "error_rate": 0.62, "avg_latency_p95_ms": 22.5, "max_latency_p99_ms": 10754.9 } ] }
  4. List the hours that stand out. An even day answers an empty list:

    Terminal window
    curl http://localhost:3001/api/admin/apianalytics/metrics/anomalies \
    -H "Authorization: Bearer $TOKEN"
    { "anomalies": [], "window_hours": 24, "z_threshold": 2.5 }
  5. In the admin console, open Insight > API analytics to see the same figures as charts.

Every metrics route takes these:

ParameterMeaningDefault
fromStart of the range, RFC 3339, inclusive.24 hours ago
toEnd of the range, RFC 3339, inclusive.now
endpointOnly this grouped path.all
methodOnly this HTTP method.all
user_agent_familyOnly this client family: Chrome, Firefox, Safari, Edge, curl, python-requests, Go-http-client, Postman, other, or unknown for no user agent.all
tenant_idOnly this tenant. Super admin only.all, or your own
limitRows in a breakdown, 1 to 500.30

anomalies compares each hour in the trailing window with the window's average, for request count, error rate and p95 latency. An hour is flagged when it sits at least anomaly_z_threshold standard deviations away. Severity is low from the threshold, medium from 1.5 times it and high from twice it.

{
"anomalies": [
{ "hour": "2026-10-01T03:00:00Z", "metric_name": "error_rate", "actual_value": 0.21, "expected_avg": 0.02, "z_score": 5.4, "severity": "high" }
],
"window_hours": 24,
"z_threshold": 2.5
}

A super admin reads the settings with GET /api/admin/apianalytics/config and changes them with PUT, sending only the fields to change. Both answer {"config": {...}} with every setting.

Terminal window
curl -X PUT http://localhost:3001/api/admin/apianalytics/config \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"retention_days": 30, "anomaly_z_threshold": 3}'
SettingMeaningDefaultAllowed
retention_daysHourly rows older than this are deleted once a day.901 or more
anomaly_window_hoursHours the anomaly check looks back.244 or more
anomaly_z_thresholdStandard deviations that flag an hour.2.5above 0
aggregation_enabledRecord and roll up requests.true
metrics_buffer_sizeRequests held in memory between writes.10000100 to 1,000,000
flush_interval_secondsSeconds between writes to the database.605 or more
max_distinct_endpointsDistinct endpoints counted per window. Past it, new paths count as __overflow__.10000100 to 100,000

There are no environment variables. The settings above are saved in the database. If you set LYEVE_PLUGINS to choose which features start, include apianalytics in it. See licensing and tiers.

Without a license that includes apianalytics, the routes are not served and answer 404. If the license lapses while the instance runs, they answer 402 with "error": "payment_required".

All routes are on the Admin API and need an admin.

MethodPathReturns
GET/api/admin/apianalytics/metrics/summaryTotals, error rate, latency percentiles and distinct counts.
GET/api/admin/apianalytics/metrics/endpointsBreakdown by endpoint.
GET/api/admin/apianalytics/metrics/tenantsBreakdown by tenant.
GET/api/admin/apianalytics/metrics/methodsBreakdown by method.
GET/api/admin/apianalytics/metrics/agentsBreakdown by client family.
GET/api/admin/apianalytics/metrics/trendOne point per hour.
GET/api/admin/apianalytics/metrics/anomaliesHours that stand out.
GET/api/admin/apianalytics/configCurrent settings. Super admin.
PUT/api/admin/apianalytics/configChange some or all settings. Super admin.

The four breakdowns answer the same shape. key is the endpoint, tenant, method or client family.

StatusMessageCause
400invalid JSON bodyThe settings body is not JSON.
400retention_days must be >= 1 and the other range messagesA setting is outside its allowed range.
402payment_requiredThe license lapsed while the instance ran.
403super_admin requiredA tenant admin called a settings route.
403tenant scope requiredThe token carries no tenant and is not a super admin.
503database errorThe database did not answer. Retry.