Projects BibleWeb AI Tools Daily rate limiting
Done

Daily rate limiting

Area: AI Tools Milestone: v3

Context

Problem: AI API calls cost money. Without limits, a single user could run up significant costs. BibleWeb needs to control AI usage per user while still providing a generous experience for paying customers.

Solution: Two-layer rate limiting: a per-minute spam guard (20 req/min) and a daily quota tied to the user's subscription tier. Beta codes grant temporary access with specific limits.

Not included: Per-query cost tracking or billing (that's the Cost Tracking feature). This is about limiting the number of requests, not tracking their cost.

Functional

Tier limits:

Tier Daily Limit Access
Free 0 (blocked) No AI access
Beta Low 5/day Beta code: "beta" or "uitproberen"
Beta High 20/day Beta code: "vriend" or "friend"
Admin 999/day Admin users

User experience:

  • User asks a question → counter increments
  • When limit reached → 429 response with message explaining the limit
  • Limit resets every 24 hours (86,400,000ms window)

Per-minute guard: 20 requests per minute per IP — prevents rapid-fire abuse regardless of tier.

Edge cases:

  • Rate limit keyed by ask_daily:${userId or IP} — authenticated users track by user ID, anonymous by IP
  • In-memory Map<string, { count, resetAt }>, cleaned up every 60 seconds
  • Beta codes checked against session app_metadata.beta_tier or cookie fallback

UX & Design

Error display: When rate limited, the chat shows an error message explaining the daily limit and when it resets.

No visible counter — users don't see remaining questions (could be added later).

Technical

Rate limiter (apps/web/src/lib/server/rate-limit.ts):

  • In-memory Map<string, { count, resetAt }>
  • Two checks in /api/ask:
    1. Per-minute: 20 req/min per IP
    2. Daily: tier-specific limit per user

Tier limits (apps/web/src/lib/server/beta-codes.ts):

  • getAiDailyLimit(betaTier) returns the numeric limit
  • Beta codes: 'vriend'/'friend'beta_high, 'beta'/'uitproberen'beta_low

Cleanup: setInterval every 60 seconds removes expired entries from the Map.

Files:

  • apps/web/src/lib/server/rate-limit.ts — rate limiter implementation
  • apps/web/src/lib/server/beta-codes.ts — tier limits + beta code mapping
  • apps/web/src/routes/api/ask/+server.ts — applies both rate limit checks

Status

Current: DONE Milestone: v3 Priority: Core — essential for cost control

History:

  • Beta code system implemented for early testing (friends & family access)
  • In-memory rate limiting chosen for simplicity — would need Redis for multi-instance deployment

Dependencies:

  • Requires: AI chat (DONE), auth system (DONE)

Screenshots

Feature screenshot