Skip to content

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

Retrieve paginated click events for a specific link with optional date filtering.

Endpoint: GET /public/links/{id}/clicks

Parameters

NameTypeRequiredDescription
idUUIDYesLink UUID
pageintegerNoPage number (default: 1)
per_pageintegerNoItems per page, max 100 (default: 50)
start_datestringNoFilter from this date (ISO 8601 format)
end_datestringNoFilter until this date (ISO 8601 format)

Example — Basic Request

bash
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

bash
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)

json
{
  "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

FieldTypeDescription
idUUIDUnique click identifier
link_idUUIDParent link ID
variant_idUUIDWhich variant was shown
ip_addressstringVisitor IP (anonymized)
user_agentstringBrowser/device information
referrerstringReferring URL (if available)
countrystringISO country code (if detected)
is_conversionbooleanWhether this click resulted in a conversion
created_atdatetimeWhen 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

python
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

javascript
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

python
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

python
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:

  1. Including the Klinky tracking pixel on your destination pages
  2. 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

python
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

python
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

  1. Handle pagination — Always implement pagination when fetching click data
  2. Use date filters — Limit data to relevant time periods for better performance
  3. Cache results — Analytics data doesn't change frequently; cache appropriately
  4. Respect rate limits — Space out analytics requests to avoid hitting limits
  5. Anonymize data — IP addresses are provided for analysis; handle responsibly
  6. Aggregate server-side — For large datasets, consider your own aggregation pipeline

Error Responses

json
{
  "detail": "Link not found"
}

Resolution: Verify the link ID is correct and belongs to your account.

Rate Limit Exceeded (429)

json
{
  "detail": "Rate limit exceeded. Please try again later."
}

Response headers include:

Retry-After: 1800
X-RateLimit-Reset: 1705312800

See 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

Start your free trial — Track conversions and optimize your campaigns today

Released under MIT License