// the Cloudflare Worker · Hono + KV + D1 + Tailscale proxy · operator's manual for just the bridge
What it is
organized-gateway is a single Cloudflare Worker that proxies POST /v1/*
requests through a per-user rate limiter, a D1 access log, and a Tailscale bridge URL to an
upstream service of your choice. Built on Hono. ~120 lines of TypeScript. One KV namespace, one
D1 database, two secrets, one deploy.
This guide is the focused operator's manual for the Worker as a deliverable. For the broader system
context (HICAM workshop, BYOK → Codex → Stripe phases, post-training paths), see
openclaw-gateway-guide.
# 1. health
curl https://organized-gateway.<account-subdomain>.workers.dev/health
# → {"status":"ok","gateway":"organized-gateway"}# 2. one real request through the full pipe
curl -X POST https://organized-gateway.<account-subdomain>.workers.dev/v1/chat/completions \
-H "Content-Type: application/json" \
-H "X-User-ID: smoke-test" \
-H "Authorization: Bearer sk-your-key" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}'# 3. confirm row landed in D1
wrangler d1 execute organized-gateway-db \
--command "SELECT * FROM requests ORDER BY id DESC LIMIT 1"
watch -n5 'wrangler d1 execute organized-gateway-db \
--command "SELECT user_id, count(*) as n, avg(latency_ms) as ms
FROM requests
WHERE created_at > datetime(\"now\",\"-5 minutes\")
GROUP BY user_id ORDER BY n DESC"'
Rate-limit hits
wrangler d1 execute organized-gateway-db \
--command "SELECT user_id, count(*) FROM requests
WHERE status = 429 GROUP BY user_id ORDER BY 2 DESC"
Scaling beyond 50 concurrent
knob
change
effect
per-user rate
50 → 30
Halves the M4 Mini load ceiling. Safe for 75–100 concurrent users.
KV TTL
120s → 180s
Tolerates briefly higher KV write contention; minor staleness.
upstream
Hetzner CX41/AX41
Swap OPENCLAW_URL to a bigger origin; no Worker change.
D1 archival
weekly to R2
Avoid the 5 M reads/day free-tier cap on long-running events.
multi-region D1
read replicas
Latency improvement for global users; not needed for a single-event setup.
TypeScript types
# Generate the Env interface from wrangler.toml bindings
wrangler types
# → writes worker-configuration.d.ts# Re-run after editing wrangler.toml — types drift otherwise
Pitfalls
--commit-dirty=true is not optional in CI. Wrangler refuses dirty trees by default; bake the flag into scripts/deploy.sh so a clean-room deploy works first try.
Never put a personal API key in OPENAI_API_KEY Worker secret. The Worker is a pass-through; clients send keys via Authorization. A Worker-side key would be unscoped to user.
X-User-ID defaults to 'anonymous'. If clients forget to send it, all their traffic shares one rate-limit bucket and one D1 row stream. Bake the header into the client SDK or the boilerplate's setup.sh.
D1 free tier = 5 M reads/day. 50 users × 50 req/min × 8 hours ≈ 1.2 M rows worst case. Fine for a single event; archive to R2 weekly if running a continuous SaaS.
compatibility_date is load-bearing. Pin it. A floating date will quietly change runtime semantics — your rate limiter could lose a millisecond rounding decision and start over-counting.
Tailscale bridge can blip on origin reboot. If the upstream Mac mini reboots, the Tailscale bridge URL won't resume on its own. Use a Cloudflare Tunnel as the bridge for self-healing, or run tailscale up --auto-update.
The tokens_est column is best-effort. If the upstream JSON omits usage.total_tokens, the value is 0. Don't bill against it without a fallback estimator.
KV is eventually consistent. Two requests at the same minute boundary can both read 49 and both increment to 50 — net traffic 51 in a minute. Acceptable for soft rate limits; not for hard quota enforcement.