FitaLabs Gateway — 1P Bundle + LiteLLM Proxy Journey
Documentation: FitaLabs Gateway — 1P Bundle + LiteLLM Proxy Journey
FitaLabs Gateway — 1P Bundle + LiteLLM Proxy Journey
Complete documentation of the migration from custom gateway to LiteLLM Proxy Server
and the hosting of the Claude Desktop 1P bundle on claude.unifita.app.
Final Architecture
Caddy (TLS)
├── claude.unifita.app → SPA React 1P (bundle ion-dist + CDN chunks)
├── api.unifita.app → LiteLLM Proxy (models, inference)
├── gateway.unifita.app → LiteLLM Admin UI (/ui)
├── docs.unifita.app → Docusaurus (OpenAPI docs)
├── get.unifita.app → DMG downloads
└── status/search/crawley → auxiliary services
Docker Compose:
├── caddy — reverse proxy + TLS
├── gateway — LiteLLM Proxy Server (FastAPI)
├── postgres — proxy database (users, keys, spend)
├── redis — prompt cache
├── searxng — meta-search
├── crawl4ai — web crawler
└── uptime-kuma — monitoring
Models (4 Claude aliases + 4 provider-native)
| ant_alias | model_id | display_name |
|---|---|---|
claude-haiku-4-7[1m] | deepseek-v4-flash | Haiku Seek Flash |
claude-sonnet-4-6[1m] | mimo-v2.5 | Monnet v2.5 |
claude-opus-4-6[1m] | mimo-v2.5-pro | Mopus v2.5 Pro |
claude-opus-4.7[1m] | deepseek-v4-pro | Dopus Pro |
Provider-native: deepseek-v4-flash[1m], mimo-v2.5[1m], mimo-v2.5-pro[1m], deepseek-v4-pro[1m]
Audio: tts-1, whisper-1
Gateway Evolution
Phase 1: Custom FastAPI (Jun 2026)
gateway.py— custom FastAPI app with manual Tier x Effort routingrouter.py(304L) — routing table Haiku/Sonnet/Opus x Low/Med/Highhandlers/messages.py— manual HTTP forwarding to MiMo/DeepSeekservices/forwarding.py,services/fallback.py— custom proxy code- MiMo provider registered via monkey-patch in
ProviderConfigManager
Phase 2: LiteLLM Router as Library
litellm_router.py— singleton loadingmodel_listfrom YAMLrouter.acompletion()replaces manual forwarding- Custom hooks registered in pipeline (
hooks/pipeline.py)
Phase 3: LiteLLM Proxy Server (Jul 2026)
- Critical discovery:
CONFIG_FILE_PATH(notLITELLM_CONFIG_PATH) is the env var the proxy reads at startup app = litellm.proxy.proxy_server.app— the proxy IS the gateway- Custom routes (voice WS, MCP, search, API handlers) added to the proxy app
- PostgreSQL required for admin UI (login, key management, spend tracking)
- Prisma: LiteLLM's default schema is PostgreSQL, not SQLite.
libatomic1required in Dockerfile - Migrations:
prisma migrate deployruns thelitellm_proxy_extrasmigrations - Admin UI at
/ui, loginadmin/admin(UI_USERNAME/UI_PASSWORD)
Phase 4: 1P Bundle (Jul 2026)
- Bundle extracted from 1P Desktop via CDP →
~/.bezetacil/workdir/claude-desktop/ion-dist/ - 47 files copied to
webapp/claude/(React 7.2MB + 35 chunks + CSS + fonts) - CDN fallback in Caddy: missing chunks downloaded on-demand from
assets-proxy.anthropic.com - Caddy injects
Authorization: Bearer sk-gateway-localin all API calls (no Service Worker) - SPA fallback: client-side routes (
/login,/new,/onboarding) serveindex.html - API routes (
/api/*,/v1/*,/bootstrap,/account_profile,/edge-api/*,/accounts/*) proxied to gateway
1P Bundle Discoveries
Chunks and Code Splitting
- The main bundle (
index-BW29VlNm.js, 7.2MB) imports 35+ chunks via dynamicimport() - Chunks follow Vite/Rollup pattern:
vendor-react-B9b0-tId.js,c34d1f91f-DIhRpeoW.js, etc - Complete chunk list obtained from browser 404 errors
- CDN URL:
https://assets-proxy.anthropic.com/claude-ai/v2/assets/v1/
Authentication
- SPA uses Google OAuth (
accounts.google.com/gsi/status) as main provider - Magic link implemented:
POST /api/auth/send_magic_link+GET /api/auth/verify_magic_link - Login methods:
GET /api/auth/login_methods?email=... - SPA does PUT
/api/account_profileto update onboarding settings
Onboarding
- SPA checks
has_finished_claudeai_onboardingandhas_started_claudeai_onboardingon account - Mutations
E()andbe()do PUT on/api/account_profilewith settings invitesarray must exist (even empty) on account —e?.invites.some()crashes if undefined
Critical Endpoints Called by 1P SPA
GET /edge-api/bootstrap?statsig_hashing_algorithm=djb2&growthbook_format=sdk&include_system_prompts=falseGET /api/account_profilePUT /api/account_profile— atualiza settingsPOST /api/accounts/me/organizations/get_or_create_chat_organizationGET /api/auth/login_methods?email=...POST /api/auth/send_magic_linkPOST /api/event_logging/v2/batchGET /api/updates— version check
Caddy Configuration
claude.unifita.app
file_server (root /var/www/claude)
reverse_proxy /api/* → gateway:4000 (with auth header injection)
reverse_proxy /v1/* → gateway:4000
SPA fallback: client-side routes → index.html (only Accept: text/html)
CDN fallback: handle_errors 404 → proxy assets-proxy.anthropic.com
gateway.unifita.app
redir / → /ui (admin panel)
/swagger → /docs (OpenAPI)
Docker
docker-compose.yml
- PostgreSQL 16 Alpine with
pg_isreadyhealthcheck prisma-cachevolume for Prisma binaries- Bind mounts for dev:
src/,litellm_config.yaml,data/ - Env vars in
.env(not in compose), following project convention
Dockerfile
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
RUN apt-get install -y ffmpeg curl libatomic1
RUN uv sync --frozen --no-dev
CMD ["uv", "run", "uvicorn", "gateway:app", "--host", "0.0.0.0", "--port", "4000"]
Lessons Learned
- LiteLLM Proxy, not Router:
proxy_server.appis the gateway.Routeris just a library. - CONFIG_FILE_PATH: The env var the proxy actually reads.
LITELLM_CONFIG_PATHis ignored. - PostgreSQL required: Admin UI, key management, spend tracking all need Postgres.
libatomic1: Required in Dockerfile for Prisma/Node binaries.- Caddy auth injection > Service Worker: SW has caching issues. Caddy
header_upis server-side and reliable. - SPA fallback with Accept header:
header Accept *text/html*avoids serving HTML for JS imports. - CDN fallback with
handle_errors 404: Chunks downloaded on-demand, no need for pre-download.
References
- fitalabs-gateway-modelos — routing table
- fitalabs-infra — complete stack
- claude-desktop-bundles — 1P vs 3P bundle architecture
- claude-desktop-feature-flags — 226 feature flags
- bezetacil-toolset — ASAR RE tools