Skip to content

Sirloin Environment

Authoritative reference for sirloin configuration. Source of truth is apps/sirloin/internal/app/config/config.go and the SirloinXxx constants under apps/sirloin/internal/pkg/env/. Cross-cuts: deployment-env for staging and Railway, security-model for secret handling, auth-model for token issuers.

All vars are namespaced SIRLOIN_*. Defaults shown match config.go. “Source” indicates where the value typically comes from in production.

Var Required Default Source Purpose
SIRLOIN_PORT no :50051 Railway / compose gRPC + gRPC-Web listen address
SIRLOIN_STAGE no — Railway development | sandbox | staging | production; gates dev-only RPCs, and decides the stage on its own whenever it is non-blank — RAILWAY_ENVIRONMENT_NAME is only the fallback (apps/sirloin/internal/pkg/env/stage.go:36-44). Checked in the Railway UI on 24 September 2026 for beef-sirloin in staging and in beef-pr-3562: it is set in neither, so in those two the Railway environment name is what decides. production was not checked. Setting it is therefore a live change to stage-gated behaviour, not a formality — see SIRLOIN_PAYMENTS_MIRROR_ENABLED for one thing it would switch on. Only one of the two is ever parsed: whichever StageValue returns. Startup fails when both are blank, and when the one that wins is a value ParseStage does not accept — it takes the four canonical names, the aliases prod, qa, testing, test, dev, local and localhost, and anything containing -pr-, which is how beef-pr-N becomes sandbox; anything else is a config error, and main treats that as fatal
SIRLOIN_URL yes — Railway Public URL of sirloin (used in webhooks, links)
SIRLOIN_GRPC_HOST n/a — — Not read by sirloin itself; consumed by clients (e.g. STRIP_SIRLOIN_GRPC_HOST in apps/strip/internal/pkg/env/variables.go:13)
SIRLOIN_GRPC_URL n/a — — Not read by sirloin itself; consumed by clients pointing at sirloin (see compose env for brisket/round services)
SIRLOIN_TESTER_IDS no — Railway Comma-separated user IDs given test perks
SIRLOIN_NSFW_PROCESSOR_TESTER_IDS no - Railway Comma-separated NSFW processors demo accounts
Var Required Default Source Purpose
SIRLOIN_DATABASE_URL yes — Neon (prod), local Postgres (rump) in compose BUN ORM Postgres DSN; missing → startup error
SIRLOIN_DATABASE_POOLED_URL no falls back to SIRLOIN_DATABASE_URL Neon Pooled runtime DSN (split read path); when set, used as DatabaseRuntimeURL (apps/sirloin/internal/app/config/config.go:144, :449)
SIRLOIN_DATABASE_REPLICA_URL no empty (every query on the primary) Neon read replica, pooled endpoint Allowlisted read-only RPCs read this replica; everything else stays on the primary. See Neon read replicas (apps/sirloin/internal/pkg/storage/readreplica.go)

Local compose default: postgres://sirloin:sirloin@rump:5432/rump?sslmode=disable (see docker-compose.yml).

Var Required Default Source Purpose
SIRLOIN_CLERK_API_KEY yes — secret Verify Clerk session JWTs (see auth-model)
Var Required Default Source Purpose
SIRLOIN_BRAIN_HOST yes brain:3000 (compose) Railway HTTP host for the generated Brain OpenAPI client
SIRLOIN_BRAIN_API_KEY yes — secret Static token for brain
SIRLOIN_ROUND_HOST yes round:8080 (compose) Railway gRPC dial target for round
SIRLOIN_EXAMPLE_SEMANTIC_V2_BACKFILL_ENABLED no false Railway Runs versioned description and CLS embedding workers without changing search reads
SIRLOIN_EXAMPLE_SEMANTIC_V2_CUTOVER_READY no false Railway Explicit operator confirmation that the documented zero-backlog query returned zero; required before semantic v2 search can start
SIRLOIN_EXAMPLE_SEMANTIC_V2_SEARCH_ENABLED no false Railway Reads the semantic CLS vector column; requires both the backfill and cutover-ready flags

The legacy sirloin↔flank wiring (FlankStorageService, flank execution polling, and the FlankMCP route) has been deleted; brain owns all workflow data. Sirloin no longer reads any flank-related variables — SIRLOIN_FLANK_HOST, SIRLOIN_FLANK_SERVICE_TOKEN, FLANK_ENCRYPTION_KEY, SIRLOIN_FLANK_MCP_ENABLED, and SIRLOIN_FLANK_MCP_BASE_PATH can be removed from deployed environments.

Var Required Default Source Purpose
SIRLOIN_CHARGEBEE_API_KEY yes (prod) — secret Chargebee API key
SIRLOIN_CHARGEBEE_SITE yes (prod) — Railway Chargebee site name (<site>.chargebee.com)
SIRLOIN_CHARGEBEE_WEBHOOK_USERNAME recommended — Railway Basic auth user for webhook intake
SIRLOIN_CHARGEBEE_WEBHOOK_PASSWORD recommended — secret Basic auth pw; if either missing, webhooks rejected

Primer environment is auto-detected from API key prefix.

Var Required Default Source Purpose
SIRLOIN_PRIMER_API_KEY optional — secret Empty → Primer checkout disabled
SIRLOIN_PRIMER_WEBHOOK_SECRET recommended — secret HMAC secret; empty → Primer webhooks rejected
Var Required Default Source Purpose
SIRLOIN_DUNNING_SOFT_DECLINE_RETRY_ENABLED no false Railway FOXY-950 rollout flag: soft and insufficient-funds declines exempt from the 3-consecutive-declines cap (own ceiling of 6), decline counters scoped to scheduled attempts, and an immediate retry when the primary payment method changes during dunning. Only TRUE (case-insensitive) enables it.
SIRLOIN_DUNNING_CHARGEBEE_PAGE_SIZE no 100 Railway Page size of the renewal worker’s walk over unpaid Chargebee invoices (FOXY-1381). Valid values are 1–100; anything else means 100. Leave unset in production. To test paging on a preview, set 2 so a few failed renewals span several pages.
SIRLOIN_APP_BASE_URL no — Railway Customer app origin (e.g. https://app.foxy.ai) used to build update_payment_url in Klaviyo dunning events. Empty → the field is omitted. The customer app handles /profile/billing?action=fix-payment and opens the payment dialog there, so set this in prod for recovery emails to carry the link.
SIRLOIN_PAYMENTS_MIRROR_ENABLED no true Railway FOXY-1312: schedules sync_primer_payments, which keeps billing.primer_payments complete from Primer’s payment list for the Strip payments page, about 1,500 list calls a day, nearly all of them re-reading the last day. The renewal worker’s unpaid walk also reads the table to keep invoices with a pending or settled renewal payment in the retry list (FOXY-1381); with the sync off, a charge that wrote no dunning attempt waits for its invoice’s next slot. It ships enabled: the task runs unless the value is FALSE (case-insensitive), and an unset variable means on. The flag is necessary but not sufficient: the job is only registered outside the development and sandbox stages (apps/sirloin/internal/app/worker/river.go:95-97), and which stage a process is in is decided by SIRLOIN_STAGE first. apps/sirloin/internal/app/config/config.go:178 resolves it through env.StageValue, which returns SIRLOIN_STAGE whenever it is non-blank and falls back to RAILWAY_ENVIRONMENT_NAME only when it is blank (apps/sirloin/internal/pkg/env/stage.go:36-44). ParseStage does map any name containing -pr- to sandbox (apps/sirloin/internal/pkg/env/stage.go:47-51), but it only ever sees the Railway environment name when SIRLOIN_STAGE is unset, so a beef-pr-N name is not on its own a guarantee of silence: a preview that inherits SIRLOIN_STAGE=staging from the environment it was cloned from resolves to staging, schedules the sync and issues the Primer list traffic, and this flag is then the only thing that stops it there (apps/sirloin/internal/pkg/env/stage_test.go:45-50 pins exactly that combination). As configured on 24 September 2026 that inheritance does not happen: SIRLOIN_STAGE is set neither on staging nor on beef-pr-3562, the preview checked, so a preview falls back to its own name and resolves to sandbox and does not run the sync. Nothing enforces that, so it holds only for as long as staging leaves the variable unset. That is a property of the Railway configuration, not of the code, so read SIRLOIN_STAGE in the environment rather than reading its name before assuming a preview is quiet. Local development sets SIRLOIN_STAGE=development, so it never runs the sync. Do not use the page itself to confirm the switch took effect: Not synced is driven only by billing.primer_payments holding no rows at all, which needs every writer silent, not just the sync — and nothing ever deletes from that table. Turning the flag off in staging or production leaves a populated decline block that is silently no longer complete. The whole condition is four things at once (apps/sirloin/internal/app/worker/river.go:95-97): a Primer handler, which main builds only when SIRLOIN_PRIMER_API_KEY is set; a repository; a stage that is neither development nor sandbox; and this flag not set to FALSE. Staging and production satisfy the stage half, so staging is where the page is verified before release — provided the Primer key is set there too. Apart from that stage gate the flag gates this task only. The settled and failed payment pollers keep writing the payments they were already fetching, so billing.primer_payments does not go quiet: it stops being complete. The sync is the only writer that guarantees a row per payment, so with FALSE the decline block keeps gaining checkout payments while renewals and anything the pollers skip never arrive — a denominator that moves but cannot be trusted. Turn it off to stop the Primer list traffic, not to freeze the page. Read once at startup, so flipping it needs a restart. It walks its own history forward from 1 June on an empty table, bounded per run, so a deploy needs nothing run by hand; the processor and payment method come from the settled and failed payment pollers and from billing.dunning_attempts. cmd/scripts/import-primer-payments-history is optional and only adds those two dimensions for payments older than the deploy. Off, the page still shows renewal collection from billing.invoices and billing.dunning_attempts, and the decline block keeps gaining the checkout payments the pollers write, without ever covering every payment.
Var Required Default Source Purpose
SIRLOIN_S3_ENDPOINT yes — Railway S3 endpoint (Cloudflare R2 in prod)
SIRLOIN_S3_ACCESS_KEY yes — secret —
SIRLOIN_S3_SECRET_KEY yes — secret —
SIRLOIN_S3_BUCKET yes — Railway Object bucket
SIRLOIN_S3_HMAC_KEY yes — secret Signed-URL HMAC
SIRLOIN_S3_PUBLIC_URL yes — Railway Public CDN base. Its host also identifies our own storage URLs: workflow media inputs stored as URLs on that host are reduced back to their storage key before dispatch (apps/sirloin/internal/app/worker/brainmediainputs.go). A URL from any other host is left untouched, and a path-keyed input still holding one is logged as a warning
SIRLOIN_IMAGE_CGI_URL optional — Railway On-the-fly image transform endpoint. Also builds video thumbnails; unset means video media returns an empty media_thumbnail_url. The zone behind it needs Cloudflare Media Transformations enabled with the SIRLOIN_S3_PUBLIC_URL host in its allowed source origins, or frame URLs fail even when this is set

All optional; required only when KYC is enabled.

Var Required Default Source Purpose
SIRLOIN_ONDATO_CLIENT_ID conditional — secret OAuth client id
SIRLOIN_ONDATO_CLIENT_SECRET conditional — secret OAuth secret
SIRLOIN_ONDATO_CLIENT_SETUP_ID conditional — Railway Setup id
SIRLOIN_ONDATO_AUTH_URL conditional — Railway OAuth token endpoint
SIRLOIN_ONDATO_KYC_BASE_URL conditional — Railway KYC API
SIRLOIN_ONDATO_IDV_API_BASE_URL conditional — Railway IDV API
SIRLOIN_ONDATO_REDIRECT_BASE_URL conditional — Railway User-facing redirect target
SIRLOIN_ONDATO_SCOPES conditional — Railway Space-separated scopes
SIRLOIN_ONDATO_WEBHOOK_SECRET conditional — secret Shared secret for /webhooks/ondato/verification-status
Var Required Default Source Purpose
SIRLOIN_EMAIL_HOST yes — Railway SMTP host
SIRLOIN_EMAIL_PORT yes — Railway SMTP port
SIRLOIN_EMAIL_USER yes — secret SMTP user
SIRLOIN_EMAIL_PASSWORD yes — secret SMTP password
SIRLOIN_EMAIL_SENDER yes — Railway From header
Var Required Default Source Purpose
SIRLOIN_POSTHOG_API_KEY recommended — secret Server-side PostHog
SIRLOIN_KLAVIYO_API_KEY optional — secret Klaviyo events
SIRLOIN_CINDER_API_KEY optional — secret Cinder fraud signals
SIRLOIN_GOOGLE_CREDENTIALS optional — secret Google services creds
OPENROUTER_API_KEY yes — secret Legacy example-search maintenance and semantic scene descriptions through OpenRouter; separate from Google image-generation credentials

OpenTelemetry is enabled when SIRLOIN_AXIOM_TOKEN is set. See observability.

Var Required Default Source Purpose
SIRLOIN_AXIOM_TOKEN recommended — secret Axiom API token; enables OTLP export
SIRLOIN_AXIOM_DATASET yes if token set — Railway Traces + logs dataset (axiom:events:v1)
SIRLOIN_AXIOM_METRICS_DATASET yes if token set — Railway Metrics dataset (otel:metrics:v1)
Var Required Default Source Purpose
SIRLOIN_SLACK_WEBHOOK_URL_PAYMENTS optional — secret Payment notifications
SIRLOIN_SLACK_WEBHOOK_URL_SUBSCRIPTIONS optional — secret Subscription notifications
SIRLOIN_SLACK_WEBHOOK_URL_NEW_CHARACTERS optional — secret Character-creation feed
SIRLOIN_SLACK_WEBHOOK_URL_CS_REPORTS optional — secret CS-team reports
SIRLOIN_SLACK_WEBHOOK_URL_ITEAM_ALERTS optional — secret Internal-team alerts
Var Required Default Source Purpose
SIRLOIN_MONITOR_ENABLED no false Railway Synthetic generation probe
SIRLOIN_MONITOR_GENERATION_INTERVAL no 2m Railway Probe trigger cadence
SIRLOIN_MONITOR_CHECK_INTERVAL no 30s Railway Probe poll cadence
SIRLOIN_MONITOR_MAX_PENDING_AGE no 10m Railway Pending-job age threshold

Background jobs run on River in the dedicated river Postgres schema (FOXY-1038); the queue itself needs no configuration beyond the database URLs.

Var Required Default Source Purpose
SIRLOIN_RIVERUI_USER no — Railway Basic-auth user for the embedded River UI at /riverui on the webhook server; UI mounts only when both credentials are set
SIRLOIN_RIVERUI_PASSWORD no — Railway Basic-auth password for the River UI

Disabled by default. Required only when SIRLOIN_FOXY360_ENABLED=true.

Var Default Purpose
SIRLOIN_FOXY360_ENABLED false Master switch
SIRLOIN_FOXY360_HTTP_PORT :8086 MCP HTTP listen
SIRLOIN_FOXY360_BASE_PATH /foxy360 Mount path
SIRLOIN_FOXY360_CLERK_API_KEY — Clerk verification for MCP
SIRLOIN_FOXY360_ANTHROPIC_API_KEY — LLM provider
SIRLOIN_FOXY360_OPENAI_API_KEY — LLM provider
SIRLOIN_FOXY360_GEMINI_API_KEY — LLM provider
SIRLOIN_FOXY360_GOOGLE_CREDENTIALS_JSON — Google services creds
SIRLOIN_FOXY360_AXIOM_API_TOKEN — Foxy360-scoped Axiom token
SIRLOIN_FOXY360_AXIOM_DATASET — Foxy360 dataset
SIRLOIN_FOXY360_PRIMER_API_KEY — Primer connector
SIRLOIN_FOXY360_META_ADS_ACCESS_TOKEN — Meta Ads connector
SIRLOIN_FOXY360_META_AD_ACCOUNT_IDS — Comma-separated ad accounts
SIRLOIN_FOXY360_MCP_AXIOM_URL — MCP connector URL
SIRLOIN_FOXY360_MCP_AXIOM_AUTH_HEADER — Full Authorization header value
SIRLOIN_FOXY360_MCP_CHARGEBEE_DATA_URL — MCP connector URL
SIRLOIN_FOXY360_MCP_CHARGEBEE_DATA_BEARER_TOKEN — Raw token (no Bearer prefix)
SIRLOIN_FOXY360_MCP_CHARGEBEE_KB_URL — MCP connector URL
SIRLOIN_FOXY360_MCP_INTERCOM_URL — MCP connector URL
SIRLOIN_FOXY360_MCP_INTERCOM_BEARER_TOKEN — —
SIRLOIN_FOXY360_MCP_POSTHOG_URL — MCP connector URL
SIRLOIN_FOXY360_MCP_POSTHOG_AUTH_HEADER — Full Authorization header value
SIRLOIN_FOXY360_MCP_DB_DSN — Read-only DB DSN
  • SIRLOIN_DATABASE_RUNTIME_URL does not exist as a sirloin env var. The runtime-only DSN comes from optional SIRLOIN_DATABASE_POOLED_URL; when unset, sirloin reuses SIRLOIN_DATABASE_URL (apps/sirloin/internal/app/config/config.go:144, :449, apps/sirloin/internal/pkg/env/variables.go:10).
  • Ondato required-when-enabled vars (those that block ondato.NewClient): SIRLOIN_ONDATO_CLIENT_ID, SIRLOIN_ONDATO_CLIENT_SECRET, SIRLOIN_ONDATO_CLIENT_SETUP_ID, SIRLOIN_ONDATO_AUTH_URL, SIRLOIN_ONDATO_IDV_API_BASE_URL, SIRLOIN_ONDATO_KYC_BASE_URL. The rest (SCOPES, REDIRECT_BASE_URL, WEBHOOK_SECRET) are optional but the webhook secret is required to accept Ondato webhooks (apps/sirloin/internal/pkg/ondato/client.go:43-:62, apps/sirloin/internal/app/config/config.go:289-:302).
  • SIRLOIN_GRPC_HOST and SIRLOIN_GRPC_URL are not read by sirloin — see Core table notes. They are client-side conventions used by other services to dial sirloin: SIRLOIN_GRPC_URL by flank, SIRLOIN_GRPC_HOST by the k6 load tests (load-tests/.env.example). Brain does not use either; it dials CharacterRuntimeControlService through BRAIN_SIRLOIN_CHARACTER_CONTROL_HOST, the mirror of sirloin’s own SIRLOIN_BRAIN_CHARACTER_RUNTIME_HOST (apps/brain/src/modules/application/sirloin/providers/sirloin-grpc-client.provider.ts:34-:42).
  • None outstanding for env in this pass.