Skip to content

Configuration

All configuration comes from environment variables, read from .env in development and from the host's secret store in production. Settings are validated at startup; a missing key in live mode stops the service with a clear message instead of failing on the first user request.

Variable Default Required Meaning
MODEL_API_KEY yes, unless fixture mode Model API key from https://dev.meta.ai
OPL_KEY_PROBE 1 one GET /models at startup in live mode; a key that cannot work (401, 402, 403, 404) puts the service on recorded answers, said on /readyz; a busy or failing API changes nothing
MODEL_API_BASE_URL https://api.meta.ai/v1 Model API base URL
MODEL_SPARK muse-spark-1.3 Muse Spark model ID
MODEL_WONDER muse-wonder-1.0 Muse Wonder model ID; checked by ./dev check-api, never called in this version, and not listed by GET /api/version
MODEL_IMAGE muse-image-1.0 Muse Image model ID; draws a picture of what an item could become when PICTURE_ENABLED is set and the owner clicks
MODEL_VOICE muse-voice-transcribe-1.0 Muse Voice Transcribe model ID
MODEL_SAM sam-3.1 SAM model ID
OPL_FAKE_API 0 1 serves recorded fixtures and never calls the Model API
OPL_RECORD_FIXTURES 0 1 calls the Model API and saves every response as a fixture
FIXTURES_DIR backend/tests/fixtures/api where the recorded answers are read from in fixture mode and written to in record mode
DEMO_SET demo/set.yaml beside the backend the demo set whose photos are the samples the demo visitor clicks (GET /api/samples); the dev stack mounts demo/ at /demo, where the default finds it; the Modal image ships it at /root/demo and the Modal app sets DEMO_SET=/root/demo/set.yaml; a path with no set is an empty gallery
SPEND_CAP_USD 25 stop calling the Model API once the ledger reaches this amount
SPEND_LEDGER_FILE model_api_spend.jsonl ledger file name inside DATA_DIR
MODEL_API_TIMEOUT_S 60 per-request timeout
MODEL_API_MAX_ATTEMPTS 4 attempts for 429, 500 and 503 responses
MODEL_API_RETRY_AFTER_MAX_S 30 longest wait honoured from a Retry-After header
PROMPT_CACHE_NAME orphan-parts routing key sent with every call for prompt caching
EXTRACT_REASONING_EFFORT low reasoning effort for label extraction
MODEL_MAX_OUTPUT_TOKENS 0 ceiling on the max_output_tokens sent with a call: each operation's answer budget plus the room reasoning takes at its effort (1,024 at minimal, 4,096 at low, 8,192 at medium, 16,384 at high, 32,768 at xhigh), since reasoning tokens count against that cap; 0 = no ceiling. Tokens are billed as used, so the ceiling bounds the worst call, not the usual one
IMAGE_MAX_PX 1280 photos are downscaled to this long edge before upload
SEGMENT_MAX_PX 800 the copy SAM 3.1 sees is downscaled to this long edge; boxes are scaled back onto the photo
MAX_PHOTOS 4 photos accepted per item
PICTURE_ENABLED 0 1 lets the item dialog draw a picture of what an item could become: one Muse Image call per click ($0.01 an image), counted in the ledger, never automatic; off, the route answers 503 and the button is disabled; the hosted demo turns it on with the PICTURE_ENABLED repository variable
EXTRACT_ENHANCE 1 contrast stretched and edges sharpened on what goes to the model; what is kept is the photo as taken. Changing it changes what the model is shown, so fixtures recorded for a photo stop matching and the operation's fallback answers instead: record the set again
EXTRACT_BLUR_THRESHOLD 150 the sharpness (variance of the Laplacian on a 512 px copy) under which a reading warns that the label looks blurred
RETENTION_HOURS 24 reserved for a purge of stored photos; nothing but measurement photos is kept today, and no purge runs
CORS_ORIGINS http://localhost:5173 comma-separated browser origins allowed to call the API, named one by one (* is refused: calls carry credentials)
API_DOCS 1 serve the interactive API docs at /docs and the schema at /openapi.json; 0 makes both answer 404 (the hosted demo's secret sets it)
DATA_DIR ./data uploads, the spend ledger and the SQLite database
MAX_UPLOAD_MB 12 upload size limit per file
MAX_CLIP_MB 60 size limit for a clip sent with a capture
MAX_CAPTURES 200 how many captures the Inbox keeps at once; a send over it is refused until some are deleted
INVENTORY_DB_FILE inventory.sqlite3 the inventory database, inside DATA_DIR
SQLITE_JOURNAL_MODE WAL the database's journal mode; DELETE on a network filesystem (the Modal volume sets it)
MEASURE_MIN_SHARPNESS 150 least edge response (Laplacian variance) around every mark before a capture is measured
MEASURE_MIN_REFERENCE_PX 80 least span of the scale reference in pixels
VOICE_MAX_SECONDS 600 longest voice note accepted; the transcription service takes ten minutes at most, and MAX_UPLOAD_MB bounds the file first
PRICE_SEARCH_CONTEXT low how much of each page the search hands the model: low, medium or high; more costs tokens and time
PRICE_MAX_SEARCHES 4 how many web searches one price guidance may make (1 to 10), sent as max_tool_calls and said in the instructions; each search is billed per query and hands the model pages to read
PART_MAX_SEARCHES 4 how many web searches one Search the web for a part may make (1 to 10), sent as max_tool_calls and said in the instructions
PRICE_COUNTRY ISO 3166-1 country for local price results when the request names none
DEMO_ACCESS_CODE the master code of the live door, twelve characters at least; empty = no gate (a machine of your own). Codes issued as records (./dev keys) open the same door beside it; without a code a visitor is the demo
DEMO_SESSION_HOURS 12 how long a session lasts
PAIR_TOKEN_MINUTES 15 how long the pairing in a QR code lasts, 1 to 60: the time to scan it and claim the phone's code; while it lasts the QR is a live session, so it stays off a projector and out of a recording
PAIR_CODE_DAYS 30 how long the access code a phone claims on its first scan lasts, 1 to 3650; never past the code that paired it, and ended with it
PAIR_CODE_MAX 10 how many phones with current codes one code may have paired, 1 to 100; past that a claim is refused until ./dev keys revoke ends one or one runs out
DEMO_VISITOR_CALLS_PER_HOUR 20 calls that spend, per session and hour
DEMO_DAILY_CALLS 300 calls that spend, for everyone per UTC day
DEMO_CODE_ATTEMPTS 5 tries of the code per address and quarter hour
DEMO_REQUESTS_PER_MINUTE 120 requests of any kind per address and minute
DEMO_PAUSED 0 1 refuses every call that spends; /readyz says so
DEMO_TRUST_PROXY 0 read the caller's address from the forwarded header; the production compose file sets it
DEMO_COOKIE_SECURE 0 mark the session cookie Secure; set it on an https host
MARKETPLACE_SEARCH_URL https://www.facebook.com/marketplace/search/ the search page a request's handoff link opens, with the request's words and top price; the owner runs it
CANDIDATE_DAYS 14 how long a candidate's observation is trusted before it is too old
MARKETPLACE_CREATE_URL https://www.facebook.com/marketplace/create/item where Open Marketplace sends the owner with the draft copied; nothing is posted by the service
MEASURE_MIN_IMAGE_PX 480 least short side of a measurement photo
MEASURE_MARK_PX 1.5 marking precision per endpoint the uncertainty is derived from
LOG_FORMAT json json or text
LOG_LEVEL INFO Python log level

Keys are SecretStr values: they never appear in logs, error messages or repr().

Read by ./dev and the deploy scripts

These are not backend settings: ./dev and the scripts under scripts/ read them from the shell or .env. The backend container is handed .env as a whole and ignores them.

Variable Default Meaning
BACKEND_PORT, FRONTEND_PORT, DOCS_PORT 8000, 5173, 8001 the host ports of the development stack (Local development)
NATIVE 1 runs ./dev test and the live check on the host instead of in Docker
E2E_KEEP, E2E_VIDEO 1 leaves the stack up after ./dev e2e; on records every test (Testing)
DOCS_GIT_DATES true false builds the docs without the page dates, for a git worktree
LAN_ADDRESS the first address found this machine's address in the pairing code ./dev phone prints
ANDROID_HOME, ANDROID_SERIAL, ANDROID_AVD ~/Android/Sdk the Android SDK, which device to use, an emulator for the smoke run (Android app)
KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD the release key for the APK; without them the release build uses the debug key
LIVECHECK_ACCESS_CODE the access code ./dev live-check --against signs in with
DEPLOY_HOST, DEPLOY_PATH, IMAGE_PREFIX ./dev deploy onto a host over ssh (Deployment)
MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID the hosted demo's deploy credentials
MODEL_API_KEY_DEMO, DEMO_ACCESS_CODE_HOSTED, SPEND_CAP_USD_HOSTED, OPL_FAKE_API_HOSTED, PICTURE_ENABLED_HOSTED the hosted backend's key, code, cap, mode and picture switch, kept apart from this machine's
WEB_ORIGIN, PAGES_PROJECT, API_URL, OPL_MIN_CONTAINERS, HOSTED_DEPLOY see .env.example the origin browsers use for the hosted web app and its Pages project, the backend URL for a web-only deploy, whether the backend stays warm (1), and the switch that lets the deploy workflow run

BASETEN_API_KEY, FIREWORKS_API_KEY, FIREWORKS_BASE_URL and the SPARK_OPEN_* names in .env.example belong to a second model route that is planned and read by no code yet (Next steps).

Modes

OPL_FAKE_API OPL_RECORD_FIXTURES Mode Network Cost
1 any fixture none none
0 0 live Model API billed
0 1 record Model API, responses saved billed

GET /readyz reports the active mode.