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:
| Plan | API Access | Rate Limit | Best For |
|---|---|---|---|
| Free | No access | — | Dashboard only |
| Starter | No access | — | Dashboard only |
| Growth | Yes | 100 requests/hour | Small teams, basic automation |
| Scale | Yes | 1,000 requests/hour | Large 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| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests per hour | 1000 |
X-RateLimit-Remaining | Requests remaining in current window | 847 |
X-RateLimit-Reset | Unix timestamp when limit resets | 1705312800 |
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 againExceeding Rate Limits
When you exceed your rate limit, the API returns:
Status: 429 Too Many Requests
Response body:
{
"detail": "Rate limit exceeded. Please try again later."
}Response headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312800
Retry-After: 1800The 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:
curl "https://klinky-api.fly.dev/api/v1/public/keys/rate-limit" \
-H "Authorization: Bearer <your-jwt-token>"Response:
{
"limit": 1000,
"remaining": 847,
"reset_at": "2024-01-15T15:00:00Z"
}From Response Headers
Monitor headers from any API response:
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:
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 False2. Implement Exponential Backoff
When rate limited, wait before retrying:
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
break3. Batch Operations
Minimize API calls by fetching data in batches:
# 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:
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:
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:
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:
// 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:
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:
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 createdTroubleshooting
Unexpected 429 Errors
If you're hitting rate limits unexpectedly:
- Check for retries — Ensure failed requests aren't retrying too aggressively
- Review integrations — Multiple services using the same API key
- Monitor usage — Track which endpoints consume the most requests
- 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:
- Wait for the next hour window
- Upgrade to a higher plan
- 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
- Error Handling — Handle 429 errors gracefully
- Analytics API — Efficiently fetch analytics data
- Links API — Create links with minimal API calls
Questions about rate limits? — We're here to help optimize your integration