Analytics API
Data-driven decisions start here. Retrieve detailed click data, conversion metrics, and performance insights for your trackable short links through the API.
Start tracking conversions — Free 14-day trial with full analytics access
What You Can Analyze
The Analytics API provides programmatic access to:
- Click Events — Individual visitor data with timestamps, location, and device info
- Conversion Tracking — Connect clicks to meaningful business outcomes
- Variant Performance — Compare A/B test results with statistical confidence
- Traffic Sources — Understand where your visitors come from
- Geographic Data — See which regions drive the most conversions
Get Link Clicks
Retrieve paginated click events for a specific link with optional date filtering.
Endpoint: GET /public/links/{id}/clicks
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | UUID | Yes | Link UUID |
| page | integer | No | Page number (default: 1) |
| per_page | integer | No | Items per page, max 100 (default: 50) |
| start_date | string | No | Filter from this date (ISO 8601 format) |
| end_date | string | No | Filter until this date (ISO 8601 format) |
Example — Basic Request
curl "https://klinky-api.fly.dev/api/v1/public/links/550e8400-e29b-41d4-a716-446655440000/clicks" \
-H "X-API-Key: klinky_live_your_api_key_here"Example — With Date Range
curl "https://klinky-api.fly.dev/api/v1/public/links/550e8400-e29b-41d4-a716-446655440000/clicks?start_date=2024-01-01T00:00:00Z&end_date=2024-01-31T23:59:59Z&per_page=100" \
-H "X-API-Key: klinky_live_your_api_key_here"Response (200 OK)
{
"items": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"link_id": "550e8400-e29b-41d4-a716-446655440000",
"variant_id": "770e8400-e29b-41d4-a716-446655440000",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"referrer": "https://google.com",
"country": "US",
"is_conversion": true,
"created_at": "2024-01-15T14:30:00Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440002",
"link_id": "550e8400-e29b-41d4-a716-446655440000",
"variant_id": "880e8400-e29b-41d4-a716-446655440000",
"ip_address": "192.168.1.2",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)",
"referrer": "https://twitter.com",
"country": "GB",
"is_conversion": false,
"created_at": "2024-01-15T14:25:00Z"
}
],
"total": 1523,
"page": 1,
"per_page": 50
}Click Event Fields
| Field | Type | Description |
|---|---|---|
| id | UUID | Unique click identifier |
| link_id | UUID | Parent link ID |
| variant_id | UUID | Which variant was shown |
| ip_address | string | Visitor IP (anonymized) |
| user_agent | string | Browser/device information |
| referrer | string | Referring URL (if available) |
| country | string | ISO country code (if detected) |
| is_conversion | boolean | Whether this click resulted in a conversion |
| created_at | datetime | When the click occurred |
Aggregating Analytics Data
The clicks endpoint returns raw event data. For meaningful insights, you'll want to aggregate this data.
Python Example — Calculate Variant Performance
import requests
from collections import defaultdict
API_KEY = "klinky_live_your_api_key_here"
BASE_URL = "https://klinky-api.fly.dev/api/v1"
headers = {"X-API-Key": API_KEY}
link_id = "550e8400-e29b-41d4-a716-446655440000"
# Fetch all clicks (handle pagination)
all_clicks = []
page = 1
while True:
response = requests.get(
f"{BASE_URL}/public/links/{link_id}/clicks",
headers=headers,
params={"page": page, "per_page": 100}
)
data = response.json()
all_clicks.extend(data["items"])
if len(data["items"]) < 100:
break
page += 1
# Aggregate by variant
variant_stats = defaultdict(lambda: {"clicks": 0, "conversions": 0})
for click in all_clicks:
variant_id = click["variant_id"]
variant_stats[variant_id]["clicks"] += 1
if click["is_conversion"]:
variant_stats[variant_id]["conversions"] += 1
# Calculate conversion rates
for variant_id, stats in variant_stats.items():
clicks = stats["clicks"]
conversions = stats["conversions"]
rate = (conversions / clicks * 100) if clicks > 0 else 0
print(f"Variant {variant_id}: {conversions}/{clicks} = {rate:.2f}%")JavaScript Example — Traffic Sources
const API_KEY = 'klinky_live_your_api_key_here';
const BASE_URL = 'https://klinky-api.fly.dev/api/v1';
const analyzeTrafficSources = async (linkId) => {
const headers = { 'X-API-Key': API_KEY };
// Fetch all clicks
let allClicks = [];
let page = 1;
while (true) {
const response = await fetch(
`${BASE_URL}/public/links/${linkId}/clicks?page=${page}&per_page=100`,
{ headers }
);
const data = await response.json();
allClicks = allClicks.concat(data.items);
if (data.items.length < 100) break;
page++;
}
// Group by referrer
const referrers = {};
allClicks.forEach(click => {
const ref = click.referrer || 'direct';
const domain = ref === 'direct' ? 'Direct' : new URL(ref).hostname;
referrers[domain] = (referrers[domain] || 0) + 1;
});
// Sort by count
const sorted = Object.entries(referrers)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
console.log('Top traffic sources:');
sorted.forEach(([domain, count]) => {
console.log(` ${domain}: ${count}`);
});
return referrers;
};Python Example — Geographic Distribution
import requests
from collections import Counter
API_KEY = "klinky_live_your_api_key_here"
BASE_URL = "https://klinky-api.fly.dev/api/v1"
headers = {"X-API-Key": API_KEY}
link_id = "550e8400-e29b-41d4-a716-446655440000"
# Fetch clicks
response = requests.get(
f"{BASE_URL}/public/links/{link_id}/clicks",
headers=headers,
params={"per_page": 100}
)
data = response.json()
# Count by country
countries = Counter(
click["country"] or "Unknown"
for click in data["items"]
)
print("Clicks by country:")
for country, count in countries.most_common(10):
print(f" {country}: {count}")Time-Based Analysis
Daily Click Volume
import requests
from collections import defaultdict
from datetime import datetime, timedelta
API_KEY = "klinky_live_your_api_key_here"
BASE_URL = "https://klinky-api.fly.dev/api/v1"
headers = {"X-API-Key": API_KEY}
link_id = "550e8400-e29b-41d4-a716-446655440000"
# Fetch last 30 days
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=30)
response = requests.get(
f"{BASE_URL}/public/links/{link_id}/clicks",
headers=headers,
params={
"start_date": start_date.isoformat(),
"end_date": end_date.isoformat(),
"per_page": 100
}
)
data = response.json()
# Group by day
daily_clicks = defaultdict(int)
daily_conversions = defaultdict(int)
for click in data["items"]:
date = click["created_at"][:10] # Extract YYYY-MM-DD
daily_clicks[date] += 1
if click["is_conversion"]:
daily_conversions[date] += 1
# Print daily stats
for date in sorted(daily_clicks.keys()):
clicks = daily_clicks[date]
conversions = daily_conversions[date]
rate = (conversions / clicks * 100) if clicks > 0 else 0
print(f"{date}: {clicks} clicks, {conversions} conversions ({rate:.1f}%)")Conversion Tracking
To track conversions, you need to fire a conversion event when a user completes a desired action. This is typically done by:
- Including the Klinky tracking pixel on your destination pages
- Calling the conversion API when an action occurs
Learn how to set up conversion tracking — Step-by-step guide with pixel installation
Understanding Conversion Data
The is_conversion field in click events indicates whether that specific click later resulted in a conversion. A conversion could be:
- Purchase completed
- Form submitted
- Account created
- Any meaningful action you define
Calculate Overall Conversion Rate
import requests
API_KEY = "klinky_live_your_api_key_here"
BASE_URL = "https://klinky-api.fly.dev/api/v1"
headers = {"X-API-Key": API_KEY}
link_id = "550e8400-e29b-41d4-a716-446655440000"
response = requests.get(
f"{BASE_URL}/public/links/{link_id}/clicks",
headers=headers
)
data = response.json()
total_clicks = data["total"]
conversions = sum(1 for click in data["items"] if click["is_conversion"])
# Note: for accurate counts with pagination, fetch all pages
conversion_rate = (conversions / total_clicks * 100) if total_clicks > 0 else 0
print(f"Conversion rate: {conversion_rate:.2f}% ({conversions}/{total_clicks})")Exporting Data
Export to CSV
import requests
import csv
from datetime import datetime, timedelta
API_KEY = "klinky_live_your_api_key_here"
BASE_URL = "https://klinky-api.fly.dev/api/v1"
headers = {"X-API-Key": API_KEY}
link_id = "550e8400-e29b-41d4-a716-446655440000"
# Fetch all clicks
all_clicks = []
page = 1
while True:
response = requests.get(
f"{BASE_URL}/public/links/{link_id}/clicks",
headers=headers,
params={"page": page, "per_page": 100}
)
data = response.json()
all_clicks.extend(data["items"])
if len(data["items"]) < 100:
break
page += 1
# Write to CSV
with open('clicks_export.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['timestamp', 'variant_id', 'country', 'referrer', 'converted'])
for click in all_clicks:
writer.writerow([
click['created_at'],
click['variant_id'],
click['country'] or 'Unknown',
click['referrer'] or 'Direct',
'Yes' if click['is_conversion'] else 'No'
])
print(f"Exported {len(all_clicks)} clicks to clicks_export.csv")Best Practices
- Handle pagination — Always implement pagination when fetching click data
- Use date filters — Limit data to relevant time periods for better performance
- Cache results — Analytics data doesn't change frequently; cache appropriately
- Respect rate limits — Space out analytics requests to avoid hitting limits
- Anonymize data — IP addresses are provided for analysis; handle responsibly
- Aggregate server-side — For large datasets, consider your own aggregation pipeline
Error Responses
Link Not Found (404)
{
"detail": "Link not found"
}Resolution: Verify the link ID is correct and belongs to your account.
Rate Limit Exceeded (429)
{
"detail": "Rate limit exceeded. Please try again later."
}Response headers include:
Retry-After: 1800
X-RateLimit-Reset: 1705312800See Rate Limits for handling strategies.
Limitations
- Maximum 100 clicks per page
- Date range queries limited to 90 days for performance
- Historical data retention based on your plan
- Real-time data may have a 1-2 minute delay
Next Steps
- Links API — Create and manage links with conversion tracking
- Rate Limits — Optimize your analytics queries
- Error Handling — Build robust data pipelines
Start your free trial — Track conversions and optimize your campaigns today