Veefore docs

Configuration reference

All 103 environment variables Veefore reads, with their defaults.

Generated page. Built from .env.example. Edit the source, not this page.

Veefore is configured entirely through the environment. There is no configuration file the services read at runtime and no settings table that can disagree with the process that booted. Configuration is validated at startup; an invalid value stops the process rather than degrading it.

Secrets belong in a secret manager

The cryptographic keys below are the ones that matter: data encryption, platform signing, session signing and the audit chain. In anything beyond local development, inject them from a secret manager. Rotating the signing key invalidates issued passports; rotating the data encryption key requires re-sealing credential material.

Copy .env.example to .env to start. npm run dev reads the root .env, and so does the console’s Vite config, so the ports stay consistent across every process.

The Ships as column is the value in .env.example, which is also the value the services fall back to when the variable is unset. Where the example file carries a commented-out alternative, it is shown underneath.

#Runtime

Variable Ships as Notes
VEEFORE_ENV development development | test | staging | production
NODE_ENV development
LOG_LEVEL info

#Control Plane API

Variable Ships as Notes
CONTROL_PLANE_HOST 0.0.0.0
CONTROL_PLANE_PORT 4000
CONTROL_PLANE_PUBLIC_URL http://localhost:4000 Production: https://api.veefore.com
CORS_ALLOWED_ORIGINS http://localhost:5173 Platforms that assign a port (Railway, Heroku, Cloud Run) pass it as PORT. Leave CONTROL_PLANE_PORT and GATEWAY_PORT unset there and the service binds PORT; set either one and it wins, which is what keeps local development on 4000 and 4010 regardless of what the shell exports. Alternative: PORT= Comma-separated list of browser origins allowed to call the control plane. Production: https://console.veefore.com,https://veefore.com Mostly a backstop: the console reaches /v1 same-origin (Vite's proxy in development, an edge rewrite in production), so the browser sends no Origin header and CORS never engages. The list is also what social sign-in matches a forwarded host against when choosing a redirect base.
CONSOLE_PUBLIC_URL empty Where people open the console. The CLI sends engineers here to sign in from the browser. Empty means the first CORS_ALLOWED_ORIGINS entry. Production: https://console.veefore.com
MARKETING_PUBLIC_URL empty Where the public marketing site is served. Separate deployment, separate origin (apps/marketing). Production: https://veefore.com
SESSION_HINT_COOKIE_DOMAIN empty Domain for veefore_signed_in — a non-secret cookie holding the single character 1, which is how the marketing site on the apex domain knows to hand a signed-in visitor over to the console. Nothing on the server reads it and forging it grants nothing. The session cookie itself is NEVER domain-scoped: it stays HttpOnly and host-only on the console's origin. See apps/control-plane/src/http/session-cookies.ts. Empty keeps the hint host-only too, which is right for local development and for any deployment serving the site and the console from one host. Set it to the registrable domain they share when they are on sibling hosts. Production: .veefore.com
VEEFORE_HOSTED false true only on the hosted Veefore service that the published CLI connects to by default. Self-hosted installs leave it false, and setup instructions then show engineers this deployment's address to pass to veefore --control-plane once.

#Gateway (Enforcement Plane)

Variable Ships as Notes
GATEWAY_HOST 0.0.0.0
GATEWAY_PORT 4010
GATEWAY_PUBLIC_URL http://localhost:4010 Production: https://gateway.veefore.com
GATEWAY_AUDIENCE_ALIASES empty Comma-separated extra aud values the gateway accepts besides GATEWAY_PUBLIC_URL, for a gateway reachable under several addresses.
GATEWAY_EGRESS_TIMEOUT_MS 15000 Hard ceiling on outbound tool execution time (ms)
GATEWAY_ALLOW_REAL_EGRESS false Set to true to allow the gateway to make real outbound HTTP calls. In development/demo this stays false and the simulated tool runtime is used.

#Database

Variable Ships as Notes
DATABASE_URL empty When DATABASE_URL is set, the pg driver is used (production path). When it is empty, Veefore boots an embedded PGlite Postgres, which is per-process: the control plane, the gateway and the worker each get their own private copy and see none of each other's writes. Correct for isolated tests, wrong for running the stack. Set this for anything multi-process. Alternative: postgres://veefore:veefore@localhost:5432/veefore
DATABASE_SSL false
DATABASE_POOL_MAX 20
DATABASE_STATEMENT_TIMEOUT_MS 15000
PGLITE_DATA_DIR memory:// Directory used by the embedded PGlite instance. memory:// = ephemeral.
DATABASE_ENABLE_RLS false Enable PostgreSQL row level security policies (requires the pg driver).

#Redis (rate limiting, velocity counters, approval pub/sub)

Variable Ships as Notes
REDIS_URL redis://localhost:6379 Leave empty to use the in-process store (single node development only). Alternative: redis://localhost:6379

#Cryptography

Variable Ships as Notes
VEEFORE_DATA_ENCRYPTION_KEY empty 32-byte base64 key used to envelope-encrypt sensitive metadata at rest. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
VEEFORE_SIGNING_PRIVATE_KEY empty Base64 PKCS8 Ed25519 private key used to sign agent identity tokens and passports. Generate with: npm run -s migrate -- --print-keypair
VEEFORE_SIGNING_KEY_ID empty
VEEFORE_SESSION_SECRET empty Secret used to sign human browser sessions.
VEEFORE_AUDIT_CHAIN_SECRET empty Audit hash-chain pepper. Rotating this breaks historical verification.

#Session / auth policy

Variable Ships as Notes
SESSION_TTL_SECONDS 43200
SESSION_IDLE_TIMEOUT_SECONDS 3600
STEP_UP_TTL_SECONDS 300
AGENT_TOKEN_TTL_SECONDS 900
PASSPORT_TTL_SECONDS 2592000
MAX_LOGIN_ATTEMPTS 10
LOGIN_LOCKOUT_SECONDS 900

#Private beta

Variable Ships as Notes
SIGNUP_ACCESS invite_only invite_only (default): a new organization needs an invitation, issued when a platform operator approves a waitlist application (/waitlist). The link is e-mailed, works for one sign-up with the applicant's address, and expires 24 hours after it is sent. open lets anyone sign up.
PLATFORM_SUPERADMIN_EMAIL empty Platform admin (admin.veefore.com). Admin accounts are separate from customer accounts: they sign in only on the admin site, always with a password and an authenticator code, and can never sign in to the console. The superadmin below is created on start and kept in step with these values, which is how a new deployment gets its first way in. Only the password HASH goes here. Generate one (and a strong password) with: npm run admin:password Changing the hash signs the superadmin out everywhere. Further admins are added by a superadmin in the admin site, under Admins.
PLATFORM_SUPERADMIN_NAME Superadmin
PLATFORM_SUPERADMIN_PASSWORD_HASH empty
ADMIN_PUBLIC_URL http://localhost:5175 Where the admin site is served. Its origin may call the API. Production: https://admin.veefore.com
PLATFORM_ADMIN_SESSION_TTL_SECONDS 28800 Admin sessions: absolute lifetime and idle timeout, in seconds.
PLATFORM_ADMIN_IDLE_TIMEOUT_SECONDS 1800
PLATFORM_ADMIN_ALLOWED_IPS empty Optional comma-separated IPs or CIDRs the platform admin accepts requests from (e.g. 203.0.113.7,10.0.0.0/8). Empty means any network. Only set this when your edge proxy overwrites X-Forwarded-For with the real client address.

#Social sign-in (Google, GitHub). A provider appears on the sign-in page only when both its client id and secret are set. Register this redirect URI with the provider, exactly: <OAUTH_REDIRECT_BASE_URL>/v1/auth/oauth/google/callback <OAUTH_REDIRECT_BASE_URL>/v1/auth/oauth/github/callback The base is the browser-facing origin that serves /v1 (normally the console, which proxies it). Empty means CONSOLE_PUBLIC_URL. Must be https in production. Keep the secrets in your secret manager, not in this file.

Variable Ships as Notes
OAUTH_REDIRECT_BASE_URL empty
OAUTH_GOOGLE_CLIENT_ID REPLACE_WITH_GOOGLE_CLIENT_ID
OAUTH_GOOGLE_CLIENT_SECRET REPLACE_WITH_GOOGLE_CLIENT_SECRET
OAUTH_GITHUB_CLIENT_ID REPLACE_WITH_GITHUB_CLIENT_ID
OAUTH_GITHUB_CLIENT_SECRET REPLACE_WITH_GITHUB_CLIENT_SECRET
OAUTH_HTTP_TIMEOUT_MS 10000

#Passkeys and security keys (WebAuthn) Passkeys are bound to WEBAUTHN_RP_ID (a registrable domain such as veefore.example.com). Changing it later invalidates every registered passkey, so set it once. Defaults: the hostname of CONSOLE_PUBLIC_URL, and that URL plus CORS_ALLOWED_ORIGINS as the accepted origins. Browsers only allow passkeys on https origins and on http://localhost.

Variable Ships as Notes
WEBAUTHN_RP_ID empty
WEBAUTHN_RP_NAME Veefore
WEBAUTHN_ORIGINS empty

#Credential broker

Variable Ships as Notes
CREDENTIAL_BROKER_PROVIDER local local | aws-secrets-manager | azure-key-vault | gcp-secret-manager | vault
CREDENTIAL_DEFAULT_TTL_SECONDS 600
AWS_REGION empty
AWS_SECRETS_MANAGER_PREFIX veefore/
AZURE_KEY_VAULT_URL empty
GCP_PROJECT_ID empty
VAULT_ADDR empty
VAULT_NAMESPACE empty

#Observability

Variable Ships as Notes
OTEL_ENABLED false
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4318
OTEL_SERVICE_NAME veefore
METRICS_ENABLED true

#Notifications / integrations (optional)

Variable Ships as Notes
EMAIL_TRANSPORT log resend | log Every e-mail (verification codes, password resets and security notices, waitlist and beta invitations, team invitations, alerts) is sent through the Resend API with EMAIL_TRANSPORT=resend. log never sends anything: outside staging/production it keeps the last messages for GET /v1/dev/outbox (local machine only); in staging/production it is refused.
RESEND_API_KEY empty From https://resend.com/api-keys ("Sending access" is enough). Secret.
RESEND_API_URL https://api.resend.com
EMAIL_HTTP_TIMEOUT_MS 10000
EMAIL_FROM no-reply@veefore.com Must be an address on a domain verified in Resend (SPF + DKIM).
EMAIL_FROM_NAME Veefore
EMAIL_SUPPORT_ADDRESS empty Shown in e-mail footers and used as Reply-To unless EMAIL_REPLY_TO is set.
EMAIL_REPLY_TO empty
EMAIL_COMPANY_ADDRESS empty Postal address in the footer of every e-mail. Empty hides the line.
EMAIL_LOGO_URL empty Logo in the e-mail header, as an absolute http(s) URL of a PNG or JPG (SVG is not supported by most mail clients). Empty serves the bundled mark from the console at <CONSOLE_PUBLIC_URL>/brand/veefore-mark.png, which a mail client can only load once the console is reachable publicly.
VITE_TERMS_URL empty Console legal links shown on the sign-in and sign-up pages. Absolute https:// URLs or same-origin paths. Empty hides the link.
VITE_PRIVACY_URL empty
VITE_SECURITY_URL empty
SLACK_WEBHOOK_URL empty
TEAMS_WEBHOOK_URL empty

#Billing / usage metering (Veefore SaaS itself)

Variable Ships as Notes
USAGE_METERING_ENABLED true
USAGE_ROLLUP_INTERVAL_SECONDS 300

#Front ends (Vite — only VITE_* vars reach the browser; never put secrets here)

Variable Ships as Notes
VITE_CONSOLE_URL http://localhost:5173 VITE_* values are compiled into the bundle at build time. Changing one needs a rebuild and redeploy; restarting a server does nothing. There is deliberately no API base URL. The console calls /v1 and /scim as same-origin paths, proxied to the control plane by the edge (Vite in development, a Vercel rewrite in production). That is what keeps the session cookie host-only and SameSite=Lax rather than a cross-site cookie, and keeps CORS out of the browser's path. Adding one would undo all of it. Where the marketing site sends people to sign in, and where the console's "home" link goes. The two apps run on different ports locally and different origins in production. Production: https://console.veefore.com and https://veefore.com
VITE_MARKETING_URL http://localhost:5174
VITE_DOCS_URL http://localhost:4800 Where the marketing site's "Docs" links go. The docs are their own static site on their own origin, not a path on the marketing site. Production: https://docs.veefore.com (veefore.com/docs redirects there)
VEEFORE_CONSOLE_PORT 5173 Dev-server ports. Bound with strictPort, so a collision is a startup error rather than a silent move to another port. Change CORS_ALLOWED_ORIGINS to match the console's, or the browser refuses every request with no useful error.
VEEFORE_CONSOLE_PREVIEW_PORT 4173
VEEFORE_MARKETING_PORT 5174
VEEFORE_MARKETING_PREVIEW_PORT 4174
VEEFORE_ADMIN_PORT 5175
VEEFORE_ADMIN_PREVIEW_PORT 4175
VEEFORE_CONTROL_PLANE_URL empty Proxy target for the dev server. Defaults to CONTROL_PLANE_PORT on loopback.

#Documentation site (npm run docs:dev)

Variable Ships as Notes
VEEFORE_DOCS_PORT 4800 Serves docs/dist on the loopback interface only. It is unauthenticated content from a working tree and has no business being reachable remotely.

#Example downstream service (examples/real-tool-server.mjs)

Variable Ships as Notes
TOOL_SERVER_PORT 4500 A real HTTP service on loopback with its own API key, used to observe what actually reaches a downstream under each enforcement mode. The key belongs to the credential broker, never to the agent.
TOOL_SERVER_API_KEY downstream-secret-key

#Veefore-hosted AI code assist (veefore integrate --assist=veefore)

Variable Ships as Notes
VEEFORE_AI_PROVIDER empty Off unless a provider and key are set. Developers can always use their own key (--assist=own) or make the change by hand (--assist=manual) instead. Source sent here is used for the request only; it is never stored or logged.
VEEFORE_AI_API_KEY empty
VEEFORE_AI_MODELS empty Comma-separated model ids developers may pick from, e.g. the ids your provider's /v1/models lists.
VEEFORE_AI_BASE_URL empty Optional proxy in front of the provider.

#Automatic exchange rates (optional)

Variable Ships as Notes
VEEFORE_FX_PROVIDER_URL empty Off unless a URL is set; admins can always enter manual rates in Admin › Settings, and a manual rate always overrides an automatic one for the same pair. Veefore sends one GET to this URL per refresh (daily, and on demand) and nothing else: no tenant data is ever included. The response must be {"base":"USD","date":"YYYY-MM-DD","rates":{"INR":83.1,"EUR":0.92}} which is what frankfurter.app and exchangerate.host return, e.g. Alternative: VEEFORE_FX_PROVIDER_URL=https://api.frankfurter.app/latest?from=USD
VEEFORE_FX_PROVIDER_API_KEY empty Optional key, sent in the header named below.
VEEFORE_FX_PROVIDER_API_KEY_HEADER apikey
VEEFORE_FX_PROVIDER_TIMEOUT_MS 10000

Identity, authority, policy and enforcement for autonomous AI agents.