Skip to main content

Configuration

One .env, at the root of the repo​

There is one .env, read by all three processes. .env.example is its documentation — every variable the repo reads is in it, with a note, and nothing that isn't read is in it. Real environment variables always win, so on a hosting platform you configure it there and the file is a local convenience.

cp .env.example .env

Never add a per-package .env. Files that must agree are files that can disagree — an older layout had four copies of DATABASE_URL, and the failure mode was a session cookie the app couldn't verify and a browser bouncing between /sign-in and / forever.

Required​

The API refuses to start without these three:

VariableWhy it has no default
DATABASE_URLdocker compose up -d starts a Postgres matching .env.example exactly
BETTER_AUTH_SECRETSigns session cookies — openssl rand -base64 32
ALLOWED_SIGN_INThe entire authorization model

GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET are the effective fourth value — set together or not at all — but the API boots without them for an SSO-only install.

Where things are served​

VariablePurpose
API_URLOrigin that mints session cookies and serves /api/auth/*. Republished to the browser as NEXT_PUBLIC_API_URL.
APP_URLWhere the browser is — also the trusted-origin allow-list for credentialed calls and post-sign-in redirects.
AUTH_COOKIE_DOMAINOnly when the API and app are on different subdomains of one parent — then .example.com.
AGENT_URLThe research agent's own origin. Read server-side only, by the app (to proxy the bridge) and the API (to poke the dispatcher). Must include the scheme.

The session cookie is prefixed crm (__Secure-crm.session_token), not the auth library's default — a shared parent domain with another app would otherwise collide two identically named cookies and every reader would resolve null.

Optional: what the agent can do​

Every outside source is optional, and the agent is designed to run with none of them. A missing key removes a place to look — it is never an error, and it never throws. The agent prints the list at boot and states it in the session instructions so it plans around what it actually has.

VariableWhat it adds
PERPLEXITY_API_KEYOpen-web research with citations, the search that finds a LinkedIn slug, and the company real name / industry / location / socials lookup
RAPIDAPI_KEYLinkedIn profiles — name, title, employer, tenure
GITHUB_TOKENRaises the GitHub rate limit above 60/hour when matching profiles
BLOB_READ_WRITE_TOKENMirrors every logo and profile picture into blob storage rather than linking them
AGENT_BRIDGE_SECRETLets a rep talk to the agent from a record's Agent tab
REDIS_URLA shared cache. Without it, per-instance and in-memory.
CRON_SECRETBearer guard on the Gmail / Calendar sync route — required to use it, fails closed if unset

The agent's model key is not an environment variable either — it's a row, set on Settings → General. See Agent model & providers.

The landing page is a flag, and it's off​

IS_MARKETING="true" serves the marketing landing page at /. Anything else — unset, empty, false, 1 — sends a signed-out visitor to /sign-in. It's off by default because the page markets this product; a clone serving it is advertising a CRM to someone already running it. Only the literal true turns it on.

Deploying​

The three services are independent; the only things they must agree on are DATABASE_URL and BETTER_AUTH_SECRET. Set API_URL and APP_URL to the real origins, add the API's callback to the OAuth client's redirect URIs, set CRON_SECRET and point a scheduler at POST /internal/sync/google.

vercel env pull writes to .env.local, which wins

.env.local is read after .env and overrides it, and vercel env pull writes production credentials there by default. Pull once and every process in the repo silently points at production — with bun run dev and prisma migrate dev running against the live database. Pull somewhere inert instead (vercel env pull .env.vercel). The destructive db:* scripts refuse a non-local host unless you pass ALLOW_REMOTE_DB=1.

The tRPC data surface​

Everything the app reads or writes goes through tRPC routers under /api/trpc. The only REST endpoints are /api/auth/* (auth) and /health. Filtering, sorting and pagination happen in the database, never in the browser. The router type is generated and committed — bun run --filter=api trpc:generate — and build must never regenerate it.

Hrida.ai is proprietary software of Zlabs Innovation. See the license for terms. © 2026 Zlabs Innovation.