Rate Limiting
Rate limits protect the platform and ensure fair usage across all accounts. Limits are applied at two levels: a global per-plan ceiling and endpoint-specific limits for sensitive operations.
Global Rate Limits by Plan
Every API request counts against your plan's global rate limit:
| Plan | Requests/Minute |
|---|---|
| Free | 60 |
| Starter | 300 |
| Pro | 600 |
| Scale | 1,200 |
| Enterprise | Unlimited |
These limits are applied per account across all API keys and projects.
Endpoint-Specific Rate Limits
In addition to the global plan limit, certain endpoint categories have their own limits:
Notification & Platform Endpoints
| Category | Limit | Applies To |
|---|---|---|
| Dashboard Auth | 10 req/min | Dashboard login, register, password reset |
| Sensitive | 5 req/min | Key rotation, application create/delete, OAuth provider config |
| Standard API | 100 req/min | Email/push/SMS send, webhook CRUD, subscriber upsert |
| Read-only | 200 req/min | List/get endpoints (emails, push, SMS, subscribers, users) |
Auth-as-a-Service Endpoints
Auth-as-a-Service rate limits are plan-aware and split into two tiers based on the operation type:
Credential Operations
These are brute-force-sensitive operations (registration, login, password resets). They have lower limits to prevent credential stuffing:
| Plan | Limit |
|---|---|
| Free | 20 req/min |
| Starter | 60 req/min |
| Pro | 120 req/min |
| Scale | 300 req/min |
| Enterprise | Unlimited |
Endpoints in this tier:
POST /v1/auth/users/register— Register end userPOST /v1/auth/users/login— Login end userDELETE /v1/auth/users/me— Delete accountPOST /v1/auth/magic-link/send— Send magic linkPOST /v1/auth/forgot-password— Request password resetPOST /v1/auth/reset-password— Reset passwordPOST /v1/auth/validate-reset-token— Validate reset tokenPOST /v1/auth/verify-email/send— Send verification emailPOST /v1/auth/verify-email/resend— Resend verificationPOST /v1/auth/phone/register/send— Send phone OTP (register)POST /v1/auth/phone/login/send— Send phone OTP (login)
Token Operations
These are high-throughput operations your backend calls frequently (token refresh, verification, OAuth callbacks). They have much higher limits:
| Plan | Limit |
|---|---|
| Free | 120 req/min |
| Starter | 600 req/min |
| Pro | 1,200 req/min |
| Scale | 3,000 req/min |
| Enterprise | Unlimited |
Endpoints in this tier:
POST /v1/auth/sessions/refresh— Refresh access tokenPOST /v1/auth/magic-link/verify— Verify magic linkPOST /v1/auth/verify-email/confirm— Confirm email verificationPOST /v1/auth/oauth/:provider/callback— OAuth callbackPOST /v1/auth/oauth/tokens/:provider/refresh— Refresh OAuth tokensPOST /v1/auth/phone/register/verify— Verify phone OTP (register)POST /v1/auth/phone/login/verify— Verify phone OTP (login)POST /v1/auth/webauthn/registration/start— Start WebAuthn registrationPOST /v1/auth/webauthn/registration/verify— Verify WebAuthn registrationPOST /v1/auth/webauthn/authentication/start— Start WebAuthn authenticationPOST /v1/auth/webauthn/authentication/verify— Verify WebAuthn authentication
Unrestricted Auth Endpoints
These read-only and session management endpoints only count against your global plan limit:
GET /v1/auth/password-requirements— Get password policyGET /v1/auth/users/me— Get current userPATCH /v1/auth/users/me— Update current userGET /v1/auth/sessions— List sessionsPOST /v1/auth/sessions/revoke— Logout (single)POST /v1/auth/sessions/revoke-all— Logout allGET /v1/auth/oauth/*— OAuth provider info, connectionsGET /v1/auth/webauthn/*— WebAuthn credentials, status
If your app verifies auth on every API request by refreshing tokens, the token operations tier is sized for that pattern. On the Pro plan, you get 1,200 token operations per minute — enough for 20 active users each making a request per second.
SMS Per-Phone Limits
SMS endpoints have additional per-phone-number rate limits to prevent abuse:
| Operation | Limit |
|---|---|
| Send OTP | 3 per 15 minutes per phone |
| Verify OTP | 10 attempts per 15 minutes per phone |
| Send SMS | 10 per hour per phone |
Channel Quotas by Plan
Free Plan
| Channel | Daily Limit | Monthly Limit |
|---|---|---|
| 250 | 1,000 | |
| Push | 250 | 1,000 |
| SMS | 500 | 5,000 |
| In-App | 500 | 5,000 |
Free plan limits are hard-blocked — exceeding them returns a quota_exceeded error. Free plan limits are aggregated across all accounts owned by the same user.
Paid Plans (Starter, Pro, Scale)
Paid plans use monthly quotas based on your subscription. Daily limits are removed. Overage is tracked and billed separately.
Auth-as-a-Service Quotas
| Quota | Free | Starter | Pro | Scale | Enterprise |
|---|---|---|---|---|---|
| Monthly Active Users (MAU) | 1,000 | 10,000 | 25,000 | 100,000 | Unlimited |
| Credential ops/min | 20 | 60 | 120 | 300 | Unlimited |
| Token ops/min | 120 | 600 | 1,200 | 3,000 | Unlimited |
| Auth applications | 1 | 5 | 10 | Unlimited | Unlimited |
| Test environment users | 10 | 10 | 10 | 10 | 10 |
Test environment users (za_test_* keys) do not count toward MAU or auth request quotas. See Test Mode for details.
Rate Limit Headers
Every response includes rate limit information:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 595
X-RateLimit-Reset: 1705312800
X-RateLimit-Plan: pro
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window |
X-RateLimit-Remaining | Remaining requests in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-RateLimit-Plan | Your account's plan |
Error Responses
Rate Limit Exceeded (429)
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 60 seconds."
},
"meta": {
"request_id": "...",
"retry_after": 60
}
}
Channel Quota Exceeded (429)
{
"error": {
"code": "quota_exceeded",
"message": "Monthly email quota exceeded (1000 emails for free plan). Upgrade your plan to send more.",
"limit": 1000,
"current": 1000
}
}
Daily Limit Exceeded (429)
{
"error": {
"code": "daily_limit_exceeded",
"message": "Daily email limit exceeded (250 emails/day for free plan). Try again tomorrow or upgrade your plan.",
"limit": 250,
"current": 250
}
}
Test Environment Limit (403)
{
"error": {
"code": "test_environment_limit",
"message": "Test environment is limited to 10 users. Current: 10. Use live keys for production.",
"limit": 10,
"current": 10
}
}
SMS Rate Limit (429)
{
"error": {
"code": "otp_send_rate_limit_exceeded",
"message": "Too many OTP requests for this phone number. Try again in 15 minutes."
}
}
Retry Logic
Implement exponential backoff when you receive a 429:
async function sendWithRetry(fn: () => Promise<any>, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error: any) {
if (error.status === 429) {
const retryAfter = error.retryAfter || Math.pow(2, i) * 1000;
await new Promise(r => setTimeout(r, retryAfter));
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}
Email Warmup
New accounts start with email sending limits that gradually increase as your sending reputation is established. This protects deliverability for all Zyphr users.
Warmup Stages
| Stage | Daily Limit | Minimum Duration |
|---|---|---|
| 1 — Initial | 50 emails | 3 days |
| 2 — Building | 200 emails | 4 days |
| 3 — Scaling | 1,000 emails | 7 days |
| 4 — Completed | Plan limit | — |
Total minimum warmup period: 14 days
Stage Progression Requirements
To advance to the next stage, your account must meet:
- Bounce rate ≤ 5%
- Complaint rate ≤ 0.1%
- Minimum duration completed in current stage
- Minimum 50% utilization of the daily limit (when metrics are available)
If your bounce or complaint rate exceeds thresholds, stage progression is blocked until metrics improve.
Warmup Error Response
{
"error": {
"code": "DAILY_LIMIT_EXCEEDED",
"message": "Daily warmup limit of 50 emails reached. Your account is in warmup stage 1.",
"daily_limit": 50,
"daily_sent": 50,
"warmup_stage": 1
}
}
Warmup Tips
- Start with your most engaged recipients to build positive reputation signals
- Gradually increase volume each day within your stage limit
- Monitor bounce and complaint rates in the Dashboard
- Test mode (
zy_test_*keys) does not count toward warmup limits
What Bypasses Rate Limits
| Scenario | Rate Limits Applied? |
|---|---|
| Dashboard (JWT auth) | No — first-party users bypass rate limits |
API key (zy_live_*, zy_test_*) | Yes — global plan + endpoint-specific |
Application key (za_live_*, za_test_*) | Yes — global plan + auth endpoint limit |
| Enterprise plan | No — unlimited requests |
Test mode notifications (zy_test_*) | Yes — rate limits still apply, but quotas don't count |
Test mode auth (za_test_*) | Yes — rate limits still apply, but MAU/auth quotas don't count |
Best Practices
- Use batch endpoints (
/v1/emails/batch,/v1/inbox/send/batch) to reduce request count - Implement exponential backoff for retry logic
- Monitor
X-RateLimit-Remainingheaders to throttle proactively - Use test mode keys during development to avoid hitting quotas
- Add delays in scripts — if seeding test data, don't blast registration endpoints faster than 10 req/min
- Contact support for Enterprise plan with custom limits