Click analytics
Three endpoints report on clicks: an account-wide log across every link, a per-link log, and an aggregated time series of click counts. All three require the links:read scope.
| Method | Path | Scope | Returns |
|---|---|---|---|
GET | /v1/clicks | links:read | Paginated log across all your links |
GET | /v1/links/{id}/clicks | links:read | Paginated log for one link |
GET | /v1/links/{id}/stats | links:read | Click counts grouped by period |
A link belonging to another account returns 404 not_found from the nested endpoints.
The click object
{
"id": "c41d8b30-9e57-4a22-b6f8-1d0e7a94c2b5",
"ip": "203.0.113.42",
"country": "US",
"visitor_id": "visitor-9f2c1b",
"referrer": "https://twitter.com/",
"device": "iPhone",
"browser": "Safari",
"platform": "iOS",
"created_at": "2026-08-28T10:16:31.000000Z"
}| Field | Type | Description |
|---|---|---|
id | uuid | The click’s identifier |
ip | string | null | IP address the click came from |
country | string | null | Two-letter country code resolved from the IP |
visitor_id | string | null | Pseudonymous visitor identifier, stable across clicks from the same visitor |
referrer | string | null | The page the visitor came from, when the browser sent one |
device | string | null | Device model when known (iPhone), otherwise a category (Mobile, Tablet, Desktop, Bot) |
browser | string | null | Browser name |
platform | string | null | Operating system |
created_at | timestamp | When the click was recorded |
Any of the enriched fields can be null — referrer when the visitor
navigated directly, country when the IP could not be resolved, and the
device fields when the user agent could not be parsed.
About device
device mixes two vocabularies by design. When the user agent identifies a specific model it reports that (iPhone, Nexus 5); when it does not — which is the normal case for desktop browsers — it falls back to a coarse category: Mobile, Tablet, Desktop, or Bot.
Group by it and you get sensible buckets either way, but do not assume the value is drawn from a fixed set.
About link_id
link_id is present on GET /v1/clicks and in webhook deliveries, where the link a click belongs to would otherwise be ambiguous. It is omitted from GET /v1/links/{id}/clicks, because the link is already named in the request path.
List clicks across the account
GET /v1/clicksScope: links:read
Every click on every link you own, newest first, in the standard paginated shape. This is the endpoint to poll for “any new click on any of my links” — the per-link log below would need one request per link, re-checked every time a link is added.
| Query parameter | Type | Description |
|---|---|---|
link_id | uuid | Only return clicks on this link. Must be a valid UUID. |
page | integer | Page to return. Defaults to 1. |
per_page | integer | Results per page. Defaults to 15, maximum 100. |
Each click includes link_id, so you can attribute it without a second request.
curl "https://app.linksqueeze.io/api/v1/clicks?per_page=100" \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Accept: application/json"{
"data": [
{
"id": "c41d8b30-9e57-4a22-b6f8-1d0e7a94c2b5",
"link_id": "9b1f4e2a-6c3d-4a17-9f2e-7c8d5a1b3e40",
"ip": "203.0.113.42",
"country": "US",
"visitor_id": "visitor-9f2c1b",
"referrer": "https://twitter.com/",
"device": "iPhone",
"browser": "Safari",
"platform": "iOS",
"created_at": "2026-08-28T10:16:31.000000Z"
}
],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "per_page": 100, "total": 4210 }
}link_id filters by the link’s public UUID. A malformed value is rejected
with 422, and a UUID belonging to another account simply returns an empty
result rather than an error.
Polling for new clicks
Results are ordered newest first, so page 1 always holds the most recent activity. Record the id of the newest click you have processed, then read forward until you meet it again.
For anything latency-sensitive, prefer the link.clicked webhook — it pushes each click as it happens and removes the polling entirely.
List clicks for one link
GET /v1/links/{id}/clicksScope: links:read
Returns the raw click log for a single link, newest first, in the standard paginated shape. Clicks here omit link_id, since the link is named in the path.
| Query parameter | Type | Description |
|---|---|---|
page | integer | Page to return. Defaults to 1. |
per_page | integer | Results per page. Defaults to 15, maximum 100. |
curl "https://app.linksqueeze.io/api/v1/links/9b1f4e2a-6c3d-4a17-9f2e-7c8d5a1b3e40/clicks?per_page=100" \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Accept: application/json"{
"data": [
{
"id": "c41d8b30-9e57-4a22-b6f8-1d0e7a94c2b5",
"ip": "203.0.113.42",
"country": "US",
"visitor_id": "visitor-9f2c1b",
"referrer": "https://twitter.com/",
"device": "Desktop",
"browser": "Safari",
"platform": "iOS",
"created_at": "2026-08-28T10:16:31.000000Z"
}
],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "per_page": 100, "total": 128 }
}Click statistics
GET /v1/links/{id}/statsScope: links:read
Returns click counts grouped into time buckets — the shape you want for a chart, without paging through every individual click.
| Query parameter | Type | Description |
|---|---|---|
group_by | string | One of day, week, or month. Defaults to day. |
from | date | Only count clicks from this date onward. Inclusive, from the start of the day. |
to | date | Only count clicks up to this date. Inclusive, through the end of the day. |
curl "https://app.linksqueeze.io/api/v1/links/9b1f4e2a-6c3d-4a17-9f2e-7c8d5a1b3e40/stats?group_by=day&from=2026-07-01&to=2026-07-31" \
-H "Authorization: Bearer $LINKSQUEEZE_API_KEY" \
-H "Accept: application/json"{
"data": [
{ "period": "2026-07-01", "clicks": 2 },
{ "period": "2026-07-02", "clicks": 1 }
]
}| Field | Type | Description |
|---|---|---|
period | date | The start of the bucket, as YYYY-MM-DD |
clicks | integer | Number of clicks recorded within that bucket |
Buckets are sorted oldest first. period is always the first day of the bucket: the day itself for day, the Monday for week, and the first of the month for month.
Empty periods are omitted. A day with no clicks does not appear in the
response as a zero — it is simply absent. If you are drawing a chart, generate
the full range of periods yourself and fill in the gaps with 0.
This response is not paginated and has no links or meta object. Narrow it with from and to rather than by paging.
An invalid group_by value is rejected with 422 validation_error.