# Configuration (/docs/configuration)



OpenOpps loads settings from `OPENOPPS_` environment variables and an optional local `.env` file. Unknown `.env` keys are ignored. Treat configuration as local runtime wiring: the CLI writes to the SQLite URL you point it at and reads plugins from the Python environment that launched the command.

## Invocation Scope [#invocation-scope]

Both invocation styles use the same settings model:

```bash
uv run openopps status
uv tool install -e .
openopps status
```

The editable `uv tool install -e .` path makes the `openopps` command available directly from the current checkout. Relative SQLite URLs such as `sqlite:///openoppsdb.sqlite` are resolved by the running process, so keep your working directory and `OPENOPPS_DB_URL` explicit when switching between projects or smoke-test databases.

## Environment Variables [#environment-variables]

| Variable                               | Default                                 | Purpose                                                                                                                                                                                                                                                       |
| -------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENOPPS_DB_URL`                      | `sqlite:///openoppsdb.sqlite`           | Database URL used by storage commands and sync jobs.                                                                                                                                                                                                          |
| `OPENOPPS_MAX_CONNECTIONS`             | `40`                                    | Maximum HTTP connection pool size.                                                                                                                                                                                                                            |
| `OPENOPPS_SOURCE_CONCURRENCY`          | `4`                                     | Source adapter concurrency during source sync and provider health checks, not the isolated scout.                                                                                                                                                             |
| `OPENOPPS_SOURCE_TIMEOUT_SECONDS`      | `900.0`                                 | Maximum seconds one source adapter can run before timed-out skip.                                                                                                                                                                                             |
| `OPENOPPS_SOURCE_FRESHNESS_SECONDS`    | `0.0`                                   | Skip recently synced source catalogs during unscoped full-sync retries.                                                                                                                                                                                       |
| `OPENOPPS_BOARD_CONCURRENCY`           | `16`                                    | Concurrent ready board routes and board-scoped listing/detail work during job sync, route health checks, and providers that fan out across job pages.                                                                                                         |
| `OPENOPPS_JOB_ROUTE_TIMEOUT_SECONDS`   | `180.0`                                 | Maximum seconds one executable provider route may run during job sync before OpenOpps records a timeout and continues with remaining routes.                                                                                                                  |
| `OPENOPPS_JOB_ROUTE_FRESHNESS_SECONDS` | `0.0`                                   | Skip recently synced provider routes during job sync when above `0`; `0` refreshes every selected route.                                                                                                                                                      |
| `OPENOPPS_JOB_ROUTE_LIMIT`             | unset                                   | Cap stale or never-synced routes processed in one job sync; unset processes every selected route.                                                                                                                                                             |
| `OPENOPPS_PROVIDER_CONCURRENCY`        | `12`                                    | Concurrent provider route probes when OpenOpps detects executable job-board routes (for example during `admin providers probe-routes`). Not job-fetch parallelism.                                                                                            |
| `OPENOPPS_CONCURRENCY_PROFILE`         | `explicit`                              | `explicit` keeps the integer knobs on this page. `auto` / `ci` / `constrained` may size JSON-board source, board, and provider pools from available CPUs, but they never raise Workday or discovery per-host caps. Explicit `OPENOPPS_*` integers always win. |
| `OPENOPPS_WORKDAY_CONCURRENCY`         | `2`                                     | Conservative limit for public Workday CXS careers-site requests. Profiles never raise this value.                                                                                                                                                             |
| `OPENOPPS_DB_BATCH_SIZE`               | `500`                                   | Batch size for SQLite writes.                                                                                                                                                                                                                                 |
| `OPENOPPS_HTTP_TIMEOUT`                | `30.0`                                  | HTTP timeout in seconds.                                                                                                                                                                                                                                      |
| `OPENOPPS_RETRY_ATTEMPTS`              | `3`                                     | Number of retry attempts for retriable upstream requests.                                                                                                                                                                                                     |
| `OPENOPPS_USER_AGENT`                  | `openopps/0.1 openopps@wyattowalsh.com` | User agent sent to public source and provider endpoints.                                                                                                                                                                                                      |
| `OPENOPPS_CACHE_ENABLED`               | `true`                                  | Enables the shared JSON request cache in the SQLite database.                                                                                                                                                                                                 |
| `OPENOPPS_CACHE_TTL_SECONDS`           | `3600`                                  | Default cache freshness window in seconds.                                                                                                                                                                                                                    |
| `OPENOPPS_CACHE_REFRESH`               | `false`                                 | Bypasses cache reads while still updating successful responses.                                                                                                                                                                                               |
| `OPENOPPS_CACHE_STALE_ON_ERROR`        | `false`                                 | Allows eligible stale cache data after retryable upstream errors.                                                                                                                                                                                             |
| `OPENOPPS_PLUGIN_AUTOLOAD`             | `false`                                 | Execute every discovered plugin entry point without allow-listing.                                                                                                                                                                                            |
| `OPENOPPS_PLUGIN_DISABLED`             | empty                                   | Comma-separated plugin entry-point names to skip.                                                                                                                                                                                                             |
| `OPENOPPS_PLUGIN_ALLOWED`              | empty                                   | Comma-separated plugin entry-point names allowed to execute.                                                                                                                                                                                                  |
| `OPENOPPS_NO_INTRO`                    | unset                                   | Suppress the interactive startup portal animation.                                                                                                                                                                                                            |

### Isolated discovery scout [#isolated-discovery-scout]

The isolated scout uses a separate settings model (`DiscoverySettings` in `src/openopps/discovery/settings.py`). `OPENOPPS_DISCOVERY_*` budget fields are not part of `OpenOppsSettings`, do not load `.env`, and do not change `openopps sync`. `OPENOPPS_CONCURRENCY_PROFILE` never derives `OPENOPPS_DISCOVERY_PER_HOST_CONCURRENCY`. Values must be canonical positive decimals. Invalid values fail closed without echoing the input.

Scout output is an explicit directory, not the operational database. `openopps discovery scout --output /absolute/quarantine-root --json` (also `openopps admin sources scout`) writes one evaluation quarantine bundle under that required `--output` path. There is no scout-output environment variable. `OPENOPPS_DB_URL` still names the CLI SQLite ledger for sync, cache, and `admin db` commands; the scout does not open or mutate that file, catalogs, Git, Kaggle, or Cloudflare.

| Variable                                          | Default    | Purpose                                                                                            |
| ------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------- |
| `OPENOPPS_DISCOVERY_WHOLE_RUN_TIMEOUT_SECONDS`    | `300`      | Whole isolated invocation wall-clock limit (max `3600`).                                           |
| `OPENOPPS_DISCOVERY_CHANNEL_TIMEOUT_SECONDS`      | `120`      | Per-channel wall-clock limit (max `1800`).                                                         |
| `OPENOPPS_DISCOVERY_CHANNEL_QUERY_LIMIT`          | `20`       | Trusted predeclared queries per channel (max `1000`).                                              |
| `OPENOPPS_DISCOVERY_CHANNEL_REQUEST_LIMIT`        | `100`      | Requests including retries, redirects, and pagination per channel (max `5000`).                    |
| `OPENOPPS_DISCOVERY_ORIGIN_LIMIT`                 | `25`       | Distinct validated public HTTPS origins per channel (max `500`).                                   |
| `OPENOPPS_DISCOVERY_REDIRECT_LIMIT`               | `5`        | Manually validated redirect hops per logical request (max `10`).                                   |
| `OPENOPPS_DISCOVERY_PER_HOST_CONCURRENCY`         | `2`        | Simultaneous connections per origin (max `16`).                                                    |
| `OPENOPPS_DISCOVERY_OVERALL_CONCURRENCY`          | `8`        | Simultaneous requests across channels (max `64`).                                                  |
| `OPENOPPS_DISCOVERY_RESPONSE_MAX_BYTES`           | `1048576`  | Decoded bytes admitted from one response (max `10485760`).                                         |
| `OPENOPPS_DISCOVERY_AGGREGATE_RESPONSE_MAX_BYTES` | `67108864` | Decoded bytes admitted across the invocation (max `268435456`).                                    |
| `OPENOPPS_DISCOVERY_CANDIDATE_LIMIT`              | `1000`     | Candidate occurrences per channel before remaining work is unstarted (max `10000`).                |
| `OPENOPPS_DISCOVERY_RETRY_ATTEMPT_LIMIT`          | `3`        | Total attempts per logical request (max `10`).                                                     |
| `OPENOPPS_DISCOVERY_PAGINATION_LIMIT`             | `20`       | Pagination requests per logical enumeration (max `1000`).                                          |
| `OPENOPPS_DISCOVERY_PARSER_MAX_DEPTH`             | `32`       | Trusted structural nesting depth (max `128`).                                                      |
| `OPENOPPS_DISCOVERY_EVIDENCE_RETENTION_SECONDS`   | `86400`    | Maximum age for exact verified quarantine evidence to be reusable by a later scout (max `604800`). |

| Variable                     | Public/CI/offline value | Purpose                                                                                                                                             |
| ---------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENOPPS_DISCOVERY_NETWORK` | `disabled`              | Gate flag for public CI, `just ci-discovery` / `just source-discovery-*`, and `scripts/source_discovery_gates.py`. Not a `DiscoverySettings` field. |

Public CI (the `discovery` job in `.github/workflows/ci.yml`), local offline recipes, and other CI-shaped runs require `OPENOPPS_DISCOVERY_NETWORK=disabled`. The gate refuses any other value; unset defaults to `disabled`. Public CI replays committed sanitized fixtures and does not run a live scout.

A live scout schedule is a separate, unexercised maintainer authority gate. This page does not provision GitHub Actions `schedule:`, Cloudflare Cron, hosted runners, or a private live-network profile, and it does not document a live-scheduler environment as if one were enabled.

Remote content cannot raise a trusted limit. The scout does not use the runtime SQLite HTTP cache or stale-on-error behavior. See [CLI](/docs/cli#quarantined-source-discovery) for command strings.

### Web app public data release [#web-app-public-data-release]

These variables configure the Fumadocs/Next.js host when it loads public search artifacts. They are **not** part of the CLI `OpenOppsSettings` model. Server and browser values should select the same v7 origin and channel; the `NEXT_PUBLIC_` values are embedded into the browser build.

| Variable                                     | Default            | Purpose                                                                                                                                            |
| -------------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENOPPS_PUBLIC_DATA_ORIGIN`                | site URL           | Trusted origin for server-side snapshot reads (never the request Host). Must be `https://` in production unless the insecure override is explicit. |
| `OPENOPPS_PUBLIC_DATA_CHANNEL`               | unset              | Safe lowercase v7 channel, normally `production`. When unset, server consumers use the bounded v6 transition reader.                               |
| `OPENOPPS_PUBLIC_DATA_ORIGIN_ALLOW_INSECURE` | unset (`1` allows) | Allow non-HTTPS origins. Implicitly allowed in development and Vercel preview.                                                                     |
| `OPENOPPS_PUBLIC_DATA_ORIGIN_ALLOW_HOSTS`    | empty              | Comma-separated extra hostnames allowlisted in production (alongside the configured site host and `VERCEL_URL`).                                   |
| `NEXT_PUBLIC_OPENOPPS_PUBLIC_DATA_ORIGIN`    | unset              | Browser-visible v7 publication origin used by Jobs/Explorer and its search worker.                                                                 |
| `NEXT_PUBLIC_OPENOPPS_PUBLIC_DATA_CHANNEL`   | unset              | Browser-visible safe lowercase v7 channel; normally the same value as `OPENOPPS_PUBLIC_DATA_CHANNEL`.                                              |

When neither channel is set, the web app reads the committed v6 transition tree. When v7 is enabled, both browser and server clients resolve the channel once, validate the manifest, pin its immutable release, and verify each asset before use. Production origins require HTTPS. Server-side production origins must also pass the explicit hostname allowlist; add the Workers hostname to `OPENOPPS_PUBLIC_DATA_ORIGIN_ALLOW_HOSTS` when it differs from the site host.

```dotenv
OPENOPPS_PUBLIC_DATA_ORIGIN=https://openopps-data-production.<account-subdomain>.workers.dev
OPENOPPS_PUBLIC_DATA_CHANNEL=production
OPENOPPS_PUBLIC_DATA_ORIGIN_ALLOW_HOSTS=openopps-data-production.<account-subdomain>.workers.dev
NEXT_PUBLIC_OPENOPPS_PUBLIC_DATA_ORIGIN=https://openopps-data-production.<account-subdomain>.workers.dev
NEXT_PUBLIC_OPENOPPS_PUBLIC_DATA_CHANNEL=production
```

Local `next start` / Playwright e2e may use an explicitly allowed loopback origin. Do not enable `OPENOPPS_PUBLIC_DATA_ORIGIN_ALLOW_INSECURE=1` in production merely to bypass a configuration error.

## Examples [#examples]

```bash
OPENOPPS_DB_URL=sqlite:///data/openopps.db uv run openopps admin db status
OPENOPPS_BOARD_CONCURRENCY=8 OPENOPPS_JOB_ROUTE_LIMIT=100 uv run openopps jobs sync --provider workday --metrics-json
OPENOPPS_PROVIDER_CONCURRENCY=4 uv run openopps admin providers probe-routes --source a16z --provider any --limit 25 --json
OPENOPPS_USER_AGENT='openopps/0.1 (contact: jobs@example.com)' uv run openopps admin sources test yc
OPENOPPS_DISCOVERY_NETWORK=disabled uv run python scripts/source_discovery_gates.py ci
uv run openopps discovery scout --output /absolute/quarantine-root --json
```

For repeated local use from the repository checkout, place values in `.env` at the repository root:

```dotenv
OPENOPPS_DB_URL=sqlite:///openoppsdb.sqlite
OPENOPPS_CONCURRENCY_PROFILE=explicit
OPENOPPS_SOURCE_CONCURRENCY=4
OPENOPPS_SOURCE_TIMEOUT_SECONDS=900
OPENOPPS_SOURCE_FRESHNESS_SECONDS=0
OPENOPPS_BOARD_CONCURRENCY=16
OPENOPPS_JOB_ROUTE_TIMEOUT_SECONDS=180
OPENOPPS_JOB_ROUTE_FRESHNESS_SECONDS=0
OPENOPPS_PROVIDER_CONCURRENCY=12
OPENOPPS_CACHE_TTL_SECONDS=3600
OPENOPPS_PLUGIN_ALLOWED=trusted-plugin
OPENOPPS_PLUGIN_DISABLED=broken-plugin
```

`DiscoverySettings` ignores `.env`; do not put `OPENOPPS_DISCOVERY_*` or `OPENOPPS_DISCOVERY_NETWORK` in that file. Unscoped `jobs sync` targets persisted ready catalog routes and excludes reserved `url-pull` identity. Use explicit CLI filters such as `--source`, `--board`, or `--provider` for one-off scoped syncs. Do not pin `--source url-pull`.

URL pull does not use an environment persist switch. `jobs pull` and `openopps <URL>` stay ephemeral unless you pass `--save`; `--no-save` is the explicit alias for that default. HTTP cache writes remain independently governed. Persist failure exits `9` (`PERSISTENCE_FAILED`) and does not claim a saved success. See [CLI](/docs/cli#url-pull) and [Operations](/docs/operations).

The [Jobs](/) and [Explorer](/explorer) surfaces use the committed v6 snapshot only while no v7 channel is configured. Refresh that transition artifact with `just web-search-index` after updating the local `kaggle/openoppsdb.sqlite` export. See [Public Data Releases](/docs/public-data-releases) for v7 configuration, governance, delivery, and cutover gates.

Docs-app telemetry is configured separately from CLI runtime settings. When instrumentation is enabled, keep collection env-gated: default to a no-op sink in local development, use `local-event-lake` for canonical raw events on persistent disk, and set `OPENOPPS_POSTHOG_PROJECT_API_KEY` only when a sanitized hosted product-analytics mirror is desired. Browser PostHog replay is a separate opt-in through `NEXT_PUBLIC_OPENOPPS_POSTHOG_PROJECT_API_KEY`; it remains gated by `NEXT_PUBLIC_OPENOPPS_TELEMETRY_ENABLED=true`, disables automatic PostHog pageview/autocapture events, masks text and inputs, disables network body/header capture, and leaves replay sampling plus trigger controls to the PostHog project. See [Data Model](/docs/data-model#telemetry-event-lake) for the event shape and [Operations](/docs/operations#telemetry-operations) for operating guidance.

## Flags vs Persistent Settings [#flags-vs-persistent-settings]

Prefer flags for one run and environment variables for durable local policy:

| Need                                           | Prefer                                                |
| ---------------------------------------------- | ----------------------------------------------------- |
| Refresh one upstream read                      | `--refresh-cache`                                     |
| Persist one URL pull                           | `--save` (opt-in; default False)                      |
| Keep one URL pull ephemeral                    | `--no-save` (default; persist failure exits 9)        |
| Always bypass cache reads in a shell/session   | `OPENOPPS_CACHE_REFRESH=true`                         |
| Narrow one job sync                            | `--source`, `--board`, or `--provider`                |
| Opt into place-aware JSON-board pools          | `OPENOPPS_CONCURRENCY_PROFILE=auto` (Workday stays 2) |
| Lower Workday pressure across all runs         | `OPENOPPS_WORKDAY_CONCURRENCY=1` or `2`               |
| Cap one unscoped job sync batch                | `OPENOPPS_JOB_ROUTE_LIMIT=500`                        |
| Skip recently synced routes in job sync        | `OPENOPPS_JOB_ROUTE_FRESHNESS_SECONDS=86400`          |
| Lower concurrent job-route execution           | `OPENOPPS_BOARD_CONCURRENCY=8`                        |
| Lower concurrent route probing                 | `OPENOPPS_PROVIDER_CONCURRENCY=6`                     |
| Hide the animation once                        | `--no-intro`                                          |
| Hide the animation everywhere                  | `OPENOPPS_NO_INTRO=1`                                 |
| Bound one isolated scout                       | `OPENOPPS_DISCOVERY_*`                                |
| Write scout output                             | `--output <dir>` (not `OPENOPPS_DB_URL`)              |
| Keep public/CI/offline discovery gates offline | `OPENOPPS_DISCOVERY_NETWORK=disabled`                 |

By default, discovered plugins are visible but not executed. Set `OPENOPPS_PLUGIN_ALLOWED` to run specific trusted entry points. If `OPENOPPS_PLUGIN_AUTOLOAD=true`, every discovered plugin is eligible unless its entry-point name appears in `OPENOPPS_PLUGIN_DISABLED`. Disabled entries are still skipped even if also allow-listed.

For CLI one-offs, prefer command flags such as `--source` and `--refresh-cache` over persistent environment changes.

Do not commit `.env` files with private or environment-specific values.
