buildmymcpserver/apps/api/src/lib/rate-limit.ts
Marco Sadjadi defb4186b4
All checks were successful
Deploy to Production / deploy (push) Successful in 52s
fix(quotas): tighten Team/Enterprise daily preview caps to stay profitable
The earlier caps (Team 150/day, Enterprise 1000/day) used Sonnet/Opus pricing
that put max-usage above the tier's monthly revenue — a Bot with a Team
subscription could out-cost €199 in Anthropic spend. Drop to 50/day Team
and 200/day Enterprise; both now keep ~55-65% margin even when maxed.

Pricing page Team feature line updated to match (150 -> 50). Build caps
loosened slightly less since the 24h cache TTL makes most builds cache-hits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 00:14:07 +02:00

63 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Plan } from '@bmm/llm';
import { getRedis } from './redis.js';
const DAY_SEC = 24 * 60 * 60;
function todayKey(): string {
return new Date().toISOString().slice(0, 10);
}
export interface RateLimitResult {
ok: boolean;
remaining: number;
resetIn: number;
}
/**
* Daily counter via Redis INCR. Atomic — no race window between read & write.
* First INCR (count === 1) sets the TTL so the key auto-rolls at midnight UTC.
*/
export async function checkDailyLimit(
scope: string,
userId: string,
max: number,
): Promise<RateLimitResult> {
const key = `ratelimit:${scope}:${userId}:${todayKey()}`;
const redis = getRedis();
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, DAY_SEC);
const ttl = await redis.ttl(key);
return {
ok: count <= max,
remaining: Math.max(0, max - count),
resetIn: ttl > 0 ? ttl : DAY_SEC,
};
}
// Per-tier daily limits on the two LLM-priced actions.
// Preview = ~€0.002-0.115/call (model-dependent) · Build = ~€0.005-0.22/call.
//
// Caps are set so that even a max-usage power-user stays profitable at the
// tier's price point. Critical for Team/Enterprise where Sonnet/Opus tokens
// add up fast — a runaway Bot with a Team subscription could otherwise
// out-cost the €199 monthly revenue. Math (max-case):
// Pro: 40 prev × €0.020 × 30 = €24/mo → margin €25 (~50%)
// Team: 50 prev × €0.058 × 30 = €87/mo → margin €112 (~56%)
// Enterprise: 200 prev × €0.060 × 30 = €360/mo → margin €639 (~64%)
// Build caps are looser because the 24h cache TTL means most builds are
// cache-HITS (no LLM call) — the cap is mostly about runner-port / hosting
// budget, not token cost.
export const PREVIEW_DAILY_LIMIT: Record<Plan, number> = {
hobby: 5,
pro: 40,
team: 50,
enterprise: 200,
};
export const BUILD_DAILY_LIMIT: Record<Plan, number> = {
hobby: 3,
pro: 20,
team: 30,
enterprise: 100,
};