Developer docs

Get started

Authentication

In short

Every request sends an API key in the Authorization header, as Bearer clik_live_…. A key belongs to one workspace. It acts as the person who made it, and it can never do more than that person can.

You need

A plan
Any plan. The API works on Free too.
A role
Admin in the workspace. Only admins can create, rename, revoke or delete keys.
A safe place
Somewhere to keep the key, such as an environment variable or a secrets manager.

Create a key

Open the API settings

Sign in at app.clik.so. Open Settings, then the API tab. Press Generate New Key.

Name the key and pick its permission

Give the key a name of up to 30 characters, such as "Order emails". The name helps you spot it later.

Then tick a permission. Write also gives Read, and Admin gives both. Press Generate Key.

Copy the key once

The Save your API key window shows the full key. This is the only time you will see it. Copy it now and store it safely.

A key looks like clik_live_ followed by 32 letters and digits.

Send the key

Put the key in the Authorization header of every request, after the word Bearer and a space.

Header
Authorization: Bearer clik_live_your_key_here

The X-API-Key header does not work. Only Authorization: Bearer does.

Check your key

This call reads your workspace's usage. Any key with Read permission can make it, so it is a good first test.

curl "https://app.clik.so/api/workspace/usage" \
  -H "Authorization: Bearer $CLIKSO_API_KEY"
const res = await fetch("https://app.clik.so/api/workspace/usage", {
  headers: {
    Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
  },
});

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.get(
    "https://app.clik.so/api/workspace/usage",
    headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
)

print(response.status_code, response.json())

Uses requests. Set CLIKSO_API_KEY first.

<?php

$ch = curl_init('https://app.clik.so/api/workspace/usage');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CLIKSO_API_KEY'),
    ],
]);

$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"
)

func main() {
	req, err := http.NewRequest("GET", "https://app.clik.so/api/workspace/usage", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("CLIKSO_API_KEY"))

	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"

uri = URI('https://app.clik.so/api/workspace/usage')
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"

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 {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.clik.so/api/workspace/usage"))
            .header("Authorization", "Bearer " + System.getenv("CLIKSO_API_KEY"))
            .GET()
            .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("GET"), "https://app.clik.so/api/workspace/usage");
request.Headers.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));

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());
    std::string response;

    curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/workspace/usage");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    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": {
    "workspace_id": "8a1f2c3d-4b5e-4f60-9a7b-1c2d3e4f5a6b",
    "links_created_this_month": 42,
    "clicks_this_month": 1873,
    "ai_analytics_today": 0,
    "ai_slug_gens_today": 3,
    "last_reset_ai": "2026-10-09",
    "last_reset_monthly": "2026-10-01",
    "next_reset": "2026-11-01T00:00:00.000Z",
    "updated_at": "2026-10-09T09:30:00.000Z",
    "plan_key": "starter",
    "plan_links_limit": 500,
    "plan_domains_limit": 2,
    "is_simulated": false
  }
}

A 200 means the key works. A 401 means the key is wrong, revoked or missing.

What each permission allows

A key's permission sets the most it can do. Your own role in the workspace also counts. The key gets whichever of the two is lower.

  • Read acts like a viewer. It can read links, folders, tags, domains, usage and click analytics.
  • Write acts like an editor. It can do everything Read can, and also create, change and delete links, folders and tags.
  • Admin is the same as Write for now. No API route needs more than Write today.

So an editor's Admin key still acts as an editor. And an admin's Read key can only read.

What keys can never do

Some parts of Clikso stay in the dashboard. A key that calls them gets 403 API_KEY_NOT_ALLOWED, whatever its permission.

  • Create, list or revoke API keys.
  • Change billing, members, invitations or workspace settings.
  • Add, verify or delete custom domains. Keys can only read them.
  • Reveal a link's password.
  • Upload a social preview image, or use the AI analytics chat.

This keeps a leaked key from taking over your account.

One key, one workspace

Each key belongs to the workspace you made it in. Every link it creates lands there. To work with two workspaces, make one key in each.

You can also send a Workspace-Id header with the workspace id. You do not have to. But if you send it, it must match the key's workspace, or you get 403 FORBIDDEN. That stops a script from writing to the wrong place by mistake.

When people change

A key follows its creator, live, on every request.

  • If the creator becomes a viewer, their Write key can only read.
  • If the creator leaves the workspace, their keys stop working with 403 FORBIDDEN.
  • If the creator's account is suspended, every call gets 403 ACCOUNT_BANNED.
  • If the creator has not verified their email address, calls can get 403 EMAIL_NOT_VERIFIED.

To keep a script running when people change, have an admin who stays make its key.

What you get back

When the key is fine, you get the normal response for that endpoint. When it is not, you get an error with a code that tells you why. Every error has the same shape. See Requests and responses.

Common errors

401UNAUTHORIZEDThe header is missing, or the key is mistyped, deleted or unknown.Send Authorization: Bearer clik_live_… with a key from Settings → API.
401API_KEY_REVOKEDSomeone revoked the key in Settings → API.Create a new key and replace the old one.
401API_KEY_EXPIREDThe key had an expiry date, and it has passed.Create a new key.
403FORBIDDENThe permission or role is too low, or Workspace-Id names another workspace.Check the key’s permission and your role. If you send Workspace-Id, it must match the key’s workspace.
403API_KEY_NOT_ALLOWEDThis endpoint never accepts API keys.Use only the endpoints in the API reference.
403EMAIL_NOT_VERIFIEDThe person who made the key has not verified their email.Sign in to the dashboard and verify the email.
429RATE_LIMITEDToo many requests this minute, or too many failed key checks from your IP address.Wait the number of seconds in the Retry-After header, then try again.

Good to know

  • Your workspace can hold 3 keys on Free, 10 on Starter, 20 on Pro and 30 on Business. Revoked keys do not count; each browser connected to the Clikso extension holds one that does.
  • The key limit only applies when you create a key. If you move to a smaller plan, the keys you have keep working.
  • Each key has its own request limit per minute. See Limits.
  • One IP address can fail 30 key checks a minute. Past that, its failed checks get 429 RATE_LIMITED until the minute ends.
  • To change a key, make a new one, switch your code to it, then revoke the old one.
  • Keep keys out of web pages, apps and Git. See Keep your API key safe.