Guides
Click analytics
In short
Three calls read your click data. The summary gives totals and breakdowns. The time series gives clicks per hour, day, week or month. The raw list gives single clicks. All three take the same JSON filters and share one quota per workspace.
You need
- An API key
- Read permission or higher.
- A role
- Any member of the workspace.
- A plan
- Any plan, including Free. Your plan sets how far back you can read and how many calls you can make.
Get a summary
The summary counts every click in a date range. It also breaks them down by country, city, device, browser, operating system, referrer, link and UTM value. This request counts one link's clicks for one week.
curl -X POST "https://app.clik.so/api/analytics/summary" \
-H "Authorization: Bearer $CLIKSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
]
}'const res = await fetch("https://app.clik.so/api/analytics/summary", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
]
}),
});
const data = await res.json();
console.log(res.status, data);Uses fetch (Node 18+ or the browser). Set CLIKSO_API_KEY first.
import os
import requests
response = requests.post(
"https://app.clik.so/api/analytics/summary",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
json={
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale",
],
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$body = <<<'JSON'
{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
]
}
JSON;
$ch = curl_init('https://app.clik.so/api/analytics/summary');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CLIKSO_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
echo $status, PHP_EOL, $response, PHP_EOL;Uses the curl extension. Set CLIKSO_API_KEY first.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
]
}`)
req, err := http.NewRequest("POST", "https://app.clik.so/api/analytics/summary", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("CLIKSO_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(res.StatusCode, string(data))
}Uses net/http. Set CLIKSO_API_KEY first.
require "net/http"
require "json"
uri = URI('https://app.clik.so/api/analytics/summary')
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = {
'from' => '2026-10-01T00:00:00Z',
'to' => '2026-10-08T00:00:00Z',
'slugs' => [
'clik.is/summer-sale'
]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.code, response.bodyUses Net::HTTP. Set CLIKSO_API_KEY first.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
]
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.clik.so/api/analytics/summary"))
.header("Authorization", "Bearer " + System.getenv("CLIKSO_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}Uses java.net.http (Java 15+). Set CLIKSO_API_KEY first.
using System.Net.Http.Headers;
using var client = new HttpClient();
var request = new HttpRequestMessage(new HttpMethod("POST"), "https://app.clik.so/api/analytics/summary");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));
request.Content = new StringContent(
"""
{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
]
}
""",
System.Text.Encoding.UTF8,
"application/json");
using var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());Uses HttpClient (C# 11+). Set CLIKSO_API_KEY first.
#include <cstdlib>
#include <iostream>
#include <string>
#include <curl/curl.h>
static size_t collect(char* data, size_t size, size_t n, void* out) {
static_cast<std::string*>(out)->append(data, size * n);
return size * n;
}
int main() {
CURL* curl = curl_easy_init();
if (!curl) return 1;
const char* key = std::getenv("CLIKSO_API_KEY");
std::string auth = std::string("Authorization: Bearer ") + (key ? key : "");
struct curl_slist* headers = curl_slist_append(nullptr, auth.c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
const char* body = R"json({
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
]
})json";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/analytics/summary");
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, collect);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
CURLcode rc = curl_easy_perform(curl);
long status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (rc != CURLE_OK) {
std::cerr << curl_easy_strerror(rc) << std::endl;
return 1;
}
std::cout << status << "\n" << response << std::endl;
return 0;
}Uses libcurl. Set CLIKSO_API_KEY first.
{
"status": 200,
"success": true,
"message": "Success",
"data": {
"total_clicks": 412,
"referrers": [
{
"label": "https://www.google.com/",
"count": 120
},
{
"label": "https://t.co/",
"count": 64
}
],
"devices": [
{
"label": "mobile",
"count": 268
},
{
"label": "desktop",
"count": 131
},
{
"label": "tablet",
"count": 13
}
],
"os": [
{
"label": "iOS",
"count": 160
},
{
"label": "Android",
"count": 108
},
{
"label": "Windows",
"count": 90
}
],
"browsers": [
{
"label": "Chrome",
"count": 221
},
{
"label": "Safari",
"count": 150
}
],
"short_links": [
{
"label": "https://clik.is/summer-sale",
"count": 412
}
],
"destinations": [
{
"label": "https://example.com/summer-sale",
"count": 412
}
],
"countries": [
{
"label": "US",
"count": 201
},
{
"label": "IN",
"count": 97
}
],
"cities": [
{
"label": "New York",
"count": 48
},
{
"label": "Bengaluru",
"count": 41
}
],
"continents": [
{
"label": "NA",
"count": 230
},
{
"label": "AS",
"count": 120
}
],
"timeseries": [
{
"timestamp": "2026-10-01T00:00:00Z",
"clicks": 52
},
{
"timestamp": "2026-10-02T00:00:00Z",
"clicks": 61
}
],
"map_data": {
"US": 201,
"IN": 97
},
"utm": {
"sources": [
{
"label": "newsletter",
"count": 88
}
],
"mediums": [
{
"label": "email",
"count": 88
}
],
"campaigns": [
{
"label": "summer",
"count": 88
}
],
"terms": [],
"contents": []
},
"link_performance": [
{
"link": "https://clik.is/summer-sale",
"breakdown": [
{
"label": "https://example.com/summer-sale",
"count": 412
}
]
}
],
"retention_clamped": false,
"retention_days": 90,
"effective_from": "2026-10-01T00:00:00.000Z",
"link_filter_truncated": false,
"link_filter_limit": 250
}
}One summary call answers many questions at once. Use it before you reach for the other two calls.
Get clicks over time
The time series gives click counts in buckets, ready for a chart. Set interval to hour, day, week or month. If you leave it out, you get hourly buckets.
curl -X POST "https://app.clik.so/api/analytics/timeseries" \
-H "Authorization: Bearer $CLIKSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day"
}'const res = await fetch("https://app.clik.so/api/analytics/timeseries", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day"
}),
});
const data = await res.json();
console.log(res.status, data);Uses fetch (Node 18+ or the browser). Set CLIKSO_API_KEY first.
import os
import requests
response = requests.post(
"https://app.clik.so/api/analytics/timeseries",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
json={
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day",
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$body = <<<'JSON'
{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day"
}
JSON;
$ch = curl_init('https://app.clik.so/api/analytics/timeseries');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CLIKSO_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
echo $status, PHP_EOL, $response, PHP_EOL;Uses the curl extension. Set CLIKSO_API_KEY first.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day"
}`)
req, err := http.NewRequest("POST", "https://app.clik.so/api/analytics/timeseries", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("CLIKSO_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(res.StatusCode, string(data))
}Uses net/http. Set CLIKSO_API_KEY first.
require "net/http"
require "json"
uri = URI('https://app.clik.so/api/analytics/timeseries')
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = {
'from' => '2026-10-01T00:00:00Z',
'to' => '2026-10-08T00:00:00Z',
'interval' => 'day'
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.code, response.bodyUses Net::HTTP. Set CLIKSO_API_KEY first.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.clik.so/api/analytics/timeseries"))
.header("Authorization", "Bearer " + System.getenv("CLIKSO_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}Uses java.net.http (Java 15+). Set CLIKSO_API_KEY first.
using System.Net.Http.Headers;
using var client = new HttpClient();
var request = new HttpRequestMessage(new HttpMethod("POST"), "https://app.clik.so/api/analytics/timeseries");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));
request.Content = new StringContent(
"""
{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day"
}
""",
System.Text.Encoding.UTF8,
"application/json");
using var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());Uses HttpClient (C# 11+). Set CLIKSO_API_KEY first.
#include <cstdlib>
#include <iostream>
#include <string>
#include <curl/curl.h>
static size_t collect(char* data, size_t size, size_t n, void* out) {
static_cast<std::string*>(out)->append(data, size * n);
return size * n;
}
int main() {
CURL* curl = curl_easy_init();
if (!curl) return 1;
const char* key = std::getenv("CLIKSO_API_KEY");
std::string auth = std::string("Authorization: Bearer ") + (key ? key : "");
struct curl_slist* headers = curl_slist_append(nullptr, auth.c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
const char* body = R"json({
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"interval": "day"
})json";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/analytics/timeseries");
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, collect);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
CURLcode rc = curl_easy_perform(curl);
long status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (rc != CURLE_OK) {
std::cerr << curl_easy_strerror(rc) << std::endl;
return 1;
}
std::cout << status << "\n" << response << std::endl;
return 0;
}Uses libcurl. Set CLIKSO_API_KEY first.
{
"status": 200,
"success": true,
"message": "Success",
"data": [
{
"timestamp": "2026-10-01T00:00:00Z",
"clicks": 52
},
{
"timestamp": "2026-10-02T00:00:00Z",
"clicks": 61
},
{
"timestamp": "2026-10-04T00:00:00Z",
"clicks": 47
}
]
}Buckets with no clicks are left out. Fill those gaps with zero when you draw the chart.
List raw clicks
The raw list gives single clicks, newest first. Each click has its time, link, country, device, browser, operating system and referrer.
curl -X POST "https://app.clik.so/api/analytics/filter" \
-H "Authorization: Bearer $CLIKSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US"
]
}'const res = await fetch("https://app.clik.so/api/analytics/filter", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US"
]
}),
});
const data = await res.json();
console.log(res.status, data);Uses fetch (Node 18+ or the browser). Set CLIKSO_API_KEY first.
import os
import requests
response = requests.post(
"https://app.clik.so/api/analytics/filter",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
json={
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US",
],
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$body = <<<'JSON'
{
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US"
]
}
JSON;
$ch = curl_init('https://app.clik.so/api/analytics/filter');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CLIKSO_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
echo $status, PHP_EOL, $response, PHP_EOL;Uses the curl extension. Set CLIKSO_API_KEY first.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US"
]
}`)
req, err := http.NewRequest("POST", "https://app.clik.so/api/analytics/filter", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("CLIKSO_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(res.StatusCode, string(data))
}Uses net/http. Set CLIKSO_API_KEY first.
require "net/http"
require "json"
uri = URI('https://app.clik.so/api/analytics/filter')
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = {
'from' => '2026-10-07T00:00:00Z',
'to' => '2026-10-08T00:00:00Z',
'country' => [
'US'
]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.code, response.bodyUses Net::HTTP. Set CLIKSO_API_KEY first.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US"
]
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.clik.so/api/analytics/filter"))
.header("Authorization", "Bearer " + System.getenv("CLIKSO_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}Uses java.net.http (Java 15+). Set CLIKSO_API_KEY first.
using System.Net.Http.Headers;
using var client = new HttpClient();
var request = new HttpRequestMessage(new HttpMethod("POST"), "https://app.clik.so/api/analytics/filter");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));
request.Content = new StringContent(
"""
{
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US"
]
}
""",
System.Text.Encoding.UTF8,
"application/json");
using var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());Uses HttpClient (C# 11+). Set CLIKSO_API_KEY first.
#include <cstdlib>
#include <iostream>
#include <string>
#include <curl/curl.h>
static size_t collect(char* data, size_t size, size_t n, void* out) {
static_cast<std::string*>(out)->append(data, size * n);
return size * n;
}
int main() {
CURL* curl = curl_easy_init();
if (!curl) return 1;
const char* key = std::getenv("CLIKSO_API_KEY");
std::string auth = std::string("Authorization: Bearer ") + (key ? key : "");
struct curl_slist* headers = curl_slist_append(nullptr, auth.c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
const char* body = R"json({
"from": "2026-10-07T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"country": [
"US"
]
})json";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/analytics/filter");
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, collect);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
CURLcode rc = curl_easy_perform(curl);
long status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (rc != CURLE_OK) {
std::cerr << curl_easy_strerror(rc) << std::endl;
return 1;
}
std::cout << status << "\n" << response << std::endl;
return 0;
}Uses libcurl. Set CLIKSO_API_KEY first.
{
"status": 200,
"success": true,
"message": "Success",
"data": [
{
"timestamp": "2026-10-07T18:42:10Z",
"workspace_id": "8a1f2c3d-4b5e-4f60-9a7b-1c2d3e4f5a6b",
"url_id": "3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c",
"short_url": "https://clik.is/summer-sale",
"destination_url": "https://example.com/summer-sale",
"country": "US",
"device": "mobile",
"browser": "Safari",
"os": "iOS",
"referrer": "https://t.co/"
}
]
}You get at most the 1,000 most recent clicks that match. There is no next page, so use a shorter date range to see older clicks.
Choose which clicks to count
All three calls take the same JSON body. Every field is optional.
{
"from": "2026-10-01T00:00:00Z",
"to": "2026-10-08T00:00:00Z",
"slugs": [
"clik.is/summer-sale"
],
"country": [
"US",
"IN"
],
"device": [
"mobile"
]
}fromandtoset the time window, as ISO 8601 dates in UTC. If either one is missing, you get the last 7 days. A value that is not a date, or afromafterto, gets a422before the call counts toward your quota.url_ids,slugsanddomainspick links. A slug is written asdomain/short_code, likeclik.is/summer-sale.country,city,region,continent,device,os,browserandreferrerfilter by where and how people clicked.short_linksanddestination_urlsfilter by the exact URL. Short links includehttps://.utm_source,utm_medium,utm_campaign,utm_termandutm_contentfilter by the UTM value recorded for the click.
Different filters combine with AND. Values inside one list combine with OR. So the body above means: this link, and the United States or India, and phones.
Each list can hold up to 100 values. Country codes have two letters, like US. Device values are mobile, tablet, desktop and tv.
How far back you can read
Your plan keeps clicks for a set number of days.
Click history per plan
- Click history you can readOlder clicks are left out, not an error
- 30 days
| Limit | Free | Starter | Pro | Business |
|---|---|---|---|---|
| Click history you can readOlder clicks are left out, not an error | 30 days | 90 days | 180 days | 365 days |
If from is older than that, Clikso moves it forward to the oldest day your plan keeps. This is not an error. The response tells you it happened:
- The summary has
retention_clamped,retention_daysandeffective_fromindata. - All three calls send the headers
X-Analytics-Retention-Clamped,X-Analytics-Retention-DaysandX-Analytics-Effective-From.
Quota and caching
Analytics calls have their own quota. It is counted per workspace, so all your keys share it.
Analytics calls per workspace
- Analytics requests per minutePer workspace, shared by all keys
- 5
- Analytics requests per dayPer workspace, resets at 00:00 UTC
- 100
| Limit | Free | Starter | Pro | Business |
|---|---|---|---|---|
| Analytics requests per minutePer workspace, shared by all keys | 5 | 10 | 30 | 60 |
| Analytics requests per dayPer workspace, resets at 00:00 UTC | 100 | 1,000 | 5,000 | 20,000 |
Every response carries headers that show where you stand:
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Resetfor the current minute.X-Analytics-Quota-LimitandX-Analytics-Quota-Remainingfor the day. The day resets at 00:00 UTC.
The same request within 60 seconds is often answered from a cache. It is faster, but it still counts toward your quota. The X-Analytics-Cache header says HIT or MISS.
What you get back
- Summary:
dataholdstotal_clicks, then one list per breakdown. Each list item is alabeland acount. You also gettimeseries(clicks per day),map_data(country code to clicks),utmandlink_performance. - Time series:
datais a list oftimestampandclickspairs, oldest first. Each timestamp is the start of its bucket, in UTC. - Raw clicks:
datais a list of clicks, newest first. Fields with no value are empty strings.
Common errors
UNAUTHORIZEDThe key is missing or wrong.Send Authorization: Bearer clik_live_… with a key from Settings → API.Send Authorization: Bearer clik_live_… with a key from Settings → API.FORBIDDENThe Workspace-Id header names a different workspace than the key.Check the key’s permission and your role. If you send Workspace-Id, it must match the key’s workspace.Check the key’s permission and your role. If you send Workspace-Id, it must match the key’s workspace.VALIDATION_ERRORfrom or to is not an ISO 8601 date, from is after to, interval is not hour, day, week or month, a filter list is too long, or the body is not JSON.Fix the field and send the request again.Fix the field and send the request again.RATE_LIMITEDToo many analytics calls this minute.Wait the number of seconds in Retry-After.Wait the number of seconds in Retry-After.USAGE_LIMIT_REACHEDYour workspace used today's analytics quota.Wait until error.details.resets_at (00:00 UTC), or upgrade.Wait until error.details.resets_at (00:00 UTC), or upgrade.INTERNAL_ERRORThe analytics store did not answer.Try again in a moment.Try again in a moment.Good to know
- New clicks show up within about a minute.
- Each click is counted once, even when its delivery was retried.
- There is no unique-visitor count and no session count. Every visit is a click.
- Bots are not filtered out.
- Country and city come from the visitor's IP address. A VPN or a work network can change them.
- Each summary breakdown lists the top 250 values.
link_performancelists the top 500. - A
domainsfilter covers at most 250 links. When it hits that,link_filter_truncatedistrueand the numbers cover only those links. - The summary's own
timeseriesis always per day. For other buckets, use the time series call. - A request with a bad body (a
422) does not use your quota. Neither does a call refused by the per-minute limit. - The AI analytics chat is only in the dashboard. It is not part of the API.
