Environment reference

Every environment variable the social pipeline reads — what sets it, its default, and whether it is currently configured in production.

Environment reference

Every variable the social pipeline actually reads, extracted from the source rather than from memory. Defaults are what the code falls back to when a variable is unset — several of them are load-bearing.

Production column reflects 2026-07-30, read from vercel env ls (names only), and matches Status.

Core

VariableDefaultRequiredProdWhat it does
CRON_SECRET—Yes✅Bearer secret for the cron, drain, metrics, queue, relay, and approval-signing. Whoever holds it can publish — treat as a publishing credential.
DATABASE_URL—Yes✅Neon Postgres — the ledger (SocialPiece/SocialVariant/SocialMetric) behind scheduling, single-use approvals, the drain, and metrics. The Prisma client constructs lazily, so a build never needs it.
SOCIAL_PUBLIC_URLhttps://kun.databayt.orgNo❌Base URL for approval links. Falls back to the canonical production origin — deliberately never the request's Host header, which is attacker-controlled.

CRON_SECRET must exist in two places. The routes read it from Vercel env; the drain and metrics workflows read it from a GitHub repo secret of the same name (gh secret set CRON_SECRET). Missing the GitHub copy, both scheduled lanes self-disable with a ::warning:: on every run — quietly, by design.

Drafting

VariableDefaultRequiredProdWhat it does
SOCIAL_AUTOPOST_PRODUCTSemptyFor the cron❌Comma-separated brands opted into auto-drafting. Empty means the cron does nothing — this is why it is currently a no-op.
SOCIAL_DRAFT_SOURCEhermesNo❌hermes hands the request to the gateway; anthropic drafts server-side. The default dead-ends on a cloud deployment, since Hermes is not reachable from it.
SOCIAL_DRAFT_FALLBACKunsetNo❌Set to anthropic to draft server-side only when Hermes is down, instead of skipping the run. Opt-in for the same billing reason — an outage never silently spends.
SOCIAL_DRAFT_MODELclaude-opus-4-8No❌Model used when the source is anthropic (or the fallback fires).
SOCIAL_DRAFT_LOCALEarNo❌ar or en. Arabic-first is the house default.
ANTHROPIC_API_KEY—If source is anthropic✅Server-side drafting. Costs money per draft — an explicit spend decision, not a config tweak. Unfunded (401) — see D-20260730.
GEMINI_API_KEY—For inline drafting✅The inline lane and drain-google: AI SDK generateObject on gemini-3.6-flash, free tier (measured 20 requests/day/model — D-20260807). Absent, every ask queues for the Mac drain.
SOCIAL_DRAFT_INLINEunset (= on)No❌off reverts the agent window to queue-only drafting — D-20260807's one-variable revert. Anything else means inline ON.

Review destination

The draft-and-approve loop needs somewhere private to send drafts. It tries Hermes/Slack first, then Telegram. If neither is configured, the cron fails after drafting.

VariableDefaultRequiredProdWhat it does
SOCIAL_REVIEW_CHANNELslackNo❌Which channel Hermes relays drafts into.
TELEGRAM_REVIEW_CHAT_ID—Fallback❌A private chat id. Must never be the public brand channel — drafts carry one-click publish links.

Telegram

VariableDefaultRequiredProdWhat it does
TELEGRAM_BOT_TOKEN—Yes❌From BotFather. Present locally, never pushed to production.
TELEGRAM_CHANNEL_ID—Yes❌Public brand channel, @handle or -100…. Does not exist anywhere yet.

Telegram setup →

Facebook

Per brand, with the product id uppercased (HOGWARTS, MKAN, DATABAYT, …):

VariableRequiredProdWhat it does
FACEBOOK_PAGE_ID_<PRODUCT>Yes✅ ×3The Page to post to
FACEBOOK_PAGE_ACCESS_TOKEN_<PRODUCT>Yes✅ ×3Permanent Page token (expires_at: 0), insights scopes included since 2026-07-27
FACEBOOK_PAGE_IDLegacy❌Unsuffixed hogwarts fallback in code only — deleted from Vercel 2026-07-27 so a stale token can never silently reactivate
FACEBOOK_PAGE_ACCESS_TOKENLegacy❌"

Configured for hogwarts, mkan, databayt. Not for sijillee or Moallimee (registry id moalimee, single L — it is the env-var suffix FACEBOOK_PAGE_ID_MOALIMEE) — no Page exists.

Facebook setup →

Hermes gateway

VariableDefaultRequiredProdWhat it does
HERMES_API_URL—For the Hermes lane❌Gateway endpoint. Only meaningful where Hermes is reachable — i.e. local dev, never a cloud deployment.
HERMES_API_KEY—No❌Optional bearer for that direction.
NEXT_PUBLIC_HERMES_API_URL—No❌Display-only, shown in the status panel. The only browser-exposed value here — never put a token in a NEXT_PUBLIC_* variable.

Slack tokens (SLACK_BOT_TOKEN, SLACK_APP_TOKEN, SLACK_HOME_CHANNEL) belong on the Hermes machine, not in the web app. Slack setup →

Not yet wired

Named here so the shape is predictable when these channels land:

ChannelVariables
InstagramINSTAGRAM_USER_ID_<PRODUCT> (reuses the Facebook Page token)
LinkedInLINKEDIN_ORG_URN_<PRODUCT>, LINKEDIN_ACCESS_TOKEN_<PRODUCT>, LINKEDIN_REFRESH_TOKEN_<PRODUCT>, LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET
XX_API_KEY, X_API_SECRET, X_ACCESS_TOKEN_<PRODUCT>, X_ACCESS_TOKEN_SECRET_<PRODUCT>

Rules

Server-side only. The single browser-exposed variable is NEXT_PUBLIC_HERMES_API_URL, which is a URL for a status display. Every token stays server-side. A NEXT_PUBLIC_ token is a public token.

Trim on read. Vercel environment variables can carry a trailing newline that survives copy-paste and breaks header and URL construction in ways that are painful to debug. Every read in this codebase calls .trim() — do the same.

One central .env locally. No .env.local, no .env.development.

Per-brand, never shared. One leaked token should compromise one brand, not all of them.

Quickest path from nothing to a working loop

Given a deployment that already has CRON_SECRET and Facebook configured, three variables bring the entire draft-and-approve loop alive:

TELEGRAM_BOT_TOKEN=...          # from BotFather
TELEGRAM_CHANNEL_ID=@yourbrand  # the public channel
TELEGRAM_REVIEW_CHAT_ID=-100... # a PRIVATE chat, not the above

Then opt a brand in and pick a reachable draft source:

SOCIAL_AUTOPOST_PRODUCTS=databayt
SOCIAL_DRAFT_SOURCE=anthropic   # spend decision — the default is unreachable from the cloud

anthropic is a billing decision, not a config tweak. The house billing doctrine is subscription-only (Claude Max, no API-key spend), so flipping the cron's draft source to anthropic — or setting SOCIAL_DRAFT_FALLBACK — still needs /decide + Abdout's sign-off. Everything unattended stays no-spend: SOCIAL_AUTOPOST_PRODUCTS empty, source defaults to hermes. The zero-spend drafting path remains a Claude Code session (/draft, Max pool).

One surface was decided and then blocked. D-20260730 sanctioned the Social Agent window on /social drafting through the key per press (~$0.02–0.05/draft, SOCIAL_DRAFT_MODEL as the cost knob). A live production test the same day returned 401 invalid x-api-key: the key in Vercel has been rejected since it was added, which is what subscription-only billing predicts — there are no credits for a key to spend. So no ANTHROPIC_API_KEY value in this table is currently funded. The window ships on the queue instead: it records a SocialDraftRequest and a Claude Code session answers it on the Max pool. Funding the lane is a separate decision that also has to clear a payment method.