Appearance
Rate Limits
Fyralynx enforces rate limits at two layers: on the edge redirector (per-IP and per-creator) and on the REST API (per-API-key). This guide documents every limit, explains the response format, and provides retry strategies and patterns for high-throughput integrations.
API Rate Limits (REST API)
Rate limits are applied per API key. The limit tier is determined by the plan associated with the key.
| Plan | Requests / minute | Burst headroom |
|---|---|---|
| Publisher | 60 | Up to 90 in a 10-second window |
| Enterprise | 600 | Up to 900 in a 10-second window |
Rate limits are measured in a sliding 60-second window. A new request is allowed if fewer than the limit's count of requests have been made in the past 60 seconds from the same API key.
Endpoint-specific limits
Some endpoints have additional per-minute limits regardless of plan tier:
| Endpoint | Additional limit |
|---|---|
POST /links/bulk | 10 bulk requests per minute (Enterprise) |
POST /route | 120 requests per minute (Publisher), 600/min (Enterprise) |
POST /route has a higher limit than POST /links because it does not persist data or count clicks. It is the recommended endpoint for high-frequency routing lookups in automated pipelines.
Rate Limit Response
When you exceed the rate limit, the API returns HTTP 429 Too Many Requests:
http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 14
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1747660814json
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit of 60 requests/minute exceeded. Retry after 14 seconds.",
"requestId": "01HX4Z9Y3ABCDEF1234567890AB"
}Rate Limit Headers
Every API response includes rate limit headers (not just 429 responses):
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed per 60-second window for your key. |
X-RateLimit-Remaining | Requests remaining in the current window. |
X-RateLimit-Reset | Unix timestamp when the current window resets. |
Retry-After | Seconds until you can safely retry (only on 429 responses). |
Edge Redirector Rate Limits
The Cloudflare Workers edge redirector (go.fyralynx.com) has its own rate limits applied per IP address and per creator ID. These limits exist to protect against abuse and bot traffic, not to limit legitimate affiliate traffic.
Per-IP limit
| Limit | Value | Behaviour on exceed |
|---|---|---|
| Requests per IP per minute | 200 | HTTP 429; original URL served unchanged |
Legitimate affiliate traffic from real visitors never approaches this limit. If you see 429 from the edge redirector in analytics, it almost always indicates bot or scraper traffic targeting your links.
Per-creator limits
| Plan | Requests / minute to edge | Behaviour on exceed |
|---|---|---|
| Free | 200 | 429; original URL |
| Creator | 200 | 429; original URL |
| Pro | 1,000 | 429; original URL |
| Publisher | 5,000 | 429; original URL |
| Enterprise | 10,000 | 429; original URL |
Important: When the edge redirector returns 429, it always redirects to the original URL unchanged. Real end-users never see an error page - they are seamlessly sent to the merchant. The rate limiting is transparent to your audience. The click is not counted toward your quota.
Retry Strategy
For API integrations, implement an exponential backoff with jitter strategy to handle 429 responses gracefully:
TypeScript / JavaScript
typescript
const FYRALYNX_BASE_URL = 'https://api.fyralynx.com/v1';
interface RetryConfig {
maxRetries: number;
baseDelayMs: number;
maxDelayMs: number;
}
const DEFAULT_RETRY_CONFIG: RetryConfig = {
maxRetries: 4,
baseDelayMs: 1000,
maxDelayMs: 30000,
};
async function fyralynxRequest<T>(
path: string,
init: RequestInit,
config: RetryConfig = DEFAULT_RETRY_CONFIG,
): Promise<T> {
let attempt = 0;
while (true) {
const response = await fetch(`${FYRALYNX_BASE_URL}${path}`, {
...init,
headers: {
'Authorization': `Bearer ${process.env.FYRALYNX_API_KEY}`,
'Content-Type': 'application/json',
...init.headers,
},
});
if (response.status !== 429) {
if (!response.ok) {
const error = await response.json();
throw new Error(`Fyralynx API ${response.status}: ${error.code} - ${error.message}`);
}
return response.json() as Promise<T>;
}
// 429 - compute retry delay
attempt++;
if (attempt > config.maxRetries) {
throw new Error(`Fyralynx rate limit: exceeded ${config.maxRetries} retries`);
}
const retryAfterHeader = response.headers.get('Retry-After');
const retryAfterSecs = retryAfterHeader ? parseInt(retryAfterHeader, 10) : null;
const backoffMs = Math.min(
config.baseDelayMs * Math.pow(2, attempt - 1), // exponential
config.maxDelayMs,
);
const jitterMs = Math.random() * backoffMs * 0.2; // ±20% jitter
const delayMs = retryAfterSecs != null
? retryAfterSecs * 1000 // honour Retry-After if present
: backoffMs + jitterMs;
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}Python
python
import time
import random
import httpx
from typing import Any
def fyralynx_request(
method: str,
path: str,
*,
max_retries: int = 4,
base_delay: float = 1.0,
max_delay: float = 30.0,
**kwargs: Any,
) -> dict:
url = f"https://api.fyralynx.com/v1{path}"
headers = {
"Authorization": f"Bearer {FYRALYNX_API_KEY}",
"Content-Type": "application/json",
**kwargs.pop("headers", {}),
}
attempt = 0
while True:
response = httpx.request(method, url, headers=headers, **kwargs)
if response.status_code != 429:
response.raise_for_status()
return response.json()
attempt += 1
if attempt > max_retries:
raise RuntimeError(f"Fyralynx rate limit: exceeded {max_retries} retries")
retry_after = response.headers.get("Retry-After")
if retry_after:
delay = float(retry_after)
else:
backoff = min(base_delay * (2 ** (attempt - 1)), max_delay)
jitter = random.uniform(0, backoff * 0.2)
delay = backoff + jitter
time.sleep(delay)Bulk Operations and Rate Limits
POST /links/bulk (Enterprise only) lets you create up to 100 links per request. Use it to minimise round-trips when creating links at scale.
Effective throughput with bulk:
| Tier | Rate limit | Links/request | Max links/minute |
|---|---|---|---|
| Publisher | 60 req/min | 1 (single create) | 60 |
| Enterprise | 600 req/min (bulk: 10/min) | 100 | 1,000 |
For bulk workloads, the POST /links/bulk endpoint is ~16× more efficient than individual POST /links calls.
Recommended pattern for large-scale link creation:
typescript
async function createLinksInBatches(urls: string[]): Promise<Link[]> {
const BATCH_SIZE = 100; // max per bulk request
const results: Link[] = [];
for (let i = 0; i < urls.length; i += BATCH_SIZE) {
const batch = urls.slice(i, i + BATCH_SIZE);
const response = await fyralynxRequest<BulkCreateResponse>('/links/bulk', {
method: 'POST',
body: JSON.stringify({
links: batch.map(url => ({ url, preset: 'long-form' })),
}),
});
results.push(
...response.data.results
.filter(r => r.link !== undefined)
.map(r => r.link!),
);
// Brief pause between batches to stay comfortably under limit
if (i + BATCH_SIZE < urls.length) {
await new Promise(resolve => setTimeout(resolve, 6100)); // ~10 req/min
}
}
return results;
}Rate Limiting for WordPress Sites
The WordPress plugin automatically caches routed links in post meta (7-day TTL by default). This means API calls per visitor are nearly zero on cached pages.
However, on first render of uncached posts with many affiliate links, multiple API calls may fire concurrently. To prevent bursts:
- Enable page caching - WP Rocket, W3 Total Cache, or WP Super Cache. Each post is rendered once and cached.
- Use the WP-CLI warm command before a traffic spike:
wp fyralynx warm-cache. - Increase the link cache TTL to 14-30 days if your affiliate URLs are stable.
- Stagger post publishing - if you publish many posts at once, use a cron job to warm the cache over time.
Monitoring Rate Limit Usage
Via Response Headers
Every API response includes X-RateLimit-Remaining. When this value drops below 10, slow down:
typescript
function checkRateLimit(response: Response): void {
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') ?? '60', 10);
const resetAt = parseInt(response.headers.get('X-RateLimit-Reset') ?? '0', 10);
if (remaining < 10) {
const secsUntilReset = resetAt - Math.floor(Date.now() / 1000);
console.warn(`Rate limit low: ${remaining} remaining, resets in ${secsUntilReset}s`);
}
}Via Webhooks (Enterprise)
If you have webhooks configured, the account.usage_warning webhook fires at 80% and 100% of your monthly click quota (not the per-minute API rate limit). This is useful for alerting on billing overage risk, not real-time API throttling.
For real-time API rate limit monitoring, read the response headers.
Rate Limits vs Click Quota
These are two separate concepts:
| Concept | What it measures | Resets | Exceeding it |
|---|---|---|---|
| API rate limit | REST API requests per minute | Every 60 seconds | HTTP 429; retry with backoff |
| Click quota | Monthly routing clicks through the edge | 1st of each month | Free: hard stop; Paid: overage charges |
The monthly click quota is checked on the edge redirector per click served. The API rate limit is checked per API request. They are independent counters.
Increasing Your Limits
Publisher → Enterprise upgrade
Enterprise tier increases the API limit from 60/min to 600/min and enables bulk operations. Upgrade at fyralynx.com/pricing.
Custom Enterprise agreements
For integrations requiring > 600 requests/minute or > 100 links per bulk request, contact the Fyralynx enterprise sales team at enterprise@fyralynx.com. Custom rate limits are available under Enterprise contracts with SLA.
Edge Redirector - Bot Detection and Passthrough
The edge redirector uses Cloudflare's Bot Score to identify non-human traffic:
- Bot Score < 30: Request is silently passed through to the original URL. The routing engine does not run. The click is not counted.
- Bot Score ≥ 30: Normal routing logic applies.
This means bot/crawler traffic never consumes your monthly click quota and never triggers rate limits. Legitimate traffic is unaffected.
See Also
- Authentication - API key setup and security
- Getting Started - complete setup walkthrough
- Webhooks - real-time event delivery for Enterprise
- OpenAPI Specification - full API reference with response schemas