Cost and operations
What each check costs, how the free paths hold up under load, and what a production deployment changes.
AI cost, kept small
Reading all 583 remedies cost $14.01 in one batch run. Each photo check is one vision call, and only when the free checks pass.
Identical requests replay
A request that matches one already answered (same model, prompt, images and settings) is read back from a local cache in test runs instead of paid for again.
An identical photo reuses its result
The same claim photo for the same recall and rule reuses its checked result from the database, with no new AI call.
Images are downscaled
Photos and official images are shrunk before the vision call: up to 16% fewer input tokens on large photos, measured on recorded calls (content/verify/tokens.json).
The recall's part is cached
Each request puts the recall's data and official images first, so repeat checks on one recall read that part from Claude's prompt cache.
A daily spend cap
The hosted site keeps to a daily AI budget. Past it, every free check still runs and a person at the firm looks at the rest.
Today’s spend, by route, is on Status in the Technical view.
Under load
One hundred visitors at once on the free paths, against the production build. The claim uses the recall’s own official picture, which the perceptual hash settles before any model call.
100 visitors at once, 300 requests in 14.5 s (20.7 a second), 0 errors. Measured from the client, request start to the full answer, against the local production build on one laptop (Intel(R) Core(TM) Ultra 9 185H), worker not running (inline fallback); not the hosted site.
| Path | Requests | p50 | p95 | p99 | Errors |
|---|---|---|---|---|---|
| POST /api/claims (official picture, no AI) | 100 | 7,865 ms | 8,893 ms | 8,938 ms | 0 |
| GET /api/v1/recalls?query=power bank | 100 | 3,812 ms | 5,495 ms | 5,572 ms | 0 |
| GET /api/v1/scan/sample | 100 | 20 ms | 72 ms | 423 ms | 0 |
162 ms
one claim alone, nothing else running: client time, request start to the full answer.
7,865 ms
the same claim among 100 at once, p50 of client time (p95 8,893 ms): it includes each job's wait for the server to reach it.
1,077 ms
the check itself inside each of those 100 jobs, p50 of server time (p95 1,701 ms): decode, the recall's pictures and the hash match.
Before running a claim's job at once when no worker is alive, instead of after a 2 s wait: p50 10,149 ms, p95 10,905 ms for the claim among 100, measured November 8, 2026 with the worker not running (inline fallback).
The method and the machine
100 virtual users started at once, each running the three calls in a row (claim, search, scan sample), 300 requests in all; latency measured in the client from request start to the full JSON body; a run counts as an error when the status is not 200 or the body is not the expected answer. Each user carries its own x-forwarded-for so the per-visitor limits see 100 visitors. The claim uses the recall's own official picture, settled by the perceptual hash with no model call.
Server: next start, local production build, worker not running (inline fallback), recall search live, Postgres 17 in Docker on the same machine. Machine: Intel(R) Core(TM) Ultra 9 185H, 22 cores, 34 GB, Windows_NT 10.0.26300, Node v24.13.1. Measured November 8, 2026; the file is content/public/load-test.json.
Live latency by route, from the request log, is on Operations.
Running it for real
What the demo runs on today, and what a production deployment changes. Live health is on Operations.
| Part | The demo today | A real deployment |
|---|---|---|
| Web service | One Render Starter instance (0.5 CPU, 512 MB), always on, with the API and MCP server in the same process | Two or more instances behind Render's load balancer, autoscaling on CPU, with the same code |
| Checks and payouts | One background worker; a route runs its own job inline if no worker answers in 2 s | Workers scaled with the queue depth shown on Operations; the same job table |
| Database | A 256 MB Postgres with a 1 GB disk, reached only from this workspace | A larger Postgres with a connection pooler, point-in-time recovery, and backups restored on a schedule to prove they work |
| Your photo | Stored in Postgres as a 1,600 px copy with no camera or location (EXIF, GPS) data, the original cleared once checked. A photo that shows a person is deleted at once, and every owner's photo 90 days after the claim is decided; its hash is kept | Object storage with signed URLs for upload and view, the same deletion rules, and the hash kept in Postgres |
| AI | $5 of Claude a day (AI_DAILY_USD in render.yaml); past it, photos go to a person at the firm | A budget per firm, the Message Batches API for proof rules, and the same cache so a repeat photo costs nothing |
| Firm console | Open, for the fictional sample firm | Login and roles per firm (reviewer, compliance lead, read only), with every action in the ledger under a named person |
| PayPal | Sandbox, one app, the hosted webhook plus event lookup for local servers | A live app and a registered webhook per environment, with the same signature check |
| Limits | 20 API requests a minute without a key, 300 photo claims a day site-wide, each payment under $50 | Limits per firm and per key, set with the firm, counted in the same table |