Architecture¶
High-level structure of the Brandvelia platform and how the pieces connect.
Stack¶
| Layer | Technology |
|---|---|
| App | Next.js, App Router, src/, Tailwind v4, shadcn/ui + Lucide |
| Language | TypeScript, strict |
| Data | Supabase (panoptes project, brandvelia schema), RLS + views |
| AI | OpenRouter via a server-only internal gateway (packages/ai) |
| Integrations | packages/integrations — ClickBank (LINKBuilder/INS/QuickStats/Marketplace/Occasion Score) |
| Types | packages/types — nominal IDs + Zod schemas + domain enums |
Domains (workspaces)¶
Each signed-in affiliate has an isolated workspace (bv_workspace_id): a set of
products (from ClickBank/LINKBuilder), traffic metrics, finance data, and AI models.
Isolation is enforced with RLS on the base tables.
Data access flow: bv_* table → auto-refreshing view public.bv_*
(security_invoker = true) → REST API. Base-table RLS applies at the view, so data never
leaks across workspaces.
Data flow¶
Visitor Operator workspace Shared Supabase
│ │ │
│ POST /api/track │ CREATE TABLE bv_visits │
│───────────────▶ ┌───▶│ (attribution via click_id) │
│ └───▶│ (RLS by workspace_id) │
│ │ │
│ /api/radar │ CREATE /refresh │
│◀────┬────────────┬────│ products + opportunity score │
│ │ ClickBank │ │ (additive, brandvelia only)│
│ │ (API v2) │ │ │
│◀────┴─────────────┴───┤ linkbuilder → public.bv_offer
Frontend flow¶
- Vision Board — capture a product idea; generate LinkBuilder affiliate URL → creates a
BV_Offer(synchronous, persisted in Supabase). - Radar — query ClickBank for a product; rank it with an Opportunity Score
(
economics,gravityFit,funnelQuality,trafficFit,creativePotential,complianceScore) to surface what's worth building. - Offers — one dashboard per product; analytics and settlement metrics.
- Campaigns — traffic routing across platforms (Google, Meta, TikTok) with budgets.
- Creatives — AI-generated creative per platform, audience, and tone.
- Landing Pages — ownable metric destinations (preview + slug).
- Leads — capture → CRM → finance → settlement.
- Finance — all-time P&L reconciled against the marketplace snapshot (CSV import).
AI pipeline¶
[copilot component]
└─ [snapshot: workspace_id, transactions] → [invoke model: google/gemini-2.5-flash]
└─ text response (cites SQL-computed values) → [aiRuns.log]
- Model keys are server-only; the request→provider boundary is isolated per feature (
AI_LINKER). - Every run is logged (
bv_ai_runs); per-call cost comes from thebv_ai_modelsregistry. - No provider/model is called directly from the client.
Agent entrypoint¶
fetch(api, {
headers: { "AI_Model": name, "AI_Tags": JSON.stringify(t), "Content-Type": "application/json" },
method: "POST",
});
Scheduling & function limits (Vercel)¶
Verified against Vercel docs on 2026-08-20 (plan limits depend on the account tier — re-verify after any plan change):
| Concern | Hobby | Pro |
|---|---|---|
| Cron jobs per project | 100 | 100 |
| Minimum cron frequency | once per day (more frequent expressions fail the deploy) | once per minute |
| Cron precision | per-hour (±59 min) | per-minute |
| Function max duration | 300 s (default and max) | 300 s default, up to 800 s (1800 s beta) |
Consequences for Brandvelia:
- Cron expressions are always UTC. Daily marketplace/QuickStats syncs use a single daily
expression (e.g.
35 0 * * *); on Hobby the effective run window is ~±59 min. - Cron-triggered routes carry the
vercel-cron/1.0user agent and must additionally be guarded by aCRON_SECRETbearer token (defense in depth: anyone with the URL could otherwise invoke the job). - Batch work (feed upserts, retention deletes) must fit the 300 s budget: chunked writes (500 rows/batch) and short, index-backed deletes.
Status¶
- V1 (Feature Manager): working (Vision Board, Radar, dashboard).
- V2: EXPERIMENT tab, charts, custom reports, and full finance dashboard under development.