API reference / Analytics
Get clicks over time
https://app.clik.so/api /analytics/timeseriesIn short
You need
- API key
readpermission or higher. See Authentication.- Role
- Any member (viewer and up) in the key's workspace.
- Plan
- Any plan, including Free.
Example
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
}
]
}Request body
fromstring · date-timeStart of the window, inclusive. An ISO 8601 date-time with a time zone, like
2026-10-01T00:00:00Z. Withto; if either is missing, the last 7 days are used. Anything that is not a valid date-time, or afromafterto, is a422.tostring · date-timeEnd of the window, exclusive. An ISO 8601 date-time with a time zone, like
2026-10-08T00:00:00Z.intervalstringBucket size. Only used by the time-series endpoint.
One of:
hour,day,week,month· Default"hour"url_idsarray of string · uuidOnly these links, by id.
Up to 100 items
slugsarray of stringOnly these links, as
domain/short_code, likeclik.is/summer-saleor, for a nested code,clik.is/acme/launch(split at the first/).Up to 100 items
domainsarray of stringOnly links on these domains. Covers at most 250 links.
Up to 100 items
countryarray of string2-letter country codes, like
US.Up to 100 items
cityarray of stringCity names, as they appear in the summary.
Up to 100 items
regionarray of stringRegion names.
Up to 100 items
continentarray of stringContinent codes:
AF,AN,AS,EU,NA,OC,SA.Up to 100 items
devicearray of stringmobile,tablet,desktoportv.Up to 100 items
osarray of stringOperating systems, like
iOS,Android,Windows,Mac OS,Linux.Up to 100 items
browserarray of stringBrowsers, like
Chrome,Safari,Firefox.Up to 100 items
referrerarray of stringExact referrer URLs, as they appear in the summary.
Up to 100 items
short_linksarray of stringFull short URLs, like
https://clik.is/summer-sale.Up to 100 items
destination_urlsarray of stringExact destination URLs.
Up to 100 items
utm_sourcearray of stringutm_sourcevalues on the clicked short URL.Up to 100 items
utm_mediumarray of stringutm_mediumvalues on the clicked short URL.Up to 100 items
utm_campaignarray of stringutm_campaignvalues on the clicked short URL.Up to 100 items
utm_termarray of stringutm_termvalues on the clicked short URL.Up to 100 items
utm_contentarray of stringutm_contentvalues on the clicked short URL.Up to 100 items
What you get back
200 with the usual envelope — status, success, message, data. data is an array; each item has these fields:
timestampstring · date-timerequiredStart of the bucket, in UTC.
clicksintegerrequiredClicks in the bucket.
Min 0
Response headers
X-RateLimit-LimitintegerAnalytics calls allowed per minute for this workspace.
X-RateLimit-RemainingintegerAnalytics calls left in the current minute.
X-RateLimit-ResetintegerSeconds until the current minute window resets.
X-Analytics-Quota-LimitintegerAnalytics calls allowed per UTC day for this workspace.
X-Analytics-Quota-RemainingintegerAnalytics calls left today (UTC).
X-Analytics-Retention-Clampedstringtruewhenfromwas older than your plan keeps and was moved forward.X-Analytics-Retention-DaysintegerHow many days of clicks your plan keeps.
X-Analytics-Effective-FromstringThe start of the window that was actually used, after any move forward (ISO 8601).
X-Analytics-Link-Filter-Truncatedstringtruewhen a link filter matched more links than one query can cover, so the numbers are for a subset.X-Analytics-Link-Filter-LimitintegerThe most links one analytics query can cover (250).
Common errors
UNAUTHORIZEDThe API key is missing, malformed or unknown.VALIDATION_ERRORinterval is not hour, day, week or month, the body is not JSON, a filter list has more than 100 entries, from or to is not an ISO 8601 date-time, or from is after to.RATE_LIMITEDThe workspace went over its per-minute analytics quota, or the key over its per-minute limit. Wait for Retry-After seconds.USAGE_LIMIT_REACHEDThe workspace used its daily analytics quota. details.resets_at says when it resets (midnight UTC).INTERNAL_ERRORThe analytics store did not answer. Try again in a moment.Every code and what to do about it: Errors.
Good to know
- Rate limit: Analytics quota per workspace, shared by all its keys: per minute Free 5, Starter 10, Pro 30, Business 60; per UTC day Free 100, Starter 1,000, Pro 5,000, Business 20,000.
intervalishour,day,weekormonth, and defaults tohour. Any other value is a422.- Buckets with no clicks are left out, so fill the gaps with zero when you draw a chart.
- Timestamps are the start of each bucket, in UTC. Weeks start on Sunday.
- Leave out
fromortoand you get the last 7 days. Afromolder than your plan keeps is moved forward; theX-Analytics-Retention-Clampedheader says when. - The same request within 60 seconds is answered from a cache. It still counts toward your quota.
