Skip to main content

Configuration reference

The backend is configured through environment variables, loaded by backend/app/core/config.py (Pydantic settings) from backend/.env. Variable names are the field names in upper case. The frontend reads a small set of NEXT_PUBLIC_* build-time variables. This page lists them all.

Start from the example

Copy backend/.env.example to backend/.env and fill it in. Never commit .env.

AI services

VariableDescriptionDefaultRequired
OPENROUTER_API_KEYVision and analysis via OpenRouter (Claude). The pipeline's main AI.emptyYes
DEEPGRAM_API_KEYTranscription via Deepgram Nova-2. Without it, inventories are visual-only.emptyYes (for narration/audio)
ANTHROPIC_API_KEYAnthropic key. Present for compatibility; vision goes through OpenRouter.emptySituational
OPENROUTER_BASE_URLOpenRouter API base.https://openrouter.ai/api/v1No
OPENROUTER_MODELThe vision/analysis model id.anthropic/claude-opus-4-5No
OPENROUTER_TEMPERATURESampling temperature for every AI call. Pinned to 0 so repeated uploads of the same video/photos return the same items and names (AS-719). Raise only to trade repeatability for variety.0No
OPENROUTER_CONCURRENCYMax simultaneous AI calls per job.6No
OPENROUTER_GLOBAL_CONCURRENCYMax simultaneous AI calls across all in-flight jobs in one process (a shared limiter on top of the per-job cap, so concurrent jobs don't collectively trip the provider rate limit). Keep at or below the httpx max_connections (20).16No
CLAUDE_MODELModel id reported by /health.claude-opus-4-7No
DEEPGRAM_MODELTranscription model.nova-2No

AdjustSquare SSO (login)

VariableDescriptionDefaultRequired
ADJUSTSQUARE_JWT_SECRETShared HS256 secret used to validate login tokens. If unset, all logins fail.emptyYes
ADJUSTSQUARE_LOGIN_URLWhere unauthenticated users are redirected to sign in.https://demo.adjustsquare.com/loginYes
ADJUSTSQUARE_COOKIE_NAMECookie name AdjustSquare sets on shared subdomains.bt_shared_dataNo
ADMIN_EXTERNAL_USER_IDSAllowlist of Adjust Square user IDs (matched against users.external_id) that may see the internal admin dashboard. JSON list, e.g. [7, 10].{7, 10} (Dan, Nathan)No

Adjust Square API (claim submission)

VariableDescriptionDefaultRequired
ADJUST_SQUARE_BASE_URLBase URL of the Adjust Square API. One host per deployment.emptyYes (to submit)
ADJUST_SQUARE_BEARER_TOKENOptional global Adjust Square API token, used only as a fallback for a call made without a team token. Submission authenticates as the signed-in user's team.team_token (from the SSO JWT), so this is not required and is not the configured-gate.emptyNo (optional fallback)
ADJUST_SQUARE_APP_URLThe Adjust Square web app origin (distinct from the API base above), used only to deep-link a submitted claim into its assignment: {app_url}/lkq/{claim_key}. Empty renders the claim key as plain text (no link).emptyNo
Per-user auth

Claim submission authenticates as the signed-in user's team (team.team_token, captured from the SSO JWT's current_team_token) and attributes the claim to the user's user.external_id (see the Adjust Square integration), both read from the database at submit time. Only ADJUST_SQUARE_BASE_URL must be set for the integration to count as "configured"; ADJUST_SQUARE_BEARER_TOKEN is an optional fallback for calls made without a team token and is not required. There is no longer a shared adjuster-id environment variable (the old ADJUST_SQUARE_ADJUSTER_ID was removed; the adjuster is the signed-in user).

Contents Estimation (claim push)

Contents Estimation is the clean-stack rebuild of Adjust Square. A finished claim can be pushed to it in one call, which creates the claim there and starts pricing. This is a separate action from the Adjust Square submission above; both can be used independently on the same claim.

VariableDescriptionDefaultRequired
CONTENTS_ESTIMATION_BASE_URLBase URL of the Contents Estimation API. One host per deployment. Prod: https://api-production-36ce3.up.railway.app. Local: http://localhost:8000.emptyYes (to send)
CONTENTS_ESTIMATION_API_KEYOptional global fallback intake key (<key_id>.<secret>, cek_...), used only for a request whose team has no per-team key yet. Auth is normally per-team (Team.contents_estimation_key, from the SSO JWT). Mirrors ADJUST_SQUARE_BEARER_TOKEN; not required and not the configured-gate.emptyNo (optional fallback)
CONTENTS_ESTIMATION_APP_URLThe Contents Estimation web app origin (distinct from the API base above), used only to deep-link a pushed claim: {app_url}/claims/{claim_uuid}.https://contentsestimation.comNo
PUBLIC_BASE_URLContentsVision's own public origin (e.g. https://app.contentsvision.com), used to turn item photo paths (/api/photos/...) into absolute URLs, since the intake takes photos as URLs, not bytes. Empty pushes the inventory without photos.emptyNo
Per-team auth

Only CONTENTS_ESTIMATION_BASE_URL must be set for the integration to count as "configured" (settings.contents_estimation_configured), exactly like ADJUST_SQUARE_BASE_URL. The intake key is per-team: captured from the AdjustSquare SSO JWT's current_team_ce_intake_key claim into Team.contents_estimation_key and resolved from the signed-in user's team at submit, so each team's claims land under its own Contents Estimation tenant. CONTENTS_ESTIMATION_API_KEY is only a fallback for a team with no key yet. A team with neither gets a clear 409. See the Contents Estimation integration.

Contents Capture (media import)

VariableDescriptionDefaultRequired
CONTENTS_CAPTURE_API_BASE_URLBase URL of the Contents Capture API. Empty disables the integration entirely (the Integrations tab reports it unavailable). Dev/stage: https://contents-capture-api.dev-908.workers.dev.emptyYes (to import)
CONTENTS_CAPTURE_WEBHOOK_SECRETHMAC secret for Contents Capture webhooks. Optional; webhooks are a later phase.emptyNo
CONTENTS_CAPTURE_IMPORT_CONCURRENCYParallel media downloads per import run.4No
Per-team tokens

The org integration token (cci_..., minted by a Capture org admin) is pasted by a team under Settings, then Integrations, and stored on that team's storage_connections row. It is not an environment variable. See the Contents Capture integration.

Storage and database

VariableDescriptionDefaultRequired
DATABASE_URLSQLAlchemy async DB URL.sqlite+aiosqlite:///./storage/inventory.dbNo
UPLOAD_DIRWhere raw uploads are streamed../uploadsNo
STORAGE_DIRPer-job files (frames, photos, crops), the SQLite DB, and the settings JSON files../storageNo
MAX_UPLOAD_SIZE_MBPer-file upload size cap for video/audio (returns 413 if exceeded).2000No
MAX_PHOTO_UPLOAD_MBRunning-total cap for one photo job across all its batched requests. Far higher than a single video because one room can hold hundreds of photos (a 757-photo phone upload is ~3GB).8000No
STUCK_JOB_MINUTESMinutes a processing job's row may go without an update before the recovery watchdog requeues (or fails) it. See troubleshooting.15No

Pipeline tuning

VariableDescriptionDefault
FRAME_EXTRACTION_FPSFrames extracted from video per second.1.0
CHUNK_DURATION_SECONDSLength of each analyzed time chunk.30.0
FRAMES_PER_MINUTETarget frame density per chunk.60
MAX_FRAMES_PER_CALLHard cap on frames per vision call.60
VIDEO_DEDUP_WINDOW_SECONDSBoundary window for cross-chunk dedup.3.0
MAX_PHOTOS_PER_CALLPhotos per call in the legacy batched photo pipeline.8
MAX_PHOTOS_PER_ITEMHard cap on photos grouped into one item.20
MAX_FRAMES_PER_ROOMLegacy, unused after the time-chunk pipeline.8

These defaults are tuned together; see the AI pipeline before changing them.

Billing rates (credits)

VariableDescriptionDefault
COST_PER_VIDEO_MINUTECredits per minute of video.10
COST_PER_AUDIO_MINUTECredits per minute of audio.3
COST_PER_PHOTO_REQUESTCredits per photo AI request (one per group).1
COST_PER_CONTENTS_ESTIMATION_ITEMCredits charged when a claim is sent to Contents Estimation, per active item (charged once per claim, at send). Only applies with ENABLE_BILLING on.1
DEFAULT_TEAM_CREDIT_RATEFallback dollars of revenue per credit, used for any team with no per-team Team.credit_rate. Feeds the Revenue field of the Slack notification.0.15

Prepaid billing (external service)

Optional, instance-wide. Off by default; invoiced / enterprise instances leave it off and the app only meters usage. When on, uploads are gated on a team's prepaid balance held by an external billing service, and completed jobs are charged against it. See billing.

VariableDescriptionDefault
ENABLE_BILLINGMaster switch for the prepaid balance gate + charge.false
BILLING_APP_DOMAINBase URL of the external billing service (no trailing slash).(empty)
BILLING_APP_API_KEYBearer key for the billing service.(empty)
BILLING_EXTERNAL_APPIdentifier sent to the billing service as external_app.contentsvision

When ENABLE_BILLING is true but the domain/key are missing, the integration degrades to fail-open (no balance read, no charge) and logs a warning, so a misconfigured instance never blocks uploads.

Product analytics (PostHog)

Optional. Off by default: with no key the backend no-ops and the frontend ships with no tracking, so neither touches the network. See analytics.

VariableDescriptionDefaultRequired
POSTHOG_API_KEYBackend project key (phc_…). Enables server-side capture of every user action and job outcome. Empty = off.emptyNo
POSTHOG_HOSTBackend ingestion host: https://us.i.posthog.com, https://eu.i.posthog.com, or a self-hosted URL.https://us.i.posthog.comNo
NEXT_PUBLIC_POSTHOG_KEYFrontend project key (use the same project as the backend). Enables page views, autocapture, session replay, and identify. Build-time (see below). Empty = off.emptyNo
NEXT_PUBLIC_POSTHOG_HOSTFrontend ingestion host. App defaults to US Cloud when blank. Build-time.empty (US Cloud)No

The backend keys are read at runtime (a restart picks up a change). The two NEXT_PUBLIC_* keys are public and build-time: compose passes them to the frontend image as build args and Next.js inlines them into the bundle, so changing them requires a rebuild, not just a restart.

Slack notifications (media processing)

Optional. Off by default: with no webhook the backend no-ops and never calls out. When set, a message is posted to a Slack channel every time a media-processing job finishes (success and failure), carrying company, user, media type, credits used, and revenue (credits used × the team credit rate). See billing.

VariableDescriptionDefaultRequired
SLACK_WEBHOOK_URLSlack Incoming Webhook URL (https://hooks.slack.com/services/…). Empty = off.emptyNo
DEFAULT_TEAM_CREDIT_RATEFallback dollars per credit for the Revenue field (see billing rates); a per-team Team.credit_rate overrides it.0.15No

Read at runtime, so a restart picks up a change.

Networking

VariableDescriptionDefault
CORS_ORIGINSComma-separated allowed origins (no trailing slash).http://localhost:3000

Error tracking (Sentry)

Sentry is off until a DSN is set, on both halves of the stack, so local dev and the test suite never ship anything. See observability for what reports from where.

VariableDescriptionDefaultRequired
SENTRY_DSNBackend DSN (API process and ARQ worker). Empty = error tracking off.emptyNo
SENTRY_TRACES_SAMPLE_RATEBackend performance tracing sample rate. 0.0 keeps tracing off (OTel owns tracing); errors still report.0.0No
NEXT_PUBLIC_SENTRY_DSNFrontend DSN. Public and build-time: passed as a compose build arg and baked into the bundle, so changing it needs a frontend rebuild. Empty = off.emptyNo
SENTRY_AUTH_TOKENOptional, build-time. When present the frontend build uploads source maps so stack traces are readable. Needs SENTRY_ORG and SENTRY_PROJECT alongside it.emptyNo

Observability (OpenTelemetry)

Telemetry (traces, metrics, logs to the Grafana/Loki/Prometheus/Tempo stack) is off until OTEL_ENABLED=true and an endpoint are both set, so local dev and the test suite never ship off-box. config.py reads OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT, APP_ENV, and COMMIT_SHA; the rest are read by the OpenTelemetry SDK and the frontend build directly. See observability for what flows where.

VariableDescriptionDefaultRequired
OTEL_ENABLEDMaster switch for backend telemetry.falseNo
OTEL_EXPORTER_OTLP_ENDPOINTOTLP HTTP collector URL. One endpoint receives all three signals; the SDK appends /v1/traces, /v1/metrics, /v1/logs.emptyWhen telemetry is on
OTEL_EXPORTER_OTLP_PROTOCOLOTLP transport. Keep http/protobuf: it traverses proxies and Railway TLS more reliably than gRPC.http/protobufNo
OTEL_EXPORTER_OTLP_HEADERSCollector auth, comma-separated key=value (e.g. Authorization=Basic <b64>).emptyIf the collector requires auth
OTEL_SERVICE_NAMEOverrides the default contentsvision-backend service name.contentsvision-backendNo
NEXT_PUBLIC_OTEL_ENABLEDMaster switch for browser telemetry. Baked at build time.falseNo
NEXT_PUBLIC_OTEL_BROWSER_ENDPOINTSame-origin path the browser posts OTLP to, forwarded to the collector by the backend./api/telemetryNo
APP_ENVStamped on all telemetry as deployment.environment.localNo
COMMIT_SHAStamped on all telemetry as service.version.devNo

Deployment and frontend (env-only, not in config.py)

VariableDescriptionWhere
APP_ENVproduction, stage, or local. Drives the env badge and title.compose / build arg
NEXT_PUBLIC_APP_ENVThe above, exposed to the frontend at build time. Also gates the imgproxy thumbnail CDN: when unset it defaults to production, so a stage build that forgets it enables the prod CDN and breaks thumbnails (see imgproxy rows below). The stage and prod compose files set it explicitly.frontend build
NEXT_PUBLIC_IMGPROXY_ENABLEDtrue/false to force the thumbnail CDN on or off. Unset = on only when NEXT_PUBLIC_APP_ENV is production.frontend build arg
NEXT_PUBLIC_IMGPROXY_URLimgproxy base host for server-resized thumbnails.frontend build arg (default https://cdn.adjustsquare.com)
NEXT_PUBLIC_PHOTO_PUBLIC_ORIGINPublic origin imgproxy prefixes onto relative /api/photos paths so it can fetch the source. Must point at the origin that actually serves this environment's photos.frontend build arg (default https://contentsvision.com)
NEXT_PUBLIC_API_URLWhere the frontend proxies /api (e.g. http://backend:8000). Must be a build arg: the standalone build freezes the rewrite destination at build time, so a runtime-only value is ignored.frontend build arg
NEXT_PUBLIC_POSTHOG_KEYFrontend PostHog project key. Build arg (inlined into the bundle). Empty = analytics off. See analytics.frontend build arg
NEXT_PUBLIC_POSTHOG_HOSTFrontend PostHog ingestion host. Build arg. Defaults to US Cloud in app code.frontend build arg
CLOUDFLARE_TUNNEL_TOKENToken for the cloudflared service.prod compose
COMMIT_SHA / COMMIT_TIMEOptional build metadata baked into the frontend.build args

How settings are loaded

config.py reads .env once at import, exposing a settings singleton. Auto-approval rules and AI prompt presets are not environment variables; they are stored as JSON files and edited from the Settings page. They are scoped per team, one file per team under STORAGE_DIR/team_settings/<team_id>/ (automation_settings.json, prompt_settings.json). See automation and prompts.