Public manifesto site at granat.world. Single Node/Express app: serves the static manifesto and proxies voice-processing requests to the upstream voice API, hiding the API key from the browser.
Browser → granat.world (this app) → upstream voice API
│ (X-API-Key injected here)
└─ serves /public/index.html
└─ POST /api/voice/process → upstream /api/v1/voice/process
└─ GET /api/voice/result/:id → upstream /api/v1/voice/result/:id
└─ GET /api/voice/result/:id/audio → upstream same
The browser never sees the upstream API key or domain.
cp .env.example .env
# edit .env, set GRANAT_API_KEY
npm install
npm run dev
# open http://localhost:3000
granat-world/granat-manifesto, branch mainnpm installnpm startGRANAT_API_KEY — the upstream API key (from Cone Red)GRANAT_API_BASE — e.g. https://granat-api.apps.cone.redAPI_KEY_HEADER — X-API-Key (default) or Authorization if upstream uses Bearer| Method | Path | Notes |
|---|---|---|
| GET | / |
Static manifesto (public/index.html) |
| GET | /health |
Liveness probe — returns {ok: true} |
| POST | /api/voice/process |
multipart/form-data with field file (WAV). Returns {job_id} from upstream. |
| GET | /api/voice/result/:jobId |
Job status JSON from upstream |
| GET | /api/voice/result/:jobId/audio |
Processed WAV (audio/wav) |
Voice endpoints are rate-limited to 10 requests / minute / IP.
.
├── public/ static manifesto site (HTML + favicons)
├── server.js Express app: static serve + voice proxy
├── package.json
├── .env.example template — copy to .env locally, set in Timeweb for prod
└── .gitignore