Skip to content

Configuration Reference

Karthik P edited this page Oct 15, 2026 · 1 revision

Configuration Reference

Job Matcher is configured in two places. Environment variables, set in .env, configure each process at startup: the database, Redis, authentication, provider keys, scraping mode and LLM routing. Per-user settings, edited on the Settings page and stored in PostgreSQL, configure what each user's runs look for: resume, locations, experience, title filters, run size, triage, schedule and Discord alerts. This page lists every variable and every setting, with its default, when it is required, what it does, and which service reads it. For background on the scraping modes and credit rules, see Firecrawl Credits and Caching and AI Matching.

Where the values come from

Context File How it is loaded
Development .env at the repository root, copied from .env.example The API scripts run bun --env-file=../../.env (apps/api/package.json). Docker Compose also reads it for POSTGRES_PORT, COMPOSE_PROFILES and the SEARXNG_* values.
Self-hosted .env next to compose.yml, from deploy/.env.example deploy/compose.yml passes the whole file to migrate, api and worker with env_file: .env, and overrides DATABASE_URL and REDIS_URL. The web container gets no environment from .env.
CI Job-level env: in ci.yml See CI/CD and Releases.

How values are validated

  • apps/api/src/config.ts decodes the core variables with an Effect Schema when the module is first imported. Both the API (src/main.ts) and the worker (src/worker.ts) import it, so both processes exit at startup if DATABASE_URL, API_PORT, BETTER_AUTH_SECRET, BETTER_AUTH_URL or REDIS_URL is missing or empty, or if PROVIDERS is not live or fake.
  • Provider variables are read only by the worker, and only when PROVIDERS=live (providers/live.ts). The worker builds its provider layers when it boots (worker/effect-runtime.ts), so a missing key or bad value stops the worker at startup, not mid-run. The API process never reads them.
  • For the Firecrawl and OpenRouter routing variables, an empty value (a KEY= line) counts as unset and takes the default (scrape-router.ts, routing.ts). This does not apply to OPENROUTER_MODEL or the core variables, which must be non-empty if present.

Core

Variable Default Required when Effect Read by
DATABASE_URL none (dev example: postgresql://postgres:postgres@localhost:5432/job_matcher) Always PostgreSQL connection string for Prisma (@prisma/adapter-pg). Also read by prisma.config.ts for migrations. In deploy/compose.yml it is set from POSTGRES_PASSWORD and need not be in .env. Playwright appends _e2e to its database name. API, worker, Prisma CLI, Playwright
API_PORT none (examples: 4000) Always Port the NestJS API listens on. Parsed as a number. The production api healthcheck and the web image's API_URL both assume 4000, so leave it at 4000 in the self-hosted stack. API, worker (validated only)
BETTER_AUTH_SECRET none Always Secret better-auth uses to sign session cookies and tokens. Use a long random value, for example openssl rand -hex 32. Changing it signs every user out. API, worker (validated only)
BETTER_AUTH_URL none (dev: http://localhost:3000) Always The exact origin users open, passed to better-auth as baseURL. Sign-in and sign-up from any other origin fail with an invalid origin error. Must include the port, and the scheme (https:// behind a TLS proxy). Also the origin of the run-page links in Discord alerts (run-page-url.ts). API, worker
REDIS_URL none (dev: redis://localhost:6379) Always Redis connection for BullMQ and the health check. Set to redis://redis:6379 by deploy/compose.yml. API, worker
PROVIDERS live in code; fake in deploy/.env.example; live in .env.example Optional live uses Firecrawl, OpenRouter and Discord. fake swaps all three for fixtures (demo mode, tests). See Glossary: fake providers. API (validated), worker

Self-hosted stack

These are read by Docker Compose itself, from .env next to deploy/compose.yml.

Variable Default Required when Effect Read by
POSTGRES_PASSWORD none Always (Compose fails with "Set POSTGRES_PASSWORD in .env") Password of the postgres user, used to initialise the database volume and to build DATABASE_URL for migrate, api and worker. It only takes effect when the volume is first created, so changing it later locks the app out of an existing database. Compose (postgres, migrate, api, worker)
WEB_PORT 8080 Optional Host port published for the web container (${WEB_PORT}:3000). The installer picks the first free port from 8080. Change BETTER_AUTH_URL to match. Compose (web)
JOB_MATCHER_VERSION latest Optional Image tag for ghcr.io/karthik-beta/job-matcher-api and -web, for example 1.0.0. The installer writes the release it installed. Compose

The installer itself reads JOB_MATCHER_DIR, JOB_MATCHER_VERSION, JOB_MATCHER_REF and JOB_MATCHER_NONINTERACTIVE from your shell; see Getting Started.

OpenRouter (live mode)

Variable Default Required when Effect Read by
OPENROUTER_API_KEY none PROVIDERS=live (worker exits without it) Key for every LLM call: evaluation, listing extraction and title triage. A management or provisioning key is refused by the run's preflight. Worker
OPENROUTER_MODEL openai/gpt-6-luna:floor Optional Any OpenRouter model id. Part of each run's context key, so changing it re-evaluates jobs. CI uses nvidia/nemotron-3-super-120b-a12b:free. API (stored in the run's context key), worker
OPENROUTER_SERVICE_TIER flex Optional One of auto, default, flex, priority, scale. With flex, preflight requires a flex endpoint, and a reply served on another tier is logged as a warning. Worker
OPENROUTER_PROVIDERS openai Optional Comma-separated providers a request may be routed to. Worker
OPENROUTER_MAX_PRICE_PROMPT 0.05 Optional Price cap in USD per million prompt tokens (OpenRouter provider.max_price). Worker
OPENROUTER_MAX_PRICE_COMPLETION 0.25 Optional Price cap in USD per million completion tokens. Worker
OPENROUTER_REASONING_EFFORT low Optional One of max, xhigh, high, medium, low, minimal, none. Worker

An invalid value for a routing variable stops the worker at startup with a schema decode error. See AI Matching for preflight and the flex back-off schedule.

Firecrawl (live mode)

Variable Default Required when Effect Read by
FIRECRAWL_MODE cloud Optional cloud (every scrape on Firecrawl's cloud), local (a self-hosted Firecrawl, no credits) or hybrid (local first, cloud only for blocked pages). Any other value stops the worker: "FIRECRAWL_MODE must be cloud, local or hybrid, and FIRECRAWL_CLOUD_PROXY basic or auto." In demo mode it also picks which fake instances are used. Worker
FIRECRAWL_API_KEY none cloud and hybrid Firecrawl cloud key. Missing: "FIRECRAWL_MODE=cloud needs FIRECRAWL_API_KEY." Worker
FIRECRAWL_LOCAL_URL none (.env.example: http://localhost:3002) local and hybrid Base URL of the self-hosted Firecrawl; trailing slashes are removed. Must be http or https. Missing: "FIRECRAWL_MODE=local needs FIRECRAWL_LOCAL_URL (e.g. http://localhost:3002)." Worker
FIRECRAWL_CLOUD_PROXY basic Optional Cloud mode's proxy: basic, or auto (retries blocked pages with enhanced proxies). Hybrid mode always uses auto for its cloud fallback. Worker
FIRECRAWL_CREDIT_RESERVE 50 Optional (cloud, hybrid) Cloud credits every run leaves untouched on the plan. Must be a whole number: "FIRECRAWL_CREDIT_RESERVE must be a whole number of credits, got ...". Scheduled runs also keep to an even daily share of what is above it. See Glossary: credit reserve. Worker
FIRECRAWL_LOCAL_SEARCH false Optional (local) true when the self-hosted Firecrawl has SEARXNG_ENDPOINT set, so search sources can run in local mode. Only true or false (case-insensitive) are accepted. Without it, search sources are skipped in local mode with a warning. Worker

Development-only variables

Variable Default Required when Effect Read by
POSTGRES_PORT 5432 Optional Host port for the dev Postgres container in docker-compose.yml. Update the port in DATABASE_URL to match. Redis is always published on 6379. Compose (dev)
COMPOSE_PROFILES unset (commented out in .env.example) To run a self-hosted Firecrawl in dev firecrawl makes docker compose up (and so bun run dev) also start the Firecrawl services on port 3002. Compose (dev)
SEARXNG_ENDPOINT empty Optional Passed to the dev firecrawl-api container. A SearXNG instance with JSON output, used for search in local mode. Empty means Firecrawl falls back to scraping DuckDuckGo, which the app does not rely on. Compose (dev firecrawl-api)
SEARXNG_ENGINES empty Optional Passed to firecrawl-api. Compose (dev firecrawl-api)
SEARXNG_CATEGORIES empty Optional Passed to firecrawl-api. Compose (dev firecrawl-api)

Web app

Variable Default Required when Effect Read by
API_URL http://localhost:4000 in dev; build argument http://api:4000 in the Docker image Optional Where the Next.js rewrite sends /api/:path* (next.config.ts). Rewrites are resolved at build time, so in the image it is a build argument (--build-arg API_URL=...); setting it on a running container has no effect. Web (build time)
NODE_ENV production in both images Set by the Dockerfile Standard Node/Next.js production mode. API, web
NEXT_TELEMETRY_DISABLED 1 in the web image Set by the Dockerfile Turns off Next.js telemetry. Web

The browser never needs the API's address: apps/web/lib/api.ts calls relative /api/... paths, which the web server proxies. This keeps the session cookie first-party. See Frontend.

Test and CI variables

Variable Default Effect Read by
CI unset In playwright.config.ts: one retry per test, an HTML report, a production build of the web app instead of next dev, and no reuse of a running web server. Playwright
PROVIDERS, DATABASE_URL set by Playwright The e2e API and worker get PROVIDERS=fake and DATABASE_URL with _e2e appended to the database name. Playwright
FIRECRAWL_API_KEY, OPENROUTER_API_KEY, FIRECRAWL_MODE deleted apps/api/test/setup.ts deletes them and sets PROVIDERS=fake before any test file loads. API tests

See Testing.

Variables by file

Variable .env.example deploy/.env.example deploy/compose.yml docker-compose.yml
DATABASE_URL yes no (built by compose) set for migrate/api/worker no
POSTGRES_PORT yes no no yes
POSTGRES_PASSWORD no yes (required) yes no (fixed postgres)
API_PORT yes yes no no
BETTER_AUTH_SECRET, BETTER_AUTH_URL yes yes (required) via env_file no
REDIS_URL yes no (built by compose) set no
WEB_PORT, JOB_MATCHER_VERSION no yes yes no
PROVIDERS yes (live) yes (fake) via env_file no
OPENROUTER_* yes yes via env_file no
FIRECRAWL_MODE, FIRECRAWL_LOCAL_URL, FIRECRAWL_LOCAL_SEARCH yes no (cloud only by default; may be added) via env_file no
FIRECRAWL_API_KEY, FIRECRAWL_CLOUD_PROXY, FIRECRAWL_CREDIT_RESERVE yes yes via env_file no
COMPOSE_PROFILES, SEARXNG_* commented no no yes

The self-hosted stack has no Firecrawl services. To use local or hybrid mode there, run a Firecrawl instance separately and add FIRECRAWL_MODE and FIRECRAWL_LOCAL_URL to .env. The URL must be reachable from inside the worker container, so localhost does not work; use a host name or address the container can reach.

User settings stored in the database

Each user has one Settings row (prisma/schema.prisma), created on the first save. Until then the API returns defaultSettings from packages/shared/src/index.ts. The Settings page saves with PUT /api/settings (settings.controller.ts); the body is validated by the shared UpdateSettings schema, so the web form and the API enforce the same rules.

Field Settings section and label Default Validation Effect
resumeUrl Resume: "Resume URL" null null or an http/https URL The resume every job is checked against: a public PDF (Google Drive and Dropbox share links work) or a page. A run cannot start without it ("Add a resume URL in settings before starting a run"). Its content hash is part of the context key, so a new file at the same URL is evaluated again.
experienceYears Matching: "Experience in years" 0 Integer, 0 or more Your experience. A job whose minimum experience exceeds years plus months is filtered out.
experienceMonths Matching: "And months" 0 Integer 0 to 11 ("Months must be between 0 and 11") Added to years as a fraction of 12.
targetMinYears Matching: "Target minimum years" null null or a number 0 or more Drops jobs whose maximum experience is below it.
targetMaxYears Matching: "Target maximum years" null null or a number 0 or more, at least targetMinYears ("Maximum years must be at least the minimum") Drops jobs whose minimum experience is above it.
locations Matching: "Cities" (comma-separated, e.g. Bengaluru, Remote) [] Array of strings When not empty, a job must mention one of these cities as a whole word in one of its locations (case-insensitive). Empty means any location.
includeUnknown Matching: "Include listings with unknown experience or location" true Boolean Whether a job that states no location (when cities are set) or no experience range (when a target range is set) is kept. A job page is read before it is excluded for missing details.
titleInclude Matching: "Job titles must include" [] Up to 30 keywords, each 1 to 60 characters after trimming When not empty, a title must contain one of these words or phrases, matched as whole words.
titleExclude Matching: "Job titles must not include" [] Same as titleInclude A title must contain none of these.
maxJobsPerRun Runs: "Jobs to check per run" 5 Integer 1 to 20 ("Jobs per run must be between 1 and 20") The run size: how many jobs one run reads and evaluates. Jobs already evaluated in the same context are skipped, so the next run continues with the rest.
triageEnabled Runs: "Rank job titles before checking jobs" true Boolean Title triage: one model request per 150 titles scores each title 0 to 3 against the resume, and only titles scoring 2 or more are read, best first. See Glossary: triage.
scheduleMinutes Schedule & alerts: "Check every N minutes" null null or an integer of 15 or more ("Use 15 minutes or more") Scheduled checks. Saving creates or removes a BullMQ job scheduler (user:<userId>) that starts a SCHEDULED run over all of the user's sources every N minutes. Empty means manual runs only.
discordWebhookUrl Schedule & alerts null null or an https URL on discord.com, discordapp.com, ptb.discord.com or canary.discord.com with a path under /api/webhooks/ ("Enter a Discord webhook URL") Each saved match is posted to this webhook. A failed post is logged and never fails the job. A stored value that no longer passes the check shows "Alerts are not being sent" on the form.

Two more columns hold onboarding state and are not editable on the form: onboardingCompletedAt (set when the wizard is finished or the first run starts) and onboardingDismissedAt (set when the setup checklist is hidden). They are changed through POST /api/onboarding/complete, /dismiss and /restore. See Data Model.

Per-source settings

Each JobSource row has its own settings (schema.prisma, UpdateJobSource in packages/shared):

Field Default Editable in the UI Effect
url none Set when adding The board, careers page, or for a search a generated URL. Unique per user.
kind Detected from the URL (PAGE otherwise) Yes (PATCH /api/sources/:id) GREENHOUSE, LEVER, ASHBY, SMARTRECRUITERS, RECRUITEE, PAGE; SEARCH only through POST /api/sources/search. A source cannot be switched to SEARCH. Changing the kind clears its cached listings.
adapterKey Detected Yes ("Board name") The ATS board token or company slug. Required for ATS kinds; letters, numbers, dots, dashes and underscores.
useMap false Yes, page sources only Read job links from Firecrawl's site map (1 credit) before the page, falling back to the page.
query null Set when adding a search The search text, 1 to 200 characters.
selector null No (database only) CSS selector limiting which part of a page source is read for job links.
maxPages 3 No (database only) How many "next" pages of a page source's listings to follow, counting the first.
suggestedKind, suggestedAdapterKey null Shown as a suggestion An ATS board found embedded in a page source, offered as a switch.

Changing configuration

Change Development Self-hosted
An environment variable Edit .env and restart bun run dev; --watch restarts the API and worker on source changes, and .env is read when the process starts. Edit .env and run docker compose up -d, which recreates the containers whose configuration changed.
A user setting Settings page; takes effect on the next run. Same.
API_URL Restart next dev. Rebuild the web image with --build-arg API_URL=....

Clone this wiki locally