> ## Documentation Index
> Fetch the complete documentation index at: https://vlyai-1c28d863.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limiting

> Per-user, per-IP, per-endpoint patterns for rate limiting. Defending against abuse and runaway bills.

Rate limit any endpoint exposed to the public — login, signup, password reset, forms. Without limits, abuse hits hard and fast.

## Three axes

<CardGroup cols={3}>
  <Card title="Per IP" icon="globe">
    Limit by source address. Best for unauthenticated endpoints.
  </Card>

  <Card title="Per user" icon="user">
    Limit by user ID. Best for authenticated endpoints.
  </Card>

  <Card title="Per workspace / per resource" icon="folder">
    Limit by tenant. Best for shared rate budgets.
  </Card>
</CardGroup>

Combine: rate limit signup at 5 per IP per hour AND 10 per email per day.

## Pattern in Convex

```typescript convex/rateLimit.ts theme={null}
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const checkAndIncrement = mutation({
  args: {
    key:    v.string(),     // e.g., "signup:1.2.3.4" or "login:user@example.com"
    limit:  v.number(),     // e.g., 5
    windowMs: v.number(),   // e.g., 60_000
  },
  handler: async (ctx, { key, limit, windowMs }) => {
    const now    = Date.now();
    const cutoff = now - windowMs;

    const recent = await ctx.db
      .query("rateLimitEvents")
      .withIndex("by_key", q => q.eq("key", key))
      .filter(q => q.gte(q.field("at"), cutoff))
      .collect();

    if (recent.length >= limit) {
      throw new ConvexError({ code: "rate_limited", message: "Too many requests" });
    }

    await ctx.db.insert("rateLimitEvents", { key, at: now });
  },
});
```

Call it from any mutation you want to limit:

```typescript theme={null}
export const signup = mutation({
  args: { email: v.string(), password: v.string() },
  handler: async (ctx, { email, password }) => {
    await ctx.runMutation(api.rateLimit.checkAndIncrement, {
      key:      `signup:${email}`,
      limit:    5,
      windowMs: 60 * 60 * 1000,  // 5 per hour
    });

    // ... actual signup
  },
});
```

A scheduled cleanup deletes events older than the longest window.

## Where to limit

| Endpoint | Limit |
| - | - |
| Login | 10 per IP per minute, 20 per user per hour |
| Signup | 5 per IP per hour |
| Password reset | 3 per email per hour |
| Comments / posts | 60 per user per minute |
| API endpoints | Per-key (handled by [API rate limits](/api-reference/rate-limits)) |
| Form submissions | 10 per IP per minute |

## Beyond per-endpoint

For broader abuse patterns:

* **CAPTCHA / hCaptcha** on signup and login.
* **Email verification** before letting users do anything destructive.
* **Cost-aware rate limits**: charge users credits for expensive operations (LLM calls, image generation), so abuse is self-limiting.

## Related

<CardGroup cols={3}>
  <Card title="Auth best practices" icon="lock" href="/security/auth-best-practices">
    Rate limiting auth endpoints.
  </Card>

  <Card title="Security overview" icon="shield" href="/security/overview">
    Where rate limiting fits.
  </Card>

  <Card title="API rate limits" icon="gauge" href="/api-reference/rate-limits">
    The vly API's built-in limits.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.