Developer docs

API reference / Analytics

Get click summary

POSThttps://app.clik.so/api/analytics/summary

In short

Returns the total clicks for a date range, broken down by country, city, device, browser, OS, referrer, link and UTM tag. Narrow it to some links or some countries with filters.

You need

API key
read permission 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/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.body

Uses 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.

Response 200
{
  "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
  }
}

Other responses: 401 · 422 · 429 · 500

Request body

  • fromstring · date-time

    Start of the window, inclusive. An ISO 8601 date-time with a time zone, like 2026-10-01T00:00:00Z. With to; if either is missing, the last 7 days are used. Anything that is not a valid date-time, or a from after to, is a 422.

  • tostring · date-time

    End of the window, exclusive. An ISO 8601 date-time with a time zone, like 2026-10-08T00:00:00Z.

  • intervalstring

    Bucket size. Only used by the time-series endpoint.

    One of: hour, day, week, month · Default "hour"

  • url_idsarray of string · uuid

    Only these links, by id.

    Up to 100 items

  • slugsarray of string

    Only these links, as domain/short_code, like clik.is/summer-sale or, for a nested code, clik.is/acme/launch (split at the first /).

    Up to 100 items

  • domainsarray of string

    Only links on these domains. Covers at most 250 links.

    Up to 100 items

  • countryarray of string

    2-letter country codes, like US.

    Up to 100 items

  • cityarray of string

    City names, as they appear in the summary.

    Up to 100 items

  • regionarray of string

    Region names.

    Up to 100 items

  • continentarray of string

    Continent codes: AF, AN, AS, EU, NA, OC, SA.

    Up to 100 items

  • devicearray of string

    mobile, tablet, desktop or tv.

    Up to 100 items

  • osarray of string

    Operating systems, like iOS, Android, Windows, Mac OS, Linux.

    Up to 100 items

  • browserarray of string

    Browsers, like Chrome, Safari, Firefox.

    Up to 100 items

  • referrerarray of string

    Exact referrer URLs, as they appear in the summary.

    Up to 100 items

  • short_linksarray of string

    Full short URLs, like https://clik.is/summer-sale.

    Up to 100 items

  • destination_urlsarray of string

    Exact destination URLs.

    Up to 100 items

  • utm_sourcearray of string

    utm_source values on the clicked short URL.

    Up to 100 items

  • utm_mediumarray of string

    utm_medium values on the clicked short URL.

    Up to 100 items

  • utm_campaignarray of string

    utm_campaign values on the clicked short URL.

    Up to 100 items

  • utm_termarray of string

    utm_term values on the clicked short URL.

    Up to 100 items

  • utm_contentarray of string

    utm_content values on the clicked short URL.

    Up to 100 items

What you get back

200 with the usual envelope — status, success, message, data. data has these fields:

  • total_clicksintegerrequired

    All matching clicks in the window.

    Min 0

  • referrersarray of LabelCountrequired

    Top referrers.

  • referrers[].labelstringrequired

    The value, like a country code or a browser name.

  • referrers[].countintegerrequired

    Clicks with that value.

    Min 0

  • devicesarray of LabelCountrequired

    Clicks by device type.

  • devices[].labelstringrequired

    The value, like a country code or a browser name.

  • devices[].countintegerrequired

    Clicks with that value.

    Min 0

  • osarray of LabelCountrequired

    Clicks by operating system.

  • os[].labelstringrequired

    The value, like a country code or a browser name.

  • os[].countintegerrequired

    Clicks with that value.

    Min 0

  • browsersarray of LabelCountrequired

    Clicks by browser.

  • browsers[].labelstringrequired

    The value, like a country code or a browser name.

  • browsers[].countintegerrequired

    Clicks with that value.

    Min 0

  • short_linksarray of LabelCountrequired

    Top short URLs.

  • short_links[].labelstringrequired

    The value, like a country code or a browser name.

  • short_links[].countintegerrequired

    Clicks with that value.

    Min 0

  • destinationsarray of LabelCountrequired

    Top destination URLs.

  • destinations[].labelstringrequired

    The value, like a country code or a browser name.

  • destinations[].countintegerrequired

    Clicks with that value.

    Min 0

  • countriesarray of LabelCountrequired

    Top countries, as 2-letter codes.

  • countries[].labelstringrequired

    The value, like a country code or a browser name.

  • countries[].countintegerrequired

    Clicks with that value.

    Min 0

  • citiesarray of LabelCountrequired

    Top cities.

  • cities[].labelstringrequired

    The value, like a country code or a browser name.

  • cities[].countintegerrequired

    Clicks with that value.

    Min 0

  • continentsarray of LabelCountrequired

    Clicks by continent code.

  • continents[].labelstringrequired

    The value, like a country code or a browser name.

  • continents[].countintegerrequired

    Clicks with that value.

    Min 0

  • timeseriesarray of TimeseriesPointrequired

    Clicks per day, oldest first.

  • timeseries[].timestampstring · date-timerequired

    Start of the bucket, in UTC.

  • timeseries[].clicksintegerrequired

    Clicks in the bucket.

    Min 0

  • map_dataobjectrequired

    Country code → clicks, ready for a world map.

  • utmobjectrequired

    Top UTM values on the clicked short URLs.

  • utm.sourcesarray of LabelCountrequired
  • utm.mediumsarray of LabelCountrequired
  • utm.campaignsarray of LabelCountrequired
  • utm.termsarray of LabelCountrequired
  • utm.contentsarray of LabelCountrequired
  • link_performancearray of objectrequired

    Clicks per short URL, split by destination.

  • link_performance[].linkstringrequired

    The short URL.

  • link_performance[].breakdownarray of LabelCountrequired

    Clicks per destination URL.

  • retention_clampedboolean

    true when from was older than your plan keeps and was moved forward.

  • retention_daysinteger

    How many days of clicks your plan keeps.

  • effective_fromstring · date-time

    The start of the window actually used.

  • link_filter_truncatedboolean

    true when a link filter matched more links than one query covers; the numbers are for a subset.

  • link_filter_limitinteger

    The most links one query can cover (250).

Response headers

  • X-RateLimit-Limitinteger

    Analytics calls allowed per minute for this workspace.

  • X-RateLimit-Remaininginteger

    Analytics calls left in the current minute.

  • X-RateLimit-Resetinteger

    Seconds until the current minute window resets.

  • X-Analytics-Quota-Limitinteger

    Analytics calls allowed per UTC day for this workspace.

  • X-Analytics-Quota-Remaininginteger

    Analytics calls left today (UTC).

  • X-Analytics-Retention-Clampedstring

    true when from was older than your plan keeps and was moved forward.

  • X-Analytics-Retention-Daysinteger

    How many days of clicks your plan keeps.

  • X-Analytics-Effective-Fromstring

    The start of the window that was actually used, after any move forward (ISO 8601).

  • X-Analytics-Link-Filter-Truncatedstring

    true when a link filter matched more links than one query can cover, so the numbers are for a subset.

  • X-Analytics-Link-Filter-Limitinteger

    The most links one analytics query can cover (250).

Common errors

401UNAUTHORIZEDThe API key is missing, malformed or unknown.
422VALIDATION_ERRORThe 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.
429RATE_LIMITEDThe workspace went over its per-minute analytics quota, or the key over its per-minute limit. Wait for Retry-After seconds.
429USAGE_LIMIT_REACHEDThe workspace used its daily analytics quota. details.resets_at says when it resets (midnight UTC).
500INTERNAL_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.
  • Leave out from or to and you get the last 7 days.
  • Your plan keeps clicks for a number of days (Free 30, Starter 90, Pro 180, Business 365). If from is older, it is moved forward and retention_clamped is true. This is not an error.
  • Each breakdown lists the top 250 values. link_performance lists the top 500 link and destination pairs.
  • The timeseries inside the summary is always per day. For hourly, weekly or monthly buckets, use Get clicks over time.
  • A domains filter covers at most 250 links. When it hits that, link_filter_truncated is true and the numbers are for those 250 only.
  • Clicks are counted once each, even if delivery was retried. There are no unique-visitor counts and no bot filtering. New clicks show up within about a minute.
  • The same request within 60 seconds is answered from a cache. It still counts toward your quota.