Developers
Environment variables
Every setting the app reads, whether you need it, and what happens without it. Locally they go in apps/app/.env.local (start from .env.example); on Render, in each service's Environment.
Read at build time
Anything starting NEXT_PUBLIC_ is written into the app when Next builds it, on the server as well as in the browser. Changing one on Render means a new build and deploy, not just a restart; locally, restart pnpm dev. Everything else is read when the server starts.
Core
| Variable | Needed | What it does | Without it |
|---|---|---|---|
DATABASE_URL | Yes | Postgres with pgvector. Locally postgres://resell:resell@localhost:5433/resell; on Render it comes from the database | Nothing works |
NEXT_PUBLIC_ROOT_DOMAIN | Yes | The marketplace host, with port if any: localhost:5689 or resell.store. Stores, links, cookies and the api/docs/mcp hosts all hang off it | Defaults to localhost:5689 |
NODE_VERSION | Render | Set to 24 in the blueprint so Render uses Node 24 | Render's default Node |
Sign-in
| Variable | Needed | What it does | Without it |
|---|---|---|---|
BETTER_AUTH_SECRET | Yes | Signs sessions and tokens. openssl rand -hex 32. The blueprint generates one and copies it to the workflow | Auth fails |
BETTER_AUTH_URL | Yes | The app's own URL: http://localhost:5689, or https://resell.store | Sign-in links point at the wrong place |
| Variable | Needed | What it does | Without it |
|---|---|---|---|
RESEND_API_KEY | Production | Sends sign-in links and every notification through Resend | Emails are printed to the server log. In development the sign-in link also shows on the page; in production nobody can sign in by email |
EMAIL_FROM | With Resend | The sender, on a domain verified in Resend | "resell.store <hello@resell.store>" |
SUPPORT_EMAIL | Optional | Where problems escalated to resell.store are sent for a decision | Escalations still happen; nobody at resell.store gets an email |
AI
| Variable | Needed | What it does | Without it |
|---|---|---|---|
ANTHROPIC_API_KEY | Recommended | The AI model (Anthropic), for identifying and pricing items, writing listings, the listing chat, the store agent, the negotiator's wording and the Sidekick | Research prices from catalog and comps numbers; the chat, the words step, store agent answers and the Sidekick are off; counters use a template |
AI_MODEL | Optional | The main model as provider:model | The default Anthropic main model |
AI_MODEL_FAST | Optional | The quick model, for the store agent, negotiator, search queries and Sidekick | The default Anthropic fast model |
Anthropic is the only provider registered today, so out of the box both models start anthropic:. Any other model the AI SDK supports works too: add its provider in lib/server/ai.ts and its key here. See Changing the model.
Research
| Variable | Needed | What it does | Without it |
|---|---|---|---|
CHANNEL3_API_KEY | Recommended | The product catalog: what an item is, new prices, used offers, maker photos | Research skips the catalog step and prices from comps and the model |
KERNEL_API_KEY | Optional | Headless browsers that read public listings on eBay, Poshmark and Depop. Needs ANTHROPIC_API_KEY too | No live comps step in research; the Shopping sidekick shows Coming soon |
KERNEL_MAX_BROWSERS | Optional | How many Kernel browsers may run at once; extra searches queue | 5, the free plan's limit |
Search
| Variable | Needed | What it does | Without it |
|---|---|---|---|
JINA_EMBEDDING_MODEL_KEY | Recommended | Jina embeddings for semantic search, written when a listing is published | Search is Postgres full text only |
JINA_EMBEDDING_MODEL | Optional | The Jina model. It must give 1024-dimension vectors to fit listing.embedding | jina-embeddings-v5-text-small |
Files
| Variable | Needed | What it does | Without it |
|---|---|---|---|
R2_URL | Production | The account's S3 endpoint, https://{account_id}.r2.cloudflarestorage.com | Uploads are stored in Postgres (file.data) |
R2_ACCESS_KEY | Production | An R2 API token's access key, with read and write on the bucket | Same |
R2_SECRET_ACCESS_KEY | Production | Its secret | Same |
R2_BUCKET | Optional | The bucket name | resell-store |
R2_PUBLIC_URL | Optional | A public domain for the bucket (e.g. files.resell.store). /api/files/[id] then redirects there instead of streaming the file | Files are streamed through the app |
PayPal
Checkout uses PayPal when the client id, secret and partner merchant id are all set. This project only runs against the sandbox.
| Variable | Needed | What it does | Without it |
|---|---|---|---|
PAYPAL_ENV | Optional | sandbox or live | sandbox |
PAYPAL_CLIENT_ID | For payments | The Platform app's client id | Test checkout: orders are real, no money moves |
PAYPAL_CLIENT_SECRET | For payments | Its secret | Test checkout |
PAYPAL_PARTNER_MERCHANT_ID | For payments | The platform's own PayPal account id (the partner) | Test checkout |
NEXT_PUBLIC_PAYPAL_CLIENT_ID | With PayPal | Same value as PAYPAL_CLIENT_ID. Only changes wording on seller screens ("PayPal holds the buyer's money until they have it") | Seller screens say it's a test checkout |
PAYPAL_BN_CODE | Optional | Sent as PayPal-Partner-Attribution-Id on every call | Calls aren't attributed to the platform |
PAYPAL_WEBHOOK_ID | Production | The webhook's id from the dashboard, used to verify every delivery | Every webhook is rejected with 401 |
PAYPAL_DEMO_SELLER_ID | Sandbox demo | The shared demo seller's merchant id, from scripts/paypal-demo-seller.mjs. Sellers who haven't connected PayPal are paid here, and "Use demo PayPal" links to it | A seller's items can't be bought until they connect their own PayPal |
PAYPAL_DEMO_SELLER_EMAIL | Sandbox demo | The demo seller's sandbox login, shown on Connections so people can watch sales land | Not shown |
PAYPAL_DEMO_SELLER_PASSWORD | Sandbox demo | Its password | Not shown |
PAYPAL_DEMO_BUYER_EMAIL | Sandbox demo | A sandbox buyer login, shown at checkout | Not shown |
PAYPAL_DEMO_BUYER_PASSWORD | Sandbox demo | Its password | Not shown |
Fees and timers
| Variable | Needed | What it does | Without it |
|---|---|---|---|
PLATFORM_FEE_BPS | Optional | The platform fee in basis points of the item price (shipping is never charged). 1000 = 10% | 1000. A blank value also means 1000 |
PAYOUT_DEMO_MINUTES_PER_DAY | Demo only | Makes every "day" in the shipping, delivery and release timers this many minutes. Set it on the workflow too | Real days |
AGENT_SUMMARY_HOUR | Optional | The UTC hour from which the seller's daily agent summary goes out. Not in the blueprint; add it to the workflow | 1 (early evening in the US) |
Timed jobs
| Variable | Needed | What it does | Without it |
|---|---|---|---|
CRON_SECRET | Production | Required as Authorization: Bearer on POST /api/cron/sweep outside development. The blueprint generates one | The sweep route answers 401 (it runs without one in development) |
RENDER_API_KEY | Cron job | Lets scripts/start-sweeps.ts start tasks on the workflow. A Render API key from your account settings | The cron job fails; nothing timed runs |
RENDER_WORKFLOW_SLUG | Cron job | The workflow's slug. The blueprint fills it in from the resell-sweeps service | resell-sweeps |
Demo accounts
| Variable | Needed | What it does | Without it |
|---|---|---|---|
DEMO | Demo only | Set to true to offer "Sell as" and "Shop as" on the sign-in page: shared accounts that sign in with no email. They can't publish, edit what was there, or deal with real sellers, and what they add is reset. Set it on the workflow too | No demo accounts |
DEMO_SELLER_EMAIL | Demo only | The shared seller. Must already exist with a store (the seed makes it) | dana.okafor@example.com |
DEMO_BUYER_EMAIL | Demo only | The shared buyer | ava.lindqvist@example.com |
DEMO_RESET_MINUTES | Demo only | The least time between resets. The sweep cron and the next demo sign-in reset once it has passed | 10 |
DEMO_MAX_DRAFTS | Demo only | New drafts the demo seller may start between resets (each runs research) | 5 |
Everything else
| Variable | Needed | What it does | Without it |
|---|---|---|---|
TEST_DATABASE_URL | Tests | The tests' own database. Its name must end in _test | postgres://resell:resell@localhost:5433/resell_test |
RESELL_API_KEY | stdio MCP | For running @repo/mcp's dist/stdio.js yourself: a secret key or agent link token | The seller server won't start; the buyer server runs with public marketplace tools only |
RESELL_API_URL | stdio MCP | Which API the stdio server talks to, e.g. http://localhost:5689/api/v1 | https://api.resell.store/v1 |
Which service needs what
- Web service: everything above except
RENDER_API_KEY,RENDER_WORKFLOW_SLUGand the test and stdio ones. - Workflow (
resell-sweeps):DATABASE_URL,NEXT_PUBLIC_ROOT_DOMAIN(for links in emails),BETTER_AUTH_SECRETandBETTER_AUTH_URL(the sweeps import code that sets up auth), the Resend settings andSUPPORT_EMAIL, the PayPal keys (releases and refunds),PAYPAL_DEMO_SELLER_ID,PLATFORM_FEE_BPSandPAYOUT_DEMO_MINUTES_PER_DAY. The workflow runs from source with tsx, so itsNEXT_PUBLIC_values are read at start, not at build. - Cron job (
resell-sweeps-cron):RENDER_API_KEYandRENDER_WORKFLOW_SLUGonly.
render.yaml lists all of these with sync: false for secrets, so the Blueprint asks for them on first deploy. Step by step: Deploy to Render.