Skip to content

Limits

Two kinds: the ones we chose, and the ones rented from somebody else. The second kind has shaped real decisions in this project, so this is the page to read before deciding something, not after.

External figures checked on 2026-08-21. Free-tier terms change; the numbers below are a starting point, not a contract.

The ones we chose

Limit Value Where
Finished vlog length 120 s default_max_total_seconds
Single take 20 s default_clip_max_seconds
Raw footage allowed 1.5× the finished length recording_allowance_factor
Premiere 24 h premiere_hours
Strikes before removal 3 strikes_before_removal
Cadence 3, 5 or 7 days Chosen per circle
Upload, whole vlog 200 MB max_upload_bytes
Upload, one raw take 100 MB max_clip_bytes

The allowance being larger than the result is deliberate: recording more than fits is expected, and the edit trims the last take and says what did not make it.

Rate limits we impose

Per client address, in memory, in app/api/ratelimit.py.

Route Limit
POST /auth/login 10 per 5 min
POST /auth/register 5 per hour
POST /circles/join 20 per hour
PUT/POST /turns/… 60 per hour
POST /auth/password-reset/request 3 per hour
POST /auth/password-reset/confirm 10 per hour
POST /auth/refresh 60 per hour
POST /auth/password 10 per hour

The reset limits are load-bearing rather than precautionary. A request sends mail to somebody else's inbox, which is the one thing an abuser can do here without an account; and ten confirm attempts an hour against 31⁸ combinations is what makes an eight-character code safe enough to type in.

In memory is the important word: the counters live in the process, which is one of the two reasons the app must run as exactly one machine. Two machines would each count roughly half the requests and every limit would silently be double what it says.

The registration limit is easy to hit while testing — five accounts an hour goes quickly when a demo script makes two of them.

Rented limits

Neon — free plan

Compute 100 CU-hours per project per month (≈400 h at 0.25 CU)
Storage 0.5 GB per project
Autosuspend After 5 min idle, cannot be disabled
Point-in-time restore 6 hours
Network transfer 5 GB per month

Two consequences worth doing the arithmetic for.

The scheduler interval is not a preference, it is a requirement. At 0.25 CU, staying awake all month costs about 182 CU-hours against an allowance of 100. The compute suspends after 5 idle minutes, so a tick every 60 seconds keeps it awake permanently and blows the budget. At 15 minutes it is up for roughly a third of the time — around 60 CU-hours — which fits, though not enormously comfortably. If anything else starts querying on a timer, redo this sum.

/health deliberately touches no database, or the 30-second health check would undo the whole thing.

A 6-hour restore window is why scripts/backup.py exists. Neon's own recovery covers a mistake noticed the same morning. It does not cover one noticed on Monday, and it does not cover the account going away.

Cloudflare R2

  • No egress charge. This is the reason R2 was chosen over S3: the shape of the product is one upload read by every member of a circle, forever.
  • Served from vlogroulette-media.mintos.space, a custom domain on the project's own zone, so Cloudflare's rate-limited r2.dev hostname is no longer in the path.
  • Storage is billed by volume and nothing in this app ever deletes a vlog, so it only grows. At 9 MB total today that is theoretical; it is a product decision waiting to be made, not a bug.

Fly.io

  • One machine, shared-cpu-1x, 1 GB. The memory is for FFmpeg on the fallback render, not for the API.
  • Not a limit but a constraint: the machine may not be scaled out. See decisions.md.

Expo — EAS, free plan

Builds 15 Android and 15 iOS per month
Concurrency 1
Build timeout 45 min
Queue Low priority; waits can exceed 90 min at busy times
EAS Update 1,000 monthly active users

This is the tightest limit in the project. August 2026 used 17 Android builds, over the allowance, and a build cycle is nine minutes plus queueing. It is why npx expo-doctor and the native compile job in CI matter: catching a mistake locally is free, catching it on EAS costs one of fifteen.

Expo Push

Free and unmetered in practice, but it is a best-effort service in front of FCM and APNs. It answers 200 to a message it is refusing — the verdict is in the body. Sending is fire-and-forget everywhere except send_now, which exists so scripts/push_check.py can tell you the truth.

Sentry — Developer (free) plan

Errors 5,000 per month
Retention 30 days
Seats 1 user
Replays 50 per month (not used)

Traces are switched off deliberately: this is about errors nobody sees, not latency nobody has measured. 5,000 errors a month is generous for a handful of users and would be consumed in minutes by a crash loop — if quota disappears suddenly, look for something failing repeatedly rather than assuming the plan is too small.

Firebase Cloud Messaging

No meaningful quota at this scale. The constraint is configuration, not volume: google-services.json is baked into the binary at build time, so changing Firebase project means a new build.

Sources