self-hosting
External Providers

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)

  1. Create a project at supabase.com (opens in a new tab)
  2. Go to Settings → API and copy your keys
  3. Set environment variables:
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJI...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJI...
  1. Run migrations against the remote database:
supabase link --project-ref your-project-ref
supabase db push
  1. 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 -d

Your 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 error

Redis / Upstash

Redis powers caching (brand data, scrape results) and rate limiting (via @upstash/ratelimit).

Option A: Upstash Redis (Recommended for Production)

  1. Create a database at upstash.com (opens in a new tab)
  2. Select REST API mode (required for @upstash/redis)
  3. Copy the REST URL and token:
UPSTASH_REDIS_REST_URL=https://us1-xxxxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXxxxxxxxxxxxx

Why 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 yes

To 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:latest

Then set:

UPSTASH_REDIS_REST_URL=http://localhost:8079
UPSTASH_REDIS_REST_TOKEN=your-proxy-token

Verify

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

ProviderTypeDashboard
BrightDataDC, Residential, ISPbrightdata.com (opens in a new tab)
OxylabsDC, Residentialoxylabs.io (opens in a new tab)
SmartProxyDC, Residentialsmartproxy.com (opens in a new tab)
CustomAny 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:22227

BrightData Setup

  1. Sign up at brightdata.com (opens in a new tab)
  2. Create zones for each proxy type:
    • Datacenter zone → copy the proxy URL
    • Residential zone → copy the proxy URL
    • ISP zone → copy the proxy URL
  3. URL format: http://brd-customer-XXXXX-zone-ZONE:PASSWORD@brd.superproxy.io:22225

Oxylabs Setup

  1. Sign up at oxylabs.io (opens in a new tab)
  2. 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:8080

Verify

# Test datacenter proxy
curl -x "${PROXY_DATACENTER_URL}" https://httpbin.org/ip
# Should return the proxy's IP, not yours

LLM 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-...
  1. Get an API key at platform.openai.com (opens in a new tab)
  2. Recommended model: gpt-4o (default for extraction tasks)

Anthropic

ANTHROPIC_API_KEY=sk-ant-...
  1. Get an API key at console.anthropic.com (opens in a new tab)
  2. 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/v1

Orsa'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 queries

Verify

curl https://api.openai.com/v1/models \
  -H "Authorization: Bearer ${OPENAI_API_KEY}" | head -5
# Should return a list of models

Email (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_...
  1. Sign up at resend.com (opens in a new tab)
  2. Add and verify your domain (for custom from addresses)
  3. Create an API key with sending permission

Alternatives

Swap the email provider by modifying packages/core/src/email/:

ProviderSDKNotes
Resend (default)resendSimple API, good DX
SendGrid@sendgrid/mailEnterprise-grade
PostmarkpostmarkTransactional focus
AWS SES@aws-sdk/client-sesCheapest 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

  1. Create a Stripe account at stripe.com (opens in a new tab)
  2. Get your API keys from Developers → API Keys
  3. 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
  1. 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_failed

Test Mode

Use sk_test_ and pk_test_ keys for development. Stripe provides test card numbers:

  • 4242 4242 4242 4242 — Successful payment
  • 4000 0000 0000 0002 — Declined

Verify

stripe balance retrieve
# Should return your Stripe balance

Storage

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
  1. Create an R2 bucket in the Cloudflare Dashboard (opens in a new tab)
  2. Generate an API token with R2 read/write permissions
  3. (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=supabase

Create 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=s3

Verify

# R2 — list bucket contents
aws s3 ls s3://orsa-assets \
  --endpoint-url "${CLOUDFLARE_R2_ENDPOINT}" \
  --profile r2

Browser 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.dev

Deploy:

cd infrastructure/fly
./deploy.sh production

See 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
  1. Create a new project on railway.app (opens in a new tab)
  2. Deploy from the services/browser-worker directory
  3. 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-worker

Verify

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
  1. Create a project at trigger.dev (opens in a new tab)
  2. Copy your secret key from the project settings
  3. Deploy jobs:
cd services/trigger
npx trigger.dev@latest deploy

Option 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 -d
TRIGGER_SECRET_KEY=<your-self-hosted-key>
TRIGGER_API_URL=http://localhost:3030

Development

cd services/trigger
npx trigger.dev@latest dev

This 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 runs

Provider Checklist

Use this checklist to track your configuration:

ProviderRequiredConfiguredEnv Vars Set
Supabase✅ YesNEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY
Redis/Upstash✅ YesUPSTASH_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⚠️ RecommendedPROXY_DATACENTER_URL, PROXY_RESIDENTIAL_URL, PROXY_ISP_URL
Storage (R2/S3)⚠️ RecommendedCLOUDFLARE_R2_* or AWS_*
Browser Pool✅ YesBROWSER_POOL_URL (or FLY_API_TOKEN)
Trigger.dev⚠️ For crawl/batch jobsTRIGGER_SECRET_KEY, TRIGGER_API_URL
Resend⚠️ For email featuresRESEND_API_KEY