diff --git a/apps/docs/content/docs/developers/api/rate-limits.mdx b/apps/docs/content/docs/developers/api/rate-limits.mdx index ecd50558e..e86dc0b1f 100644 --- a/apps/docs/content/docs/developers/api/rate-limits.mdx +++ b/apps/docs/content/docs/developers/api/rate-limits.mdx @@ -11,9 +11,14 @@ Documenso enforces rate limits on all API endpoints to ensure service stability. ## HTTP Rate Limits -**Limit:** 100 requests per minute per IP address +**Limit:** 1000 requests per minute per IP address **Response:** 429 Too Many Requests + + This is the global per-IP ceiling. Your organisation may have its own rate limits configured below + this value, in which case you can be rate-limited before reaching the global limit. + + ### Rate Limit Response ```json diff --git a/apps/docs/content/docs/developers/examples/common-workflows.mdx b/apps/docs/content/docs/developers/examples/common-workflows.mdx index fe7887d5b..e0447d7c4 100644 --- a/apps/docs/content/docs/developers/examples/common-workflows.mdx +++ b/apps/docs/content/docs/developers/examples/common-workflows.mdx @@ -472,7 +472,7 @@ Send the same document to multiple recipients in parallel. Useful for policy ack distributeDocument: true - Process in batches with a short delay to respect rate limits (e.g. 100 requests/minute) + Process in batches with a short delay to respect rate limits (e.g. 1000 requests/minute) @@ -638,8 +638,8 @@ done - The API allows 100 requests per minute. For large batches, implement rate limiting with delays - between requests to avoid hitting limits. + The API allows 1000 requests per minute (your organisation may have its own lower limit). For large + batches, implement rate limiting with delays between requests to avoid hitting limits. --- diff --git a/apps/docs/content/docs/developers/getting-started/first-api-call.mdx b/apps/docs/content/docs/developers/getting-started/first-api-call.mdx index 5ae0a6c67..665a90afd 100644 --- a/apps/docs/content/docs/developers/getting-started/first-api-call.mdx +++ b/apps/docs/content/docs/developers/getting-started/first-api-call.mdx @@ -483,7 +483,7 @@ The API returns standard HTTP status codes and JSON error responses: ### Handling Rate Limits -The API allows 100 requests per minute per IP address. When rate limited, wait at least 60 seconds before retrying: +The API allows 1000 requests per minute per IP address. Your organisation may have its own lower rate limits. When rate limited, wait at least 60 seconds before retrying: ```javascript async function fetchWithRetry(url, options, maxRetries = 3) { diff --git a/apps/docs/content/docs/policies/fair-use.mdx b/apps/docs/content/docs/policies/fair-use.mdx index 0c4de348d..98d94dd5f 100644 --- a/apps/docs/content/docs/policies/fair-use.mdx +++ b/apps/docs/content/docs/policies/fair-use.mdx @@ -41,12 +41,17 @@ When a limit is reached, requests return a `429 Too Many Requests` response with | Action | Limit | Window | | --- | --- | --- | -| API requests (v1 and v2) | 100 requests | 1 minute | +| API requests (v1 and v2) | 1000 requests | 1 minute | | File uploads | 20 requests | 1 minute | | AI features | 3 requests | 1 minute | Authentication endpoints (login, signup, password reset, etc.) are also rate-limited to protect against abuse. + + The API request limit above is the global per-IP ceiling. Individual organisations also have their + own rate limits, which may be configured below this value. + + Rate limits may vary by plan. Enterprise plans can include higher or custom limits. Contact [sales](https://documen.so/sales) for details. diff --git a/packages/app-tests/e2e/api/v1/organisation-rate-limits.spec.ts b/packages/app-tests/e2e/api/v1/organisation-rate-limits.spec.ts index d5e1fff6d..373f3738e 100644 --- a/packages/app-tests/e2e/api/v1/organisation-rate-limits.spec.ts +++ b/packages/app-tests/e2e/api/v1/organisation-rate-limits.spec.ts @@ -50,7 +50,7 @@ import type { Organisation, Team, User } from '@prisma/client'; * * --- GLOBAL LIMIT AWARENESS --- * apps/remix/server/router.ts applies a GLOBAL per-IP limiter to /api/v1/*: - * apiV1RateLimit = 100 requests / 1 minute (action `api.v1`, see rate-limits.ts). + * apiV1RateLimit = 1000 requests / 1 minute (action `api.v1`, see rate-limits.ts). * Every per-org limit/quota configured here is kept FAR below that ceiling (single * digits) and the suite runs serially so the shared-IP global bucket is never the * thing that trips. A global-limit 429 is shaped `{ error }` whereas an org-limit @@ -62,7 +62,7 @@ const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL(); const baseUrl = `${WEBAPP_BASE_URL}/api/v1`; // Run serially: all workers share one IP, and the global /api/v1 limiter is -// per-IP. Serial execution keeps the shared global bucket well under 100/min. +// per-IP. Serial execution keeps the shared global bucket well under 1000/min. test.describe.configure({ mode: 'serial' }); // This suite is only meaningful with real rate limiting enabled. CI sets the @@ -125,7 +125,7 @@ const setClaimLimits = async (team: Team, limits: ClaimLimits) => { * GLOBAL /api/v1 IP bucket so a fresh scenario starts from zero. * * - The org windowed limiter keys its rows `ip:org:`. - * - The GLOBAL limiter (apps/remix/server/router.ts -> apiV1RateLimit, 100/min + * - The GLOBAL limiter (apps/remix/server/router.ts -> apiV1RateLimit, 1000/min * per IP, action `api.v1`) is shared by EVERY v1 request from this test client. * Across the suite (and especially across repeated local runs within the same * minute) that shared bucket would otherwise fill up and trip BEFORE the org diff --git a/packages/app-tests/e2e/api/v2/organisation-rate-limits.spec.ts b/packages/app-tests/e2e/api/v2/organisation-rate-limits.spec.ts index df261eb08..d64ab28c2 100644 --- a/packages/app-tests/e2e/api/v2/organisation-rate-limits.spec.ts +++ b/packages/app-tests/e2e/api/v2/organisation-rate-limits.spec.ts @@ -37,7 +37,7 @@ import type { Organisation, Team, User } from '@prisma/client'; * * --- GLOBAL LIMIT AWARENESS --- * apps/remix/server/router.ts applies a GLOBAL per-IP limiter to /api/v2/*: - * apiV2RateLimit = 100 requests / 1 minute (see rate-limits.ts). + * apiV2RateLimit = 1000 requests / 1 minute (see rate-limits.ts). * Every per-org limit/quota configured here is kept FAR below that ceiling (single * digits) and the suite runs serially so the shared-IP global bucket is never the * thing that trips. A global-limit 429 is shaped `{ error }` whereas an org-limit @@ -49,7 +49,7 @@ const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL(); const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`; // Run serially: all workers share one IP, and the global /api/v2 limiter is -// per-IP. Serial execution keeps the shared global bucket well under 100/min. +// per-IP. Serial execution keeps the shared global bucket well under 1000/min. test.describe.configure({ mode: 'serial' }); // This suite is only meaningful with real rate limiting enabled. CI sets the diff --git a/packages/lib/server-only/rate-limit/rate-limits.ts b/packages/lib/server-only/rate-limit/rate-limits.ts index 46233354c..002cccd6e 100644 --- a/packages/lib/server-only/rate-limit/rate-limits.ts +++ b/packages/lib/server-only/rate-limit/rate-limits.ts @@ -84,19 +84,19 @@ export const syncSubscriptionRateLimit = createRateLimit({ export const apiV1RateLimit = createRateLimit({ action: 'api.v1', - max: 100, + max: 1000, window: '1m', }); export const apiV2RateLimit = createRateLimit({ action: 'api.v2', - max: 100, + max: 1000, window: '1m', }); export const apiTrpcRateLimit = createRateLimit({ action: 'api.trpc', - max: 100, + max: 1000, window: '1m', });