Guides
Link expiry
In short
Set expires_at on a link, and from that moment the short URL stops sending people to the destination. Add expired_redirect_url to send late visitors somewhere useful instead. Expiry works on every plan, including Free.
You need
- API key
- A key with Write permission.
- Role
- Editor or admin in the key’s workspace.
- Plan
- Any plan, including Free.
Set an expiry date
Send expires_at as an ISO 8601 date and time. Add Z at the end to mean UTC, so there is no doubt about the time zone.
curl -X POST "https://app.clik.so/api/links" \
-H "Authorization: Bearer $CLIKSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended"
}'const res = await fetch("https://app.clik.so/api/links", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended"
}),
});
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/links",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
json={
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended",
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$body = <<<'JSON'
{
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended"
}
JSON;
$ch = curl_init('https://app.clik.so/api/links');
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(`{
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended"
}`)
req, err := http.NewRequest("POST", "https://app.clik.so/api/links", 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/links')
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = {
'destination_url' => 'https://example.com/summer-sale',
'short_code' => 'summer-sale',
'expires_at' => '2026-12-31T23:59:00Z',
'expired_redirect_url' => 'https://example.com/sale-ended'
}.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 = """
{
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.clik.so/api/links"))
.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/links");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));
request.Content = new StringContent(
"""
{
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended"
}
""",
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({
"destination_url": "https://example.com/summer-sale",
"short_code": "summer-sale",
"expires_at": "2026-12-31T23:59:00Z",
"expired_redirect_url": "https://example.com/sale-ended"
})json";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/links");
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.
You get the new link back. Its expires_at is the same moment, written as 2026-12-31T23:59:00.000Z.
expired_redirect_url is optional. It must be a full http:// or https:// URL, up to 2,048 characters.
Change or remove the expiry
To move the date, update the link with a new expires_at:
curl -X PUT "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c" \
-H "Authorization: Bearer $CLIKSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expires_at": "2027-01-31T23:59:00Z"
}'const res = await fetch("https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"expires_at": "2027-01-31T23:59:00Z"
}),
});
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.put(
"https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
json={
"expires_at": "2027-01-31T23:59:00Z",
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$body = <<<'JSON'
{
"expires_at": "2027-01-31T23:59:00Z"
}
JSON;
$ch = curl_init('https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
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(`{
"expires_at": "2027-01-31T23:59:00Z"
}`)
req, err := http.NewRequest("PUT", "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c", 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/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c')
request = Net::HTTP::Put.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = {
'expires_at' => '2027-01-31T23:59:00Z'
}.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 = """
{
"expires_at": "2027-01-31T23:59:00Z"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c"))
.header("Authorization", "Bearer " + System.getenv("CLIKSO_API_KEY"))
.header("Content-Type", "application/json")
.method("PUT", 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("PUT"), "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));
request.Content = new StringContent(
"""
{
"expires_at": "2027-01-31T23:59:00Z"
}
""",
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({
"expires_at": "2027-01-31T23:59:00Z"
})json";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c");
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
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.
To remove the expiry, send null. The link then works with no end date.
curl -X PUT "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c" \
-H "Authorization: Bearer $CLIKSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expires_at": null
}'const res = await fetch("https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"expires_at": null
}),
});
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.put(
"https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
json={
"expires_at": None,
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$body = <<<'JSON'
{
"expires_at": null
}
JSON;
$ch = curl_init('https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
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(`{
"expires_at": null
}`)
req, err := http.NewRequest("PUT", "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c", 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/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c')
request = Net::HTTP::Put.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = {
'expires_at' => nil
}.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 = """
{
"expires_at": null
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c"))
.header("Authorization", "Bearer " + System.getenv("CLIKSO_API_KEY"))
.header("Content-Type", "application/json")
.method("PUT", 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("PUT"), "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));
request.Content = new StringContent(
"""
{
"expires_at": null
}
""",
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({
"expires_at": null
})json";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c");
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
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.
expired_redirect_url: null removes the redirect for late visitors in the same way.
What visitors see
Before expires_at, the short link works as usual.
After it, a visitor is sent to expired_redirect_url if you set one. If you did not, they land on a "link expired" page on the same domain. That page answers with 410 Gone, so search engines drop the link.
Clikso checks the date on every visit. Nothing has to run on your side.
Find expired links
Filter the link list with status=expired:
curl "https://app.clik.so/api/links?status=expired&limit=20" \
-H "Authorization: Bearer $CLIKSO_API_KEY"const res = await fetch("https://app.clik.so/api/links?status=expired&limit=20", {
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/links",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
params={
"status": "expired",
"limit": 20,
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$ch = curl_init('https://app.clik.so/api/links?status=expired&limit=20');
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/links?status=expired&limit=20", 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/links?status=expired&limit=20')
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/links?status=expired&limit=20"))
.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/links?status=expired&limit=20");
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/links?status=expired&limit=20");
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.
This also finds links whose expires_at has passed but whose stored status still says active. In the same way, status=active leaves those links out.
Expire or archive?
They both stop a link, but they do different jobs:
- Expire — set a date in advance. The link stops by itself at that moment.
- Archive — stop the link now, by hand. Send
status: "archived"in an update.
An archived link answers like a link that does not exist, with a "not found" page. To turn it back on, send status: "active".
curl -X PUT "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c" \
-H "Authorization: Bearer $CLIKSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "archived"
}'const res = await fetch("https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.CLIKSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"status": "archived"
}),
});
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.put(
"https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c",
headers={"Authorization": f"Bearer {os.environ['CLIKSO_API_KEY']}"},
json={
"status": "archived",
},
)
print(response.status_code, response.json())Uses requests. Set CLIKSO_API_KEY first.
<?php
$body = <<<'JSON'
{
"status": "archived"
}
JSON;
$ch = curl_init('https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
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(`{
"status": "archived"
}`)
req, err := http.NewRequest("PUT", "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c", 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/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c')
request = Net::HTTP::Put.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("CLIKSO_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = {
'status' => 'archived'
}.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 = """
{
"status": "archived"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c"))
.header("Authorization", "Bearer " + System.getenv("CLIKSO_API_KEY"))
.header("Content-Type", "application/json")
.method("PUT", 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("PUT"), "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("CLIKSO_API_KEY"));
request.Content = new StringContent(
"""
{
"status": "archived"
}
""",
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({
"status": "archived"
})json";
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://app.clik.so/api/links/3f6c1a2e-9b4d-4e8f-a1c2-7d9e0f1a2b3c");
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
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.
To bring back an expired link, send status: "active" with a new expires_at in the future, or with expires_at: null.
What you get back
A 200 (or 201 for a new link) with the whole link in data. These fields matter here:
expires_at— the expiry moment in UTC, ornull.expired_redirect_url— where late visitors go, ornull.status—active,archivedorexpired.
Common errors
FORBIDDENThe key has only Read permission, or its owner is a viewer.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.NOT_FOUNDNo link with that id in this workspace.Check the id. Ids from another workspace are never found.Check the id. Ids from another workspace are never found.VALIDATION_ERRORexpires_at is not a date, expired_redirect_url is too long, or status is not active, archived or expired. details.field names the field.Fix the field and send the request again.Fix the field and send the request again.DESTINATION_BLOCKEDexpired_redirect_url points at another link shortener or a blocked site.Show error.message to the user as it is. Do not retry.Show error.message to the user as it is. Do not retry.Good to know
- A link has one
expires_at. There are no repeating windows. - There is no "expire after a number of clicks". Expiry is by date and time only.
- Send a real date in ISO 8601, like
2026-12-31T23:59:00Z. Anything else gets422 VALIDATION_ERROR. - A date in the past is accepted. The link is then expired from the start.
- Clikso stores an exact moment, not a local clock time. A teammate in another time zone sees the same moment.
- Expired and archived links still count toward the monthly link limit. Their clicks stay in your analytics.
