Every variable apps/web reads, generated from the process.env reads in
src/, scripts/ and db/ and kept complete by
apps/web/src/lib/env-vars-doc.test.ts: a new read without a row here fails
the suite, and so does a row nothing reads any more. A copy-ready file with
the same names is apps/web/.env.example.
"Exposed" means a host someone other than a developer can reach — see
isExposedDeployment() in src/lib/repo.ts: the Postgres backend, any
NEXT_PUBLIC_THIDE_ENV other than local, or NODE_ENV=production without
THIDE_ALLOW_DEV_KEYS=1. On the VPS the service reads /etc/thide-mvp-db.env
through the systemd drop-in thide-mvp.service.d/database.conf
(infra/postgres/RUNBOOK.md); the other variables live in the unit's
environment. GET /api/v1/health reports the credential gaps below by name.
unset (shown as development in the logo badge; any value but local counts as exposed)
Which deployment this is. local is the only value that keeps the dev fallbacks on; production is the only value on which a live Stripe key is accepted.
yes — Studio mutations throw without it under NODE_ENV=production
http://localhost:3000 (mails); the request's host (API tile)
Public origin: every link in a mail, Stripe return URLs, the integrator "how to connect" tile, and the origin Studio posts its own API calls to.
PORT
src/app/studio/_lib/mutations.ts
no
3000
Local port Studio posts to when APP_BASE_URL is unset (never in production).
THIDE_TERMS_DIR
src/lib/terms.ts
no
content/terms next to the app
Override for a deployment that mounts the contract texts elsewhere.
| CLIENT_PORTAL_BASE_URL | src/app/portal/settings/actions.ts | no | APP_BASE_URL | Client portal origin for saved payment-method return links. |
Credentials (resolved only by src/lib/deployment-secrets.ts)¶
Each has a dev fallback that is honoured only on an unexposed host; on an
exposed host an unset value fails closed. missingProductionSecrets() names
the first three in /api/v1/health and in the boot log.
Variable
Required on an exposed host
Default (unexposed only)
Purpose
STUDIO_DEV_KEY
yes
repo dev key
The x-thide-studio header value (translator-side API key) and the operator bootstrap key for /api/v1/cron/acceptance.
THIDE_SESSION_SECRET
yes
repo dev secret
HMAC secret for the portal/Studio session cookie, login codes and login-challenge code hashes.
FILE_LINK_SECRET
yes
repo dev secret
HMAC secret behind every expiring document link.
THIDE_API_KEYS
no — without it there is no bootstrap integrator key
one repo dev key
Comma-separated integrator keys for x-thide-key (TD/KSc bootstrap; owners create their own keys under Settings).
Comma-separated operator addresses that may sign in to any professional's Studio (owner picked under /studio/allocation) and that receive the professional mails of a request that has no owner yet.
Without RESEND_API_KEY or the four GRAPH_* values every mail lands in
THIDE_DATA_DIR/outbox/ as .eml. With a live transport, a recipient must
also be whitelisted or the mail still lands in the outbox (fail closed).
Variable
Read in
Required on an exposed host
Default
Purpose
GRAPH_TENANT_ID
src/lib/email.ts
yes (with the other three) for live mail via Microsoft 365
unset
Entra tenant of the sending mailbox.
GRAPH_CLIENT_ID
src/lib/email.ts
same
unset
App registration (client-credentials flow).
GRAPH_CLIENT_SECRET
src/lib/email.ts
same
unset
Its secret.
GRAPH_SENDER
src/lib/email.ts
same
unset
Mailbox the mail is sent as. Graph wins over Resend when both are configured.
RESEND_API_KEY
src/lib/email.ts
alternative to Graph
unset
Resend transport.
THIDE_MAIL_WHITELIST
src/lib/email.ts
yes for any mail to leave
unset — nothing leaves
Comma-separated addresses or @domain suffixes allowed to receive live mail.
MAIL_FROM
src/lib/email.ts
no
THIDE <no-reply@thide.io>
From header on Resend sends.
THIDE_REPLY_TO
src/lib/email.ts
no
TRANSLATOR_EMAIL
Reply-To until an inbound pipeline exists.
THIDE_SUPPORT_EMAIL
src/lib/email.ts
no
support@thide.io
The "write to us" address in mails.
TRANSLATOR_EMAIL
src/lib/email.ts
yes if the legacy professional should be mailed at a real address
translator@dev.THIDE.local (outbox only)
Overrides the legacy professional's own address for her notifications.
TRANSLATOR_EMAIL_CC
src/lib/email.ts
no
unset
Comma-separated copies of the legacy professional's mails (TD, 1 Sep), each through the whitelist.
1 skips OCR for scans (counts pause for the professional); the preflight script reports it as a failure.
THIDE_OCR_REVIEW_ENABLED
src/lib/upload-count-jobs.ts
no
unset (off)
1 runs the visual OCR page review; part of COUNT_ENGINE_VERSION, so flipping it invalidates the count cache.
THIDE_OCR_REVIEW_MODEL
src/lib/upload-count-jobs.ts
with the review
unset
Model id for the visual review; also part of COUNT_ENGINE_VERSION.
ANTHROPIC_API_KEY
src/lib/upload-count-jobs.ts
with the review
unset — no reviewer is created
API key the visual review calls with.
THIDE_REPEAT_SECTION_CORRECTION
src/lib/count-policy.ts
no
unset (full count)
1 restores the retained repeat-section deduction; part of both count-cache keys, so results of the other policy are not reused. Existing quotes and manual overrides are not repriced. Restart the counting workers after changing it.
THIDE_WEASYPRINT_BIN
src/lib/pdf.ts, src/test/node-setup.ts
no
the VPS venv binary, else weasyprint on PATH
PDF renderer for receipts, invoices and credit notes; tests pin a stub.
| THIDE_TOKEN_LEDGER_MODULE | src/lib/ocr-usage.ts | no | unset | Absolute path to the local Python token ledger module. Without it, usage remains in the durable OCR usage outbox. |
| THIDE_TOKEN_LEDGER_DB | src/lib/ocr-usage.ts | no | ledger module default | Optional local ledger database path forwarded to the usage bridge. |
0 stops the in-process hourly acceptance sweep and deferred-count resume for a host that schedules POST /api/v1/cron/acceptance itself. The upload-count resume and the five-minute tick (callback retries, idempotency and login-challenge purge) keep running: they have no external entry point.
THIDE_PROFESSIONAL_ROUTING
src/lib/professional-routing.ts
no
unset (off)
1 activates automatic professional routing (rollout gate; capability evidence first).
THIDE_BEHIND_CLOUDFLARE
src/lib/rate-limit.ts
no
unset
1 trusts cf-connecting-ip for the per-IP meters even when the peer is not a known Cloudflare range.
THIDE_RATE_LIMIT_FORCE
src/lib/rate-limit.ts
no
unset
1 turns the rate limiters on under NODE_ENV=test (used by the limiter tests).
Operations detail, webhook registration and the production checklist:
docs/mvp-sept2026/einvoice-ops.md. Nothing here is required for a PDF-only
deployment: with the stubs selected every path runs without a network.
Variable
Read in
Required on an exposed host
Default
Purpose
THIDE_ISSUER_REGISTRY
src/lib/issuer-registry.ts
recommand once professionals are verified at the access point
stub
Who creates and reads the professionals' sending companies and answers the recipient lookup: stub (in-process, verified at once) or recommand. recommand needs the three RECOMMAND_* credentials and fails loudly, naming the variable, when one is missing.
THIDE_DELIVERY_SENDER
src/lib/einvoice-sender.ts
recommand once e-invoices leave the house
stub
Which access point sends the UBL: stub (answers per THIDE_STUB_SENDER_OUTCOME) or recommand. Same credentials as the registry — one key pair, one team.
thideworks BV's own sending company at the access point. Unset, a THIDE-MT delivery attempt fails issuer_not_verified and the register row falls back to channel pdf — the PDF the client holds stays the original.
RECOMMAND_WEBHOOK_SECRET
src/app/api/v1/webhooks/recommand/route.ts
yes once the webhook is registered
unset
HMAC-SHA256 key for X-Signature: sha256=<hex> over the raw body. Unset, the route answers 501 and records nothing; must equal the secret registered at Recommand.
THIDE_EINVOICE_STATUS_POLL_MINUTES
src/lib/einvoice-delivery.ts
no
15
How long a sent row is left alone before the five-minute status sweep asks the access point; the webhook normally settles the row first. Must be a positive number.
THIDE_MT_EINVOICE
src/lib/machine-einvoice.ts
no — stays off until D4 and the KSc sign-off of the MT note
unset (off)
1 or true builds, stores and queues the THIDE-MT machine-tier e-invoice next to its PDF. Off, the machine invoice stays PDF-only and its register row keeps channel pdf.
THIDE_STUB_SENDER_OUTCOME
src/lib/einvoice-sender.ts
never
delivered
Development only: forces the stub sender's answer (delivered, unreachable, validation, pending, unavailable) so every outbox branch can be exercised without a network. Ignored when the sender is recommand.
THIDE_VIES_STUB
src/lib/vies.ts
never
unset (VIES)
1 replaces the VIES REST check of a Belgian VAT number at opt-in by an in-process check-digit stub, for development and tests.
production under next start (exposed unless THIDE_ALLOW_DEV_KEYS=1), development under next dev (wider sign-in meter), test under vitest (limiters off).
NEXT_RUNTIME
src/instrumentation.ts
Next sets nodejs / edge; the timers only start on the node runtime.
Child-process probe of the native count lifecycle.
THIDE_STUDIO_KEY
src/app/api/v1/_lib/envelope-redaction.test.ts
Studio header value one test sends; empty by default.
PATH
src/lib/count-native-lifecycle.test.ts
Passed through to the probe child process by that test only.
RECOMMAND_PLAYGROUND
scripts/recommand-playground-e2e.ts
1 is the explicit consent the playground end-to-end script demands before it talks to an access point (it cannot tell a playground from production by the URL).
UPDATE_GOLDEN
src/lib/ubl/ubl-documents.test.ts
1 regenerates the UBL goldens (UPDATE_GOLDEN=1 npx vitest run src/lib/ubl); the diff is the review.