External Providers
Orsa integrates with several external services. This page covers how to configure each one — self-hosted alternatives where available, required environment variables, and verification steps.
Supabase
Supabase provides PostgreSQL, Auth, Storage, and Realtime. It's the primary database and auth layer.
Option A: Supabase Cloud (Recommended)
- Create a project at supabase.com (opens in a new tab)
- Go to Settings → API and copy your keys
- Set environment variables:
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJI...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJI...- Run migrations against the remote database:
supabase link --project-ref your-project-ref
supabase db push- Enable required Postgres extensions in the Supabase dashboard:
pgcrypto(UUID generation)pg_trgm(fuzzy text search)vector(embeddings — for AI search)
Option B: Self-Hosted Supabase
Run the full Supabase stack via Docker:
# Clone the Supabase repo
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
# Copy and configure environment
cp .env.example .env
# Edit .env — set POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY
# Start all services
docker compose up -dYour self-hosted URLs:
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-generated-anon-key>
SUPABASE_SERVICE_ROLE_KEY=<your-generated-service-role-key>Verify
curl "${NEXT_PUBLIC_SUPABASE_URL}/rest/v1/" \
-H "apikey: ${NEXT_PUBLIC_SUPABASE_ANON_KEY}"
# Should return metadata, not an errorRedis / Upstash
Redis powers caching (brand data, scrape results) and rate limiting (via @upstash/ratelimit).
Option A: Upstash Redis (Recommended for Production)
- Create a database at upstash.com (opens in a new tab)
- Select REST API mode (required for
@upstash/redis) - Copy the REST URL and token:
UPSTASH_REDIS_REST_URL=https://us1-xxxxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXxxxxxxxxxxxxWhy Upstash? The API uses @upstash/redis (HTTP-based Redis client) and @upstash/ratelimit. These libraries require Upstash's REST API, not a raw Redis TCP connection. For self-hosted Redis, you'll need to swap the client or use the Upstash REST proxy.
Option B: Self-Hosted Redis
Run Redis locally via Docker (included in docker-compose.yml):
docker run -d --name redis \
-p 6379:6379 \
-v redis-data:/data \
redis:7-alpine \
redis-server --appendonly yesTo use self-hosted Redis with the Upstash SDK, run the Upstash REST proxy (opens in a new tab):
docker run -d --name redis-rest-proxy \
-p 8079:8079 \
-e REDIS_URL=redis://redis:6379 \
ghcr.io/upstash/upstash-redis-rest-proxy:latestThen set:
UPSTASH_REDIS_REST_URL=http://localhost:8079
UPSTASH_REDIS_REST_TOKEN=your-proxy-tokenVerify
curl "${UPSTASH_REDIS_REST_URL}/ping" \
-H "Authorization: Bearer ${UPSTASH_REDIS_REST_TOKEN}"
# → {"result":"PONG"}Proxy Providers
Orsa uses a tiered proxy escalation strategy: datacenter → residential → ISP. If a request fails or gets blocked on a cheaper proxy, it escalates to the next tier.
Supported Providers
| Provider | Type | Dashboard |
|---|---|---|
| BrightData | DC, Residential, ISP | brightdata.com (opens in a new tab) |
| Oxylabs | DC, Residential | oxylabs.io (opens in a new tab) |
| SmartProxy | DC, Residential | smartproxy.com (opens in a new tab) |
| Custom | Any HTTP/SOCKS5 proxy | — |
Configuration
Set one URL per proxy tier. The URL format is http://user:pass@host:port:
# Datacenter proxies (cheapest, fastest, most likely to get blocked)
PROXY_DATACENTER_URL=http://user:pass@brd.superproxy.io:22225
# Residential proxies (mid-tier, rotating IPs)
PROXY_RESIDENTIAL_URL=http://user:pass@brd.superproxy.io:22226
# ISP proxies (most expensive, least likely to get blocked)
PROXY_ISP_URL=http://user:pass@brd.superproxy.io:22227BrightData Setup
- Sign up at brightdata.com (opens in a new tab)
- Create zones for each proxy type:
- Datacenter zone → copy the proxy URL
- Residential zone → copy the proxy URL
- ISP zone → copy the proxy URL
- URL format:
http://brd-customer-XXXXX-zone-ZONE:PASSWORD@brd.superproxy.io:22225
Oxylabs Setup
- Sign up at oxylabs.io (opens in a new tab)
- URL format:
http://customer-XXXXX:PASSWORD@pr.oxylabs.io:7777
Adding Your Own Proxy Provider
Any HTTP or SOCKS5 proxy works. Set the URL in the corresponding tier variable:
PROXY_DATACENTER_URL=socks5://user:pass@your-proxy.com:1080
PROXY_RESIDENTIAL_URL=http://user:pass@your-proxy.com:8080Verify
# Test datacenter proxy
curl -x "${PROXY_DATACENTER_URL}" https://httpbin.org/ip
# Should return the proxy's IP, not yoursLLM Providers
Orsa uses LLMs for AI extraction (/v1/brand/ai/query), product parsing, and NAICS classification. You need at least one provider configured.
OpenAI
OPENAI_API_KEY=sk-...- Get an API key at platform.openai.com (opens in a new tab)
- Recommended model:
gpt-4o(default for extraction tasks)
Anthropic
ANTHROPIC_API_KEY=sk-ant-...- Get an API key at console.anthropic.com (opens in a new tab)
- Recommended model:
claude-sonnet-4-20250514
Local Models (Ollama)
For fully self-hosted AI without external API calls:
# Install Ollama
curl -fsSL https://ollama.com/install.sh | sh
# Pull a model
ollama pull llama3.1
# Set env vars
OPENAI_API_KEY=ollama # Any non-empty string
OPENAI_BASE_URL=http://localhost:11434/v1Orsa's AI module uses the OpenAI SDK format. Any OpenAI-compatible API (Ollama, vLLM, LiteLLM, Together) works by setting OPENAI_BASE_URL.
Model Routing
To configure which model handles which task, set:
# Default model for all AI tasks
LLM_DEFAULT_MODEL=gpt-4o
# Override for specific tasks (optional)
LLM_EXTRACTION_MODEL=gpt-4o # Structured data extraction
LLM_CLASSIFICATION_MODEL=gpt-4o-mini # NAICS classification (lighter)
LLM_QUERY_MODEL=gpt-4o # Free-form AI queriesVerify
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer ${OPENAI_API_KEY}" | head -5
# Should return a list of modelsEmail (Resend)
Orsa uses Resend (opens in a new tab) for transactional email — welcome emails, API key notifications, usage alerts.
Resend Setup
RESEND_API_KEY=re_...- Sign up at resend.com (opens in a new tab)
- Add and verify your domain (for custom from addresses)
- Create an API key with sending permission
Alternatives
Swap the email provider by modifying packages/core/src/email/:
| Provider | SDK | Notes |
|---|---|---|
| Resend (default) | resend | Simple API, good DX |
| SendGrid | @sendgrid/mail | Enterprise-grade |
| Postmark | postmark | Transactional focus |
| AWS SES | @aws-sdk/client-ses | Cheapest at scale |
Verify
curl -X POST https://api.resend.com/emails \
-H "Authorization: Bearer ${RESEND_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"from":"test@yourdomain.com","to":"you@email.com","subject":"Test","text":"Works!"}'Stripe
Stripe handles billing, subscription management, and credit purchases.
Setup
STRIPE_SECRET_KEY=sk_test_... # or sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... # or pk_live_...Step-by-Step
- Create a Stripe account at stripe.com (opens in a new tab)
- Get your API keys from Developers → API Keys
- Create products and prices for your credit tiers:
# Using Stripe CLI
stripe products create --name="Starter" --description="1,000 credits/month"
stripe prices create \
--product=prod_XXX \
--unit-amount=2900 \
--currency=usd \
--recurring[interval]=month
stripe products create --name="Pro" --description="10,000 credits/month"
stripe prices create \
--product=prod_YYY \
--unit-amount=9900 \
--currency=usd \
--recurring[interval]=month- Set up the webhook endpoint:
# Local development — forward webhooks to your local API
stripe listen --forward-to localhost:3001/api/webhooks/stripe
# Production — create webhook in Stripe Dashboard
# URL: https://api.yourdomain.com/api/webhooks/stripe
# Events to listen for:
# - checkout.session.completed
# - customer.subscription.created
# - customer.subscription.updated
# - customer.subscription.deleted
# - invoice.paid
# - invoice.payment_failedTest Mode
Use sk_test_ and pk_test_ keys for development. Stripe provides test card numbers:
4242 4242 4242 4242— Successful payment4000 0000 0000 0002— Declined
Verify
stripe balance retrieve
# Should return your Stripe balanceStorage
Orsa stores extracted logos, screenshots, and assets in object storage.
Option A: Cloudflare R2 (Default)
CLOUDFLARE_R2_ACCESS_KEY=your-access-key
CLOUDFLARE_R2_SECRET_KEY=your-secret-key
CLOUDFLARE_R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
CLOUDFLARE_R2_BUCKET=orsa-assets- Create an R2 bucket in the Cloudflare Dashboard (opens in a new tab)
- Generate an API token with R2 read/write permissions
- (Optional) Set up a custom domain for the bucket for CDN delivery
Option B: Supabase Storage
If you're already running Supabase, use its built-in storage:
# No additional env vars needed — uses existing Supabase credentials
STORAGE_PROVIDER=supabaseCreate a public bucket named assets in Supabase Studio → Storage.
Option C: AWS S3
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_S3_BUCKET=orsa-assets
AWS_S3_REGION=us-east-1
STORAGE_PROVIDER=s3Verify
# R2 — list bucket contents
aws s3 ls s3://orsa-assets \
--endpoint-url "${CLOUDFLARE_R2_ENDPOINT}" \
--profile r2Browser Pool
The browser worker runs headless Chromium for scraping, screenshots, and JavaScript rendering.
Option A: Fly.io (Recommended for Production)
Fly.io Machines auto-scale and have good cold-start times:
FLY_API_TOKEN=your-fly-token
BROWSER_POOL_URL=https://orsa-browser-pool.fly.devDeploy:
cd infrastructure/fly
./deploy.sh productionSee the Fly.io config for VM specs. The default is performance-2x (2 vCPU, 4 GB RAM).
Option B: Railway
BROWSER_POOL_URL=https://orsa-browser.up.railway.app- Create a new project on railway.app (opens in a new tab)
- Deploy from the
services/browser-workerdirectory - Set environment variables:
PORT=3000,POOL_SIZE=3,MAX_CONTEXTS=10
Option C: Self-Hosted Docker
Run the browser worker as a Docker container:
docker build -t orsa-browser-worker ./services/browser-worker
docker run -d \
--name orsa-browser \
-p 3002:3000 \
-e PORT=3000 \
-e POOL_SIZE=5 \
-e MAX_CONTEXTS=20 \
--shm-size=512m \
orsa-browser-workerVerify
curl "${BROWSER_POOL_URL}/health"
# → {"status":"ok","browsers":3,"pages":0}Trigger.dev
Trigger.dev (opens in a new tab) handles background jobs — full-site crawls, batch brand extraction, async AI queries.
Option A: Trigger.dev Cloud
TRIGGER_SECRET_KEY=tr_dev_...
TRIGGER_API_URL=https://api.trigger.dev- Create a project at trigger.dev (opens in a new tab)
- Copy your secret key from the project settings
- Deploy jobs:
cd services/trigger
npx trigger.dev@latest deployOption B: Self-Hosted Trigger.dev
Run Trigger.dev locally:
# Clone and run Trigger.dev
git clone https://github.com/triggerdotdev/trigger.dev
cd trigger.dev
docker compose up -dTRIGGER_SECRET_KEY=<your-self-hosted-key>
TRIGGER_API_URL=http://localhost:3030Development
cd services/trigger
npx trigger.dev@latest devThis connects to Trigger.dev and registers your jobs for local development.
Verify
curl "${TRIGGER_API_URL}/api/v1/runs" \
-H "Authorization: Bearer ${TRIGGER_SECRET_KEY}"
# Should return a list (possibly empty) of job runsProvider Checklist
Use this checklist to track your configuration:
| Provider | Required | Configured | Env Vars Set |
|---|---|---|---|
| Supabase | ✅ Yes | ☐ | NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY |
| Redis/Upstash | ✅ Yes | ☐ | UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN |
| Stripe | ✅ Yes (for billing) | ☐ | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
| OpenAI or Anthropic | ✅ Yes (for AI features) | ☐ | OPENAI_API_KEY and/or ANTHROPIC_API_KEY |
| Proxy | ⚠️ Recommended | ☐ | PROXY_DATACENTER_URL, PROXY_RESIDENTIAL_URL, PROXY_ISP_URL |
| Storage (R2/S3) | ⚠️ Recommended | ☐ | CLOUDFLARE_R2_* or AWS_* |
| Browser Pool | ✅ Yes | ☐ | BROWSER_POOL_URL (or FLY_API_TOKEN) |
| Trigger.dev | ⚠️ For crawl/batch jobs | ☐ | TRIGGER_SECRET_KEY, TRIGGER_API_URL |
| Resend | ⚠️ For email features | ☐ | RESEND_API_KEY |