Analytics API
Read delivery totals, zero-filled time series, top files, traffic sources, workspace activity, and storage ownership for one workspace.
On this page
Use these routes to build a usage dashboard, alert on error spikes, report which sites send traffic to your files, or find the files that take up the most storage. Every route is read-only and scoped to the workspace that the API key belongs to (or the workspace selected with X-Workspace-Id for a signed-in session).
Before you start#
- Scope. API keys need
analytics:readfor every/api/analytics/*route. Any workspace member, including a viewer, can read analytics from a signed-in session. - Where the numbers come from. Delivery figures are aggregated from hourly rollups of requests to
https://cdn.steadylink.io/a/{asset_id}and your delivery domains. A request that has not been rolled up yet does not appear, so expect the current hour to fill in gradually. - Time windows are UTC.
rangeaccepts24h,7d, or30d. Any other value returns422.
range | Window start | Bucket size | Points in a series |
|---|---|---|---|
24h | Start of the current UTC hour, minus 23 hours | 1 hour | 24 |
7d | Midnight UTC today, minus 6 days | 1 day | 7 |
30d | Midnight UTC today, minus 29 days | 1 day | 30 |
The current hour or day is always the last bucket and is still accumulating.
Percentages in these responses (cacheHitRate, errorRate, share) are expressed from 0 to 100, not as fractions. When there were no requests in the window they are 0.
Summary#
Get delivery and storage totals#
/api/analytics/summaryanalytics:readReturns one set of totals for the selected window, plus the storage the workspace holds right now. Use it for headline numbers; use the series when you need a chart.
Query parameters
rangestringDefault7d24h,7d, or30d.
curl "https://api.steadylink.io/api/analytics/summary?range=7d" \
-H "X-API-Key: $STEADYLINK_API_KEY"summary = client.request("GET", "/api/analytics/summary?range=7d"){
"range": "7d",
"requests": 3840000,
"bandwidthBytes": 1840000000000,
"cacheHitRate": 96.8,
"errors": 112,
"actionableDeliveryIssues": {
"missingFileOrRevision": 37
},
"errorRate": 0.0029,
"p95Ms": 18,
"storedBytes": 30494267801,
"objectCount": 128
}Response fields
requestsinteger- Delivery requests in the window.
bandwidthBytesinteger- Bytes sent to clients in the window.
cacheHitRatenumber- Percentage of requests served from cache, rounded to two decimals.
errorsinteger- Requests that returned a status of 400 or higher. This includes expected refusals, such as an expired signed link or a private file requested without one.
actionableDeliveryIssues.missingFileOrRevisioninteger- Requests for a file or revision that does not exist. This is the error class you can usually fix yourself, for example a link to a deleted file or a pinned revision that was removed.
errorRatenumbererrorsas a percentage ofrequests, rounded to four decimals.p95Msinteger- The highest hourly 95th-percentile response time in the window, in milliseconds. It is a worst-hour figure, not a percentile computed across the whole window.
storedBytesinteger- Bytes held by every revision of every file in the workspace, including retained older revisions. Not limited by
range. objectCountinteger- Number of files in the workspace. Not limited by
range.
Time series#
Get a time series#
/api/analytics/seriesanalytics:readReturns one point per hour (24h) or per day (7d, 30d). The series is always complete: buckets without traffic are filled with zeros, so you can plot points directly without checking for gaps. Each at value is the start of its bucket in UTC, without a timezone suffix.
Query parameters
rangestringDefault7d24h,7d, or30d.
curl "https://api.steadylink.io/api/analytics/series?range=24h" \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"range": "24h",
"interval": "hour",
"points": [
{
"at": "2026-10-07T15:00:00",
"requests": 18240,
"bandwidthBytes": 9120044312,
"cacheHits": 17702,
"cacheHitRate": 97.05,
"errors": 3,
"missingFileOrRevision": 1,
"p95Ms": 21
},
{
"at": "2026-10-07T16:00:00",
"requests": 0,
"bandwidthBytes": 0,
"cacheHits": 0,
"cacheHitRate": 0,
"errors": 0,
"missingFileOrRevision": 0,
"p95Ms": 0
}
]
}The example shows two of the 24 points. interval is hour for 24h and day otherwise.
Top files#
List the most requested files#
/api/analytics/objectsanalytics:readReturns files ordered by request count in the window, most requested first. Files with no delivery requests in the window are not listed.
Query parameters
rangestringDefault24h24h,7d, or30d. Note that the default here is24h, unlike the other routes.limitintegerDefault25- Number of files, from 1 to 25. Values outside that range return
422rather than being clamped.
curl "https://api.steadylink.io/api/analytics/objects?range=7d&limit=10" \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"range": "7d",
"limit": 10,
"items": [
{
"assetId": "3f2a9c1e-6b7d-4e21-9a0c-1d5e8f7b2a44",
"bucketId": "9b1c7e52-0f3a-4c6d-8e2b-5a7d1c3e9f10",
"name": "campaign-hero.webp",
"path": "campaign/campaign-hero.webp",
"visibility": "public",
"requests": 412880,
"bandwidthBytes": 98211400192,
"cacheHitRate": 98.41,
"errors": 0,
"missingFileOrRevision": 0,
"p95Ms": 16
}
]
}visibility is the file's own setting (public, private, or inherit when it follows the bucket). bucketId and path are null for a file that is no longer in a bucket.
Traffic sources#
List traffic sources#
/api/analytics/sourcesanalytics:readReturns where delivery requests came from, grouped by source and ordered by request count. Pass assetId to see the sources for one file, for example to find out which site embeds an image.
Each request is classified once, in this order:
- A tag on the link.
?ref=newsletteror?utm_source=newsletteron a delivery URL makes the sourcenewsletterwith kindtagged. Tags are the only signal that survives apps and email clients that strip the referrer, so add one to links you place in those channels. These parameters never change the bytes that are delivered. - A link-preview crawler, such as Discord or Slack fetching a preview card. Kind
preview. - The referring site's host, never its path or query. Well-known platforms are grouped, so
t.coandtwitter.comboth count asx.com. Kindssearch,social,site, orsteadylink. directwhen nothing identifies the source.
Query parameters
rangestringDefault7d24h,7d, or30d.assetIduuid- Limit the result to one file.
limitintegerDefault10- Number of sources to return, from 1 to 50.
totalRequestsandsourceCountalways cover every source, not just the returned ones.
curl "https://api.steadylink.io/api/analytics/sources?range=30d&assetId=3f2a9c1e-6b7d-4e21-9a0c-1d5e8f7b2a44" \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"range": "30d",
"assetId": "3f2a9c1e-6b7d-4e21-9a0c-1d5e8f7b2a44",
"totalRequests": 52310,
"sourceCount": 14,
"items": [
{ "source": "direct", "kind": "direct", "requests": 30112, "bandwidthBytes": 7180223004, "files": 1, "share": 57.56 },
{ "source": "reddit.com", "kind": "social", "requests": 9844, "bandwidthBytes": 2350118400, "files": 1, "share": 18.82 },
{ "source": "newsletter", "kind": "tagged", "requests": 6020, "bandwidthBytes": 1437312000, "files": 1, "share": 11.51 }
]
}Item fields
sourcestring- Canonical source: a tag, a platform host such as
google.com, a referring host, ordirect. kindstringdirect,search,social,site,tagged,preview, orsteadylink.filesinteger- Number of distinct files this source requested.
sharenumber- This source's percentage of
totalRequests.
Activity#
List recent activity#
/api/analytics/activityanalytics:readReturns the workspace audit trail, newest first: uploads, new revisions, restores, renames, visibility changes, blocked uploads, and signed-link changes. range does not apply here.
Query parameters
limitintegerDefault25- Number of events, from 1 to 100.
curl "https://api.steadylink.io/api/analytics/activity?limit=50" \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"items": [
{
"id": "c41e8a20-77b5-4f0e-9a61-0e2d4b8c3f19",
"title": "New revision uploaded",
"action": "asset.object_version_create",
"assetId": "3f2a9c1e-6b7d-4e21-9a0c-1d5e8f7b2a44",
"assetName": "campaign-hero.webp",
"actor": null,
"success": true,
"metadata": { "version": 4 },
"createdAt": "2026-10-08T09:12:44.381022"
}
]
}title is a readable label for known actions and a title-cased form of action otherwise, so match on action in code. actor is "You" when the signed-in user performed the action and null in every other case, including every request made with an API key. metadata varies by action.
Storage#
Get storage by bucket and file#
/api/analytics/storageanalytics:readReturns how much storage each bucket holds, down to each file's current revision and all of its retained revisions. Use it to find files whose revision history is worth trimming. Buckets are sorted largest first; files inside a bucket are sorted by allVersionsBytes, largest first.
curl "https://api.steadylink.io/api/analytics/storage" \
-H "X-API-Key: $STEADYLINK_API_KEY"{
"usedBytes": 30494267801,
"limitBytes": 107374182400,
"remainingBytes": 76879914599,
"buckets": [
{
"bucketId": "9b1c7e52-0f3a-4c6d-8e2b-5a7d1c3e9f10",
"name": "Marketing",
"bytes": 21877391360,
"fileCount": 84,
"files": [
{
"assetId": "3f2a9c1e-6b7d-4e21-9a0c-1d5e8f7b2a44",
"name": "launch-video.mp4",
"path": "video/launch-video.mp4",
"currentBytes": 412090368,
"allVersionsBytes": 1648361472,
"versionCount": 4
}
]
}
]
}Response fields
limitBytesinteger- The storage allowance of the workspace's active plan. A paid plan whose billing is not active counts as the free plan here.
buckets[].bytesinteger- Sum of
allVersionsBytesfor the files in the bucket. files[].currentBytesinteger- Size of the revision the link serves now.
files[].allVersionsBytesinteger- Size of every retained revision of the file, current included.
The response lists every file in every bucket, so it can be large for big workspaces. Cache it rather than polling it.