Reference only: SDK options, HTTP routes, environment variables, and reason codes
# Environment variables
> Every environment variable each service reads.
Every variable each service actually reads, from `os.Getenv` (Opslane server) and `process.env` (worker). The [drift check](https://github.com/opslane/opslane/blob/main/scripts/check-docs-drift.mjs) fails the repository test gate (`pnpm test`, which CI runs) if code and this page disagree. ## Browser SDK build [Section titled “Browser SDK build”](#browser-sdk-build) These variables are read only by the Vite build process; none is shipped as a secret to the browser. | Variable | Required | Purpose | | ----------------------- | -------- | -------------------------------------------------------------------------------- | | `OPSLANE_COMMIT_SHA` | no | First environment override for the commit recorded in source-map build metadata. | | `GITHUB_SHA` | no | GitHub Actions commit fallback. | | `VERCEL_GIT_COMMIT_SHA` | no | Vercel commit fallback. | | `CF_PAGES_COMMIT_SHA` | no | Cloudflare Pages commit fallback. | | `CI_COMMIT_SHA` | no | GitLab and compatible CI commit fallback. | | `RENDER_GIT_COMMIT` | no | Render commit fallback. | | `BITBUCKET_COMMIT` | no | Bitbucket commit fallback. | | `GIT_COMMIT` | no | Generic CI commit fallback. | | `BUILD_SOURCEVERSION` | no | Azure Pipelines commit fallback. | The precedence is the plugin’s explicit `commitSha`, then the variables in the table order, then `.git/HEAD`. Only lowercase 40- or 64-character hexadecimal values are accepted. COMMIT\_REF is intentionally unsupported because many CI systems use it for a branch name. ## Opslane API [Section titled “Opslane API”](#opslane-api) | Variable | Required | Purpose | | -------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DATABASE_URL` | yes | Postgres connection string | | `PORT` | no (8080) | HTTP listen port | | `JWT_SECRET` | yes | Signs session tokens and derives the notification destination encryption key (≥32 bytes). Rotating it invalidates stored webhook configs; users must re-enter their webhook URLs. | | `OPSLANE_PUBLIC_INGEST_URL` | for creating source-map keys | Public Opslane server origin that source-map uploads should reach. Read by `cmd/mint-key`, which stores it in every `-scope sourcemaps` key it creates; a build therefore configures uploads with that one key and no endpoint variable. Must be an absolute origin (https, or http only for loopback) with no path, query, or fragment. `-endpoint` overrides it and must agree if both are set. | | `AUTH_PROVIDER` | no | Identity provider: `github` (default) or `workos`. Selection is explicit and invalid/partial WorkOS configuration fails boot. | | `AUTH_CALLBACK_ORIGIN` | no | Public Opslane server origin used to construct the allowed `/auth/callback` URL. Never derived from the request Host header. Defaults to the local server port; Compose sets `http://localhost:8082`. | | `WORKOS_API_KEY` | when `AUTH_PROVIDER=workos` | WorkOS secret API key used for AuthKit code exchange. | | `WORKOS_CLIENT_ID` | when `AUTH_PROVIDER=workos` | WorkOS project client ID used for AuthKit authorization. | | `AUTH_WORKOS_SOCIAL` | no | Comma-separated social login buttons to show under `AUTH_PROVIDER=workos` (e.g. `google,github`). UI capability only; the WorkOS dashboard governs which methods actually work. | | `DASHBOARD_DIR` | no | Directory of built dashboard SPA to serve (set in the Docker image) | | `DASHBOARD_ORIGIN` | no | Allowed dashboard origin for CORS **and** the OAuth redirect target. For the bundled Compose setup, set `http://localhost:8082`. This is separate from the worker’s `DASHBOARD_URL`, which supplies links in pull requests and notifications. | | `DASHBOARD_URL` | no | Public or private HTTP(S) dashboard base URL used for notification links shown to users. Configure it explicitly; loopback URLs are rejected, and `DASHBOARD_ORIGIN` is not used as a fallback. This mirrors the worker variable of the same name. | | `USAGE_EVENTS_SLACK_WEBHOOK` | no | Slack incoming webhook for best-effort operator usage notifications. Unset disables them. Configure the same private-channel webhook on both server-side services. | | `AUTUMN_SECRET_KEY` | no | Autumn secret key. Billing is completely disabled when this is unset, empty, or whitespace-only. | | `AUTUMN_BASE_URL` | no (`https://api.useautumn.com`) | Autumn API base URL. Override only for testing or an Autumn-compatible deployment. | | `AUTUMN_PRO_PLAN_ID` | no (`pro`) | Autumn plan ID attached by the Pro checkout flow. | | `AUTUMN_FREE_PLAN_ID` | no (`free`) | Autumn plan ID automatically enabled when a customer is first created. | | `BILLING_SWEEP_INTERVAL_SECONDS` | no (300) | How often the server reports durable merged-PR outcomes to Autumn and checks operator-only session ceilings. Invalid values use the default. | | `BILLING_SESSION_ALERT_THRESHOLD` | no (5000) | Monthly session count above which the server emits one operator alert per organization. This alert is plan-unaware and does not enforce a limit. | | `GROUPING_DEBUG_ID_FRAMES` | no (`false`) | Uses valid SDK build identifiers to normalize temporary JavaScript stack-frame locations whose bundle URL changes between page loads. A build identifier is embedded in a built file and its source map so Opslane can match them. Only the literal `true` enables this setting, and the server reads it once at startup. Keep it consistent across server replicas. After source-map processing, Opslane groups each error into its final issue. | | `NOTIFY_UNSAFE_EXTRA_WEBHOOK_HOSTS` | no | **Development/test only.** Comma-separated exact `host[:port]` additions to the Slack webhook allowlist; added hosts may use HTTP. Never set this in production. | | `GITHUB_APP_ID` | for GitHub App | App ID | | `GITHUB_APP_CLIENT_ID` | for OAuth sign-in | OAuth client ID | | `GITHUB_APP_CLIENT_SECRET` | for OAuth sign-in | OAuth client secret | | `GITHUB_APP_PRIVATE_KEY` | for GitHub App | App private key (PEM) | | `GITHUB_APP_SLUG` | no | App slug used in install URLs | | `GITHUB_WEBHOOK_SECRET` | for webhooks | HMAC secret for webhook verification | | `REPLAY_STORE_ENDPOINT` / `REPLAY_STORE_PUBLIC_ENDPOINT` | for replays | S3-compatible endpoint (internal / browser-visible) | | `REPLAY_STORE_ACCESS_KEY` / `REPLAY_STORE_SECRET_KEY` | for replays | Storage credentials | | `REPLAY_STORE_BUCKET` / `REPLAY_STORE_REGION` | for replays | Bucket and region | | `INTERNAL_READ_TOKEN` | for worker replay evidence | Shared secret guarding recording-part reads from the worker to the Opslane server. Unset disables the internal endpoint. | | `SESSION_IDLE_CLOSE_MINUTES` | no (30) | Idle minutes before a recording session closes and the server queues its `session_analysis` job to detect problems in the recording | | `RETENTION_SWEEP_INTERVAL_SECONDS` | no (3600) | How often a background task closes idle sessions and removes expired recordings | | `PRIORITY_SCORE_INTERVAL_SECONDS` | no (1800) | How often the server recomputes priority scores for open incidents and finds pages whose importance has not yet been assessed. Must be a positive integer number of seconds; invalid values use the default. | | `SCRUB_INTERVAL_SECONDS` | no (15) | How often a background task looks for completed parts of recordings that need redaction. Tests may shorten it. | | `RESOLVE_SWEEP_INTERVAL_SECONDS` | no (300) | How often a background task retries source-map processing and, after waiting too long, continues with the minified stack trace. | | `IDENTITY_SETTLE_INTERVAL_SECONDS` | no (5) | How often the server groups reported errors after source-map processing finishes. | | `FILTER_SWEEP_INTERVAL_SECONDS` | no (30) | How often the server checks uninvestigated errors for enough recent users in enabled environments, then queues a short repository review that decides whether to start a full investigation. Invalid values use the default. | | `MCP_FRAME_URL_TTL` | no (`15m`) | Lifetime of signed session-frame URLs returned by MCP tools. Use a positive Go duration such as `5m` or `1h`. Invalid values use the default. | | `ADMIN_EMAILS` | no | Comma-separated operator email allowlist for the cross-tenant admin dashboard. Empty disables the admin API. Docker Compose maps it from the host-side `OPSLANE_ADMIN_EMAILS`. | | `VERSION` | no | Reported by `/health` | The Opslane server reads **only** the `REPLAY_STORE_*` names; `MINIO_*` names appear in its test code, not runtime configuration. ## Worker [Section titled “Worker”](#worker) | Variable | Required | Purpose | | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DATABASE_URL` | yes (hard exit without it) | Postgres connection string | | `INGESTION_BASE_URL` | for session replay evidence | Opslane server base URL used to fetch decoded recording parts with sensitive values removed | | `INTERNAL_READ_TOKEN` | for session replay evidence | Shared secret sent to the Opslane server as `X-Internal-Token` | | `ANTHROPIC_API_KEY` | for investigation | Claude API access; jobs that require it dead-letter as operator configuration failures when it is missing. | | `INVESTIGATION_MODEL` | no (`claude-sonnet-5`) | Anthropic model used by the codebase-aware diagnosis pass. Unknown model names use the default pricing estimate for budget enforcement. | | `DIGEST_MODEL` | no (`INVESTIGATION_MODEL`, then `claude-sonnet-5`) | Anthropic model used to write daily-summary items selected by the server. | | `DIGEST_WRITER_MAX_WRITES` | no (unlimited) | Most daily-summary items one run may write from scratch. Items reused from an earlier day cost nothing and are always delivered; anything over the limit is held for the next day with a stated reason. `0` delivers only reused items. A value that is not a whole number of zero or more runs unlimited and logs one warning. Compose passes this through; on AWS, add it to the worker task definition in the deploy repository or it has no effect there. | | `INQUIRY_MODEL` | no (`INVESTIGATION_MODEL`, then `claude-sonnet-5`) | Anthropic model used for a short, read-only repository review that decides whether an issue that met the review threshold warrants a full investigation. | | `PRODUCT_CONTEXT_MODEL` | no (`INVESTIGATION_MODEL`, then `claude-sonnet-5`) | Anthropic model used to refresh Opslane’s understanding of your pages and user actions after a default-branch push. | | `PRODUCT_CONTEXT_MAX_TURNS` | no (route-scaled, max 80) | Overrides the route classifier’s model/tool-step limit. Without an override it scales from 20 with the number of routes. | | `PRODUCT_CONTEXT_BUDGET_USD` | no (route-scaled, max 3.00) | Overrides the route classifier’s estimated model-spend ceiling. Without an override it scales from $0.50 with the number of routes. | | `FIX_JUDGE_MODEL` | no (`claude-sonnet-5`) | Second Anthropic model that reviews a fix after tests run. Automated fixes stop when this model does not approve them. | | `INVESTIGATION_MAX_TURNS` | no (10) | Maximum model and tool steps allowed for one investigation. | | `INVESTIGATION_BUDGET_USD` | no (2.00) | Estimated model-spend ceiling for one investigation. It is a runaway backstop, not an operating target. Crossing it stops the attempt without inventing a conclusion. | | `FRICTION_INVESTIGATION_MODEL` | no (`claude-sonnet-4-6`) | Anthropic model that reviews whether a session recording shows a real user problem. | | `FRICTION_INVESTIGATION_MAX_TURNS` | no (30) | Maximum model and tool steps for exploring the repository before the model must submit a conclusion. Zero stops without a conclusion. | | `FRICTION_INVESTIGATION_BUDGET_USD` | no (2.00) | Estimated model-spend ceiling in USD for investigating a problem detected from a session recording. Exceeding it stops the investigation without publishing a cause. | | `E2B_API_KEY` | for verification | Sandbox where fixes are tested | | `GITHUB_TOKEN` | one of the two GitHub modes | PAT for clone + PR | | `GITHUB_APP_ID` / `GITHUB_APP_PRIVATE_KEY` | the other mode | GitHub App installation tokens | | `DASHBOARD_URL` | no | Public or private HTTP(S) dashboard base URL used for incident links in PR bodies and notifications. Configure it explicitly; loopback URLs are rejected, and the Opslane server’s `DASHBOARD_ORIGIN` is not used as a fallback. | | `USAGE_EVENTS_SLACK_WEBHOOK` | no | Slack incoming webhook for best-effort operator usage notifications. Unset disables them. Messages can contain customer email addresses and error titles, so use a private channel. | | `AUTUMN_SECRET_KEY` | no | Autumn secret key. Worker quota checks are a complete no-op when this is unset, empty, or whitespace-only. | | `AUTUMN_BASE_URL` | no (`https://api.useautumn.com`) | Autumn API base URL used by worker quota checks. | | `AUTUMN_FREE_PLAN_ID` | no (`free`) | Autumn plan ID automatically enabled when the worker first touches an organization. | | `WORKER_ID` | no (generated) | Stable worker identity used when one worker takes temporary ownership of a job | | `POLL_INTERVAL_MS` | no (5000) | How long the worker waits when the queue is empty (it drains continuously while work exists). Accepted range 50-300000; out-of-range or non-integer values log a warning and fall back to the default | | `SHUTDOWN_GRACE_MS` | no (25000) | Maximum time to wait for the poll loop during shutdown. Must stay below the platform’s container termination grace period, or the container is killed before the graceful path runs. Compose sets `stop_grace_period: 30s` on the worker for this reason; Docker’s 10s default is *below* the 25s grace. Accepted range 1000-120000; out-of-range values log a warning and fall back to the default | | `LEASE_DURATION_MS` / `REAPER_INTERVAL_MS` / `SILENCE_CHECK_INTERVAL_MS` | no | Duration of a worker’s temporary job ownership, expired-job cleanup interval, and stalled-queue check interval | | `OPSLANE_BUILD_SHA` | no (`unknown`) | Git commit the worker image was built from. CI sets it through the image build argument of the same name, and every agent run log records it so a run can be rebuilt with the code that produced it. Agent run logs are off when `LEASE_DURATION_MS` is below 60000 or object storage is not configured | | `RESOLVE_AGE_DAYS` | no (14) | Inactivity period before eligible human-review or completed-analysis issues resolve automatically | | `INACTIVITY_CHECK_INTERVAL_MS` | no (900000) | How often the worker sweeps for inactive issues (15 minutes by default) | | `SESSION_ANALYSIS_MAX_CONCURRENT` | no (2) | Fleet-wide cap on `session_analysis` jobs running at the same time; `0` prevents workers from starting analysis jobs. Raising it only helps if a worker also runs enough concurrent loops to use the extra room; see `WORKER_CONCURRENCY` | | `WORKER_CONCURRENCY` | no (process 1, Compose 4) | How many jobs one worker process runs at once (any job type, mixed). Accepted range 1-16; invalid or out-of-range values log a warning and fall back to 1, or clamp to 16. The simultaneous-analysis ceiling is `min(SESSION_ANALYSIS_MAX_CONCURRENT, replicas × WORKER_CONCURRENCY)`. Narration, frame checks, matching and confirmation have their own caps; investigation and fix jobs do not, so they scale with this value times the replica count. | | `OPENAI_API_KEY` | no | OpenAI key for `text-embedding-3-small` ticket retrieval. Without it, new tickets have no embedding: the careful first look sees no similar tickets and creates duplicates, and the publish gate never asks the one-fix question. The worker warns at startup, and `backfill-tickets` jobs fail on a worker without it unless scheduled with `--allow-missing-embeddings`. | | `FRICTION_MATCH_MODEL` | no (`claude-haiku-4-5-20251001`) | Model for matching recorded behavior to known problems; uses the narrative API key and endpoint, with Anthropic fallbacks. | | `FRICTION_FIRST_LOOK_MODEL` | no (`claude-sonnet-5`) | Strong review model for newly reported problems. | | `FRICTION_FIRST_LOOK_MAX_TOKENS` | no (16384) | Output token ceiling for the careful first look. The request timeout grows with it (20 ms per token, at least 120 seconds). Invalid values, or values outside 1024 to 32000, fall back to the default. | | `FRICTION_MATCH_MAX_CONCURRENT` | no (2) | Fleet-wide cap on running `friction_match` jobs. `0` pauses new matches. | | `FRICTION_CONFIRM_MODEL` | no (`claude-sonnet-5`) | Model for checking known problems against recordings and screenshots and deciding whether one fix covers two problems. Recording checks keep the model’s default thinking with a 16,000-token limit. | | `FRICTION_MAX_OPEN_FIX_PRS` | no (5) | Maximum open fix PRs per project for automatic fixes. Manual requests remain available. | | `FRICTION_FOLD_MIN_SIMILARITY` | no (0.75) | Embedding similarity floor for duplicate candidates at the publish gate; each candidate still needs a yes to the one-fix question before a fold. | | `FRICTION_INSIGHT_INVESTIGATE_USERS` | no (5) | Identified users whose confirmed recordings an insight (kind `ux_insight`) needs before it is investigated automatically. Defects are investigated on publication. Invalid values fall back to 5. | | `FRICTION_CONFIRM_DAILY_CAP` | no (2000) | Maximum recording checks per project per UTC day across all workers. A read’s single retry uses no extra unit; job retries count again, and finished checks resume without using more budget. `0` pauses checks until the limit is raised. | | `FRICTION_CONFIRM_MAX_CONCURRENT` | no (1) | Fleet-wide cap on running `friction_confirm` jobs. `0` pauses confirmation and publication reconciliation. | | `NARRATIVE_API_KEY` | when session narratives are enabled | Model API key for session narratives and frame verification. Falls back to `ANTHROPIC_API_KEY`. Without either key, narrative reservations remain pending. | | `NARRATIVE_MODEL` | no (`claude-sonnet-5`) | Model used to write session narratives and verify findings against captured frames. Both use the model’s default thinking (adaptive on Claude Sonnet 5); frame verification allows at least 16,000 output tokens so thinking cannot cut its answer off. | | `NARRATIVE_BASE_URL` | no (Anthropic default) | Alternate Anthropic-compatible endpoint for narrative and frame-verification calls. | | `NARRATIVE_MAX_TOKENS` | no (8192) | Maximum output tokens for a narrative call. Values below 1024 use the default. | | `NARRATIVE_APP_CONTEXT` | no | Application details included in the session-narrative prompt. | | `NARRATIVE_DAILY_CAP` | no (2000) | Per-project daily cap on distinct sessions that may reserve narrative or frame-verification calls. | | `NARRATIVE_RENDER_BUDGET_MS` | no (60000) | Wall-clock limit for rendering one replay timeline before the model call. | | `NARRATIVE_MAX_CONCURRENT` | no (2) | Fleet-wide cap on concurrent `session_narrate` jobs. `0` pauses this job type. | | `FRAMES_MAX_CONCURRENT` | no (1) | Fleet-wide cap on concurrent `session_verify_frames` jobs. `0` pauses this job type. | | `NARRATIVE_MONTHLY_BUDGET_USD` | no | Optional per-project estimated monthly spend ceiling for narrative calls. It takes effect only when both narrative token-price variables are valid numbers. | | `NARRATIVE_COST_PER_MTOK_INPUT` | with narrative monthly budget | Estimated input-token price in USD per million tokens. | | `NARRATIVE_COST_PER_MTOK_OUTPUT` | with narrative monthly budget | Estimated output-token price in USD per million tokens. | | `HEALTH_PORT` | no (8081) | Health endpoint port. The worker’s `/health` returns `status: ok`, `stalled` (eligible work waiting, no jobs started in the last minute, and none running), or `unknown` (no queue sample has landed, or the last one is stale). It also returns `claims_per_minute`, a per-job-type `queue_depth`, and `dead_letters_24h` grouped by job type and failure class. `dead_letters_given_up` counts the dead letters the worker will never re-run again (three requeues spent); it never ages out, so a stuck incident stays visible until an operator acts. Queue and dead-letter counts are sampled once a minute, not every time a worker starts a job. | | `REPLAY_STORE_ENDPOINT` / `REPLAY_STORE_ACCESS_KEY` / `REPLAY_STORE_SECRET_KEY` / `REPLAY_STORE_BUCKET` | for replay analysis | Reading stored replays | | `MINIO_ENDPOINT` / `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` / `MINIO_BUCKET` | legacy aliases | Worker-side fallback names for the same settings | | `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` / `LANGFUSE_BASE_URL` / `LANGFUSE_PROJECT_ID` | no | Optional LLM tracing | | `ANTHROPIC_BASE_URL` | no (Anthropic default) | Alternate Claude API endpoint; automated tests use it for a fake model server | | `OPSLANE_SANDBOX_BACKEND` | no (`e2b`) | Fix-verification sandbox backend; `local` is only for trusted automated reliability tests and also requires `OPSLANE_RELIABILITY_HARNESS=1` | | `SANDBOX_LIFETIME_MS` | no (`1800000`) | Wall-clock ceiling for a verification sandbox. Values below `900000` fall back to the default; values above `1800000` are clamped to it (E2B enforces account-tier maximums). The ceiling is not billed unless consumed; raising it increases orphan exposure if the worker crashes. | | `OPSLANE_E2B_JAVASCRIPT_TEMPLATE` | when `E2B_API_KEY` is set | Immutable E2B template name built by `packages/worker/e2b-javascript/build.ts`. On the default `e2b` backend, the worker refuses to start when `E2B_API_KEY` is set and this is missing. | | `FIX_SANDBOX_EGRESS_DISABLED` | no | Emergency escape hatch. Set to `1` to leave JavaScript fix sandboxes on unrestricted egress; Python fix sandboxes remain unrestricted until their dependency hosts are measured. | | `OPSLANE_PYTHON_PIPELINE` | no (off) | Routes Python errors through the Python-specific fix workflow for `1` or `true`; Opslane saves the chosen platform with the repair task. | | `OPSLANE_E2B_PYTHON_TEMPLATE` | no (`opslane-python`) | Overrides the E2B template name used by Python repair tasks. | | `OPSLANE_RELIABILITY_HARNESS` | no | Explicit guard required before tests can use the local process runner, which does not isolate commands | | `OPSLANE_GITHUB_URL` | no (`https://github.com`) | Alternate git host for clones; used by tests and self-hosted git | | `OPSLANE_GITHUB_API_URL` | no (GitHub default) | Alternate GitHub REST API base URL for PR creation | The worker starts with only `DATABASE_URL` and logs a warning for missing `ANTHROPIC_API_KEY`, `E2B_API_KEY`, and `GITHUB_TOKEN`. A half-configured E2B worker with `E2B_API_KEY` but no JavaScript template exits at startup. Work that needs another missing credential dead-letters as an operator configuration failure. ## Set in Compose but consumed by no code (known dead config) [Section titled “Set in Compose but consumed by no code (known dead config)”](#set-in-compose-but-consumed-by-no-code-known-dead-config) | Variable | Status | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VITE_OPSLANE_RELEASE` | Read by no code in this repository. It fed the published legacy uploader’s `release`; that plugin now throws, and debug IDs replaced release matching. Your own application may still read it to pass `init({ release })`, which is display metadata only. | | `ALLOW_REGISTRATION` | Read by nothing; there is no self-serve registration path (sign-in is GitHub OAuth). | | `OPSLANE_ADMIN_EMAILS` | Host-side name that docker-compose.yml maps into the Opslane server service (`ingestion`) as `ADMIN_EMAILS`; consumed by Compose interpolation, not read by code directly. | | `INGESTION_PORT` | Host port published for the Opslane API and dashboard (default 8082). Compose interpolation only. `AUTH_CALLBACK_ORIGIN` follows it unless set explicitly. | | `OPSLANE_POSTGRES_HOST_PORT` | Host port published for the bundled Postgres (default 5434). Compose interpolation only. Set it, plus a matching `DATABASE_URL`, to run a second stack beside an existing one. | | `OPSLANE_MINIO_HOST_PORT` | Host port published for the bundled MinIO (default 9012). Compose interpolation only. `REPLAY_STORE_PUBLIC_ENDPOINT` follows it unless set explicitly. Browsers upload replay chunks to that origin, so the two must agree. | | `OPSLANE_INFRA_BIND_ADDR` | Interface the Postgres and MinIO host ports bind to (default `127.0.0.1`; they carry committed dev credentials). Compose interpolation only. Widen it only when loopback is inside a VM, as with Colima or a remote `DOCKER_HOST`. | | `OPSLANE_MINIO_READY_TIMEOUT_SECONDS` | How long `minio-setup` waits for MinIO before exiting non-zero with a diagnostic (default 60). Compose interpolation only. | | `ENCRYPTION_KEY` | Reserved for future encrypted-at-rest token storage; not read by current code. |
# HTTP routes
> Every registered HTTP route with its authentication mode.
All routes registered by the Opslane API (`packages/ingestion/handler/routes.go`). Auth column legend: **none** (public), **poll token** (`X-Opslane-Poll-Token` for one agent setup session), **SDK** (`X-API-Key` project-scoped public ingest key; rate-limited per project, and origin-gated on browser requests), and **session** (signed-in dashboard session). These are curated tables, not a stability contract. The API is early-stage and may change. The [drift check](https://github.com/opslane/opslane/blob/main/scripts/check-docs-drift.mjs) fails the repository test gate (`pnpm test`, which CI runs) if this page and `routes.go` disagree. ## Public [Section titled “Public”](#public) | Method | Path | Auth | Purpose | | -------- | -------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GET | `/health` | none | Liveness + dependency checks | | GET | `/metrics` | none | Internal metrics | | POST | `/auth/refresh` | none | Rotate session tokens | | GET | `/auth/config` | none | Discover embedded sign-in, sign-up, and password-reset capabilities | | POST | `/auth/password` | none | Sign in with provider-managed email and password; issues local session cookies | | POST | `/auth/signup` | none | Create a provider account and begin required email verification | | POST | `/auth/verify-email` | none | Complete email verification and issue local session cookies | | POST | `/auth/oauth/verify-email` | flow cookie + same origin | Complete a hosted OAuth email challenge and resume sign-in | | POST | `/auth/password/forgot` | none | Send a password-reset email with an enumeration-safe response | | POST | `/auth/password/reset` | none | Set a new password from a reset token and revoke local refresh sessions | | GET | `/auth/login` | none | Begin the configured identity-provider sign-in | | GET | `/auth/github` | none | Compatibility redirect to `/auth/login` | | GET | `/auth/callback` | none | Configured identity-provider callback | | GET | `/auth/github/callback` | none | Compatibility callback alias for existing GitHub App configurations | | GET+POST | `/oauth/authorize` | none | Begin an authorization request using PKCE | | POST | `/oauth/token` | none | Exchange a PKCE authorization code for a session token | | POST | `/api/v1/agent/setup` | none | Register a two-hour setup session with `project_name`, optional `agent_name` and `git_remote`; return the approval link and poll token | | GET | `/api/v1/agent/poll/{sessionID}` | poll token | Read approval status, approved keys, and server facts; `wait=0..30` long-polls approval, or the first event with `until=event` | | GET | `/agent/auth/{sessionID}` | none | Redirect to the dashboard approval page; expired links return 410 | | POST | `/api/v1/github/webhook` | HMAC | Receive GitHub `pull_request` and default-branch `push` events; both require `X-GitHub-Delivery` (400 without it). State-based `installation` and `installation_repositories` events keep installation records current without a delivery ID. | | POST | `/mcp` | MCP key in `Authorization: Bearer ...` | Call the remote MCP tools for one project | Agent setup uses normal dashboard sign-in and an explicit approval. Approval creates or attaches a project and seals ingest, MCP, and source-map keys to the session. GitHub is an optional later integration. The agent uses only `X-Opslane-Poll-Token` for polling and session actions; expired sessions return 410 before facts or keys. Agent responses use `Cache-Control: no-store`. ## Agent approval and session actions [Section titled “Agent approval and session actions”](#agent-approval-and-session-actions) | Method | Path | Auth | Purpose | | ------ | ---------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GET | `/api/v1/agent/approve/{sessionID}` | session | Read proposed setup, available projects, suggested match, and bound project progress | | POST | `/api/v1/agent/approve/{sessionID}` | session; admin on cloud | Approve a new project or `existing_project_id` owned by the active organization | | POST | `/api/v1/agent/approve/{sessionID}/deny` | session; admin on cloud | Decline a pending setup | | POST | `/api/v1/agent/github/{sessionID}/install-url` | session; admin on cloud | Create a GitHub App installation URL and callback state for the session’s organization | | GET | `/api/v1/agent/poll/{sessionID}/state` | poll token | Read server facts and reported progress; `wait=0..30`, `until=event`, `github`, `slack`, or `change` (default) | | POST | `/api/v1/agent/poll/{sessionID}/github` | poll token | Validate and attach `{repo}`. Errors include 400 `repo_not_in_installation` with `add_repo_url`, 400 `github_not_installed` with `github_connect_url`, 409 `github_installation_gone` after retirement, and 503 `github_unreachable` with `Retry-After`. | | POST | `/api/v1/agent/poll/{sessionID}/slack` | poll token | Store an encrypted webhook disabled, test delivery, then enable; failed tests remove the disabled destination | | POST | `/api/v1/agent/poll/{sessionID}/progress` | poll token | Report SDK, MCP, and pull-request progress, or failed/skipped diagnostics for server-derived steps | | POST | `/api/v1/agent/poll/{sessionID}/complete` | poll token | Complete onboarding after this session’s first event, or return 422 with `missing: ["first_event"]`; an already-onboarded org still needs the event | ## SDK (X-API-Key) [Section titled “SDK (X-API-Key)”](#sdk-x-api-key) | Method | Path | Origin-gated | Purpose | | ------ | ------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | POST | `/api/v1/events` | browser callers only | Store an error event and queue source-map processing. Receiving an error does not immediately create an issue, investigation, or alert. | | POST | `/api/v1/replays/init` | yes | Begin a replay upload | | POST | `/api/v1/replays/{replayID}/complete` | yes | Finish a replay upload | | POST | `/api/v1/replays/{replayID}/fail` | yes | Record a replay upload failure | | POST | `/api/v1/sessions/init` | yes | Register a tenant-owned session with optional payload `environment`; returns whether the project allows session recording | | POST | `/api/v1/sessions/{sessionID}/chunks/{seq}` | yes | Store and commit one gzipped replay chunk (max 5MiB) | | POST | `/api/v1/ingest/ping` | no | Verify that a public ingest key still authenticates; returns 204 without project data | ## Source-map upload (X-API-Key) [Section titled “Source-map upload (X-API-Key)”](#source-map-upload-x-api-key) | Method | Path | Auth | Purpose | | ------ | ------------------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------- | | PUT | `/api/v1/sourcemaps/{debugID}` | secret source-map key | Upload one immutable source map after the server verifies its build identifier, which matches the map to a built file | ## Signed-in dashboard [Section titled “Signed-in dashboard”](#signed-in-dashboard) | Method | Path | Purpose | | ------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | GET | `/api/v1/auth/me` | Current user | | GET | `/api/v1/auth/verify` | Validate session | | POST | `/api/v1/auth/logout` | End session | | POST | `/auth/switch-org` | Cloud only: rotate the current session into another member organization | | GET | `/api/v1/invitations` | Cloud org admin: list active-org invitations | | POST | `/api/v1/invitations` | Cloud org admin: create an active-org invitation | | DELETE | `/api/v1/invitations/{invitationID}` | Cloud org admin: revoke an outstanding invitation | | POST | `/api/v1/invitations/accept` | Cloud: accept a single-use, verified-email-bound invitation | | GET | `/api/v1/billing/summary` | Billing-enabled deployments: read the active org’s plan and feature balances (admin on cloud) | | POST | `/api/v1/billing/checkout` | Billing-enabled deployments: start Pro checkout for the active org (admin on cloud) | | POST | `/api/v1/billing/portal` | Billing-enabled deployments: open the active org’s billing portal (admin on cloud) | | GET | `/api/v1/admin/overview` | Operator-only cross-tenant monitoring overview, including best-effort progress through automated repository setup (404 unless allowlisted) | | GET | `/api/v1/admin/jobs` | Operator-only recent jobs (404 unless allowlisted) | | GET | `/api/v1/onboarding/state` | Read server-derived onboarding facts: completion, the newest project, whether any project has an event, and GitHub and Slack connection | | POST | `/api/v1/onboarding/complete` | Mark onboarding complete after any project in the org receives its first event; GitHub and Slack are optional (admin or owner on cloud) | | GET | `/api/v1/projects` | List projects | | POST | `/api/v1/projects` | Create project | | PATCH | `/api/v1/projects/{projectID}` | Update project settings. `friction_autonomy` (`ask_first` or `auto_fix`) controls automatic fixes for session-recording issues; the retired `auto_fix_ux` is accepted as a deprecated alias and stored as `auto_fix`; `pr_posture` controls whether unverified fixes may open as drafts. A same-project `default_environment_id` must be an explicit UUID string when present. | | GET | `/api/v1/projects/{projectID}/fix-stats` | Fix-attempt and pull-request outcome counts by issue type | | GET | `/api/v1/projects/{projectID}/environments` | List all environments, or only environments that contain issues with `used_by=incidents` or sessions with `used_by=sessions` | | GET | `/api/v1/projects/{projectID}/event-count` | Return `has_events` and the nullable `latest_error_group_id` | | GET | `/api/v1/projects/{projectID}/digest/latest` | Latest delivered daily summary, or an empty summary when none has been delivered | | POST | `/api/v1/projects/{projectID}/api-keys` | Create an `api` (MCP), `ingest`, or `sourcemaps` key; secret returned once; only `api` accepts expiry (admin) | | GET | `/api/v1/projects/{projectID}/api-keys` | List the project’s MCP, ingest, and sourcemaps keys without showing their secrets (admin) | | DELETE | `/api/v1/projects/{projectID}/api-keys/{keyID}` | Revoke an MCP, ingest, or sourcemaps key (admin) | | GET | `/api/v1/projects/{projectID}/incidents` | List issues | | GET | `/api/v1/projects/{projectID}/incidents/{incidentID}` | Issue detail | | GET | `/api/v1/projects/{projectID}/incidents/{incidentID}/evidence` | Saved stack frames, failed requests, links to recordings, and available supporting data for the current issue | | GET | `/api/v1/projects/{projectID}/notification-destinations` | List project notification destinations and recent delivery state | | POST | `/api/v1/projects/{projectID}/notification-destinations` | Create a Slack notification destination | | PATCH | `/api/v1/projects/{projectID}/notification-destinations/{destID}` | Update a notification destination | | DELETE | `/api/v1/projects/{projectID}/notification-destinations/{destID}` | Delete a notification destination | | POST | `/api/v1/projects/{projectID}/notification-destinations/{destID}/test` | Send a test notification | | GET | `/api/v1/projects/{projectID}/replays/{replayID}` | Fetch a replay | | GET | `/api/v1/projects/{projectID}/sessions` | List sessions with filters and keyset pagination | | GET | `/api/v1/projects/{projectID}/sessions/{sessionID}` | Session detail and recording metadata with sensitive values removed | | GET | `/api/v1/projects/{projectID}/sessions/{sessionID}/narrative` | Fetch the session narrative, finding grades, and verification timestamp | | GET | `/api/v1/projects/{projectID}/sessions/{sessionID}/chunks/{seq}` | Fetch one decoded, redacted part of the recording | | GET | `/api/v1/projects/{projectID}/incidents/{incidentID}/affected-users` | Affected users | | GET | `/api/v1/projects/{projectID}/incidents/{incidentID}/sample-event` | Fetch the redacted representative event with available source-mapped frames, the raw stack, breadcrumbs, and request context | | POST | `/api/v1/projects/{projectID}/incidents/{incidentID}/fix` | Start a fix for an issue that is ready to fix, whether it came from an error or a session recording. Optional body fields: `guidance` (up to 2000 characters) and `intent`, the signed fix action from a digest link. An intent is scoped to this project and issue; an expired or mismatched intent returns 409 `fix link has expired or no longer matches this issue` | | POST | `/api/v1/projects/{projectID}/incidents/{incidentID}/review` | Request another short repository review for the current issue; reuses an investigation already in progress | | POST | `/api/v1/projects/{projectID}/incidents/{incidentID}/link-pr` | Record a same-repository GitHub pull request without marking the issue resolved | | POST | `/api/v1/projects/{projectID}/incidents/{incidentID}/resolve` | Resolve issue | | POST | `/api/v1/projects/{projectID}/incidents/{incidentID}/archive` | Archive issue | | POST | `/api/v1/projects/{projectID}/incidents/{incidentID}/snooze` | Snooze an actionable issue for up to 30 days; a null or past `until` clears the snooze | | POST | `/api/v1/projects/{projectID}/incidents/{incidentID}/unarchive` | Restore archived issue | | GET | `/api/v1/projects/{projectID}/accounts` | List B2B accounts | | GET | `/api/v1/projects/{projectID}/accounts/{accountID}` | Account detail | | GET | `/api/v1/projects/{projectID}/accounts/{accountID}/incidents` | Issues for one account | | GET | `/api/v1/github/setup` | GitHub App install callback | | GET | `/api/v1/github/status` | GitHub App status: `installed`, `installation_id`, and `install_available`; never creates install state | | POST | `/api/v1/github/install-url` | Start a GitHub App installation for the active organization: returns `install_url` and sets its single-use, 30-minute callback state; admin on cloud; 400 `github_app_not_configured` without an App | | GET | `/api/v1/github/repos` | List installable repos; returns typed 400/409 installation errors or 503 `github_unreachable` with `Retry-After` | | PUT | `/api/v1/projects/{projectID}/github` | Set project repo config; returns 400 `repo_not_in_installation` with `add_repo_url`, 400 `github_not_installed` with `github_connect_url`, 409 `github_installation_gone`, or 503 `github_unreachable` with `Retry-After` | | GET | `/api/v1/projects/{projectID}/github` | Get project repo config | | DELETE | `/api/v1/projects/{projectID}/github` | Remove project repo config | ## Internal service reads [Section titled “Internal service reads”](#internal-service-reads) | Method | Path | Auth | Purpose | | ------ | --------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------- | | GET | `/internal/v1/projects/{projectID}/sessions/{sessionID}/chunks/{seq}` | `X-Internal-Token` | Worker fetch of one decoded, redacted part of the recording | | GET | `/internal/v1/projects/{projectID}/incidents/{incidentID}/status` | `X-Internal-Token` | Read one issue’s status for the deployment smoke test | ## Method mismatch [Section titled “Method mismatch”](#method-mismatch) A request whose path is registered but whose method is not returns a JSON `404`, not a `405`. The router sets one `MethodNotAllowed` handler, so this holds for every route above; an unregistered method is indistinguishable from an unregistered path. Handlers that write `405` themselves (password-reset flows) are unaffected. ## Catch-all [Section titled “Catch-all”](#catch-all) Any other path serves the dashboard SPA from `DASHBOARD_DIR` (missing static assets 404 rather than falling back to `index.html`).
# Reason codes
> Every handoff reason code with its remediation.
When Opslane stops and hands an issue to a person, it records a `reason_code`, a `reason_message`, and a `remediation`. The catalog lives in `packages/worker/src/reason-codes.ts` as an exhaustive `Record`. Adding a code to `shared/src/types.ts` without a remediation entry is a compile error, so this table cannot silently fall behind the type. ## Credentials and access [Section titled “Credentials and access”](#credentials-and-access) | Code | Remediation | | ---------------------- | ------------------------------------------------------------------------------------------------------ | | `missing_github_token` | Connect the GitHub App to this repository (Settings > GitHub) so Opslane can read and open PRs. | | `repo_access_denied` | Confirm the GitHub App is installed on this repository and has read + pull-request permissions. | | `token_decrypt_failed` | Reconnect the GitHub integration; the stored credential could not be decrypted. | | `auth_invalid` | Re-authenticate the GitHub connection; the existing token was rejected as invalid. | | `policy_blocked` | A repository or org policy blocked the change. Review branch-protection / app permissions, then retry. | | `missing_llm_key` | Set the `ANTHROPIC_API_KEY` environment variable on the worker with a valid Anthropic API key. | ## Repository state [Section titled “Repository state”](#repository-state) | Code | Remediation | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `empty_repository` | Push at least one commit to this repository, then retry; there is no branch for Opslane to work from yet. | | `invalid_default_branch` | This repository’s default branch points at a branch that no longer exists. Set a valid default branch in GitHub (Settings > Branches), then retry. | | `unresolvable_head` | Opslane could not determine this repository’s default branch. Check the repository is not in an unusual state, then retry. | ## Investigation and fix quality [Section titled “Investigation and fix quality”](#investigation-and-fix-quality) | Code | Remediation | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `malformed_diff` | Review the error manually; the agent could not produce a valid, applicable diff. | | `verification_failed` | Review the candidate fix manually; Opslane could not verify it satisfied the failing behavior. | | `tests_failed` | Review the candidate diff manually; the test suite still fails, so the fix may be partial or cause regressions. | | `low_confidence_fix` | Review the candidate diff and evidence manually; the candidate did not pass independent fix review or the checks required before Opslane opens a pull request. This code does not gate error diagnosis by confidence. | | `repro_not_achievable` | Review the candidate diff and evidence manually; Opslane could not construct a reliable reproduction test, so it checked the fix against the existing suite but did not call it verified. | | `draft_cap_reached` | Review or close an existing Opslane draft PR, then retry this fix. The candidate diff remains available on the incident. | | `billing_limit_reached` | This organization has used its included fix PRs for the month. Upgrade in Settings > Billing to resume automated fix PRs. | | `budget_exhausted` | Review the error manually; the agent could not complete within its step or budget limits. Consider guiding it with more context. | | `insufficient_context` | Add a replay, breadcrumbs, or a reproduction so Opslane has enough context to investigate. | ## Inputs and artifacts [Section titled “Inputs and artifacts”](#inputs-and-artifacts) | Code | Remediation | | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | | `sourcemap_unresolved` | Upload source maps for this release (see the SDK build plugin) so the stack trace shows original source locations. | | `artifact_fetch_failed` | Retry later; Opslane could not fetch a stored artifact needed to analyze this error. | ## Issues not fixed automatically [Section titled “Issues not fixed automatically”](#issues-not-fixed-automatically) | Code | Remediation | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `triage_unfixable` | Review the error manually; Opslane determined it cannot be fixed with application code changes. | | `unfixable_no_app_frames` | Add the `crossorigin` attribute to your `