API Analytics
Every API's page has an Analytics card, above Gateway calls. It shows how the API is used and how it behaves over time: how many calls, how many failed and why, and how fast it answered. The numbers come from the gateway call log, summarised every 5 minutes.
The same numbers also feed a view across all your APIs, alerts by webhook, email and in the app, the AI Assistant and metrics for Prometheus and Grafana.
What it shows
Headline numbers, each with the change from the previous period of the same length (for example, the last 24 hours against the 24 hours before):
| Number | Meaning |
|---|---|
| Calls | Calls in the period |
| Error rate | Share of calls with a status of 400 or above |
| Availability | Share of calls that did not fail on the server side (no 5xx) |
| Latency p95 / p50 | 95% / half of the calls answered within this time (approximate, see below) |
| Cache hits | Share of calls answered from the response cache |
| Rate limited | Calls refused by a rate limit |
Charts: calls over time, split into successful calls, 4xx and 5xx, and latency over time (≈p50 and ≈p95).
Breakdowns with calls, error rate and latency for each:
- By operation: which operations are used and which fail.
- By environment: dev, uat, prod, … (see Environments).
- By caller type: Studio agents, outside callers with a consumer key, the test console, or direct calls with the gateway secret.
- By consumer key: each outside caller by key name. Calls from a key that has since been deleted are shown as Deleted key.
Errors, grouped by who has to act on them:
| Group | Error codes |
|---|---|
| Caller errors | unauthorized, caller_forbidden, caller_identity_required, caller_token_expired, caller_token_invalid, invalid_path, no_matching_operation, not_found, request_invalid, request_not_json, request_too_large, rate_limited, SOAP faults the caller caused, and upstream_4xx (the upstream rejected the request) |
| Gateway errors | request_encoding_failed, request_mapping_failed, response_decoding_failed, response_mapping_failed, response_invalid |
| Upstream errors | upstream_auth_failed, upstream_auth_unsupported, upstream_blocked, upstream_circuit_open, upstream_response_too_large, upstream_timeout, upstream_tls_config_invalid, upstream_unreachable, service-side SOAP faults, and upstream_5xx (the upstream itself returned a 5xx) |
| Platform | suspended (the API's space, environment or gateway is suspended) |
The codes are explained under request pipeline.
Filters and time ranges
Choose Last 24 hours, 7 days, 30 days, 90 days, 12 months or a custom range, and narrow the view by environment, operation, caller type or consumer key. Clicking a row in a breakdown filters by it.
Ranges up to 7 days are shown per hour, longer ones per day. Hourly detail is kept for 90 days and daily detail for 2 years by default (see retention).
Test-console calls are left out by default: they are people (or the AI Assistant) trying the API, not usage. Choose Test console as the caller to see them.
From a number to the calls behind it
View calls opens the Gateway calls list with the same filters and time range. Clicking an error code shows just the calls that failed with it. Show all calls removes the filter again.
Export
Export CSV downloads what the card shows, as one table with a section column: the totals, the previous period, the time series, each breakdown and the errors. Names that a spreadsheet would run as a formula (starting with =, +, - or @) are exported with a leading ' so they stay text.
Analytics across APIs
Admins, space admins and lifecycle managers can also see many APIs at once:
- Admins: Admin → Dashboard → API Analytics covers every API, with filters for catalog and space.
- Space admins and lifecycle managers: the Analytics tab on the API Builder page covers the active space. It appears when you hold one of those roles in that space. Admins see the tab too.
It shows:
- Headline numbers for all the APIs together (calls, error rate, availability, ≈p95 latency and rate-limited calls), each with the change from the previous period, plus how many APIs were called.
- Calls and errors over time: successful calls, client errors (4xx) and server errors (5xx).
- APIs: each API's calls, change against the previous period, error rate, availability and ≈p95. Sort by most calls, highest error rate, slowest, lowest availability or fastest growing. The table shows 50 rows; the CSV export lists every API.
- By environment, gateway, space and catalog: where the traffic goes and where it fails. The gateway is the platform gateway that serves each environment.
- Errors, grouped by who has to act on them, as on an API's card.
- Top consumer keys: the outside callers sending the most calls, with their error rate.
- Live APIs without calls in this period: APIs that prod serves but nobody called. They may be ready to retire, or their callers may not be reaching them.
Filter by time range and environment; Export CSV downloads the whole view. Test-console calls are never counted here.
Alerts
Alert rules tell you when an API starts failing, slows down or goes quiet, so you hear about it before your users do. Each API's page has an Alerts card above Analytics.
What a rule watches
| Metric | Fires when | Example |
|---|---|---|
| Error rate | The share of calls with a status of 400 or above is over the threshold | Above 5% over 15 minutes |
| Availability | The share of calls without a server error (5xx) is under the threshold | Below 99% over 1 hour |
| Latency p95 | The ≈95th percentile latency is over the threshold, in milliseconds | Above 2000 ms over 30 minutes |
| No traffic | There were no calls in the window, after a window that had calls | No calls for 1 hour |
| Circuit breaker | The gateway's circuit breaker pauses calls to the upstream | Fires at once and resolves when calls resume |
An API that has never been called doesn't raise a no-traffic alert. Each rule also has:
- Environment: one environment, such as prod, or all of them.
- Window: 5, 10, 15 or 30 minutes, or 1, 2, 3, 6, 12 or 24 hours.
- Minimum calls (error rate, availability and latency): the rule fires only when the window has at least this many calls, so one failed call on a quiet API doesn't wake anyone. The default is 20.
- Reminders: optionally repeat the alert every 30 minutes, 1, 4 or 24 hours while it keeps firing.
An API can have up to 25 rules.
Where alerts go
-
Webhook: Slack, Google Chat, Discord and Microsoft Teams (Workflows and the older incoming-webhook connectors) each get a message in their own format. Any other URL receives a JSON
POST:json{ "text": "🔴 API \"Payments\" (prod): error rate 12.5% (threshold 5.0%) over the last 15 minutes.", "state": "firing", "api": "Payments", "api_definition_id": "…", "environment": "prod", "metric": "error_rate", "value": 0.125, "threshold": 0.05, "rule_id": "…" }stateisfiring,resolvedortest. -
Email: up to 10 addresses, sent through Studio's outgoing email settings (
SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASSWORD, andSMTP_FROMwhen the login isn't an email address, as with Amazon SES, SendGrid or Mailgun; see Environment variables). Without them, email is skipped and the incident records why. Messages link to the API whenHRIDAAI_URLis set. -
In the app: open incidents are listed on the API's Alerts card, and the API Builder list marks the API with a red alert(s) firing badge.
Send test sends a sample message to the rule's channels straight away and shows what happened on each one.
Names in messages can't mention a whole channel: <!channel>, <users/all> or @everyone in an API name is sent as plain text.
Webhook URLs get the same protection as API upstreams: no localhost, link-local or cloud-metadata addresses (checked again when sending), and redirects are not followed. They are not limited to the allowed upstream hosts, which apply to APIs only. A saved URL is shown masked (https://hooks.slack.com/…) because it often contains a secret; leave it as it is to keep it.
How alerts are checked
- Rules are checked every 5 minutes, right after the summaries are updated, by one worker at a time.
- Windows shorter than an hour read the raw call log. Longer windows read the hourly summaries from the start of the clock hour the window begins in, so they can be up to an hour longer. For example, a 1-hour window checked at 12:05 covers 11:00 to 12:05.
- Test-console calls are left out.
- Circuit breaker rules don't wait for the check: they fire as soon as the gateway pauses calls, with one incident per environment. As a safety net, the check also resolves a breaker incident that is more than 5 minutes old once a call has gone through to the upstream successfully (not from the cache), in case the gateway's recovery signal was lost, for example in a restart.
- If the summaries are more than 15 minutes old because the summary job is failing, rules with a window of an hour or more wait rather than judge stale numbers: they neither fire nor resolve until the summaries catch up. The Alerts job status counts them as
stale, and the summary job's error is shown under Gateway settings.
Incidents, reminders and muting
Each time a rule fires it opens an incident: one firing message when it starts, one resolved message when it ends, and any reminders in between. The card lists open incidents and, under Recent incidents, the latest ones with their start, end and last value.
- Mute a rule for 1, 8 or 24 hours during planned work or a known outage. Incidents are still recorded, but nothing is sent. If an alert is still firing when the mute ends (or you Unmute), its message goes out then.
- A message that can't be delivered is retried every 5 minutes: while the rule is over its hourly limit, until it can be sent, and after a failed delivery, for up to an hour. The card shows why a message is pending, or that it couldn't be delivered. An incident whose firing message never got through ends without a resolved message. Email that is skipped because SMTP isn't set up is not retried.
- A rule sends at most 30 messages an hour, so a flapping service can't flood a channel.
- Disabling a rule, or changing its metric or environment, closes its open incident without a message. Deleting a rule deletes its incidents.
- Resolved incidents are kept for a year.
- Creating, changing, muting and deleting rules is recorded in the API's audit log.
Who manages alerts
| Who | Can |
|---|---|
| Admins, and the space admins and lifecycle managers of the API's home space | Create, edit, test, mute and delete rules |
| Anyone who can read the API | See its open and recent incidents, without delivery details such as webhook errors |
Ask the AI Assistant
The AI Assistant reads the same numbers. Ask, for example, "How is the Payments API doing this week?" or "Why did errors go up on prod since yesterday?". It compares the period with the one before, names the operations, environments and error codes behind a change, says who has to act, and mentions any alerts that are firing. Like the card, it leaves out test-console calls and treats latency percentiles as approximate.
Metrics for Prometheus and Grafana
With OpenTelemetry metrics on (ENABLE_OTEL=true and ENABLE_OTEL_METRICS=true; see OpenTelemetry), the gateway also exports live metrics for the dashboards and alerting you already run:
| Instrument | Type | Meaning |
|---|---|---|
hridaai.api_gateway.calls | Counter | One per gateway call |
hridaai.api_gateway.call.duration | Histogram (ms) | Call duration, in the same buckets as the analytics latency |
hridaai.api_gateway.breaker.opened | Counter | Circuit breaker openings |
Their attributes are api_id, environment (unknown for calls refused before the environment is known, such as calls to an API that isn't deployed), source (agent, consumer, test, direct or unknown), status_class (2xx to 5xx), error_category (caller, gateway, upstream, platform, other or none) and cache_hit; the breaker counter has api_id and environment only. They never include users, chats or consumer keys, which keeps cardinality low. Unlike analytics, they include test-console calls: filter them out with source!="test".
Example queries, with the names the OpenTelemetry Collector's Prometheus exporter gives them:
# Calls per second, per API
sum by (api_id) (rate(hridaai_api_gateway_calls_total{source!="test"}[5m]))
# Error rate per API
sum by (api_id) (rate(hridaai_api_gateway_calls_total{source!="test", status_class=~"4xx|5xx"}[5m]))
/ sum by (api_id) (rate(hridaai_api_gateway_calls_total{source!="test"}[5m]))
# p95 latency per API, in ms
histogram_quantile(0.95, sum by (le, api_id) (rate(hridaai_api_gateway_call_duration_milliseconds_bucket[5m])))
# Circuit breaker openings in the last hour
sum by (api_id, environment) (increase(hridaai_api_gateway_breaker_opened_total[1h]))
The metrics are kept in memory and exported every 10 seconds by default. With OpenTelemetry metrics off, recording them does nothing.
Accuracy and freshness
- Data is at most about 5 minutes old. The card says up to when its numbers are complete. If the summary job fails, that time stops moving, and admins see the job's error under Gateway settings.
- After upgrading, calls already in the log are summarised in the background, a week of history every 5 minutes, so 30 days of history appear within about half an hour. The per-API totals behind analytics across APIs and alerts are then built from those summaries: the last week within minutes, older history over about an hour.
- Percentiles are approximate. Latency is summarised into fixed buckets (10 ms, 25 ms, 50 ms, 100 ms, 250 ms, 500 ms, 1 s, 2.5 s, 5 s, 10 s, 30 s and above), and p50/p95/p99 are estimated within a bucket. That is why they are marked ≈. Counts and error rates are exact.
- Times are summarised in UTC hours and days; the card shows them in your local time.
Who sees what
| Viewer | Sees |
|---|---|
| Anyone who can read the API (its owner, members of its home space, admins) | Everything on the card |
| Members of a space the API is only published to | The numbers, charts, operations, environments and errors, but not who calls it: no caller-type or consumer-key breakdown. Their call list still shows only their own calls |
| Space admins and lifecycle managers | Analytics across the APIs of the spaces they manage, and the alert rules of those APIs |
| Admins | Also analytics across every API, alert rules on any API, the call log's retention and archive settings, and the archives |
How the call log stays small
The gateway writes one row per call, so on a busy instance the raw table grows quickly. Raw rows are therefore kept only for a short time, and history is carried by small summaries and by compressed archives:
| Tier | What it holds | Kept by default | Used for |
|---|---|---|---|
| Raw call log | One row per call | 30 days | The call list and debugging |
| Hourly summaries | Calls, errors and a latency histogram per API, environment, operation, caller type, consumer key, status class and error code, per hour, plus smaller per-API totals | 90 days | Charts up to 90 days back, analytics across APIs and alerts |
| Daily summaries | The same per day | 2 years | Long-range trends |
| Archives | Each finished UTC day of raw calls, as a compressed file in storage | 1 year (0 = forever) | Audits and investigations |
Background jobs maintain this:
- Summaries: every 5 minutes, updates the hourly summaries and, once a day has ended, its daily summary.
- Alerts: right after each summaries run, checks the alert rules.
- Archive: once a day, writes each finished day of raw calls to storage before anything is deleted.
- Clean-up: once a day, deletes raw calls older than the retention, in small batches. With archiving on it deletes only days that were archived, and it never deletes calls that haven't been summarised yet.
Settings (admins)
On the API Builder page, open Gateway settings › Call log retention & archive. You can set:
- how long to keep raw calls, hourly summaries, daily summaries and archives
- whether to archive each day before deleting it (off: old raw calls are deleted without a copy)
The same card shows the table sizes, where archives are stored, how each job last went (with Run now), and the archives with download links. The settings can also be set with environment variables; see Environment variables.
Archives
Archives follow Studio's storage setting (STORAGE_PROVIDER):
| Storage | Location |
|---|---|
| Local (default) | api-gateway-call-archive/ in Studio's data volume (DATA_DIR), so a backup of the data volume includes it |
| S3 / MinIO | <bucket>/<S3_KEY_PREFIX>/api-gateway-call-archive/ |
| Google Cloud Storage | <bucket>/api-gateway-call-archive/ |
| Azure Blob Storage | <container>/api-gateway-call-archive/ |
Each day is one file, YYYY/MM/calls-YYYY-MM-DD.jsonl.gz (split into -part-001, -part-002, … above 500,000 calls). It holds gzip-compressed JSON Lines, one call per line with every column of the call log and a schema_version. The admin card shows each file's sha256 so you can check a download. Delete all files in the admin settings does not touch archives.
On S3, a bucket lifecycle rule can move older archives to cheaper storage.
Running the call log in production
The call log is built for production use: its size stays bounded, the gateway never waits for it, views and alerts read small summaries, and nothing is deleted before it has been summarised and archived. This section covers where its data lives, what to back up, how big it gets, and its limits.
Where everything is stored
| What | Where | Kept by default |
|---|---|---|
| Raw calls: one row per call with the API, operation, status, error code, duration, caller and environment, never request or response bodies | Table api_gateway_call in Studio's database | 30 days |
| Hourly and daily summaries, including the per-API totals behind analytics across APIs and alerts | Table api_gateway_call_rollup | 90 days and 2 years |
| Archives | Compressed files in storage (see Archives), listed in the table api_gateway_call_archive with their row count, size and sha256 | 1 year |
| State of the summary, alert, archive and clean-up jobs | Table api_gateway_job | — |
| Alert rules and incidents | Tables api_alert_rule and api_alert_event | Incidents: 1 year |
| Live metrics | Your OpenTelemetry collector, for example Prometheus and Grafana; Studio doesn't keep them | — |
Because only metadata is logged, customer data in requests and responses never reaches the call log. Studio's own application logs (errors and warnings) are separate: they go to the container's output (docker logs) and, with OpenTelemetry logging on, to your collector (see OpenTelemetry).
Daily partitions (PostgreSQL)
On PostgreSQL, the raw call log is split by day: one table per UTC day (api_gateway_call_d20261017), created a few days ahead by the summary job, plus a default one for anything else. Clean-up drops a whole day at once, which takes milliseconds and leaves nothing for autovacuum to clean, however many calls the day had, and charts for a time range read only that range's days.
The upgrade converts an existing call log of up to 5 million rows by itself. A larger one is left as it is, still working, with clean-up deleting in batches as before. Convert it at a quiet time, with Studio running:
docker exec <studio container> python -m hrida_ai_studio.utils.api_gateway_call_partitionsIt copies the rows into the new table in one transaction, so writes to the call log wait until it has finished (the gateway keeps answering calls), and it needs free disk space for a second copy of the call log for that time.
Backups
- The database holds the raw calls, the summaries, the archive index and the alerts. Back it up with the rest of Studio's data; on PostgreSQL, continuous archiving with point-in-time recovery is the safest choice.
- Archives with local storage are files in the data volume (
api-gateway-call-archive/), so include the data volume in your backups (see Backups). With S3 or MinIO they live in the bucket: turn on versioning or replication there.
Rough sizes
These are estimates; real sizes depend on your traffic:
- Raw call log: about 0.5 KB per call in PostgreSQL, including indexes. One million calls a day is about 15 GB at 30-day retention.
- Archives: about 50–80 MB per million calls.
- Summaries: grow with the number of APIs and operations, not with traffic.
With local storage, archives sit on the server's data volume. For high volumes, use S3/MinIO storage or a shorter archive retention. With several Studio nodes (for example Docker Swarm), use S3/MinIO: local archives are written on whichever node ran the job, and only that node can serve them.
Impact on the server
The call log is designed not to slow down the gateway or anything else on the server:
- Recording a call only adds it to memory. Calls are written to the database in batches, about once a second, so a call doesn't wait on a database write. A crash can lose at most the last second of calls; a normal restart writes them first.
- Analytics and the call list's 24-hour summary read the small summaries, not the raw table.
- The jobs run in one worker at a time (even with several workers or replicas), use indexes, move rows in small batches with pauses between them, and compress archives in a background thread.
- The daily archive and clean-up run once a day after 01:00 UTC. Move them to a quiet hour with
API_GATEWAY_CALL_LOG_JOBS_HOUR_UTC, for example21for 02:30 in India. - Measured on PostgreSQL: a 12-month view across 200 APIs takes about half a second, and an alert check of 400 rules about a tenth of a second.
Limits
- It shares Studio's database. That is comfortable up to a few million calls a day: one million calls a day is about 15 GB at the default 30-day retention. Above about 5 million calls a day, the raw table and its nightly clean-up start to compete with the rest of Studio. Shorten the raw retention (for example to 7 days; the summaries and archives keep the history) and give PostgreSQL disk and autovacuum headroom. Much higher volumes need a dedicated log store, which Studio doesn't include.
- It is an operations log, not a compliance ledger. A crash can lose up to about a second of calls. While the database is unreachable, each Studio worker holds at most 20,000 calls in memory, then drops the oldest and logs a warning. If every single call must be kept for an audit, that needs a durable audit pipeline of its own.
- Archives are files, not a database. To look into an old day, download its archive from the admin card and open the JSON Lines. Long-range trends come from the summaries.
Production checklist
- Use PostgreSQL. SQLite is for trying Studio out.
- Back up the database and, with local storage, the data volume.
- With several Studio nodes (for example Docker Swarm), set
STORAGE_PROVIDERto S3 or MinIO so that every node can reach the archives. - Run the nightly archive and clean-up at a quiet hour with
API_GATEWAY_CALL_LOG_JOBS_HOUR_UTC. - Check the jobs under Gateway settings › Call log retention & archive from time to time. A failing job shows its error there, and while the summary job fails, hourly alert rules wait (see How alerts are checked).
- For dashboards and alerting in your own tools, turn on the gateway metrics.
- Above about 5 million calls a day, shorten the raw retention.
- On S3, add a lifecycle rule that moves older archives to cheaper storage.
API
GET /api/v1/api-definitions/{id}/analytics?from=&to=&environment=&operation_id=&source=&consumer_key_id=
from and to are Unix timestamps (default: the last 24 hours). source is agent, consumer, test or direct; without it, test-console calls are left out. The response holds range (with the granularity, hour or day), kpis, previous, series, by_operation, by_environment, by_source, by_consumer_key, errors and freshness. by_source and by_consumer_key are null for viewers the API is only published to.
GET /api/v1/api-definitions/{id}/analytics/export?format=csv
returns the same as CSV, with the same filters. The admin endpoints for the call log are under /api/v1/api-definitions/gateway/call-log/ (settings, status, run, archives).
GET /api/v1/api-definitions/analytics/overview?from=&to=&catalog_id=&api_space_id=&environment=
GET /api/v1/api-definitions/analytics/overview/export?format=csv
Analytics across APIs: every API for admins; for space admins and lifecycle managers the spaces they manage (403 for everyone else, and for a space they don't manage). The response holds range, kpis, previous, series, apis (the 200 busiest; api_count counts them all), by_environment, by_space, by_catalog, by_gateway, errors, top_consumer_keys, unused_apis, freshness and scope.
GET /api/v1/api-definitions/{id}/alert-rules
POST /api/v1/api-definitions/{id}/alert-rules
PUT /api/v1/api-definitions/{id}/alert-rules/{rule_id}
DELETE /api/v1/api-definitions/{id}/alert-rules/{rule_id}
POST /api/v1/api-definitions/{id}/alert-rules/{rule_id}/test
POST /api/v1/api-definitions/{id}/alert-rules/{rule_id}/mute {"minutes": 60}
GET /api/v1/api-definitions/{id}/alert-events?state=firing|resolved
GET /api/v1/api-definitions/alerts/active
A rule looks like this:
{
"name": "Payments failing",
"metric": "error_rate",
"threshold": 0.05,
"environment": "prod",
"window_minutes": 15,
"min_calls": 20,
"renotify_minutes": 60,
"channels": {
"webhook_url": "https://hooks.slack.com/services/…",
"emails": ["oncall@example.com"],
"in_app": true
},
"enabled": true
}metric is error_rate, availability, p95_ms, no_traffic or breaker_open. Thresholds for error_rate and availability are fractions (0.05 = 5%) and p95_ms is in milliseconds; no_traffic and breaker_open take none. environment is empty for all environments. window_minutes is one of 5, 10, 15, 30, 60, 120, 180, 360, 720 or 1440, and renotify_minutes is 0 (no reminders) or 15 to 1440. An API can have up to 25 rules. mute with "minutes": 0 unmutes. alerts/active lists open incidents across the APIs you can see.
Related
- Gateway Call Log: the raw calls behind these numbers
- API Gateway: the request pipeline and its error codes
- Gateway Policy & Security: rate limits, caching and the circuit breaker
- Environments: dev, uat and prod for one API
- OpenTelemetry: sending the gateway metrics to your collector