# Environment Variables Reference

All configuration for the Orion platform is managed through environment variables, loaded
via Pydantic Settings from an `.env` file or the process environment. This page provides a
complete reference for every variable recognised by `app/core/config.py`.

Variables are read at startup and exposed through the `settings` singleton. In most cases
the defaults are tuned for local development; production deployments **must** override the
security-sensitive values listed in the [Production Checklist](#production-checklist) at the
bottom of this page.

---

## Core / Project

Metadata used in the OpenAPI schema and health endpoints.

| Variable | Description | Default | Required |
|---|---|---|---|
| `PROJECT_NAME` | Display name shown in API docs and health responses | `Orion - Multi-Store Marketplace Platform` | No |
| `VERSION` | Semantic version reported by the platform | `2.2.0` | No |

---

## Database

!!! danger "Production requirement"
    You **must** set `DATABASE_URL` to a real PostgreSQL connection string in every
    non-development environment. The default value contains a placeholder password and
    should never be used in production.

| Variable | Description | Default | Required |
|---|---|---|---|
| `DATABASE_URL` | PostgreSQL connection string (`postgresql://user:pass@host:port/db`) | `postgresql://orion_user:secure_password@localhost:5432/orion_db` | **Yes** |

---

## Admin Initialisation

Used by `init_production.py` and the database seeder to create the initial platform
administrator account.

!!! warning "Change the default password"
    The default `ADMIN_PASSWORD` is `admin123`. The production validation check will emit a
    warning if this value is left unchanged.

| Variable | Description | Default | Required |
|---|---|---|---|
| `ADMIN_EMAIL` | Email address for the initial admin account | `admin@orion.lu` | No |
| `ADMIN_USERNAME` | Username for the initial admin account | `admin` | No |
| `ADMIN_PASSWORD` | Password for the initial admin account | `admin123` | No (but **must change** in production) |
| `ADMIN_FIRST_NAME` | First name of the admin user | `Platform` | No |
| `ADMIN_LAST_NAME` | Last name of the admin user | `Administrator` | No |

---

## JWT Authentication

Controls JSON Web Token generation and expiry.

!!! danger "Production requirement"
    `JWT_SECRET_KEY` **must** be replaced with a strong random value. Generate one with:

    ```bash
    openssl rand -hex 32
    ```

| Variable | Description | Default | Required |
|---|---|---|---|
| `JWT_SECRET_KEY` | Secret used to sign and verify JWTs | `change-this-in-production` | **Yes** |
| `JWT_EXPIRE_HOURS` | Hours component of the token lifetime | `24` | No |
| `JWT_EXPIRE_MINUTES` | Minutes component of the token lifetime | `30` | No |

---

## API Server

Settings passed to Uvicorn when the application starts.

| Variable | Description | Default | Required |
|---|---|---|---|
| `API_HOST` | Bind address for the API server | `0.0.0.0` | No |
| `API_PORT` | Port the API server listens on | `8000` | No |
| `DEBUG` | Enable debug mode (extra logging, auto-reload) | `True` | No (set `False` in production) |

---

## Documentation

| Variable | Description | Default | Required |
|---|---|---|---|
| `DOCUMENTATION_URL` | URL where the MkDocs site is served | `http://localhost:8001` | No |

---

## Security / Middleware

!!! warning "Restrict allowed hosts"
    The default `ALLOWED_HOSTS` value of `["*"]` accepts requests with any `Host` header.
    In production, restrict this to your actual domain names.

| Variable | Description | Default | Required |
|---|---|---|---|
| `ALLOWED_HOSTS` | JSON list of permitted `Host` header values | `["*"]` | No (but **restrict** in production) |
| `RATE_LIMIT_ENABLED` | Enable request rate limiting | `True` | No |
| `RATE_LIMIT_REQUESTS` | Maximum number of requests per window | `100` | No |
| `RATE_LIMIT_WINDOW` | Rate limit window duration in seconds | `3600` | No |

---

## Logging

| Variable | Description | Default | Required |
|---|---|---|---|
| `LOG_LEVEL` | Python log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`) | `INFO` | No |
| `LOG_FILE` | Path to a log file; `None` means stdout only | `None` | No |

---

## Platform Domain

Controls the base domain for store subdomains and custom-domain features.

| Variable | Description | Default | Required |
|---|---|---|---|
| `MAIN_DOMAIN` | Root domain under which store subdomains are created | `wizard.lu` | No |
| `ALLOW_CUSTOM_DOMAINS` | Allow stores to use their own domain names | `True` | No |
| `REQUIRE_DOMAIN_VERIFICATION` | Require DNS verification before activating a custom domain | `True` | No |
| `SSL_PROVIDER` | SSL certificate provider (`letsencrypt`, `cloudflare`, `manual`) | `letsencrypt` | No |
| `AUTO_PROVISION_SSL` | Automatically provision SSL certificates for custom domains | `False` | No |
| `DNS_VERIFICATION_PREFIX` | TXT record prefix used for domain ownership verification | `_orion-verify` | No |
| `DNS_VERIFICATION_TTL` | TTL in seconds for DNS verification records | `3600` | No |

---

## Platform Limits

Guard-rails for multi-tenant resource usage.

| Variable | Description | Default | Required |
|---|---|---|---|
| `MAX_STORES_PER_USER` | Maximum number of stores a single user can own | `5` | No |
| `MAX_TEAM_MEMBERS_PER_STORE` | Maximum team members allowed per store | `50` | No |
| `INVITATION_EXPIRY_DAYS` | Days before a team invitation link expires | `7` | No |

---

## Stripe Billing

!!! info "Required for payments"
    All three Stripe keys must be set to enable subscription billing and payment
    processing. Obtain them from the [Stripe Dashboard](https://dashboard.stripe.com/apikeys).

| Variable | Description | Default | Required |
|---|---|---|---|
| `STRIPE_SECRET_KEY` | Stripe secret API key | `""` (empty) | Yes (for payments) |
| `STRIPE_PUBLISHABLE_KEY` | Stripe publishable API key | `""` (empty) | Yes (for payments) |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | `""` (empty) | Yes (for payments) |
| `STRIPE_TRIAL_DAYS` | Length of the free trial period in days | `30` | No |

---

## Email Configuration

Orion supports multiple email providers. Set `EMAIL_PROVIDER` to choose one, then
configure the matching provider-specific variables below.

| Variable | Description | Default | Required |
|---|---|---|---|
| `EMAIL_PROVIDER` | Email transport backend (`smtp`, `sendgrid`, `mailgun`, `ses`) | `smtp` | No |
| `EMAIL_FROM_ADDRESS` | Sender address for outgoing emails | `noreply@orion.lu` | No |
| `EMAIL_FROM_NAME` | Sender display name | `Orion` | No |
| `EMAIL_REPLY_TO` | Optional reply-to address | `""` (empty) | No |
| `EMAIL_ENABLED` | Master switch to enable/disable all outgoing email | `True` | No |
| `EMAIL_DEBUG` | Log emails to console instead of sending (development only) | `False` | No |

### SMTP Settings

Used when `EMAIL_PROVIDER=smtp`.

| Variable | Description | Default | Required |
|---|---|---|---|
| `SMTP_HOST` | SMTP server hostname | `localhost` | No |
| `SMTP_PORT` | SMTP server port | `587` | No |
| `SMTP_USER` | SMTP authentication username | `""` (empty) | No |
| `SMTP_PASSWORD` | SMTP authentication password | `""` (empty) | No |
| `SMTP_USE_TLS` | Use STARTTLS (port 587) | `True` | No |
| `SMTP_USE_SSL` | Use implicit SSL (port 465) | `False` | No |

### SendGrid Settings

Used when `EMAIL_PROVIDER=sendgrid`.

| Variable | Description | Default | Required |
|---|---|---|---|
| `SENDGRID_API_KEY` | SendGrid API key | `""` (empty) | Yes (if using SendGrid) |

### Mailgun Settings

Used when `EMAIL_PROVIDER=mailgun`.

| Variable | Description | Default | Required |
|---|---|---|---|
| `MAILGUN_API_KEY` | Mailgun API key | `""` (empty) | Yes (if using Mailgun) |
| `MAILGUN_DOMAIN` | Mailgun sending domain | `""` (empty) | Yes (if using Mailgun) |

### Amazon SES Settings

Used when `EMAIL_PROVIDER=ses`.

| Variable | Description | Default | Required |
|---|---|---|---|
| `AWS_ACCESS_KEY_ID` | AWS access key for SES | `""` (empty) | Yes (if using SES) |
| `AWS_SECRET_ACCESS_KEY` | AWS secret key for SES | `""` (empty) | Yes (if using SES) |
| `AWS_REGION` | AWS region for the SES endpoint | `eu-west-1` | No |

---

## Storefront Defaults

Default locale and currency applied to new storefronts. Individual stores can override
these through the admin interface or the `AdminSetting` database table.

| Variable | Description | Default | Required |
|---|---|---|---|
| `DEFAULT_STOREFRONT_LOCALE` | Locale code for currency and number formatting | `fr-LU` | No |
| `DEFAULT_CURRENCY` | ISO 4217 currency code | `EUR` | No |

---

## Seed Data

Controls the volume of demo data generated by the database seeder.

| Variable | Description | Default | Required |
|---|---|---|---|
| `SEED_DEMO_STORES` | Number of demo stores to create | `3` | No |
| `SEED_CUSTOMERS_PER_STORE` | Number of demo customers per store | `15` | No |
| `SEED_PRODUCTS_PER_STORE` | Number of demo products per store | `20` | No |
| `SEED_ORDERS_PER_STORE` | Number of demo orders per store | `10` | No |

---

## Celery / Redis

Background task processing. When `USE_CELERY` is `False`, tasks fall back to FastAPI's
built-in `BackgroundTasks`.

!!! tip "Enable Celery in production"
    Set `USE_CELERY=True` and ensure a Redis instance is reachable at `REDIS_URL` for
    reliable background task processing.

| Variable | Description | Default | Required |
|---|---|---|---|
| `REDIS_URL` | Redis connection string used as Celery broker and result backend | `redis://localhost:6379/0` | No |
| `USE_CELERY` | Use Celery for background tasks instead of FastAPI BackgroundTasks | `False` | No (set `True` in production) |
| `FLOWER_URL` | URL of the Flower monitoring dashboard | `http://localhost:5555` | No |
| `FLOWER_PASSWORD` | Password for Flower authentication | `changeme` | No (but **change** in production) |

---

## Sentry

Error tracking and performance monitoring via [Sentry](https://sentry.io).

| Variable | Description | Default | Required |
|---|---|---|---|
| `SENTRY_DSN` | Sentry Data Source Name; `None` disables Sentry | `None` | No |
| `SENTRY_ENVIRONMENT` | Environment tag sent with events (`development`, `staging`, `production`) | `development` | No |
| `SENTRY_TRACES_SAMPLE_RATE` | Fraction of transactions sampled for performance monitoring (0.0--1.0) | `0.1` | No |

---

## Monitoring

Prometheus metrics and Grafana dashboard integration.

| Variable | Description | Default | Required |
|---|---|---|---|
| `ENABLE_METRICS` | Expose a `/metrics` endpoint for Prometheus scraping | `False` | No (set `True` in production) |
| `GRAFANA_URL` | URL of the Grafana instance | `https://grafana.wizard.lu` | No |
| `GRAFANA_ADMIN_USER` | Grafana admin username | `admin` | No |
| `GRAFANA_ADMIN_PASSWORD` | Grafana admin password | `""` (empty) | No |

---

## Cloudflare R2 Storage

Object storage for media uploads. When `STORAGE_BACKEND` is `local`, files are stored on
the server filesystem.

| Variable | Description | Default | Required |
|---|---|---|---|
| `STORAGE_BACKEND` | Storage backend to use (`local` or `r2`) | `local` | No |
| `R2_ACCOUNT_ID` | Cloudflare account ID | `None` | Yes (if using R2) |
| `R2_ACCESS_KEY_ID` | R2 API access key | `None` | Yes (if using R2) |
| `R2_SECRET_ACCESS_KEY` | R2 API secret key | `None` | Yes (if using R2) |
| `R2_BUCKET_NAME` | R2 bucket name | `orion-media` | No |
| `R2_PUBLIC_URL` | Custom public URL for media access (e.g. `https://media.yoursite.com`) | `None` | No |

---

## Loyalty Module

Configuration for the loyalty module (stamp/points programs, wallet integration).
All variables use the `LOYALTY_` prefix and are managed by `app/modules/loyalty/config.py`.

### Anti-Fraud Defaults

| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_DEFAULT_COOLDOWN_MINUTES` | Minimum minutes between stamps for the same card | `15` | No |
| `LOYALTY_MAX_DAILY_STAMPS` | Maximum stamps per card per day | `5` | No |
| `LOYALTY_PIN_MAX_FAILED_ATTEMPTS` | Failed PIN attempts before lockout | `5` | No |
| `LOYALTY_PIN_LOCKOUT_MINUTES` | Duration of PIN lockout in minutes | `30` | No |

### Points

| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_DEFAULT_POINTS_PER_EURO` | Points earned per euro spent | `10` | No |

### Google Wallet

!!! info "Required for Google Wallet passes"
    Both variables must be set for loyalty cards to appear in Google Wallet.
    See [Hetzner Step 25](hetzner-server-setup.md#step-25-google-wallet-integration) for setup guide.

| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_GOOGLE_ISSUER_ID` | Google Wallet Issuer ID (numeric string from [Pay & Wallet Console](https://pay.google.com/business/console)) | `None` | Yes (for Google Wallet) |
| `LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON` | Path to the Google service account JSON key file | `None` | Yes (for Google Wallet) |

### Apple Wallet

!!! info "Required for Apple Wallet passes"
    All five variables must be set for `.pkpass` generation.
    Requires an Apple Developer account ($99/year).

| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_APPLE_PASS_TYPE_ID` | Pass type identifier (e.g., `pass.com.example.loyalty`) | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_TEAM_ID` | Apple Developer Team ID | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_WWDR_CERT_PATH` | Path to Apple WWDR intermediate certificate | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_SIGNER_CERT_PATH` | Path to pass signing certificate (`.pem`) | `None` | Yes (for Apple Wallet) |
| `LOYALTY_APPLE_SIGNER_KEY_PATH` | Path to pass signing private key (`.pem`) | `None` | Yes (for Apple Wallet) |

### QR Code

| Variable | Description | Default | Required |
|---|---|---|---|
| `LOYALTY_QR_CODE_SIZE` | QR code image size in pixels | `300` | No |

---

## Cloudflare CDN / Proxy

| Variable | Description | Default | Required |
|---|---|---|---|
| `CLOUDFLARE_ENABLED` | Set to `True` when the application sits behind Cloudflare proxy (adjusts trusted-proxy headers) | `False` | No (set `True` when proxied) |

---

## Production Checklist

Before deploying to production, ensure the following variables are set correctly. Items
marked **critical** will trigger a startup warning if left at their default values.

!!! danger "Critical -- must change"
    - [x] `DATABASE_URL` -- point to a production PostgreSQL instance
    - [x] `JWT_SECRET_KEY` -- generate with `openssl rand -hex 32`
    - [x] `ADMIN_PASSWORD` -- choose a strong, unique password
    - [x] `DEBUG` -- set to `False`
    - [x] `ALLOWED_HOSTS` -- restrict to your domain(s)

!!! warning "Strongly recommended"
    - [x] `USE_CELERY` -- set to `True` with a production Redis instance
    - [x] `FLOWER_PASSWORD` -- change from the default `changeme`
    - [x] `ENABLE_METRICS` -- set to `True` for observability
    - [x] `SENTRY_DSN` -- configure for error tracking
    - [x] `SENTRY_ENVIRONMENT` -- set to `production`
    - [x] `STORAGE_BACKEND` -- set to `r2` for scalable media storage
    - [x] `CLOUDFLARE_ENABLED` -- set to `True` if behind Cloudflare proxy

!!! info "Required for specific features"
    - [x] **Payments:** `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`, `STRIPE_WEBHOOK_SECRET`
    - [x] **Email (SendGrid — recommended):** `EMAIL_PROVIDER=sendgrid`, `SENDGRID_API_KEY` — handles transactional + marketing in one account
    - [x] **Email (Mailgun):** `MAILGUN_API_KEY`, `MAILGUN_DOMAIN` — transactional only, no marketing features
    - [x] **Email (SES):** `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` — cheapest at scale
    - [x] **R2 Storage:** `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`
    - [x] **Google Wallet:** `LOYALTY_GOOGLE_ISSUER_ID`, `LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON`
    - [ ] **Apple Wallet:** `LOYALTY_APPLE_PASS_TYPE_ID`, `LOYALTY_APPLE_TEAM_ID`, `LOYALTY_APPLE_WWDR_CERT_PATH`, `LOYALTY_APPLE_SIGNER_CERT_PATH`, `LOYALTY_APPLE_SIGNER_KEY_PATH`

### Example `.env` file (production)

```bash
# Core
DATABASE_URL=postgresql://orion:STRONG_PASSWORD@db.internal:5432/orion
JWT_SECRET_KEY=a]3f...your-random-hex-here...9c2b
DEBUG=False
ALLOWED_HOSTS=["wizard.lu","*.wizard.lu"]

# Admin
ADMIN_PASSWORD=your-strong-admin-password

# Celery / Redis
REDIS_URL=redis://redis.internal:6379/0
USE_CELERY=True
FLOWER_PASSWORD=a-secure-flower-password

# Stripe
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Email (example: SendGrid)
EMAIL_PROVIDER=sendgrid
SENDGRID_API_KEY=SG....

# R2 Storage
STORAGE_BACKEND=r2
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-access-key
R2_SECRET_ACCESS_KEY=your-secret-key
R2_PUBLIC_URL=https://media.wizard.lu

# Monitoring
ENABLE_METRICS=True
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
SENTRY_ENVIRONMENT=production
CLOUDFLARE_ENABLED=True

# Google Wallet (Loyalty)
LOYALTY_GOOGLE_ISSUER_ID=3388000000023089598
LOYALTY_GOOGLE_SERVICE_ACCOUNT_JSON=/app/google-wallet-sa.json
```
