Skip to content

Rate Limits

Build responsibly. The Klinky API implements rate limiting to ensure fair usage and platform stability. Understanding these limits helps you build robust integrations.

Start building — Free trial with 100 API calls/hour on Growth plan


Plan-Based Limits

API access and rate limits are determined by your plan:

PlanAPI AccessRate LimitBest For
FreeNo accessDashboard only
StarterNo accessDashboard only
GrowthYes100 requests/hourSmall teams, basic automation
ScaleYes1,000 requests/hourLarge teams, heavy API usage

Upgrade your plan — Increase your rate limit and unlock more features


Rate Limit Headers

Every API response includes rate limit information in the headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1705312800
HeaderDescriptionExample
X-RateLimit-LimitMaximum requests per hour1000
X-RateLimit-RemainingRequests remaining in current window847
X-RateLimit-ResetUnix timestamp when limit resets1705312800

Rate Limit Windows

Rate limits use a sliding window of one hour. The window resets at the top of each hour (UTC).

Example timeline:

10:00 UTC - Window resets, 1000 requests available
10:15 UTC - 200 requests made, 800 remaining
10:45 UTC - 800 requests made, 200 remaining
11:00 UTC - Window resets, 1000 requests available again

Exceeding Rate Limits

When you exceed your rate limit, the API returns:

Status: 429 Too Many Requests

Response body:

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

Response headers:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312800
Retry-After: 1800

The Retry-After header indicates how many seconds to wait before retrying.


Checking Rate Limit Status

Via API Endpoint

Check your current rate limit status without consuming a request:

bash
curl "https://klinky-api.fly.dev/api/v1/public/keys/rate-limit" \
  -H "Authorization: Bearer <your-jwt-token>"

Response:

json
{
  "limit": 1000,
  "remaining": 847,
  "reset_at": "2024-01-15T15:00:00Z"
}

From Response Headers

Monitor headers from any API response:

python
import requests

API_KEY = "klinky_live_your_api_key_here"

response = requests.get(
    "https://klinky-api.fly.dev/api/v1/public/links",
    headers={"X-API-Key": API_KEY}
)

limit = response.headers.get('X-RateLimit-Limit')
remaining = response.headers.get('X-RateLimit-Remaining')
reset_at = response.headers.get('X-RateLimit-Reset')

print(f"Rate limit: {remaining}/{limit} (resets at {reset_at})")

Best Practices

1. Monitor Rate Limits

Always check rate limit headers and throttle requests when running low:

python
def check_rate_limit(response):
    """Check if we're approaching rate limit."""
    remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
    limit = int(response.headers.get('X-RateLimit-Limit', 100))

    usage_percent = (limit - remaining) / limit * 100

    if usage_percent > 90:
        print(f"Warning: {usage_percent:.1f}% of rate limit used")
        return True
    return False

2. Implement Exponential Backoff

When rate limited, wait before retrying:

python
import time
import random

def exponential_backoff(attempt, base_delay=1, max_delay=60):
    """Calculate delay with exponential backoff and jitter."""
    delay = min(base_delay * (2 ** attempt), max_delay)
    jitter = random.uniform(0, delay * 0.1)  # 10% jitter
    return delay + jitter

# Usage
for attempt in range(5):
    response = make_request()

    if response.status_code == 429:
        retry_after = int(response.headers.get('Retry-After', 60))
        delay = max(retry_after, exponential_backoff(attempt))
        print(f"Rate limited. Waiting {delay:.1f}s...")
        time.sleep(delay)
        continue

    break

3. Batch Operations

Minimize API calls by fetching data in batches:

python
# Good: Fetch 100 items per request
response = requests.get(
    "https://klinky-api.fly.dev/api/v1/public/links",
    headers=headers,
    params={"per_page": 100}
)

# Good: Fetch clicks with date filter
response = requests.get(
    f"https://klinky-api.fly.dev/api/v1/public/links/{link_id}/clicks",
    headers=headers,
    params={
        "start_date": "2024-01-01",
        "end_date": "2024-01-31",
        "per_page": 100
    }
)

4. Cache Responses

Cache data that doesn't change frequently:

python
import time
from functools import lru_cache

# Cache link list for 5 minutes
@lru_cache(maxsize=1)
def get_cached_links(_timestamp):
    """Get links with caching. _timestamp rounds to 5-min intervals."""
    return fetch_links_from_api()

def get_links():
    # Round to nearest 5 minutes for caching
    cache_key = int(time.time() / 300)
    return get_cached_links(cache_key)

5. Use Idempotency Keys

Prevent duplicate operations when retrying:

bash
curl -X POST https://klinky-api.fly.dev/api/v1/public/links \
  -H "X-API-Key: klinky_live_your_api_key_here" \
  -H "Idempotency-Key: unique-request-id-123" \
  -H "Content-Type: application/json" \
  -d '{...}'

6. Queue Requests

For high-volume operations, use a queue:

python
from queue import Queue
import threading

class RateLimitedQueue:
    def __init__(self, requests_per_hour=1000):
        self.queue = Queue()
        self.delay = 3600 / requests_per_hour  # seconds between requests
        self.worker = threading.Thread(target=self._process_queue)
        self.worker.daemon = True
        self.worker.start()

    def add(self, request_func):
        """Add a request to the queue."""
        self.queue.put(request_func)

    def _process_queue(self):
        while True:
            request_func = self.queue.get()
            try:
                request_func()
            except Exception as e:
                print(f"Request failed: {e}")
            time.sleep(self.delay)

# Usage
queue = RateLimitedQueue(requests_per_hour=1000)
queue.add(lambda: create_link(link_data))
queue.add(lambda: get_link_clicks(link_id))

Rate Limit Strategies by Use Case

Web Application

For user-facing applications, implement client-side caching:

javascript
// Cache link data for 60 seconds
const getLinks = async () => {
  const cached = localStorage.getItem('links_cache');
  if (cached) {
    const { data, timestamp } = JSON.parse(cached);
    if (Date.now() - timestamp < 60000) {
      return data;
    }
  }

  const response = await fetch('/api/v1/public/links', {
    headers: { 'X-API-Key': API_KEY }
  });
  const data = await response.json();

  localStorage.setItem('links_cache', JSON.stringify({
    data,
    timestamp: Date.now()
  }));

  return data;
};

Background Jobs

For automated processes, spread requests evenly:

python
import schedule
import time

def sync_analytics():
    """Sync analytics data every 15 minutes."""
    links = get_all_links()

    # Spread requests across the interval
    delay = (15 * 60) / len(links) if links else 0

    for link in links:
        fetch_link_clicks(link['id'])
        time.sleep(delay)

# Run every 15 minutes
schedule.every(15).minutes.do(sync_analytics)

while True:
    schedule.run_pending()
    time.sleep(1)

Bulk Operations

When processing many items, track rate limit status:

python
def bulk_create_links(links_data):
    """Create multiple links with rate limit awareness."""
    created = []

    for link_data in links_data:
        response = create_link(link_data)

        # Check rate limit after each request
        remaining = int(response.headers.get('X-RateLimit-Remaining', 0))

        if remaining < 10:
            reset_at = int(response.headers.get('X-RateLimit-Reset', 0))
            wait_seconds = reset_at - int(time.time())

            if wait_seconds > 0:
                print(f"Approaching limit. Waiting {wait_seconds}s...")
                time.sleep(wait_seconds)

        created.append(response.json())

    return created

Troubleshooting

Unexpected 429 Errors

If you're hitting rate limits unexpectedly:

  1. Check for retries — Ensure failed requests aren't retrying too aggressively
  2. Review integrations — Multiple services using the same API key
  3. Monitor usage — Track which endpoints consume the most requests
  4. Consider caching — Cache responses to reduce API calls

Rate Limit Not Resetting

Rate limits reset hourly at the top of the hour (UTC). If you need immediate relief:

  1. Wait for the next hour window
  2. Upgrade to a higher plan
  3. Optimize your request patterns

Need Higher Limits?

Contact support if you need:

  • Custom rate limits for enterprise use
  • Dedicated infrastructure
  • Bulk operation endpoints

FAQ

Q: Do rate limits apply per API key or per account?

Rate limits apply per account across all API keys. Multiple keys don't increase your limit.

Q: Do failed requests count against my rate limit?

Yes, all requests (successful or failed) count against your rate limit except 429 responses themselves.

Q: Can I purchase additional rate limits?

Currently, rate limits are tied to plan tiers. Contact support for enterprise options.

Q: What happens if I exceed my rate limit during a critical operation?

Implement exponential backoff and retry logic. For critical operations, consider upgrading to Scale plan for higher limits.

Q: Do webhook requests count against my rate limit?

No, webhook deliveries from Klinky to your endpoints don't count against your API rate limit.


Next Steps

Questions about rate limits? — We're here to help optimize your integration

Released under MIT License