granat-manifesto

granat-world

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.

Architecture

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.

Local development

cp .env.example .env
# edit .env, set GRANAT_API_KEY
npm install
npm run dev
# open http://localhost:3000

Deploy: Timeweb App Platform

  1. Type: Backend → Express (Node.js 24)
  2. Repository: granat-world/granat-manifesto, branch main
  3. Region: Амстердам
  4. Build command: npm install
  5. Start command: npm start
  6. Environment variables:
    • GRANAT_API_KEY — the upstream API key (from Cone Red)
    • GRANAT_API_BASE — e.g. https://granat-api.apps.cone.red
    • API_KEY_HEADERX-API-Key (default) or Authorization if upstream uses Bearer
  7. Auto-deploy: enabled (push to main → redeploy)
  8. Domain: point granat.world DNS at the Timeweb-issued URL after first deploy succeeds

Endpoints

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.

Files

.
├── 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