Guides
Keep your API key safe
In short
An API key acts as the person who made it, in one workspace. Keep it on a server, give it the least access it needs, and use one key per app. If a key leaks, revoke it in Settings → API at once.
You need
- A role
- Admin in the workspace. Only admins can create, revoke and delete keys.
- A place
- Settings → API in the Clikso dashboard.
- A plan
- Any plan, including Free.
Keep keys out of your code
Store each key in an environment variable or a secrets manager. Your code then reads it at run time. Every example in these docs does this with CLIKSO_API_KEY.
export CLIKSO_API_KEY="clik_live_your_key_here"Never put a key in these places:
- A web page or a browser extension you ship. Anyone can open the source and read it. The API does not allow browser calls from other sites anyway, so call it from your server.
- A mobile app. Keys can be pulled out of an app file.
- A git repository, even a private one. Add your
.envfile to.gitignore. - Chat messages, tickets or logs. Paste the key preview, not the key.
Every key starts with clik_live_. That makes it easy to search your own code, logs and files for keys that slipped in.
Give each key only what it needs
Pick the lowest permission that does the job:
- Read — find links, folders, tags and domains, and read analytics. Best for reports and dashboards.
- Write — everything in Read, plus create, change and delete links, folders and tags.
- Admin — the same as Write for the API today. Pick Write instead.
A key can never do more than the person who made it. If that person is a viewer, even a Write key can only read.
Use one key per app
Make a separate key for each script, server or service. Name it after the app, so you know what breaks if you revoke it. Names can be up to 30 characters.
With one key per app, a leak in one place means you replace one key, not all of them.
API keys per workspace
- API keysPer workspace, revoked keys excluded
- 3
| Limit | Free | Starter | Pro | Business |
|---|---|---|---|---|
| API keysPer workspace, revoked keys excluded | 3 | 10 | 20 | 30 |
Revoked keys do not count toward this limit. Keys held by the Clikso browser extension (one per connected browser) do.
Replace a key without downtime
Make a new key
In Settings → API, press Generate New Key. Give it the same permission as the old one. Copy it now, because Clikso shows it only once.
Switch your app to the new key
Update the environment variable or secret, and deploy. Check that your app's requests still work.
Revoke the old key
Back in Settings → API, revoke the old key. Requests that still use it now get 401 API_KEY_REVOKED.
If you are at your plan's key limit, revoke the old key first, then make the new one. Expect a short gap while you deploy.
If a key leaks
- Revoke it in Settings → API right away. It stops working on the next request.
- Make a new key and deploy it, as in the steps above.
- Look for links and destinations you did not set. List links shows the newest links first.
Revoking is safe. It never touches your links, folders or tags.
Choose who creates the key
A key acts as the person who made it. If that person leaves the workspace, the key stops working with 403 FORBIDDEN. If their role drops, the key's access drops too.
So make long-lived keys from an admin account that will stay in the workspace.
Guard against the wrong workspace
A key belongs to one workspace. You can also send a Workspace-Id header with your workspace's id. If it does not match the key, Clikso refuses the request with 403 FORBIDDEN instead of changing the wrong workspace.
To find the id, read workspace_id from Get usage:
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.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 {
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.
{
"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
}
}What you get back
A request with a bad key never reaches your data. You get an error with one of the codes below. A 401 means the key itself is the problem. A 403 means the key is fine but not allowed to do this.
Common errors
UNAUTHORIZEDThe key is mistyped, deleted, or its creator's account is gone.Send Authorization: Bearer clik_live_… with a key from Settings → API.Send Authorization: Bearer clik_live_… with a key from Settings → API.API_KEY_REVOKEDSomeone revoked this key.Make a new key in Settings → API.Make a new key in Settings → API.FORBIDDENThe Workspace-Id header names another workspace, the creator left the workspace, or a Read key tried to make a change.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.EMAIL_NOT_VERIFIEDThe account that made the key has not verified its email.Sign in to the dashboard and verify the email.Sign in to the dashboard and verify the email.ACCOUNT_BANNEDThe account that made the key is suspended.Contact support. A new key will not help.Contact support. A new key will not help.RATE_LIMITEDToo many requests this minute, or too many failed key checks from your IP address.Wait the number of seconds in Retry-After.Wait the number of seconds in Retry-After.Good to know
- Clikso shows the full key once, when you make it. After that you only see a short preview.
- Keys do not expire on their own. Revoke the ones you no longer use.
- Revoke keeps the key in your list, marked as revoked. Delete removes it from the list. Both stop it working.
- Each key can make 30 requests a minute on Free, and more on paid plans. See Limits.
- After 30 failed key checks in a minute from one IP address, more failed checks from it get
429 RATE_LIMITED. - Always call
https://app.clik.so. Never send a key over plainhttp.
