Skip to content

Rate Limiting Guide

Synapse enforces per-tenant, per-endpoint-group rate limits to ensure platform stability and fair usage. This guide covers how limits are applied, how to handle 429 responses, and best practices for high-volume integrations.


How It Works

Rate limits use a sliding window algorithm with a 1-minute window. Each request is counted against your workspace's allowance for its endpoint group. When the window is full, subsequent requests receive a 429 Too Many Requests response until older entries expire.

All API keys within the same workspace share the same rate-limit window. Creating multiple keys does not increase your limit.


Endpoint Groups

Every API endpoint belongs to one of three rate-limit groups:

GroupEndpointsWhy separate
eventsPOST /v1/events, POST /v1/events/batchEvent ingestion is the primary integration point and needs higher throughput
sendPOST /v1/sendDirect email sending is capped separately to prevent email flooding
generalAll other /v1/* endpointsCRUD operations, analytics, settings, reports

Limits by Plan

Requests per minute (RPM) per endpoint group:

PlanGeneralEventsSend
Free1001,000100
Starter1,0005,0001,000
Growth10,00050,00010,000
EnterpriseUnlimitedUnlimitedUnlimited
Note

If your plan is unknown or unrecognized, Free-tier limits apply as a safety fallback.


Rate Limit Headers

Every API response includes rate limit information in these headers:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in the current window1000
X-RateLimit-RemainingRequests remaining in the current window847
X-RateLimit-ResetUnix timestamp when the oldest entry in the window expires1746528900

On a 429 response, an additional header is included:

HeaderDescriptionExample
Retry-AfterSeconds until the next request should be attempted42

Example: Normal Response

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1746528900

Example: Rate Limited

HTTP/1.1 429 Too Many Requests
Retry-After: 42
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1746528900
Content-Type: application/json
json
{
"detail": "Rate limit exceeded",
"code": "rate_limit_exceeded"
}

Handling 429 Responses

Python -- Exponential Backoff

python
import time
import requests
 
def call_with_backoff(method, url, **kwargs):
max_retries = 5
for attempt in range(max_retries):
response = method(url, **kwargs)
 
if response.status_code != 429:
return response
 
# Use the server's Retry-After value
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
print(f"Rate limited. Retrying in {retry_after}s (attempt {attempt + 1})")
time.sleep(retry_after)
 
raise Exception("Rate limit exceeded after max retries")

JavaScript -- Exponential Backoff

javascript
async function callWithBackoff(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
 
if (response.status !== 429) {
return response;
}
 
const retryAfter = parseInt(
response.headers.get("Retry-After") || String(2 ** attempt)
);
console.log(`Rate limited. Retrying in ${retryAfter}s (attempt ${attempt + 1})`);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
}
 
throw new Error("Rate limit exceeded after max retries");
}
Tip

Always use the Retry-After header value instead of a fixed delay. The server calculates the optimal wait time based on your current sliding window state.


Proactive Throttling

Instead of reacting to 429 errors, you can proactively slow down by monitoring the X-RateLimit-Remaining header:

python
import time
 
def send_event(session, url, headers, event):
response = session.post(url, headers=headers, json=event)
 
remaining = int(response.headers.get("X-RateLimit-Remaining", 100))
if remaining < 50:
# Approaching limit -- slow down
time.sleep(0.1)
elif remaining < 10:
# Very close to limit -- pause longer
time.sleep(1.0)
 
return response

Event Ingestion Rate Limits

Event ingestion (POST /v1/events) has its own endpoint group with higher limits because it is the primary integration point between your application and Synapse.

For high-volume event producers:

  1. Use batch ingestion (POST /v1/events/batch) -- send up to 50 events per request, each request counts as 1 against the rate limit
  2. Use idempotency keys -- safe to retry without duplicate processing after a 429 error
  3. Spread load across time -- if possible, avoid bursting thousands of events in a single second

Monthly Plan Limits

Separate from request rate limits, each plan has monthly caps on total usage:

PlanMonthly EmailsMonthly EventsContactsAPI Keys
Free1,00010,0001,0002
Starter10,000100,00010,00010
Growth100,0001,000,000100,00050
EnterpriseUnlimitedUnlimitedUnlimitedUnlimited

When a monthly limit is reached, the relevant operation returns 403:

json
{
"detail": "Monthly email limit reached. Upgrade your plan or wait until the next billing period.",
"code": "plan_limit_reached"
}

Exempt Endpoints

The following paths are never rate-limited:

  • /health, /health/ready, /health/live -- health probes
  • /metrics -- application metrics
  • /docs, /openapi.json -- API documentation

Best Practices

  1. Monitor X-RateLimit-Remaining -- proactively slow down before hitting the limit
  2. Use the Retry-After header -- the server knows the optimal wait time
  3. Batch events -- use POST /v1/events/batch for high-volume ingestion
  4. Use idempotency keys -- safe retries after rate limit errors
  5. Use the SDK -- the Python and JavaScript SDKs handle rate limit retries automatically
  6. Contact support for Enterprise -- custom rate tiers are available

For the complete rate limit reference table, see Rate Limits Reference.