Deploy the APIs as separate versioned services.
Once the flagship is the Security Selection Data API, the infrastructure decision becomes clearer: you are not deploying one toy endpoint. You are deploying a small portfolio of Python API services with different latency and compute profiles.
Recommendation: keep the website and docs on Firebase Hosting, and deploy each paid API family as its own Cloud Run service from the remote machine. Route them behind one public API domain.
Recommended public topology
One domain, multiple services, static docs kept separate from runtime.
Website
chenyu-li.info/api/... on Firebase Hosting for docs, pricing, and product copy.
API Gateway Domain
api.<domain> path-routed to different backend services.
Runtime Services
Separate Cloud Run services for `data`, `meta-thinking`, and `sim`.
| Public path | Service | Source | Why separate it |
|---|---|---|---|
/data/* |
Security Selection Data API | scripts/data_api/serve_data.py |
Main data product, broad route surface, likely highest traffic |
/meta-thinking/* |
Meta-Thinking API | scripts/meta_thinking/serve.py |
Async job compute profile, different concurrency needs |
/sim/* |
Corporate Simulation API | corporate_sim/serve_sim.py |
Heavier runs and streaming endpoints justify separate runtime control |
| Dedicated subdomain or private ingress | Institutional custom deployment | Client-specific branch, config, and data connectors | Lets enterprise clients buy a tailored system without forcing their requirements into the shared public product |
Anti-scraping and abuse control
You cannot completely stop a paying customer from exporting data, but you can make anonymous scraping, key sharing, and abusive automation much harder and much more expensive.
| Layer | Control | What it does |
|---|---|---|
| Ingress | Global HTTPS load balancer in front of Cloud Run | Gives one choke point for WAF, rate limiting, and bot controls |
| Edge policy | Cloud Armor `throttle` for normal abuse and `rate_based_ban` for repeated offenders | Limits burst scraping and temporarily bans clients that blow through thresholds |
| Bot friction | Cloud Armor bot management and reCAPTCHA on signup, docs scraping hotspots, and suspicious interactive paths | Stops cheap automated scraping against public-facing surfaces without putting captchas on every API request |
| API identity | Scoped API keys, per-key monthly unit quota, per-key burst cap, and endpoint-family entitlements | Separates who can call what from how much they can call |
| Paid plans | IP allowlists for Growth and mandatory allowlists plus signed requests for Scale | Reduces key sharing and makes account-level abuse attributable |
| Backend meter | Usage-unit ledger in Postgres or Redis-backed counters | Lets you enforce overage billing, hard caps, and anomaly detection per customer |
| Route hardening | Disable the default Cloud Run URL | Prevents attackers from bypassing the load balancer and Cloud Armor controls |
What to meter
Watch for per-key request bursts, too many unique symbols per minute, highly sequential CIK or ticker walks, repeated query fan-out, and key usage from too many IPs. Those are scraper signatures, not normal analyst usage.
Operational rule
Public docs stay open. The paid runtime sits behind key auth, quotas, rate limits, and load-balancer policy. Do not expose long-lived production keys in browser JavaScript.
Enterprise variation
Institutional clients can run on a dedicated service, private ingress, or a client-specific environment. That is the right place for custom workflows, proprietary connectors, and stricter access controls.
Provider comparison
The winning criterion is versioned release control for multiple Python APIs, not simply how easy it is to launch one demo app.
| Provider | Strong fit here | Weak fit here | Verdict |
|---|---|---|---|
| Cloud Run | Container-native, revisions, traffic splitting, rollback, strong fit for several FastAPI services | Custom domains should use the recommended load-balancer path, not the preview mapping path | Best default |
| Fly.io | Good for public demos, Anycast networking, ergonomic global deploys | Not my first choice once product state and controlled multi-service releases matter | Good for demos, not the primary revenue path |
| Cloudflare Workers | Great edge gateway and request economics | Less natural as the primary home for container-heavy Python services and job-style APIs | Useful future gateway option, not the core runtime |
| Railway | Friendly workflow, staged changes, simpler UX than raw cloud tooling | Weaker exact-release story than Cloud Run revisions for this use case | Reasonable fallback |
| Render | Predictable pricing, zero-downtime deploys, custom domains | Less compelling than Cloud Run for image-tagged release control across several services | Reasonable fallback |
Release workflow
The key requirement is still the same: local review, remote deploy of one chosen version, and easy rollback.
# local review machine $ git commit -am "api docs + service release config" $ git tag api-2026-03-26-01 $ git push origin main --tags # remote deploy host $ git fetch --tags $ git checkout api-2026-03-26-01 $ docker build -t .../data-api:api-2026-03-26-01 . $ docker push .../data-api:api-2026-03-26-01 $ gcloud run deploy data-api --image .../data-api:api-2026-03-26-01 --no-traffic # repeat for meta-thinking and sim if changed $ gcloud run services update-traffic data-api --to-latest
Why this is the right discipline
- The remote machine deploys an exact tag, not a branch head that may drift.
- Each API service can move on its own release cadence.
- You can smoke test a new revision before traffic shifts.
- The website docs and the runtime release can share one release identifier.
One thing to fix before launch
The curated Data API reference should be reconciled with the live route surface so the product docs and the runtime stay aligned. That matters more now that the Data API is the flagship product and usage-unit billing depends on exact endpoint classification.
What stays on Firebase
All website marketing, pricing, and docs pages. Firebase remains a good home for static releases; it is not the right home for the paid runtime itself.