IAMScouting — environment setup
Reference for production env vars and what happens when each is missing or
mis-configured. Maintained alongside /lib/logger.ts so the fail-modes here
match what actually ends up in app_error_log / the fallback file.
Required env vars
The application boots without every var below, but routes that depend on a
missing var return a 503 service_unavailable and log to app_error_log
(falling back to /var/log/iams-audit-fail.log if the DB is unavailable).
| Variable | Used by | Failure mode |
| --- | --- | --- |
| DATABASE_URL / SUPABASE_* | All DB access | App refuses to boot |
| STRIPE_SECRET_KEY | /api/network/leads/[id]/boost, billing portal | 503 + server log |
| ANTHROPIC_API_KEY | /api/network/ai-assistant | 503 + server log |
| RESEND_API_KEY | Transactional email | Mails are dropped silently |
| ADMIN_ALLOWLIST | /api/admin/* + admin pages | Defaults to start@iamscouting.com |
stripe-keys
Lead Boost (the only paid one-off on /network) creates a Stripe Checkout
session in /app/api/network/leads/[id]/boost/route.ts. The route uses
requireStripe() from /lib/stripe.ts, which throws when
STRIPE_SECRET_KEY is blank.
Operational note (2026-05-21): the VPS env file was observed with a
blank STRIPE_SECRET_KEY=. The route now returns a 503 with a generic
"Boost service temporarily unavailable" instead of leaking the raw error.
To restore the feature:
- Pull the live secret from the Stripe dashboard (Test or Live keys —
matching whichever STRIPE_MODE is set on the box).
- Write it into
/opt/iamscouting/.envasSTRIPE_SECRET_KEY=sk_live_…. - Restart the container:
docker compose up -d --force-recreate. - Verify with `curl -sX POST .../api/network/leads/<id>/boost -d
'{"duration":"14d"}' — expect a checkout_url`, not a 503.
The STRIPE_WEBHOOK_SECRET is a separate env var required for the
/api/stripe/webhook endpoint; verify both are set together.
Annual Stripe price IDs (v0.8)
/api/network/subscribe resolves the Stripe price_id for each
(tier_code, interval) pair in this order:
- Row in
stripe_priceswithinterval='year'andactive=true annual_price_idcolumn on the matching monthly row (added in
migration 0065_stripe_annual_coupons.sql)
- Env var
STRIPE_PRICE_<TIER>_ANNUAL— one of:
- STRIPE_PRICE_STARTER_ANNUAL
- STRIPE_PRICE_PRO_ANNUAL
- STRIPE_PRICE_ENTERPRISE_ANNUAL
- Falls back to the monthly price so the upgrade flow never hard-fails
(UI shows annual price label but Stripe charges monthly until the
annual SKU is wired). Logged via `event_log.price_source =
'monthly_fallback'`.
Place real price_xxx IDs from the Stripe dashboard into the env file
on the VPS and restart the container. No code redeploy required.
Coupon codes (v0.8)
The coupon_codes table (migration 0065) maps a user-facing promo
string (e.g. LAUNCH50) to the corresponding Stripe promotion_code_id
(promo_xxx). The /account/club-tier page validates codes against
/api/coupon/validate before forwarding to /api/network/subscribe,
which attaches the promo to the Checkout session. The webhook
increments used_count on checkout.session.completed. To seed:
INSERT INTO coupon_codes (code, stripe_promotion_code_id, percent_off, max_uses, valid_until)
VALUES ('LAUNCH50', 'promo_xxx', 50, 100, NOW() + INTERVAL '30 days');
Codes without a stripe_promotion_code_id still validate (useful for
soft launch testing) but won't change the price at Checkout.
anthropic-key
/api/network/ai-assistant uses the Anthropic SDK with model
claude-opus-4-7 (or whatever ANTHROPIC_MODEL_RECOMMENDER overrides).
The route now wraps every upstream call: missing key → 503, SDK throw →
503, empty draft → 503. The real error message is in
app_error_log server-side.
Operational note (2026-05-21): the production key has a $0 credit
balance and is returning HTTP 402 from Anthropic. From the client's
perspective this surfaces as a 503 "AI service temporarily unavailable" —
the same shape as a missing-key error, by design (don't leak billing
state).
To restore:
- Top up the workspace at https://console.anthropic.com/settings/billing.
- Confirm a 200 response from `curl
https://api.anthropic.com/v1/messages -H "x-api-key: $KEY" -d
'{"model":"claude-opus-4-7","max_tokens":8,"messages":[{"role":"user","content":"ping"}]}'`.
Audit log fallback
When the Postgres insert into app_error_log or admin_audit_log fails,
the logger now writes a JSON line to the path in
IAMS_FALLBACK_LOG_PATH (defaults to /var/log/iams-audit-fail.log).
This file is included in the nightly backup tarball — see
scripts/backup-daily.sh.