Analytics
Read click and scan figures for the workspace or one link, watch clicks as they arrive, and export them as CSV.
Figures are kept per day of the workspace's analytics time zone (Settings → Analytics time zone). How far back you can look depends on your plan's analytics history.
Every path below starts with https://bgum.me/api/v1/workspaces/{workspaceId}. Send your API key in the Authorization header as described in the API overview.
| Operation | Method and path |
|---|---|
| Workspace report | GET /analytics |
| Link report | GET /links/{linkId}/analytics |
| Export smart routing as CSV | GET /links/{linkId}/analytics/routes.csv |
| Latest clicks | GET /analytics/live |
| Clicks per minute | GET /analytics/live/minutes |
| Stream of clicks | GET /analytics/live/stream |
| Export as CSV | GET /analytics/export.csv |
Workspace report
GET /analytics
Totals, a day-by-day series, breakdowns, top links and chart notes for the whole workspace.
- Full path
/api/v1/workspaces/{workspaceId}/analytics- API key scope
-
analytics:read - Role needed
- Viewer or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
from |
date | No | First day, as YYYY-MM-DD, in the report's time zone. Defaults to 29 days before to. |
to |
date | No | Last day, as YYYY-MM-DD. Defaults to today in the report's time zone. Future days are ignored. |
top |
integer | No | How many rows to return in each breakdown and in topLinks: 1 to 100, default 10. |
timeZone |
string | No | IANA time zone to count days in, such as Australia/Sydney or UTC. Defaults to the workspace's analytics time zone (Settings → Analytics time zone). Another zone is counted from raw clicks, so the period must fall within the raw clicks kept (usually 90 days) and the report is slower. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/analytics?from=2026-10-01&to=2026-10-14&top=5" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/analytics?from=2026-10-01&to=2026-10-14&top=5`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/analytics?from=2026-10-01&to=2026-10-14&top=5",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"timeZone": "Australia/Sydney",
"range": {
"from": "2026-10-01",
"to": "2026-10-14",
"days": 14
},
"effectiveRange": {
"from": "2026-10-01",
"to": "2026-10-14",
"days": 14
},
"clampedByRetention": false,
"totals": {
"clicks": 1290,
"humanClicks": 1184,
"qrScans": 212,
"uniqueVisitorsEstimate": 1022,
"automatedClicks": 106
},
"previousTotals": {
"clicks": 940,
"humanClicks": 871,
"qrScans": 150,
"uniqueVisitorsEstimate": 790,
"automatedClicks": 69
},
"series": [
{
"day": "2026-10-01",
"clicks": 88,
"humanClicks": 80,
"qrScans": 12,
"uniqueVisitorsEstimate": 71
},
{
"day": "2026-10-02",
"clicks": 102,
"humanClicks": 95,
"qrScans": 17,
"uniqueVisitorsEstimate": 83
}
],
"dimensions": {
"referrer": [
{
"value": "(direct)",
"clicks": 610,
"humanClicks": 590
},
{
"value": "www.linkedin.com",
"clicks": 301,
"humanClicks": 297
}
],
"country": [
{
"value": "AU",
"clicks": 902,
"humanClicks": 860
}
],
"device": [
{
"value": "mobile",
"clicks": 700,
"humanClicks": 688
},
{
"value": "desktop",
"clicks": 480,
"humanClicks": 470
}
],
"traffic": [
{
"value": "LikelyHuman",
"clicks": 1184,
"humanClicks": 1184
},
{
"value": "LinkPreview",
"clicks": 74,
"humanClicks": 0
}
],
"channel": [
{
"value": "link",
"clicks": 1078,
"humanClicks": 972
},
{
"value": "qr",
"clicks": 212,
"humanClicks": 212
}
]
},
"topLinks": [
{
"linkId": "ZiZihoWXe3as",
"shortUrl": "https://bgum.me/spring-sale",
"title": "Spring sale",
"destinationUrl": "https://example.com/spring-sale",
"humanClicks": 640,
"clicks": 701
}
],
"conversions": {
"conversions": 37,
"rate": 0.03125,
"values": [
{
"currency": "AUD",
"value": 1848.15
}
],
"byEvent": [
{
"event": "purchase",
"conversions": 37,
"values": [
{
"currency": "AUD",
"value": 1848.15
}
]
}
],
"byCampaign": [
{
"campaignId": "aiQrz1XgekFP",
"name": "Spring sale",
"conversions": 37,
"values": [
{
"currency": "AUD",
"value": 1848.15
}
]
}
]
},
"notes": [
{
"id": "nT4wq0Xbe2Lk",
"date": "2026-10-02",
"time": "09:30:00",
"text": "Spring newsletter sent",
"linkId": null,
"campaignId": "aiQrz1XgekFP",
"createdBy": "Sam Taylor",
"createdAt": "2026-10-02T00:14:51.4410+00:00",
"updatedAt": null
}
],
"earlierDays": null
}
Good to know
- Days run from midnight to midnight in timeZone, which the response repeats. A day when daylight saving starts or ends has 23 or 25 hours. If the range goes back further than your plan's analytics history, it's shortened: effectiveRange shows the days actually reported and clampedByRetention is true.
- earlierDays is set when the workspace changed time zone and some days in the range are still counted in the earlier zone (raw clicks from back then were no longer kept): days before earlierDays.before are in earlierDays.timeZone.
- notes lists the chart notes dated in the range (see Chart notes). On a link report: that link's notes, its campaign's and the workspace-wide ones.
- previousTotals covers the same number of days immediately before effectiveRange, for comparisons.
- humanClicks leaves out known bots, link previews and security scanners; clicks includes them. uniqueVisitorsEstimate is a sum of daily estimates per link, so one person on two days counts twice.
- dimensions has a list per breakdown: referrer, country, device, browser, os, traffic, bot, utm_source, utm_medium, utm_campaign and channel (link or qr). A breakdown with no data is left out. Ignore keys you don't recognise; more may be added.
- conversions counts conversions that happened in the window (by day in timeZone), with value totals per currency, and a breakdown by event name (and, for the workspace, by campaign). rate is conversions ÷ clicks from people in the same window, or null when there were none. See Conversions.
Errors
| Status | Code | Meaning |
|---|---|---|
| 422 | analytics.time_zone_invalid | timeZone isn't a time zone we know. |
| 422 | analytics.time_zone_history | A report in a zone other than the workspace's is counted from raw clicks, and the period starts before the oldest raw clicks kept. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Link report
GET /links/{linkId}/analytics
The same report for one link. topLinks is always empty.
- Full path
/api/v1/workspaces/{workspaceId}/links/{linkId}/analytics- API key scope
-
analytics:read - Role needed
- Viewer or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
linkId |
string | Yes | The link's ID, as returned when it was created or listed. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
from |
date | No | First day, as YYYY-MM-DD, in the report's time zone. Defaults to 29 days before to. |
to |
date | No | Last day, as YYYY-MM-DD. Defaults to today in the report's time zone. Future days are ignored. |
top |
integer | No | How many rows to return in each breakdown and in topLinks: 1 to 100, default 10. |
timeZone |
string | No | IANA time zone to count days in, such as Australia/Sydney or UTC. Defaults to the workspace's analytics time zone (Settings → Analytics time zone). Another zone is counted from raw clicks, so the period must fall within the raw clicks kept (usually 90 days) and the report is slower. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/links/ZiZihoWXe3as/analytics?from=2026-10-01&to=2026-10-14" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/links/ZiZihoWXe3as/analytics?from=2026-10-01&to=2026-10-14`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/links/ZiZihoWXe3as/analytics?from=2026-10-01&to=2026-10-14",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"timeZone": "Australia/Sydney",
"range": {
"from": "2026-10-01",
"to": "2026-10-14",
"days": 14
},
"effectiveRange": {
"from": "2026-10-01",
"to": "2026-10-14",
"days": 14
},
"clampedByRetention": false,
"totals": {
"clicks": 1290,
"humanClicks": 1184,
"qrScans": 212,
"uniqueVisitorsEstimate": 1022,
"automatedClicks": 106
},
"previousTotals": {
"clicks": 940,
"humanClicks": 871,
"qrScans": 150,
"uniqueVisitorsEstimate": 790,
"automatedClicks": 69
},
"series": [
{
"day": "2026-10-01",
"clicks": 88,
"humanClicks": 80,
"qrScans": 12,
"uniqueVisitorsEstimate": 71
},
{
"day": "2026-10-02",
"clicks": 102,
"humanClicks": 95,
"qrScans": 17,
"uniqueVisitorsEstimate": 83
}
],
"dimensions": {
"referrer": [
{
"value": "(direct)",
"clicks": 610,
"humanClicks": 590
},
{
"value": "www.linkedin.com",
"clicks": 301,
"humanClicks": 297
}
],
"country": [
{
"value": "AU",
"clicks": 902,
"humanClicks": 860
}
],
"device": [
{
"value": "mobile",
"clicks": 700,
"humanClicks": 688
},
{
"value": "desktop",
"clicks": 480,
"humanClicks": 470
}
],
"traffic": [
{
"value": "LikelyHuman",
"clicks": 1184,
"humanClicks": 1184
},
{
"value": "LinkPreview",
"clicks": 74,
"humanClicks": 0
}
],
"channel": [
{
"value": "link",
"clicks": 1078,
"humanClicks": 972
},
{
"value": "qr",
"clicks": 212,
"humanClicks": 212
}
]
},
"topLinks": [],
"conversions": {
"conversions": 37,
"rate": 0.0578,
"values": [
{
"currency": "AUD",
"value": 1848.15
}
],
"byEvent": [
{
"event": "purchase",
"conversions": 37,
"values": [
{
"currency": "AUD",
"value": 1848.15
}
]
}
],
"byCampaign": []
},
"notes": [
{
"id": "nT4wq0Xbe2Lk",
"date": "2026-10-02",
"time": "09:30:00",
"text": "Spring newsletter sent",
"linkId": null,
"campaignId": "aiQrz1XgekFP",
"createdBy": "Sam Taylor",
"createdAt": "2026-10-02T00:14:51.4410+00:00",
"updatedAt": null
}
],
"earlierDays": null
}
Good to know
- Works for deleted links too, so you can still report on them.
- For a link with smart routing, routes lists each rule and split destination: route is the recorded key (rule:<id>, split:<id>, or default for visitors no rule matched), kind is rule, split or default, and label and destinationUrl are the current ones. Each has clicks, humanClicks, conversions, conversionRate (conversions ÷ humanClicks) and values. Without routing, routes is null.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | link.not_found | No link with that ID in this workspace (or it was deleted). |
| 422 | analytics.time_zone_invalid | timeZone isn't a time zone we know. |
| 422 | analytics.time_zone_history | A report in a zone other than the workspace's is counted from raw clicks, and the period starts before the oldest raw clicks kept. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Export smart routing as CSV
GET /links/{linkId}/analytics/routes.csv
One row per day (in the workspace's time zone) for each smart-routing rule and split destination of a link that had clicks or conversions (Pro and above).
- Full path
/api/v1/workspaces/{workspaceId}/links/{linkId}/analytics/routes.csv- API key scope
-
analytics:read - Role needed
- Analyst or above
- Plan
- Needs csv export in your plan (compare plans)
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
linkId |
string | Yes | The link's ID, as returned when it was created or listed. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
from |
date | No | First day, as YYYY-MM-DD, in the report's time zone. Defaults to 29 days before to. |
to |
date | No | Last day, as YYYY-MM-DD. Defaults to today in the report's time zone. Future days are ignored. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/links/ZiZihoWXe3as/analytics/routes.csv?from=2026-10-01&to=2026-10-31" \
-H "Authorization: Bearer $BGL_API_KEY" \
-o link-ZiZihoWXe3as-routes.csv
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/links/ZiZihoWXe3as/analytics/routes.csv?from=2026-10-01&to=2026-10-31`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const file = Buffer.from(await res.arrayBuffer()); // save as link-ZiZihoWXe3as-routes.csv
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/links/ZiZihoWXe3as/analytics/routes.csv?from=2026-10-01&to=2026-10-31",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
open("link-ZiZihoWXe3as-routes.csv", "wb").write(res.content)
Response
200 OK · text/csv
day,route,label,destination_url,clicks,human_clicks,conversions
2026-10-01,split:a,Short page,https://example.com/landing-a,64,60,4
2026-10-01,split:b,Long page,https://example.com/landing-b,61,58,7
Good to know
- label and destination_url are the rule's or destination's current ones; a rule removed since shows as Removed rule.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | link.not_found | No link with that ID in this workspace (or it was deleted). |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Latest clicks
GET /analytics/live
Clicks from the last hour as they arrive, oldest first. Poll with after set to the cursor from the previous response to get only new ones.
- Full path
/api/v1/workspaces/{workspaceId}/analytics/live- API key scope
-
analytics:read - Role needed
- Viewer or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
after |
integer | No | The cursor from the previous response. Leave it out to get the latest clicks of the last hour. |
limit |
integer | No | At most this many clicks: 1 to 200, default 50. |
linkId |
string | No | Only this link. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/analytics/live?after=918270&limit=100" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/analytics/live?after=918270&limit=100`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/analytics/live?after=918270&limit=100",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"clicks": [
{
"id": 918274,
"at": "2026-10-15T03:12:09.5521+00:00",
"linkId": "ZiZihoWXe3as",
"shortUrl": "https://bgum.me/spring-sale",
"title": "Spring sale",
"country": "AU",
"device": "mobile",
"browser": "Safari",
"os": "iOS",
"referrer": "www.linkedin.com",
"qr": false,
"utmSource": "newsletter",
"utmMedium": "email",
"utmCampaign": "spring-2026",
"traffic": "LikelyHuman",
"bot": null
}
],
"cursor": 918274
}
Good to know
- Clicks usually appear within a few seconds. Poll no more than every few seconds; the stream below sends them as they arrive instead.
- Each click shows what the analytics breakdowns show (link, country, device, browser, operating system, referring site, QR code, UTM tags, and traffic: LikelyHuman, KnownBot, LinkPreview or SecurityScanner) and never addresses or visitor identifiers.
- With no new clicks, clicks is empty and cursor stays the same.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | link.not_found | No link with that ID in this workspace (or it was deleted). |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Clicks per minute
GET /analytics/live/minutes
Clicks per minute over the last hour (or fewer minutes), oldest minute first, including minutes with none.
- Full path
/api/v1/workspaces/{workspaceId}/analytics/live/minutes- API key scope
-
analytics:read - Role needed
- Viewer or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
minutes |
integer | No | How many minutes back: 1 to 60, default 60. The last one is the current, unfinished minute. |
linkId |
string | No | Only this link. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/analytics/live/minutes?minutes=5" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/analytics/live/minutes?minutes=5`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/analytics/live/minutes?minutes=5",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"items": [
{
"minute": "2026-10-15T03:08:00+00:00",
"clicks": 4,
"humanClicks": 3
},
{
"minute": "2026-10-15T03:09:00+00:00",
"clicks": 0,
"humanClicks": 0
},
{
"minute": "2026-10-15T03:10:00+00:00",
"clicks": 7,
"humanClicks": 7
},
{
"minute": "2026-10-15T03:11:00+00:00",
"clicks": 2,
"humanClicks": 1
},
{
"minute": "2026-10-15T03:12:00+00:00",
"clicks": 1,
"humanClicks": 1
}
]
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | link.not_found | No link with that ID in this workspace (or it was deleted). |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Stream of clicks
GET /analytics/live/stream
Server-sent events: each new click as it arrives, and clicks per minute every 30 seconds. This is what the Live page in the app uses.
- Full path
/api/v1/workspaces/{workspaceId}/analytics/live/stream- API key scope
-
analytics:read - Role needed
- Viewer or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
after |
integer | No | Start after this cursor. A reconnecting EventSource sends Last-Event-ID instead, which wins. |
linkId |
string | No | Only this link. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/analytics/live/stream" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/analytics/live/stream`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/analytics/live/stream",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · text/event-stream
retry: 3000
event: ready
data: {"cursor":918274}
id: 918275
event: click
data: {"id":918275,"at":"2026-10-15T03:12:41.0712+00:00","linkId":"ZiZihoWXe3as","shortUrl":"https://bgum.me/spring-sale","title":"Spring sale","country":"AU","device":"mobile","browser":"Chrome","os":"Android","referrer":null,"qr":true,"utmSource":null,"utmMedium":null,"utmCampaign":null,"traffic":"LikelyHuman","bot":null}
event: minutes
data: [{"minute":"2026-10-15T03:12:00+00:00","clicks":2,"humanClicks":2}]
: keep-alive
Good to know
- The stream starts with a ready event, then the latest clicks of the last hour (each a click event whose id is the cursor), then a minutes event. A comment line every 15 seconds keeps proxies from closing it.
- The server ends the stream after about 10 minutes; EventSource reconnects by itself and carries on from Last-Event-ID, so nothing is missed or repeated.
- API keys work too, but browsers' EventSource can't send an Authorization header: from a browser, sign in and use the session instead.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | link.not_found | No link with that ID in this workspace (or it was deleted). |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Export as CSV
GET /analytics/export.csv
One row per link per day with any clicks or conversions (Pro and above).
- Full path
/api/v1/workspaces/{workspaceId}/analytics/export.csv- API key scope
-
analytics:read - Role needed
- Analyst or above
- Plan
- Needs csv export in your plan (compare plans)
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
from |
date | No | First day, as YYYY-MM-DD, in the report's time zone. Defaults to 29 days before to. |
to |
date | No | Last day, as YYYY-MM-DD. Defaults to today in the report's time zone. Future days are ignored. |
linkId |
string | No | Only this link. |
timeZone |
string | No | IANA time zone to count days in, such as Australia/Sydney or UTC. Defaults to the workspace's analytics time zone (Settings → Analytics time zone). Another zone is counted from raw clicks, so the period must fall within the raw clicks kept (usually 90 days) and the report is slower. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/analytics/export.csv?from=2026-10-01&to=2026-10-31" \
-H "Authorization: Bearer $BGL_API_KEY" \
-o clicks.csv
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/analytics/export.csv?from=2026-10-01&to=2026-10-31`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const file = Buffer.from(await res.arrayBuffer()); // save as clicks.csv
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/analytics/export.csv?from=2026-10-01&to=2026-10-31",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
open("clicks.csv", "wb").write(res.content)
Response
200 OK · text/csv
day,link_id,short_url,destination_url,clicks,human_clicks,qr_scans,unique_visitors_estimate,conversions
2026-10-01,ZiZihoWXe3as,https://bgum.me/spring-sale,https://example.com/spring-sale,88,80,12,71,3
Good to know
- Needs the Analyst role or above. Days are in timeZone, as in the reports.
Errors
| Status | Code | Meaning |
|---|---|---|
| 422 | analytics.time_zone_invalid | timeZone isn't a time zone we know. |
| 422 | analytics.time_zone_history | A report in a zone other than the workspace's is counted from raw clicks, and the period starts before the oldest raw clicks kept. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Something missing or unclear? Tell us and we'll improve this page.