Files
formbricks/docs/self-hosting/advanced/rate-limiting.mdx
T
2026-05-05 12:47:02 +05:30

147 lines
10 KiB
Plaintext

---
title: "Rate Limiting"
description: "Current request rate limits in Formbricks"
icon: "timer"
---
Formbricks applies request rate limits to protect against abuse and keep API usage fair.
Starting with Formbricks v5, rate limiting is split across two layers:
- Envoy Gateway for public and API-key routes that can be enforced at ingress
- The application server for remaining session-authenticated routes, server actions, and other flows Envoy does
not currently cover
<Warning>
Formbricks v5 removes application-level rate limiting for several routes that are now expected to be
protected by Envoy Gateway. If you self-host Formbricks without Envoy or an equivalent edge rate limiter,
those routes will no longer be throttled by the application server after upgrading.
</Warning>
<Info>
For the full self-hosted upgrade checklist, use this page together with the
[v5 migration guide](/self-hosting/advanced/migration#v5).
</Info>
Rate limits are scoped by identifier, depending on the endpoint and enforcement layer:
- IP hash (for unauthenticated/client-side routes and public actions)
- API key ID (for Envoy-managed and app-managed authenticated API calls)
- User ID (for authenticated session-based calls and server actions)
- Organization ID (for follow-up email dispatch)
When a limit is exceeded, the API returns `429 Too Many Requests`.
## v5 Migration Note
Before upgrading to Formbricks v5:
- deploy Envoy Gateway or an equivalent edge rate limiter for the covered routes below
- keep application Redis/Valkey enabled for the remaining app-enforced limits
- expect covered routes to emit gateway `429`s instead of the legacy app JSON `429`s
For the current source of truth on covered routes and thresholds, use this page together with your deployment
configuration and the [v5 migration guide](/self-hosting/advanced/migration#v5).
## Envoy-Managed Limits
These limits are expected to be enforced at the gateway layer in Formbricks v5 and later:
| **Route Group** | **Limit** | **Window** | **Identifier** |
| ------------------------------------------------------------------------ | ------------ | ---------- | -------------- |
| `POST /api/auth/callback/credentials` | 40 requests | 1 hour | IP hash |
| `POST /api/auth/callback/token` | 10 requests | 1 hour | IP hash |
| `GET, POST, PUT, PATCH, DELETE /api/v1/management/*` (API key auth only) | 100 requests | 1 minute | API key ID |
| `POST /api/v1/management/storage` (API key auth only) | 5 requests | 1 minute | API key ID |
| `GET, POST, PUT, PATCH, DELETE /api/v1/webhooks/*` (API key auth only) | 100 requests | 1 minute | API key ID |
| `GET /api/v1/client/[workspaceId]/environment` | 100 requests | 1 minute | IP hash |
| `POST /api/v1/client/[workspaceId]/responses` | 100 requests | 1 minute | IP hash |
| `PUT /api/v1/client/[workspaceId]/responses/[responseId]` | 100 requests | 1 minute | IP hash |
| `POST /api/v1/client/[workspaceId]/displays` | 100 requests | 1 minute | IP hash |
| `POST /api/v1/client/[workspaceId]/user` | 100 requests | 1 minute | IP hash |
| `POST /api/v1/client/[workspaceId]/storage` | 5 requests | 1 minute | IP hash |
| `POST /api/v2/client/[workspaceId]/responses` | 100 requests | 1 minute | IP hash |
| `PUT /api/v2/client/[workspaceId]/responses/[responseId]` | 100 requests | 1 minute | IP hash |
| `POST /api/v2/client/[workspaceId]/displays` | 100 requests | 1 minute | IP hash |
| `POST /api/v2/client/[workspaceId]/storage` | 5 requests | 1 minute | IP hash |
| `DELETE /storage/[workspaceId]/public/[fileName]` (API key auth only) | 5 requests | 1 minute | API key ID |
| `DELETE /storage/[workspaceId]/private/[fileName]` (API key auth only) | 5 requests | 1 minute | API key ID |
Session-authenticated `/api/v1/management/*`, `/api/v1/management/me`, `/api/v1/management/storage`, and
`DELETE /storage/...` requests are **not** covered by the current Envoy policies and remain app-enforced.
## App-Enforced Limits
These are the limits that still run inside the Formbricks application server:
| **Config** | **Limit** | **Window** | **Identifier** | **Used For** |
| ----------------------------- | ------------ | ---------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth.signup` | 30 requests | 60 minutes | IP hash | Signup server action |
| `auth.forgotPassword` | 5 requests | 60 minutes | IP hash | Forgot password server action |
| `auth.verifyEmail` | 10 requests | 60 minutes | IP hash | Resend verification server action |
| `api.v1` | 100 requests | 1 minute | Session user ID | Session-authenticated v1 management routes, `/api/v1/management/me`, and `/api/v1/integrations/*` |
| `api.v2` | 100 requests | 1 minute | API key ID | Authenticated v2 API wrapper outside the Envoy-managed `/api/v2/client/*` subset |
| `api.v3` | 100 requests | 1 minute | API key ID or session user ID | v3 API wrapper |
| `api.client` | 100 requests | 1 minute | IP hash | Uncovered client routes such as `GET /api/v1/client/og`, `GET /api/v2/client/[workspaceId]/environment`, and `POST /api/v2/client/[workspaceId]/user` |
| `storage.upload` | 5 requests | 1 minute | Session user ID | Session-authenticated `POST /api/v1/management/storage` |
| `storage.delete` | 5 requests | 1 minute | Session user ID | Session-authenticated `DELETE /storage/[workspaceId]/[accessType]/[fileName]` |
| `actions.emailUpdate` | 3 requests | 60 minutes | User ID | Profile email update action |
| `actions.surveyFollowUp` | 50 requests | 60 minutes | Organization ID | Survey follow-up email processing |
| `actions.sendLinkSurveyEmail` | 10 requests | 60 minutes | IP hash | Link survey email send action |
| `actions.licenseRecheck` | 5 requests | 1 minute | User ID | Enterprise license recheck action |
## Explicit Envoy Exclusions
The current Envoy policy set explicitly excludes these routes:
- `GET /api/v1/client/og` (still covered by the app-level `api.client` limiter)
- `GET /api/v2/health` (not rate-limited)
- `OPTIONS` requests (not rate-limited)
## 429 Response Shape
Application-generated v1 `429`s return:
```json
{
"code": "too_many_requests",
"details": {},
"message": "Maximum number of requests reached. Please try again later."
}
```
Application-generated v2/v3 `429`s return:
```json
{
"error": {
"code": 429,
"message": "Too Many Requests"
}
}
```
Envoy-generated `429`s are gateway responses and should include an `x-envoy-ratelimited` header. Their exact body
shape is not the same stability contract as the in-app JSON responses above.
## Disabling Rate Limiting
For self-hosters, rate limiting can be disabled if necessary. We strongly recommend keeping it enabled in production.
Set:
```bash
RATE_LIMITING_DISABLED=1
```
After changing this value, restart the server.
This setting disables only the **application-level** limiter. It does **not** disable Envoy rate-limit policies.
## Operational Notes
- Redis/Valkey is required for the application-level limiter (`REDIS_URL`).
- If you deploy Envoy rate limiting, use a dedicated Redis/Valkey backend for Envoy instead of sharing the app cache.
- If application Redis is unavailable at runtime, app rate-limiter checks currently fail open (requests are allowed through without enforcement).
- Authentication failure audit logging uses a separate throttle (`shouldLogAuthFailure()`) and is intentionally **fail-closed**: when Redis is unavailable or errors occur, audit log entries are **skipped entirely** rather than written without throttle control. This prevents spam while preserving the hash-integrity chain required for compliance. In other words, if Redis is down, no authentication-failure audit logs will be recorded—requests themselves are still allowed (fail-open rate limiting above), but the audit trail for those failures will not be written.