Skip to content

Rate Limits

Synapse enforces rate limits to ensure fair usage and platform stability. Limits vary by plan and can be overridden per API key.


Default Limits by Plan

PlanRequests / MinuteRequests / HourMonthly Emails
Free601,0001,000
Starter30010,00010,000
Growth1,00050,000100,000
EnterpriseCustomCustomUnlimited
Note

Rate limits apply per API key. If you have multiple keys, each has its own limit. JWT-authenticated dashboard requests share a separate per-user limit.


Rate Limit Headers

Every response includes rate limit information:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in the current window300
X-RateLimit-RemainingRequests remaining in the current window247
X-RateLimit-ResetUnix timestamp when the window resets1711901700
Retry-AfterSeconds until the next request is allowed (only on 429 responses)42

Example Response Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 247
X-RateLimit-Reset: 1711901700

429 Response

When you exceed the rate limit:

HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
json
{
"detail": "Rate limit exceeded. Retry after 42 seconds.",
"code": "rate_limit_exceeded"
}

Handling Rate Limits

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
 
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(retry_after)
 
raise Exception("Rate limit exceeded after max retries")

JavaScript

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)
);
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 knows the optimal wait time based on your current window state.


Endpoint-Specific Limits

Some endpoints have stricter limits independent of the per-key rate:

EndpointLimitReason
POST /v1/eventsSame as planPrimary ingestion endpoint
POST /v1/sendSame as planPrevents email flooding
POST /v1/contacts/bulk10 / minuteBatch operations are expensive
GET /v1/analytics/export5 / hourCSV exports run heavy queries
POST /v1/segments/{id}/evaluate30 / minuteEvaluation runs real-time queries

Per-Key Rate Limit Override

API keys can have custom rate limits that override the plan default:

json
{
"name": "High-Volume Ingestion Key",
"scope": "data",
"environment": "live",
"rate_limit_per_minute": 500
}

This is set during key creation or updated via the dashboard. The override cannot exceed the maximum for your plan tier.


Monthly Email Limits

Independent of request rate limits, each plan has a monthly email sending cap:

PlanMonthly EmailsWhat Counts
Free1,000Each sent or suppressed email log
Starter10,000Same
Growth100,000Same
EnterpriseUnlimited--

When the monthly limit is reached, email send requests return 403:

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

The counter resets on the first day of each billing period (or when subscription.renewed webhook fires).


Best Practices

  1. Monitor X-RateLimit-Remaining -- Proactively slow down before hitting the limit
  2. Use idempotency keys -- Safe retries after rate limit errors without duplicate processing
  3. Batch contacts, not events -- Use POST /v1/contacts/bulk for imports, but send events individually for real-time processing
  4. Use the SDK -- Both the Python and JavaScript SDKs handle rate limit retries automatically with exponential backoff
  5. Contact support for Enterprise -- If you need higher limits, custom rate tiers are available on Enterprise plans