Environment Variables
Customer Web App — Frontend (semalink-frontend)
These are build-time variables injected by GitHub Actions. They are embedded into the compiled JavaScript bundle by Vite — they are not secret from end users. Do not put secrets in VITE_* variables.
| Variable | Required | Description |
|---|---|---|
VITE_API_BASE_URL | Yes | Base URL of the Main API for this environment (e.g. https://staging-arc.semalink.africa) |
VITE_CF_ACCESS_CLIENT_ID | Yes | Cloudflare Access service token Client ID. Attached as CF-Access-Client-Id header on every API request |
VITE_CF_ACCESS_CLIENT_SECRET | Yes | Cloudflare Access service token Client Secret. Attached as CF-Access-Client-Secret header on every API request |
VITE_ENV | Yes | Environment name: staging, test, or production |
Per-environment values
| Variable | Staging | Test | Production |
|---|---|---|---|
VITE_API_BASE_URL | https://staging-arc.semalink.africa | https://test-arc.semalink.africa | https://arc.semalink.africa |
VITE_ENV | staging | test | production |
VITE_CF_ACCESS_CLIENT_ID | Staging service token | Test service token | Prod service token |
Admin Web App — Frontend (semalink-admin)
Build-time variables injected by GitHub Actions. Same rules as the customer app — these are embedded in the JS bundle and visible to users. Do not put secrets here.
| Variable | Required | Description |
|---|---|---|
VITE_API_BASE_URL | Yes | Base URL of the Main API (e.g. https://staging-arc.semalink.africa) |
VITE_ENV | Yes | Environment name: staging or production |
Per-environment values
| Variable | Staging | Production |
|---|---|---|
VITE_API_BASE_URL | https://staging-arc.semalink.africa | https://arc.semalink.africa |
VITE_ENV | staging | production |
The admin portal does not use Cloudflare Zero Trust service tokens in the build — Zero Trust is enforced at the network level (CF Access policy on the domain), not via request headers from the app.
Main API — Backend (semalink-api)
Runtime variables loaded by the server process. Validated on startup via Zod — the server will not start if required variables are missing or malformed.
Core
| Variable | Required | Default | Description |
|---|---|---|---|
PORT | No | 3000 | HTTP port to listen on |
NODE_ENV | No | development | development, test, or production |
API_VERSION | No | v1 | API path prefix (e.g. /api/v1) |
Database
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | PostgreSQL connection string (postgresql://user:pass@host:port/db) |
Cache
| Variable | Required | Description |
|---|---|---|
REDIS_URL | Yes | Redis connection string (redis://host:6379) |
Message Queue
| Variable | Required | Description |
|---|---|---|
RABBITMQ_URL | Yes | RabbitMQ AMQP connection string (amqp://user:pass@host:5672) |
JWT Authentication
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | Yes | — | Secret for signing customer access tokens. Min 32 characters |
JWT_REFRESH_SECRET | Yes | — | Secret for signing customer refresh tokens. Min 32 characters |
JWT_EXPIRES_IN | No | 15m | Customer access token lifespan. Short-lived by design |
JWT_REFRESH_EXPIRES_IN | No | 7d | Customer refresh token lifespan |
ADMIN_JWT_SECRET | Yes | — | Secret for signing admin staff access tokens. Separate Fastify JWT namespace — admin tokens cannot authenticate on customer routes and vice versa |
ADMIN_JWT_EXPIRES_IN | No | 8h | Admin access token lifespan |
App URLs
| Variable | Required | Default | Description |
|---|---|---|---|
APP_URL | No | http://localhost:5173 | Frontend URL. Used in email links (verify email, reset password, magic link) |
API_URL | No | http://localhost:3000 | API URL. Used in OAuth callback URL construction |
Email (Mailgun)
| Variable | Required | Description |
|---|---|---|
MAILGUN_API_KEY | Yes (prod) | Mailgun API key |
MAILGUN_DOMAIN | Yes (prod) | Sending domain (e.g. mg.semalink.africa) |
MAILGUN_FROM | Yes (prod) | From address (e.g. Sema Link <noreply@semalink.africa>) |
In development, if Mailgun is not configured, emails are logged to stdout instead.
OAuth Providers
All optional. If a provider's vars are absent, that provider's login button is hidden in the frontend.
| Variable | Provider | Notes |
|---|---|---|
GOOGLE_CLIENT_ID | From Google Cloud Console OAuth 2.0 client | |
GOOGLE_CLIENT_SECRET | ||
MICROSOFT_CLIENT_ID | Microsoft | Azure App Registration client ID |
MICROSOFT_CLIENT_SECRET | Microsoft | |
MICROSOFT_TENANT | Microsoft | Azure tenant ID (or common for multi-tenant) |
GITHUB_CLIENT_ID | GitHub | From GitHub Developer Settings |
GITHUB_CLIENT_SECRET | GitHub | |
APPLE_CLIENT_ID | Apple | Services ID from Apple Developer portal |
APPLE_TEAM_ID | Apple | 10-character team ID |
APPLE_KEY_ID | Apple | Key ID of the Sign in with Apple private key |
APPLE_PRIVATE_KEY | Apple | Contents of the .p8 private key file (newlines escaped) |
Cloudflare R2 (Object Storage)
Used for profile photo uploads. Optional — photo upload returns an error if not configured.
| Variable | Description |
|---|---|
R2_ACCOUNT_ID | Cloudflare account ID |
R2_ACCESS_KEY_ID | R2 API token access key ID |
R2_SECRET_ACCESS_KEY | R2 API token secret |
R2_BUCKET_NAME | R2 bucket name (e.g. semalink-uploads) |
R2_PUBLIC_URL | Public URL base for served files (e.g. https://pub-xxx.r2.dev) |
SMS Aggregator (Celcom Africa)
| Variable | Description |
|---|---|
CELCOM_API_URL | Celcom Africa REST API base URL |
CELCOM_API_KEY | Celcom Africa API authentication key |