# SellVia Docs — Frontend Context Bundle

Generated 2026-08-24 from the SellVia documentation repository.
Includes every doc tagged `frontend` or `shared` — 52 docs across 7 categories.

This file is generated. Do not edit it — edit the source `.md` files in the docs repo
(or `dashboard/content/tags.json` to change what lands in this bundle) and re-run the index.

## Contents

- Analytics / Activation, Aha Moment & Churn Signals
- Analytics / AI Token Usage Tracking
- Analytics / Automated Monthly P&L
- Analytics / Dashboards
- Analytics / Events
- Analytics / Funnel Tracking
- Analytics / KPIs
- Analytics / Unit Economics (Revenue vs Cost per User)
- API / API Authentication
- API / API-CONTRACT-SHEET
- API / Endpoint Specifications
- API / Error Responses
- API / REST Standards
- API / Versioning
- API / Webhooks
- Edge Cases / Business Edge Cases
- Edge Cases / Creator Edge Cases
- Edge Cases / Failure Modes Registry
- Edge Cases / Failure Scenarios
- Edge Cases / Infrastructure Edge Cases
- Edge Cases / Payment Edge Cases
- Edge Cases / User Edge Cases
- Product Foundation / Full Product Vision (Post-MVP)
- Product Foundation / Mission & Principles
- Product Foundation / MVP Scope
- Product Foundation / Product Glossary
- Product Foundation / Product Roadmap
- Product Foundation / Product Vision
- Product Foundation / Success Metrics
- Technical Architecture / AI Services
- Technical Architecture / API Design
- Technical Architecture / Architecture Decision Log
- Technical Architecture / Async Job Pattern & Idempotency
- Technical Architecture / Backend Architecture
- Technical Architecture / Background Jobs
- Technical Architecture / Caching Strategy
- Technical Architecture / CDN Strategy
- Technical Architecture / Event-Driven Architecture
- Technical Architecture / File Storage
- Technical Architecture / Frontend Architecture
- Technical Architecture / Search Strategy
- Technical Architecture / System Architecture
- UI / FEATURE_LIST
- UI / SCREEN_INVENTORY
- UI / SITE_MAP
- UX / Accessibility
- UX / AI Agent & Machine Readability
- UX / Components
- UX / Copy Guidelines
- UX / Design System
- UX / Interaction Patterns
- UX / Navigation

---
# Analytics / Activation, Aha Moment & Churn Signals

> Source: `Analytics/Activation, Aha Moment & Churn Signals.md` · tag: `shared` · last updated: 2026-08-23

# Activation, Aha Moment & Churn Signals

## Purpose

Three related but distinct things, worth keeping separate rather than blurred together: the **activation action** (self-directed, trackable in a fixed window), the **aha moment** (real value experienced, often depends on someone else acting too), and **churn signals** (automated follow-up when neither happens in time).

## Core Activation Action, Per Role

**Merchant: publish first Offer** (not just create it as a draft — a draft with nothing live hasn't activated). Maps to the existing `offer_published` event (11. Analytics → Events).

**Creator: submit first application.** Maps to `application_submitted`. Deliberately not "get approved" — approval depends on a Merchant's decision, outside the Creator's own control, so it's not a fair activation measure for the Creator's own behavior.

Both are chosen specifically because they're **entirely within the user's own control** — a fair activation metric shouldn't depend on another party acting first.

## The Aha Moment — Deliberately Separate From Activation

**Merchant: first sale happens through their Offer** — proof a creator actually converted a real buyer, not just that the Offer is technically live.

**Creator: first commission earned** — proof the model works for them personally, not just that they applied.

Both naturally take longer than activation, since both depend on someone else's action (a Creator applying and converting, or a buyer purchasing). **Measuring time-to-aha as its own metric matters because it can be long even when activation is fast** — a Merchant can publish an Offer in minutes and still wait days for a real sale; conflating the two would hide that gap.

## Detection — Rule-Based, Not AI

Consistent with "rules before AI" (00. Mission & Principles, 04. Fraud Prevention): whether a user has completed their core action is a deterministic query against existing events (11. Analytics → Events), not something requiring AI judgment. A scheduled Celery job (02. Background Jobs) checks, on a recurring basis, which users crossed the 24h/48h thresholds without the relevant event:

```text
Every hour:
  Find users created 24-25h ago with no offer_published/application_submitted event
    AND no 24h nudge already sent → trigger 24h nudge
  Find users created 48-49h ago with no core action completed
    AND no 48h follow-up already sent → trigger 48h churn follow-up, flag as at-risk
```

## Nudge Delivery — Three Channels, One Underlying Signal

All three read from the same "has this user completed their core action" flag — no separate logic per channel:

- **Email:** new Notification Logic trigger, `activation_nudge_24h` and `churn_followup_48h` — sent via the marketing domain (`news.wesellvia.com`, 06. Email Infrastructure), since this is a lifecycle/growth message, not a transactional receipt
- **In-app notification:** same trigger, delivered through existing notification infrastructure (03. Database → notifications table)
- **Tooltip:** purely frontend — the dashboard reads the same "core action completed" flag and conditionally renders a contextual prompt pointing at the relevant UI element. No backend push needed; this is a read, not an event.

**AI's role here is optional, not load-bearing:** the *decision* to nudge is the rule-based query above. The nudge's *wording* could optionally use the existing copy-assist AI feature (02. AI Services) for personalization later — not required for MVP, and never the thing deciding whether a nudge fires.

## Churn Signal Tracking

```text
activation_nudges
  id
  user_id
  tier            (24h_nudge / 48h_churn_followup)
  sent_at
  core_action     (which action they were nudged toward)
```

Prevents duplicate nudges, and the 48h tier doubles as a churn-risk flag — surfaced in 10. Admin Panel / Founder AI Command Console as an "at-risk new users" view, not just an outbound email with no internal visibility.

## Metrics This Feeds

Extends 11. Analytics → KPIs and Funnel Tracking with two new explicit measures: **activation rate within 24h** (per role) and **time-to-aha-moment** (median days from signup to first sale/first commission) — both currently absent from the existing funnel definitions, which stopped at "first sale verified" without distinguishing activation from aha.

## Open Questions

- Exact nudge copy/tooltip content — not written here, a 09. UX → Copy Guidelines task once ready to build the actual screens
- Whether a 72h+ tier is worth adding beyond the 48h churn follow-up — reasonable to hold off until there's real data on how much the 48h nudge actually recovers

## Update (2026-08-23): Campaign → Offer Vocabulary Reconciled

**All "campaign" references above renamed "Offer"** (Core Activation Action, the aha-moment description, and the `campaign_published` event reference, now `offer_published`) — no separate Campaign entity exists (01. Domain Model, 2026-08-23). This doc previously implied Offer and Campaign were still two separate things ("publish first campaign... not just create an Offer"); corrected to describe one entity's draft → live transition.

---

# Analytics / AI Token Usage Tracking

> Source: `Analytics/AI Token Usage Tracking.md` · tag: `shared`

# AI / Token Usage Tracking

## Purpose

Per-feature cost visibility for every AI/LLM call — extends 02. Technical Architecture → AI Services, which didn't originally track cost.

## Mechanism

Every call inside the `ai_services` module (matching/embeddings, screening, copy_assist) logs a row to a new `ai_usage_events` table:

| Field | Type | Notes |
| --- | --- | --- |
| id | uuid, PK | |
| feature | enum | matching / screening / copy_assist |
| tokens_in | integer | |
| tokens_out | integer | |
| cost_cents | integer | computed from provider's per-token pricing at call time |
| related_user_id | uuid, nullable | which Merchant/Creator this call was for, if applicable |
| related_entity_type | text, nullable | e.g. "application", "campaign" — what triggered the call |
| created_at | timestamptz | |

## Why Per-Call, Not Aggregated at Write Time

Logging every individual call (not just a running total) means cost-per-feature, cost-per-user, and cost trends over time can all be computed later from the same raw data, rather than needing to decide upfront exactly which aggregations matter — consistent with 03. Database's general preference for auditable, granular financial-adjacent records over pre-aggregated numbers.

## What This Feeds

- **Per-feature dashboards** (11. Analytics → Dashboards): "matching cost $X this month, screening cost $Y" — directly answers whether a specific AI feature is worth its cost
- **Unit Economics** (11. Analytics): AI cost is one component of cost-per-user, summed from this table filtered by `related_user_id`
- **Monthly P&L** (11. Analytics): total AI/token cost line item

## Open Questions

- Whether caching (already recommended in AI Services — e.g. cached screening summaries) means a cache-hit should still log a $0-cost event for completeness, or simply not log anything — recommend logging a $0 event so usage volume is still visible even when cost is avoided

---

# Analytics / Automated Monthly P&L

> Source: `Analytics/Automated Monthly P&L.md` · tag: `shared` · last updated: 2026-08-23

# Automated Monthly P&L

## Purpose

An automatically-generated monthly profit & loss statement, reconciling internal records against Paddle and hosting costs — not just a dashboard number, an actual reconciled report.

## Formula

```text
Revenue:  Platform fees collected (from internal platform_fees table)
Costs:    Paddle processing fees
        + Hosting/infra costs
        + AI/token costs (AI / Token Usage Tracking)
        + Other SaaS costs (Clerk, monitoring, etc.)

P&L = Revenue − Costs
```

## New Cost Line: Paddle's Own Processing Fees (gap closed 2026-08-04)

Every prior split-math example (Commission Engine, Money Flow) modeled only SellVia's 2% platform fee — **Paddle's own processing fee (roughly 2.9% + $0.30 per charge, varies by card/region) was never accounted for anywhere until now.** This comes out of SellVia's revenue, not the customer's or merchant's side of the split — actual margin per sale is thinner than the 2% figure alone suggests. This report is where that gets made visible and tracked properly.

## Data Sources and Automation Level

| Source | Pulled via | Automation |
| --- | --- | --- |
| Platform fee revenue | Internal `platform_fees` table | Fully automatic |
| Paddle processing fees | Paddle's Balance Transactions API (`fee` field per transaction) | Fully automatic |
| Hosting costs | Hosting provider's billing API, where available (e.g. DigitalOcean exposes one; a bare Hetzner box may not cleanly) | Automatic where supported, manual entry fallback otherwise |
| AI/token costs | Internal `ai_usage_events` table | Fully automatic |
| Other SaaS (Clerk, monitoring, etc.) | Most providers don't expose billing APIs | **Manual monthly entry** — realistic limitation, not a gap to pretend away |

## Process (Celery scheduled job, monthly)

1. On the 1st of each month, job runs for the prior month
2. Pull Paddle Balance Transactions for the period (revenue + Paddle's fees)
3. **Reconcile against internal `sales`/`platform_fees` records first** (extends 05. Payments → Reconciliation — the existing fraud/discrepancy check now also feeds this report) — the P&L should never be built on unreconciled numbers
4. Pull hosting costs (API where available, manual entry table otherwise)
5. Sum AI/token costs from `ai_usage_events`
6. Compute and store the P&L as a row in a new `monthly_pnl_reports` table
7. Surface in the Admin dashboard (10. Operations → Admin Panel, 11. Analytics → Dashboards)

## Manual Cost Entry (for the non-API-able sources)

A simple Admin-only form/table for entering monthly costs that can't be pulled automatically (Clerk subscription, monitoring tools, etc.) — not full automation, but keeps the P&L complete rather than silently missing real costs just because they're harder to fetch programmatically.

## Open Questions

- Exact hosting provider choice (Hetzner vs. DigitalOcean, still open per 06. Infrastructure → Hosting Strategy) determines how much of the hosting line can actually be automated — worth weighing billing-API support as a real factor in that decision, not just price/region
- Whether the report needs to be finalized/locked once generated (so historical P&L doesn't silently change if a late-arriving adjustment comes in) — recommend a "finalized" flag with a separate adjustment entry for anything discovered after the fact, rather than editing a closed month in place

## Update (2026-08-23): Paddle Removed, Swich Confirmed, Pakistan/PKR Only

Founder decisions, full reasoning in 02. Architecture Decision Log. Every "Paddle" above means **Swich** now:

- **Formula:** "Paddle processing fees" → "Swich processing fees." Same principle (a real cost line separate from SellVia's 2% platform fee) — exact rate unconfirmed pending a real Swich signup conversation, so the "roughly 2.9% + $0.30 per charge" figure above is Paddle's, not a verified Swich number; don't carry it forward as an estimate.
- **Data source table:** "Paddle's Balance Transactions API (`fee` field per transaction)" → Swich's equivalent transaction/fee API — exact endpoint unconfirmed.
- **`monthly_pnl_reports.paddle_fees_cents`** is renamed `swich_fees_cents` (03. Table Specifications, updated same date).
- **"Other SaaS (Clerk, monitoring, etc.)"** — Clerk itself is stale here too, unrelated to this update: auth switched to Ory Kratos back on 2026-08-04 (02. Architecture Decision Log); this line should read "Ory Kratos (if paid tier), monitoring, etc."
- **Currency: PKR only**, not USD — all figures in this report are PKR.

---

# Analytics / Dashboards

> Source: `Analytics/Dashboards.md` · tag: `shared`

# Dashboards

## Purpose

What gets shown, to whom, and why — the presentation layer over Events/KPIs/Funnel Tracking.

## Founder/Admin Dashboard

- Marketplace health (active merchants/creators, liquidity ratio) — the single most important view during Validation/Private Beta, directly answering the chicken-and-egg concern named repeatedly since Product Vision
- Time-to-payout trend — watching this stay fast is watching the core trust promise hold up in practice
- Funnel views for both Merchant and Creator paths

## Merchant Dashboard (per-merchant, not platform-wide)

- Their own campaign performance: clicks, conversion rate, sales, spend
- Simple charts, exportable reports — directly per the raw data doc's original "Analytics & Reporting" feature goal

## Creator Dashboard (per-creator, not platform-wide)

- Their own link performance: impressions/clicks/sales, earnings trend toward the $50 threshold

## Design Constraint

Per 09. UX → Design System, dashboards should stay in the same restrained visual language as the rest of the product — simple charts, no over-designed data-viz flourishes, consistent with "clarity over excitement."

## Open Questions

- Specific charting library/tooling choice — not decided, reasonable to pick during actual dashboard implementation rather than here

---

# Analytics / Events

> Source: `Analytics/Events.md` · tag: `shared` · last updated: 2026-08-23

# Events

## Purpose

What gets tracked as a discrete event — the raw material every KPI, funnel, and dashboard in this section is built from.

## Product Events

- `waitlist_joined` (role: business/creator)
- `offer_created`, `offer_published`, `offer_paused`, `offer_ended`
- `application_submitted`, `application_approved`, `application_rejected`
- `affiliate_link_generated`
- `attribution_click`, `attribution_cart_add`, `attribution_purchase` (per 03. Database → attribution_events, reused directly rather than duplicated)
- `sale_reported`, `sale_accepted`, `sale_rejected`, `sale_refunded`
- `payout_triggered`, `payout_paid`, `payout_failed`
- `creator_wallet_threshold_reached` (the $50 crossing moment, amount TBD in PKR)

## Where These Come From

Most of these map directly onto state transitions already defined in 01. Business Logic → State Machines and 03. Database's tables — this doc doesn't invent new tracking, it specifies that those same transitions should also emit an analytics event, not just update a database row.

## Open Questions

- Whether a dedicated analytics event pipeline (e.g. a lightweight events table, or a third-party product-analytics tool) is used, or whether KPIs/Funnel Tracking are computed via direct queries against the core tables for MVP — recommend direct queries for MVP (no extra infrastructure), revisit only if the query load becomes a real burden on the primary database (ties to 02. Caching Strategy and 03. Indexing Strategy's stated "revisit if it becomes a bottleneck" philosophy)

## Update (2026-08-23): Campaign → Offer and Sale-Status Vocabulary Reconciled

**Campaign events renamed to Offer events:** per 01. Domain Model's 2026-08-23 revision (Campaign merged into Offer, no separate entity), `campaign_created/published/paused/ended` are renamed `offer_created/published/paused/ended` above — this doc had never been swept for that rename until now.

**Sale event renamed to match current lifecycle vocabulary:** `sale_verified` is replaced with `sale_reported` and `sale_accepted` (plus `sale_rejected`), matching 01. State Machines' "reported → accepted / rejected" model — "verified" no longer applies, since SellVia trusts a merchant-reported sale rather than witnessing a payment it processed directly. `sale_refunded` is unchanged.

---

# Analytics / Funnel Tracking

> Source: `Analytics/Funnel Tracking.md` · tag: `shared` · last updated: 2026-08-23

# Funnel Tracking

## Purpose

The step-by-step conversion paths worth watching closely — built directly from 01. Business Logic → User Flows.

## Merchant Funnel

```text
Waitlist joined → Account activated → Offer created → Offer published
  → First application received → First application approved → First sale accepted
```

## Creator Funnel

```text
Waitlist joined → Account activated → First application submitted
  → First application approved → Link generated → Link shared (proxy: first click received)
  → First sale attributed → First payout received
```

## Why Track Funnels, Not Just Point Metrics

A point metric like "total sales" can look healthy while masking a specific broken step (e.g. plenty of campaigns published, but very few ever get an approved application — signaling a matching/discovery problem, not a checkout problem). Funnel tracking is what actually tells you *where* to intervene, consistent with the case study doc's original emphasis on identifying "failure modes" through usability testing — this is the same idea applied to live product data instead of a usability test session.

## Open Questions

- None blocking — direct implementation of already-defined user flows; specific drop-off thresholds worth investigating will emerge once there's real usage data.

## Update (2026-08-23): Campaign → Offer and Sale-Status Vocabulary Reconciled

**Merchant Funnel corrected:** "Offer created → Campaign published" treated Offer and Campaign as two separate funnel steps — there's no separate Campaign entity (01. Domain Model, 2026-08-23). Collapsed to "Offer created → Offer published," reflecting the Offer's own draft → live status transition. "First sale verified" is renamed "First sale accepted," matching 01. State Machines' reported → accepted/rejected model (see 11. Analytics → Events, updated same date).

---

# Analytics / KPIs

> Source: `Analytics/KPIs.md` · tag: `shared` · last updated: 2026-08-23

# KPIs

## Purpose

The metrics that actually matter — this doc operationalizes 00. Product Foundation → Success Metrics into concrete, computable definitions.

## Marketplace Health

- **Active merchants** = merchants with ≥1 live offer in the trailing 30 days
- **Active creators** = creators with ≥1 approved application or attribution event in the trailing 30 days
- **Liquidity ratio** = active creators ÷ active merchants — tracked explicitly per Success Metrics' stated concern about one-sided growth

## Conversion & Speed

- **Click-to-sale conversion rate** = accepted Sales ÷ attribution_click events, per offer and per creator
- **Time-to-first-application** = offer_published → first application_submitted
- **Time-to-payout** = sale_accepted → payout_paid — the single most important KPI given this is a core differentiator (Product Vision) vs. net-60 invoicing; should be tracked and watched closely, not buried among other metrics. **Caveat (2026-08-23):** the expected distribution of this metric changed under the bill-first-then-pay model — payout no longer follows acceptance instantly, it waits for the merchant's billing cycle to be charged via Swich (01. Commission Engine, 01. State Machines). Still meaningfully faster than net-60 invoicing, but "single most important differentiator" claims built on an instant-payout assumption should be re-benchmarked against the actual billing-cycle-gated timing.

## Trust & Fraud

- **Flagged rate** = flagged Sales/Applications ÷ total (04. Security → Fraud Prevention)
- **Refund/chargeback rate** = refunded Sales ÷ accepted Sales

## Revenue

- **Platform fee revenue** = sum of platform_fees.amount_cents over a period — the direct measure of whether the 2% flat-fee model (05. Payments) is actually working at scale

## Open Questions

- None blocking — these are direct operationalizations of already-agreed success criteria; specific dashboard thresholds/targets can be set once there's real baseline data to compare against.

## Update (2026-08-23): Campaign → Offer and Sale-Status Vocabulary Reconciled

**"Live campaign" → "live offer"** (Active merchants definition) — no separate Campaign entity (01. Domain Model). **`sale_verified` and "verified Sales" → `sale_accepted` / "accepted Sales"** throughout (Click-to-sale conversion, Time-to-payout, Refund/chargeback rate), matching 01. State Machines' reported → accepted/rejected model and 11. Analytics → Events' same-date update. Time-to-payout also gets a caveat above about the billing-cycle-gated payout timing under the current model.

---

# Analytics / Unit Economics (Revenue vs Cost per User)

> Source: `Analytics/Unit Economics (Revenue vs Cost per User).md` · tag: `shared` · last updated: 2026-08-23

# Unit Economics (Revenue vs. Cost per User)

## Purpose

What SellVia actually earns and spends per user — the number that tells you whether the business model works at the unit level, not just in aggregate.

## Revenue Per User (asymmetric by role — important distinction)

- **Merchant revenue** = sum of `platform_fees.amount_cents` (2% of their sales) over a period — this is real SellVia revenue attributable to that merchant
- **Creator revenue = $0, by design.** Creators never pay SellVia anything (05. Payments → Platform Business Model & Pricing). Reporting "revenue per creator" as a number would be misleading — track **GMV driven per creator** instead (total verified Sale amount attributed to their links), which is the real measure of a creator's value to the platform, even though it isn't SellVia revenue directly.

## Cost Per User

Two components, kept distinct rather than blended into one fuzzy number:

**Directly attributable** (precise, per-user):

- Paddle processing fees on their transactions (see Monthly P&L — this is a newly-tracked cost, not previously accounted for anywhere)
- AI/token costs tied to that user (from AI / Token Usage Tracking, filtered by `related_user_id`)

**Allocated** (shared costs, split evenly — simple starting model):

- Hosting/infra cost ÷ active users for the period. Deliberately simple (even split, not usage-weighted) for MVP — refine only if a specific user segment's actual infra load turns out to be meaningfully disproportionate, not guessed at upfront.

## Net Contribution (Merchants Only)

```text
Net contribution per merchant = Merchant revenue − (Paddle fees + AI costs + allocated infra share)
```

For Creators, there's no revenue side to net against — cost-per-creator is tracked as a standalone number, evaluated against GMV driven, not against a revenue figure that doesn't exist.

## Open Questions

- Whether allocated infra cost should eventually be split Merchant vs. Creator differently (e.g. checkout/payment processing load is more merchant-transaction-driven than creator-driven) — reasonable to defer until there's real usage data suggesting the even split is meaningfully wrong

## Update (2026-08-23): Paddle Removed, Swich Confirmed, Pakistan/PKR Only

Founder decisions, full reasoning in 02. Architecture Decision Log. "Paddle processing fees" (Cost Per User section) and the Net Contribution formula's "Paddle fees" both mean **Swich processing fees** now — same role in the math, exact rate unconfirmed pending a real Swich signup conversation (see 11. Automated Monthly P&L's 2026-08-23 update). All figures in PKR, not USD.

---

# API / API Authentication

> Source: `API/API Authentication.md` · tag: `shared` · last updated: 2026-08-04

# API Authentication

## Purpose

How every request proves who's calling — implementation detail behind 04. Security → Authorization.

## Mechanism

- Every request includes a Clerk session token (as a bearer token or cookie, depending on client)
- Backend middleware verifies the token with Clerk on every request before any route handler runs
- Verified identity + role(s) attached to the request context; route handlers never re-derive identity themselves

## Role Enforcement

- Middleware checks the caller's role against the Permission Matrix (01. Business Logic) before allowing the request to proceed — e.g. `/admin/*` routes reject anything without the Admin role before touching business logic

## Public vs. Authenticated Endpoints

- Public: campaign discovery/browse (no auth required to view live campaigns)
- Authenticated: everything involving a specific Merchant's or Creator's own data
- The Swich and Shopify webhook endpoints are special cases (updated 2026-08-23, was Paddle-only) — deliberately NOT session-authenticated (neither is a logged-in user), secured instead via their own respective signature verification schemes (04. Security → Webhook Security)

## Open Questions

- None blocking — direct implementation of already-decided Authentication/Authorization docs.

## Update (2026-08-04): Re-Platformed on Ory Kratos

Every request now carries a Kratos session token/cookie (as before, bearer token or cookie depending on client); backend verifies it against Kratos (server-validated, per 04. Security → Session Management's update) and attaches the resolved User + role(s) to the request context. Same principle, different provider — Kratos's REST API is called directly from FastAPI, no SDK dependency the way a Clerk Python integration might have needed.

---

# API / API-CONTRACT-SHEET

> Source: `API/API-CONTRACT-SHEET.md` · tag: `shared`

# API Contract Sheet — Backend ↔ Frontend Sync

Purpose: single source of truth so backend (Hamza) and frontend dev don't collide.
Rule: **nobody assumes a shape, everybody checks/updates this file first.**

---

## 1. Base rules

- Base URL: `http://localhost:8000` (local), path prefix `/api/v1/...` for all real endpoints. `/health` is the only unprefixed route.
- All request/response bodies: JSON, `camelCase` keys (frontend-friendly; backend converts from Python `snake_case` at the schema layer).
- Dates: ISO 8601 UTC strings (`2026-08-10T14:30:00Z`).
- Auth: `Authorization: Bearer <token>` header once auth lands. Until then, no auth required — note it in the table below.

## 2. Standard response envelope

Success:

```json
{ "data": { ... }, "error": null }
```

Error:

```json
{ "data": null, "error": { "code": "NOT_FOUND", "message": "Product not found" } }
```

HTTP status still reflects the outcome (200/201/400/401/404/422/500) — the envelope is for the frontend to branch on `error` without parsing status text.

## 3. Endpoint registry

Backend adds a row **before or the same day** it starts an endpoint (status `planned`), updates it when the shape is final (status `ready`) and when it's actually deployed to local/dev (status `live`). Frontend only builds against `ready`/`live` rows — anything `planned` is not stable yet, ask first.

| Endpoint  | Method | Status | Request | Response        | Owner   | Notes                          |
|-----------|--------|--------|---------|-----------------|---------|--------------------------------|
| `/health` | GET    | live   | —       | `{status, env}` | backend | no envelope, infra check only  |

*(Add rows as endpoints are built. Keep it append-only — don't delete old rows, mark `deprecated` instead.)*

## 4. Who owns what

- **Backend**: endpoint contracts, DB schema, validation rules, error codes.
- **Frontend**: UI state, client-side validation, loading/error UX.
- **Shared / must-agree-together**: field names, enum values, pagination shape, auth flow.

## 5. Avoiding conflicts in practice

1. **Branch naming**: plain `feature/<thing>` in each repo (`sellvia-backend`, `sellvia-frontend` — separate repos now, not `apps/backend`/`apps/frontend` in one, so a service prefix isn't needed to disambiguate).
2. **Breaking change to a `live`/`ready` endpoint**: post in the shared channel + update the row's Notes column with the change *before* merging, not after.
3. **New endpoint needed by frontend but backend hasn't built it**: frontend adds a `planned` row itself with the shape it needs, backend reviews/adjusts, doesn't just build in silence.
4. **Mocking while backend isn't ready**: frontend builds against a static JSON fixture matching the agreed `planned` shape — do not block on backend being live. Swap the fetch URL when status flips to `live`.
5. **Env vars**: each repo keeps its own `.env` (see `sellvia-backend/.env.example`); never commit real secrets. Add a matching `.env.example` entry when you add a new required var.
6. **CORS**: backend allowlists the frontend dev origin explicitly — ping backend when frontend's local port changes.

## 6. Error codes (append as they're introduced)

| Code | Meaning |
| --- | --- |
| `NOT_FOUND` | resource doesn't exist |
| `VALIDATION_ERROR` | bad request body/params |
| `UNAUTHORIZED` | missing/invalid token |

---

# API / Endpoint Specifications

> Source: `API/Endpoint Specifications.md` · tag: `shared` · last updated: 2026-08-23

# Endpoint Specifications

## Purpose

The concrete endpoint list — direct implementation of 03. Database's tables and 01. Business Logic's state machines.

## Campaigns

- `GET /campaigns` — public, browsable, filterable (category, commission range, niche — per 02. Search Strategy)
- `POST /campaigns` — Merchant only
- `PATCH /campaigns/:id` — Merchant (own only); rate changes follow the "locked at approval" rule (State Machines)
- `PATCH /campaigns/:id/status` — draft→live→paused→ended transitions per State Machines

## Applications

- `POST /campaigns/:id/applications` — Creator only
- `GET /campaigns/:id/applications` — Merchant (own campaigns only)
- `PATCH /applications/:id` — approve/reject, Merchant only, triggers AffiliateLink creation on approval

## Affiliate Links

- `GET /affiliate-links/:slug` — public; this is the endpoint a creator's shared link actually resolves to, which then redirects into the hosted checkout for that campaign's Offer

## Checkout / Sales

- `GET /go/:slug` — the redirect endpoint an AffiliateLink resolves to (public, no auth) — logs the click, redirects to the merchant's product page with attribution reference attached
- `GET /sales` — Merchant/Creator, scoped to their own
- Sale status transitions happen via the Paddle webhook handler, not a direct client-facing endpoint (per 02. Event-Driven Architecture)

## Payouts

- `GET /payouts` — Merchant/Creator, scoped to their own
- No client-facing "trigger payout" endpoint for creators (threshold-based, automatic per Payout Process) — though see Wallet Design's open question on manual below-threshold payout on account closure

## Admin

- `GET /admin/flagged` — fraud/moderation queue
- `POST /admin/campaigns/:id/vet` — approve/reject high-commission campaign vetting
- `POST /admin/users/:id/suspend`

## Open Questions

- None blocking — this list will grow as features are built, but the shape is fully derived from already-settled business logic and schema.

## Update (2026-08-04): Why Each Group Exists

The routes above are the "what" — here's the "why" per group, so a route's purpose is never guessed at from its name alone:

**Campaigns endpoints** exist because the merchant-side "core transaction" (01. Business Logic → User Flows) starts with listing a product for creators to discover — without this group, there's nothing for a creator to apply to.

**Applications endpoints** exist to implement the "creators apply, not the other way around" principle (01. Business Rules) as an actual enforced flow, not just a stated intention — the approve/reject action here is what triggers AffiliateLink creation, the single most consequential state transition on the creator side.

**Affiliate Links (`GET /affiliate-links/:slug`)** exists as the literal mechanism the product's whole trust story depends on — this is the endpoint a shared link actually resolves to, tying a click back to exactly one creator and campaign (01. Business Rules → Attribution Rules).

**Redirect and sale-report endpoints** exist because SellVia tracks sales via external-site attribution, not hosted checkout (reversed 2026-08-07, see 02. Architecture Decision Log) — `GET /go/:slug` logs the click and redirects to the merchant's own site; `POST /webhooks/merchant-sales` receives the onboarding snippet's report. Without this group, there'd be no way to attribute a sale happening entirely outside SellVia's infrastructure.

**Payouts endpoints** are read-only by design (no client-facing "trigger payout" endpoint) — they exist to let users see their own payout history, not to let anyone manually control payout timing, which stays automatic per 05. Payout Process.

**Admin endpoints (`/admin/*`)** exist as the enforcement surface for everything 04. Security → Fraud Prevention and 10. Operations → Moderation need to actually act on — without this group, flagging suspicious activity would have no corresponding action to take.

**`POST /jobs/export` and friends** exist specifically so heavy operations never block a request cycle (02. Async Job Pattern & Idempotency) — this group's entire reason for existing is UX and reliability, not new business logic.

## Open Questions (Update)

None — rationale is now explicit per group rather than implied by the route list alone.

## Update (2026-08-07): Checkout Endpoints Replaced with Redirect + Sale-Report Endpoints

`POST /checkout/:slug/session` no longer exists — SellVia doesn't host checkout (01. Money Flow, reversed). Replaced with:

- `GET /go/:slug` — the redirect endpoint an AffiliateLink resolves to; logs the click, redirects to the merchant's product page with a tracking reference attached
- `POST /webhooks/merchant-sales` — receives the onboarding snippet's sale report (05. Payment Flow) — authenticated per-merchant, not open/public
- `GET /billing-cycles` — Merchant, scoped to their own — view billing history
- `GET /sales` — unchanged in spirit, now shows `acceptance_status` (accepted/rejected) instead of the old verified/pending framing

## Update (2026-08-23): MAJOR REVISION — Offer Replaces Campaign, Shopify Webhook Replaces Merchant-Sales Snippet, No Paddle

Founder decisions, full reasoning in 02. Architecture Decision Log.

**Every `/campaigns` route above is renamed `/offers`** — no separate Campaign entity (01. Domain Model):

- `GET /offers`, `POST /offers`, `PATCH /offers/:id`, `PATCH /offers/:id/status` (replacing the four `/campaigns` routes)
- `POST /offers/:id/applications`, `GET /offers/:id/applications` (replacing the Applications group's `/campaigns/:id/...` routes)
- `POST /admin/offers/:id/vet` (replacing `POST /admin/campaigns/:id/vet`)
- Everywhere above that says "campaign"/"campaigns" (Affiliate Links section, rationale paragraphs) means "offer"/"offers."

**`POST /webhooks/merchant-sales` is renamed and narrowed to `POST /webhooks/shopify-sales`** — Shopify-only for MVP (05. Payment Flow), not a generic per-platform snippet report. Payload is Shopify's `orders/paid` webhook shape, not a custom snippet-generated body.

**"Sale status transitions happen via the Paddle webhook handler" (Checkout/Sales section above) is superseded** — no Paddle. Sale status transitions happen via the `POST /webhooks/shopify-sales` handler, unchanged from the note above it.

**Currency in all request/response bodies above: PKR only**, not USD/EUR/GBP.

## Update (2026-08-23, later same day): Swich Confirmed as Processor

Founder decision: Swich (swichnow.io), full reasoning in 02. Architecture Decision Log. Replaces the "Admin-facing billing-cycle-status action" note above with a real webhook-driven flow:

- `POST /webhooks/swich` — receives Swich's payment-confirmation, payout-confirmation, and payout-failed events. On a billing-cycle payment confirmation, marks the corresponding BillingCycle `charged`. On a payout confirmation, marks the corresponding Payout `paid`. On a payout-failed event, marks the corresponding Payout `failed` (per 03. Webhooks). Authenticated per Swich's own webhook-signing scheme (not yet confirmed — pending real integration).
- `GET /billing-cycles` (unchanged) now also surfaces `swich_invoice_id`/`swich_payment_reference` per 03. Table Specifications.
- No new client-facing endpoint for triggering a billing charge or a payout manually — both remain system-triggered (scheduled job → Swich API call), consistent with the existing "no direct client-facing endpoint for money-state transitions" pattern.

**Exact Swich webhook payload shape is unconfirmed** — this section describes the endpoint's role, not a verified contract, pending actual Swich API docs once integration begins.

---

# API / Error Responses

> Source: `API/Error Responses.md` · tag: `shared` · last updated: 2026-08-23

# Error Responses

## Purpose

Consistent error shape across every endpoint, so the frontend can handle failures predictably.

## Standard Error Shape

```json
{
  "data": null,
  "error": {
    "code": "APPLICATION_ALREADY_EXISTS",
    "message": "You've already applied to this offer."
  }
}
```

`status` is not duplicated inside the `error` object — the HTTP status line already carries it (see Status Code Conventions below). Codes are `SCREAMING_SNAKE_CASE`.

> Canonical reference for the full envelope (success and error) is API-CONTRACT-SHEET.md — this page and REST Standards must match it.

## Status Code Conventions

| Code | Meaning |
| --- | --- |
| 400 | Malformed request / validation failure |
| 401 | Missing or invalid auth token |
| 403 | Authenticated, but not permitted (role/ownership check failed) |
| 404 | Resource doesn't exist (or is soft-deleted — treated the same as not existing to the caller) |
| 409 | Conflict (duplicate application, offer state doesn't allow this action) |
| 422 | Valid request shape, but violates a business rule (e.g. commission_rate outside the sanity-check constraint in 03. Database) |
| 500 | Unhandled server error |

## Payments-Specific Errors

Swich errors (declined card, failed transfer, etc. — updated 2026-08-23, was Paddle) are translated into this same shape rather than passing Swich's raw error format straight through to the frontend — keeps the client-side error handling consistent regardless of which underlying service failed.

## Open Questions

- None blocking — standard, low-risk convention.

## Update (2026-08-23): Envelope Shape Reconciled to API-CONTRACT-SHEET.md

The example above previously showed an error-only body (no `data` key), lower_snake_case codes, and a `status` field embedded in the error object. Corrected to match API-CONTRACT-SHEET.md, the canonical living contract doc: `{"data": null, "error": {"code", "message"}}`, SCREAMING_SNAKE_CASE codes, no embedded `status`.

## Update (2026-08-04): Two-Layer Enforcement

The shape above is Layer 1 (user-facing) of a formal two-layer system — see 06. Infrastructure → Error Handling & Logging Pipeline for Layer 2 (full private logging), the boundary-by-boundary catching rules (API routes, background jobs, webhooks, payment callbacks), and the error-path test suite that verifies neither layer ever fails silently.

---

# API / REST Standards

> Source: `API/REST Standards.md` · tag: `shared` · last updated: 2026-08-23

# REST Standards

## Purpose

Baseline conventions every endpoint follows — companion to 02. API Design's higher-level philosophy.

## Base

- REST, JSON request/response bodies
- Base path: `/api/v1/*` (versioned from day one, even if v2 isn't needed yet — cheap to add now, costly to retrofit)

## Resource Naming

- Plural nouns: `/campaigns`, `/applications`, `/sales`, `/payouts`, `/offers`
- Nested where the relationship is owned: `/campaigns/:id/applications` (applications belonging to a specific campaign)

## Standard Response Shape

```json
{
  "data": { ... },
  "error": null,
  "meta": { "page": 1, "per_page": 20, "total": 143 }
}
```

`meta` only appears on paginated list endpoints. `error` is always present and is `null` on success; see Error Responses for the populated-error shape.

> Canonical reference for the full envelope (success and error) is API-CONTRACT-SHEET.md — this page and Error Responses must match it.

## Update (2026-08-23): Response Envelope Reconciled to API-CONTRACT-SHEET.md

The shape above previously omitted the `error` key entirely. It's been corrected to match API-CONTRACT-SHEET.md, the canonical living contract doc: every response carries both `data` and `error` keys, with exactly one non-null.

## HTTP Methods

- GET (read), POST (create), PATCH (partial update), DELETE (soft-delete, per 03. Database → Soft Delete Policy — never a hard delete)

## Open Questions

- None blocking — standard REST conventions, low-risk to lock in now.

---

# API / Versioning

> Source: `API/Versioning.md` · tag: `shared`

# Versioning

## Purpose

How the API evolves without breaking existing clients.

## Approach

`/api/v1/*` from day one (per REST Standards), even with only one client (SellVia's own frontend) — costs nothing now, avoids a painful retrofit if a public API or third-party integration (e.g. the deferred Shopify webhook integration) is added later.

## When v2 Would Be Needed

- A breaking change to an existing resource shape that existing clients depend on
- Not anticipated for MVP — this doc exists mainly to record the convention, not because a v2 is imminent

## Open Questions

- None blocking.

---

# API / Webhooks

> Source: `API/Webhooks.md` · tag: `shared` · last updated: 2026-08-23

# Webhooks

## Purpose

Endpoints where a third party (originally Paddle; as of 2026-08-23, Swich and Shopify — see that update below) calls into SellVia, rather than the frontend calling out.

## Original (superseded 2026-08-23 — see Update below): Inbound Only (Paddle → SellVia)

- `POST /webhooks/paddle` — the single endpoint handling all Paddle event types (`transaction.completed`, `charge.refunded`, `payout.paid`, `payout.failed`, `seller.updated`), per 02. Event-Driven Architecture
- Secured via Paddle signature verification only (04. Security → Webhook Security) — not user-session-authenticated, since Paddle isn't a logged-in user

## Future: Outbound (SellVia → Merchant's Store), v2

Once external-site tracking is built (deferred per Money Flow), SellVia would need to receive webhooks FROM a merchant's Shopify store (order created/paid), which is architecturally the reverse direction from today's single Paddle-inbound webhook. Not designed yet — flagged here as a known future addition, not built now.

## Open Questions

- None blocking for MVP — the v2 outbound-direction webhook system is intentionally undesigned until the external-tracking decision is revisited.

## Update (2026-08-07): This Is Now Active, Not Deferred

The "Future: Outbound (SellVia → Merchant's Store), v2" section above is **now the current MVP mechanism**, not deferred — 01. Money Flow's checkout reversal made external-site sale reporting the actual model. Resolved as a universal onboarding tracking snippet (05. Payment Flow), not a bespoke per-platform integration like a Shopify app — simpler than what this doc originally anticipated needing to build.

## Update (2026-08-23): MAJOR REVISION — Shopify Webhook Replaces the Snippet, Paddle Replaced by Swich

Founder decisions, full reasoning in 02. Architecture Decision Log. Both prior updates on this page are partially superseded:

**Inbound, revised — two webhook sources now, not one:**

- `POST /webhooks/swich` — replaces `POST /webhooks/paddle`. Handles billing-payment-confirmed, payout-confirmed, and payout-failed events (exact event names unconfirmed pending real Swich integration — see 03. Table Specifications, 07. Endpoint Specifications, both updated 2026-08-23). Secured via Swich's own signature-verification scheme (shape unconfirmed), same non-session-authenticated pattern as the Paddle handler it replaces.
- `POST /webhooks/shopify-sales` — the merchant-integration direction. **The 2026-08-07 "This Is Now Active" update above is itself superseded**: the universal onboarding snippet it describes is retired for MVP. "For now we are just going with Shopify only" — this endpoint receives Shopify's native `orders/paid` webhook per merchant store, not a snippet report. Secured via Shopify's own webhook HMAC verification, per-store.

**Not a rename, an architectural simplification:** the original "Future: Outbound" framing anticipated needing to build and maintain a bespoke integration per e-commerce platform. Scoping MVP to Shopify-only removes that multi-platform burden entirely — one webhook shape to handle, not an abstraction layer over many.

**Currency:** PKR only in every payload.

---

# Edge Cases / Business Edge Cases

> Source: `Edge Cases/Business Edge Cases.md` · tag: `shared` · last updated: 2026-08-23

# Business Edge Cases

## Purpose

Situations specific to the Merchant side that fall outside the happy path.

## Cases

- **Merchant changes commission rate mid-campaign** — resolved default (State Machines): existing approved creators keep their locked-in rate, new applicants get the new rate. No re-consent flow needed as a result.
- **Merchant pauses or ends a campaign with active creators** — resolved default (State Machines): paused campaigns keep honoring in-flight attribution within the 30-day window; ended campaigns stop attributing new clicks immediately but honor pre-end clicks within the window.
- **Merchant's Paddle account gets restricted/flagged by Paddle** (e.g. Paddle's own risk systems flag unusual activity) — not addressed in any prior doc. Needs a defined SellVia-side response: likely auto-pause all of that merchant's live campaigns until resolved, to avoid creators promoting a merchant who currently can't receive funds.
- **Merchant never completes Paddle onboarding after creating campaigns** — campaigns should not be able to go live (draft → live transition) until Paddle onboarding is verified complete; this should be an explicit gate in the Campaign State Machine.

## Open Questions

- ~~Merchant Paddle-restriction handling (see above) — genuinely unaddressed until now, worth a real decision before launch given it's not a rare edge case for any platform processing real payments at scale~~ — **RESOLVED, see Update (2026-08-07) below.**

## Update (2026-08-07): RESOLVED — Auto-Pause Immediately

**Founder-confirmed:** on `seller.updated` webhook indicating a Paddle restriction (04. Event-Driven Architecture already tracks this event type), all of that merchant's live Campaigns transition to `paused` automatically — no manual Admin step required to trigger it. Existing creators keep their in-flight attribution honored within the 30-day window per the standard "paused" behavior already defined in 01. State Machines; no new applications accepted while restricted. Merchant is notified (01. Notification Logic) explaining why, and campaigns can resume once Paddle lifts the restriction (detected via a subsequent `seller.updated` event).

## Update (2026-08-23): Paddle Removed, Swich Confirmed — Offer Replaces Campaign

Founder decisions, full reasoning in 02. Architecture Decision Log.

- **Every "campaign"/"Campaign" above means "offer"/"Offer"** — no separate Campaign entity (01. Domain Model).
- **"Merchant's Paddle account gets restricted/flagged"** → **"Merchant's Swich billing gets restricted/flagged."** The auto-pause response above is unchanged in principle — all of that merchant's live Offers transition to `paused` automatically — but the trigger event is now whatever Swich's equivalent restriction/risk-flag webhook is, not Paddle's `seller.updated`. Exact Swich event name/shape unconfirmed pending real integration.
- **"Merchant never completes Paddle onboarding"** → **"Merchant never completes Swich billing connect."** Same gate logic (Offer cannot go draft → live until this is complete), different processor. See 05. Payment Flow, 03. State Machines' Offer State Machine.

---

# Edge Cases / Creator Edge Cases

> Source: `Edge Cases/Creator Edge Cases.md` · tag: `shared`

# Creator Edge Cases

## Purpose

Situations specific to the Creator side that fall outside the happy path.

## Cases

- **Creator's audience/niche data is self-reported and unverifiable** — flagged as an open fraud implication in Domain Model. Edge case: a Creator inflates their audience size to get approved for higher-value campaigns. Mitigated partially by Fraud Prevention's conversion-rate-outlier rule (a mismatch between claimed audience and actual click/sale volume is itself a signal), but not fully solved — worth an explicit "verify audience via connected social account" feature in a later version.
- **Creator shares their link in a way that violates disclosure requirements** (e.g. no FTC-required affiliate disclosure on the post) — raw data doc names this as a goal ("easy disclosure") but no enforcement mechanism exists yet. SellVia can't control what a Creator posts externally; recommend a required disclosure-language prompt/reminder at link-generation time as the practical MVP mitigation, not true enforcement.
- **Creator below the $50 payout threshold closes their account** — per Wallet Design's proposed default, allow a one-time manual below-threshold payout on account closure rather than forfeiting the balance.
- **Creator generates a link but never shares it / gets zero clicks** — not a failure case, just an empty state; dashboard should show this clearly rather than looking broken (09. UX concern).

## Open Questions

- Audience verification (see above) — flagged as a real gap, reasonable to defer to v2 rather than block MVP on it, but shouldn't be silently forgotten

---

# Edge Cases / Failure Modes Registry

> Source: `Edge Cases/Failure Modes Registry.md` · tag: `shared` · last updated: 2026-08-07

# Failure Modes Registry

## Purpose

One table: every external dependency and internal component, what breaks if it fails, blast radius, and where the mitigation lives. Consolidates what's scattered across the six Edge Cases docs, Failure Scenarios, and component-specific notes in Hosting Strategy, Authentication, etc. — doesn't replace those, indexes them.

## External Dependencies

| Component | If it fails | Blast radius | Mitigation | Detail |
| --- | --- | --- | --- | --- |
| **Swich** (updated 2026-08-23, was Paddle) | No billing cycles or creator payouts possible — checkout itself is unaffected since it happens on the merchant's own Shopify site | Commissions accrue normally, payouts delay until Swich recovers | Managed provider reliability | 08. Failure Scenarios |
| **Ory Kratos / Ory Network** | No login/signup | No new sessions; existing sessions continue until expiry; public campaign browsing unaffected | Ory Network managed reliability; self-host migration path exists if needed | 04. Authentication |
| **Supabase (MVP) / Neon (later)** | Full outage — database is the source of truth for everything | Total app outage | Managed provider reliability; Disaster Recovery point-in-time restore; Neon migration path if Supabase specifically becomes the bottleneck | 06. Disaster Recovery, 06. Hosting Strategy |
| **Cloudflare** | DNS/CDN/WAF down | Total app unreachable (everything sits behind it) | This is the single largest concentration of risk in the stack — worth knowing plainly, not just accepted silently | 06. Hosting Strategy, 06. WAF Configuration |
| **Redis** | Celery jobs stop processing, rate limiting fails open or closed depending on implementation, caching unavailable | Payouts delay (queued, not lost); checkout itself likely still works if not directly dependent on cache | Monitoring alerts on queue depth | 02. Background Jobs, 02. Caching Strategy |
| **Email ESP (transactional)** | Payout/sale/security notifications stop sending | Users don't get notified, but underlying data/money movement is unaffected — a UX/trust gap, not a data-integrity one | Bounce/complaint monitoring catches this quickly | 06. Email Infrastructure |
| **AI/embeddings provider** | Matching, screening, copy-assist unavailable | Degrades to manual category filtering (matching was always layered on top of it, per 02. Search Strategy) — not a hard failure | Graceful fallback already built into the design | 02. AI Services |

## Internal Single Points of Failure

| Component | If it fails | Blast radius | Mitigation | Detail |
| --- | --- | --- | --- | --- |
| **FastAPI process itself crashes** | Everything it directly serves goes down together (it's one monolithic process) | Full app outage until restart | Fast detection + auto-restart (process manager); modular monolith structure allows future extraction if a specific module needs isolation | 02. Backend Architecture |
| **VPS itself goes down** | Frontend + backend unreachable | Full outage; data safe (lives in managed Postgres/S3, not on the VPS) | Disaster Recovery: new VPS can be provisioned without data loss | 06. Disaster Recovery |
| **A single bad financial-chain deploy** | Incorrect commission/payout calculations could propagate across many sales before caught | High — direct financial/trust damage | Feature flags (kill in seconds, no redeploy needed), Staging-first, manual Production approval | 06. Feature Flags Strategy |

## Data-Integrity Failure Modes (from Edge Cases, indexed here)

- Partial refund commission handling — unresolved (05. Refund Handling)
- Chargeback dispute fee allocation — RESOLVED 2026-08-07: SellVia absorbs it for a merchant's first 5 lost disputes, merchant pays from the 6th onward (05. Chargebacks)
- Self-dealing (dual-role account applying to own campaign) — RESOLVED 2026-08-07: blocked outright (08. User Edge Cases)
- Merchant Swich billing restricted mid-offer (updated 2026-08-23, was Paddle) — RESOLVED 2026-08-07 (mechanism, not vendor): auto-pause all live offers immediately (08. Business Edge Cases)
- Refund clawback with insufficient future creator balance to absorb it — accepted as a real cost of doing business, not solved away (08. Payment Edge Cases)

## Reading This Table

This is a map, not a fix-it list — several rows above point to genuinely open items (already flagged in their source docs) rather than resolved mitigations. Treat a blank/weak "Mitigation" cell as a prioritization signal, not an oversight in this registry specifically.

## Open Questions

- None new — this doc surfaces existing open items in one place rather than introducing new ones. Update this table whenever a new failure mode is identified anywhere else in the documentation.

## Update (2026-08-04): FastAPI Single-Process Risk Reduced

The "FastAPI process itself crashes" row above is improved, not fully eliminated: 06. Scaling Strategy now has multi-worker load balancing active on the VPS — a single worker crashing no longer takes the whole app down, Nginx routes around it. Full VPS-level failure (the machine itself, not just one process) is unchanged and still covered by the row below (Disaster Recovery).

---

# Edge Cases / Failure Scenarios

> Source: `Edge Cases/Failure Scenarios.md` · tag: `shared`

# Failure Scenarios

## Purpose

Broader failure scenarios that cut across multiple systems — the "what's the actual blast radius" doc.

## Scenarios

- **Swich itself has an outage** (updated 2026-08-23, was Paddle) — billing cycles can't be charged and creator payouts pause, since both depend on Swich. Sales themselves are unaffected — checkout happens on the merchant's own Shopify site, outside SellVia's dependency chain entirely (reversed 2026-08-07, see 01. Money Flow). A real resilience improvement over the original hosted-checkout design, though it trades in the merchant-reporting trust gap documented in 04. Fraud Prevention and 05. Reconciliation.
- **Ory Network/Kratos has an outage** — no one can log in; public campaign browsing (unauthenticated) would still work, but no new applications/campaigns could be created.
- **A bad deploy reaches Production** — mitigated by the Staging step in CI/CD Pipeline (06. Infrastructure), but if something still slips through, rollback process needs to be fast; tied to Disaster Recovery's point-in-time recovery and the Migration Strategy's rollback guidance (03. Database).
- **Reconciliation (05. Payments) finds a persistent, unexplained mismatch** — the actual incident-response process for this (who's alerted, what's the escalation, does checkout get paused) isn't designed yet — belongs in 10. Operations → Incident Response, not yet written.

## Open Questions

- Full incident response process (see last scenario) — explicitly deferred to 10. Operations, not solved here

---

# Edge Cases / Infrastructure Edge Cases

> Source: `Edge Cases/Infrastructure Edge Cases.md` · tag: `shared` · last updated: 2026-08-04

# Infrastructure Edge Cases

## Purpose

What happens when the infrastructure itself misbehaves, not the business logic.

## Cases

- **Swich or Shopify webhook delivery fails or is delayed** (updated 2026-08-23, was Paddle-only) — both are expected to retry automatically (Swich behavior unconfirmed pending real integration; Shopify webhooks do retry natively); SellVia's Reconciliation job (05. Payments) is the backstop that catches anything a retry still misses, for either source.
- **VPS goes down** — covered by Disaster Recovery (06. Infrastructure): data lives in managed Postgres/S3, not on the VPS itself, so a new VPS can be provisioned without data loss; there will be app downtime until it's back up, which needs a status-communication plan (10. Operations, not yet written).
- **Database connection pool exhausted under load** — not addressed in any prior doc; standard mitigation is connection pooling limits + graceful degradation (returning a clear "try again" error) rather than the app crashing outright. Worth a real decision on pool size once there's load-testing data, not guessed now.
- **Background job queue backs up** (e.g. payout batching job falls behind) — monitoring (06. Infrastructure) should alert on queue depth, since a backed-up payout queue directly undermines the "fast payout" trust story.

## Open Questions

- Database connection pool sizing — genuinely needs real load data, not a number to guess here

## Update (2026-08-04): Connection Pool Question Resolved

The "database connection pool exhausted under load — not addressed" gap above is closed: Supabase (06. Infrastructure → Hosting Strategy, confirmed for MVP) includes built-in PgBouncer connection pooling, which handles this directly rather than requiring custom pool-sizing decisions at this stage. Worth re-verifying pool limits specifically if/when Supabase is outgrown per Hosting Strategy's revisit trigger.

---

# Edge Cases / Payment Edge Cases

> Source: `Edge Cases/Payment Edge Cases.md` · tag: `shared` · last updated: 2026-08-23

# Payment Edge Cases

## Purpose

Money-specific edge cases beyond the core Refund/Chargeback docs (05. Payments).

## Cases

- **Partial refund** — flagged as a genuine open gap in Refund Handling (05. Payments): proportional commission reduction recommended but not confirmed.
- **Sale in one currency, creator's payout account set up for another** — e.g. a Creator based in the EU promoting a merchant's GBP-priced offer. Handled by Paddle's cross-currency payout support (per Money Flow's FX default), but the exact rate/timing shown to the Creator on their dashboard needs to be clear about conversion, or it will look like a discrepancy/bug to them.
- **Refund happens after a creator has already withdrawn below the $50 remaining balance** — the clawback (Commission Engine's 14-day rule) has nothing left to deduct from. This is the "real loss" scenario already flagged in Money Flow as something the platform needs to accept and size, not solve away.
- **Duplicate/replayed Paddle webhook causes a double-credit attempt** — prevented by idempotent processing (02. Event-Driven Architecture), but worth calling out here as the specific financial edge case that requirement exists to prevent.
- **Chargeback dispute fee** — flagged as unresolved in Chargebacks (05. Payments): who absorbs it isn't decided.

## Open Questions

- Partial refund handling and chargeback fee allocation are both genuinely unresolved (carried over from 05. Payments, not new) — restating here since Edge Cases is where they'll actually get tested against real scenarios first

## Update (2026-08-07): Resolved — No Creator Clawback, Ever

The "insufficient balance to absorb clawback" scenario above no longer applies to creators — there is no creator clawback at all (01. Commission Engine, reversed from the earlier 14-day rule). The real risk moved to the merchant instead: their Connect balance can go negative if they've already withdrawn funds before a refund is issued. That's the scenario worth monitoring now, not a creator-side edge case.

## Update (2026-08-07): RESOLVED

Dispute fee allocation is resolved: SellVia absorbs it for a merchant's first 5 lost disputes, merchant pays from the 6th onward. See 05. Chargebacks for the full rule.

## Update (2026-08-23): Paddle Removed, Swich Confirmed, Pakistan/PKR Only

Founder decisions, full reasoning in 02. Architecture Decision Log.

- **"Sale in one currency, creator's payout account set up for another"** — **moot.** Market is Pakistan-only, PKR only; there's no cross-currency case for MVP. This case returns only if multi-currency comes back post-MVP (Full Product Vision).
- **"Duplicate/replayed Paddle webhook"** — means **Swich webhook** now; the idempotent-processing requirement (02. Event-Driven Architecture) is unchanged in principle, just a different processor's webhook payload.
- **Chargeback dispute fee** — see 05. Chargebacks' 2026-08-23 update: the 5-dispute grace allowance was designed for card-network chargebacks specifically, and it's unconfirmed whether Swich's bank-transfer/JazzCash/EasyPaisa-settled billing cycles have an equivalent dispute mechanism at all — flagged there, not re-solved here.

---

# Edge Cases / User Edge Cases

> Source: `Edge Cases/User Edge Cases.md` · tag: `shared` · last updated: 2026-08-23

# User Edge Cases

## Purpose

Account-level edge cases not covered by the happy-path User Flows (01. Business Logic).

## Cases

- **Account holds both Merchant and Creator roles** (allowed per User Roles' default) — what happens if this same user applies to their own campaign? Should be blocked explicitly; self-dealing undermines the whole attribution/trust model.
- **Merchant or Creator deletes their account mid-active-campaign/application** — handled via Soft Delete (03. Database); existing Sales/Commissions must remain intact and resolvable even after the account is soft-deleted.
- **Duplicate application attempt** — blocked at the database level via the unique constraint on (campaign_id, creator_profile_id) per 03. Database → Constraints; API should return a clear 409 (per 07. API → Error Responses), not a generic failure.
- **Creator's Paddle onboarding incomplete** — if a Creator is approved for a campaign but hasn't finished Paddle seller onboarding, they shouldn't be able to generate a live AffiliateLink yet (a link with no way to receive payout is a trust problem, not just an inconvenience). Needs an explicit "onboarding incomplete" gate before link activation.

## Open Questions

- ~~Exact UX for the Paddle-onboarding-incomplete gate (blocked entirely vs. link works but payout is held) — recommend blocking link activation entirely rather than accruing unpayable commission, to avoid a confusing backlog~~ — **RESOLVED, see Update (2026-08-07) — Hard Block below.**

## Update (2026-08-07): RESOLVED — Hard Block

**Founder-confirmed: blocked outright, no exceptions.** A CreatorProfile cannot submit an Application to a Campaign owned by the same User's MerchantProfile. Enforced at the database constraint level where possible (matching `creator_profile.user_id` against the campaign's owning `merchant_profile.user_id` at application-creation time) and re-checked at the API layer (04. Security → Authorization's "UI is not a trust boundary" rule applies here too — this check runs server-side regardless of what the UI shows).

## Update (2026-08-23): Paddle Removed, Swich Confirmed, Offer Replaces Campaign

Founder decisions, full reasoning in 02. Architecture Decision Log.

- **"Campaign"/"campaign" throughout this page means "Offer"/"offer"** — no separate Campaign entity (01. Domain Model). The self-dealing rule, duplicate-application constraint (now `offer_id, creator_profile_id`), and soft-delete handling above are all unchanged in substance.
- **"Creator's Paddle onboarding incomplete"** → **"Creator's Swich payee registration incomplete."** Same hard-block gate (resolved: block link activation entirely, not "link works but payout held") — only the processor name changes. See 05. Payout Process, [FEATURE_LIST.md §2.4].

---

# Product Foundation / Full Product Vision (Post-MVP)

> Source: `Product Foundation/Full Product Vision (Post-MVP).md` · tag: `shared` · last updated: 2026-08-23

# Full Product Vision (Post-MVP)

## Purpose

Where SellVia goes beyond MVP — everything deliberately deferred, and the eventual full vision. This doc exists so "we're not doing X yet" doesn't quietly become "we forgot about X." Updated whenever a new post-MVP decision or deferral is made.

## Checkout & Merchant Reach

- **External-site checkout tracking** — redirect link + webhook/pixel attribution for merchants who want to keep their own checkout. **Partially superseded 2026-08-23:** the Shopify version of this is no longer post-MVP, it's the current MVP model (see MVP Scope). What remains genuinely deferred is support for platforms *other than* Shopify (WooCommerce, custom sites) — the universal snippet approach (retired for MVP in favor of Shopify's webhook) is the natural mechanism to bring back if/when non-Shopify merchants are supported.
- Public API for merchants (their own reporting/integrations) — not needed until there's demand beyond SellVia's own frontend
- **Non-Shopify merchant platforms** (added 2026-08-23) — WooCommerce, Magento, fully custom sites. Deferred until there's real demand beyond the Shopify-only Pakistan MVP.

## Payments & Currency

- ~~**PKR support** — dropped for MVP due to Paddle payout limitations~~ — **reversed 2026-08-23: PKR is now the only MVP currency**, since the market itself narrowed to Pakistan-only and Paddle was removed entirely. See Product Vision, MVP Scope.
- **Multi-currency / USD-EUR-GBP support (added 2026-08-23)** — now the deferred item, inverted from the original framing. Revisit once SellVia expands beyond Pakistan.
- ~~A payment processor / MoR provider~~ — **resolved 2026-08-23: Swich**, confirmed as the MVP processor (see MVP Scope, Architecture Decision Log). What's genuinely deferred now: **a Merchant-of-Record provider** (Paddle-like — one that absorbs tax/compliance obligations, not just moves money) — relevant if/when SellVia expands beyond Pakistan and needs multi-jurisdiction tax handling Swich doesn't provide.
- **Subscription/tiered pricing** — the earlier $49/mo idea was dropped in favor of a flat 2% fee; could return as a decoupled paid tier for extra features (analytics, priority placement) later, not as a fee discount
- Volume-based fee reduction for high-performing merchants, if retention data justifies it

## AI & Fraud

- **AI-based fraud/anomaly detection** — stays rules-based until there's real transaction volume to train against; layering an anomaly-scoring model on top of the rules is the planned evolution, not a replacement for them
- Audience verification for creators (connected social account) — flagged as a real gap in Creator Edge Cases, deferred rather than solved with self-reported data indefinitely

## Architecture

- **Service extraction from the modular monolith** — Payments is the most likely first candidate if a specific module ever needs independent scaling or failure isolation. Only pursued once there's real evidence, not preemptively.
- **Database migration off Supabase** — to Neon, RDS, or self-managed, once connection limits, compute ceiling, or cost make it a real bottleneck (Hosting Strategy's revisit trigger)
- Horizontal scaling / read replicas (Scaling Strategy) once vertical scaling on a single VPS stops being enough

## Product Surface

- **Mobile apps** — raw data doc's original vision included dedicated tracking apps; MVP is responsive web only
- Complex analytics/BI dashboards beyond the current KPIs/Funnel Tracking/Dashboards scope
- Referral program — explicitly undesigned; not even confirmed as wanted yet, let alone specified (01. Business Logic → Referral Logic)
- Tiered Admin roles (junior/senior moderator) if the team grows enough to need separation of duties
- "Request a different rate" — single counter-offer negotiation feature, if flat take-it-or-leave-it commission proves to be real friction

## Compliance (explicitly deferred by founder, not forgotten)

- Sales tax / VAT across USD/EUR/GBP jurisdictions
- T Rules Amendment 2026 (SGI/deepfake regulation) — relevance depends on whether SellVia ever has an India-based user nexus
- General intermediary/platform liability obligations, jurisdiction TBD

## Rollout Alignment (from Product Roadmap)

This doc's contents map to the **Private Beta → Public Launch** transition and beyond — MVP Scope covers up through Private Beta readiness; most of what's listed here is Public-Launch-or-later territory, not a fixed timeline.

## Auth & Enterprise Features

- **SAML/SSO** — deferred, contingent on an actual enterprise customer requiring it, not built speculatively. Clerk supports this on higher tiers already, so this is a plan upgrade when needed, not a migration or new engineering effort (04. Security → Authentication).

## Update (2026-08-07): No Longer Deferred — Now Core MVP

The "External-site checkout tracking" item above is **no longer post-MVP** — it's the current MVP model (01. Money Flow, reversed 2026-08-07). Remove from this deferred list; SellVia-hosted checkout is now the thing that's NOT built, rather than the reverse.

---

# Product Foundation / Mission & Principles

> Source: `Product Foundation/Mission & Principles.md` · tag: `shared`

# Mission & Principles

## Mission

Give every product a sales force that only gets paid when it sells — and give every creator a way into brand partnerships without cold outreach or chasing invoices.

## Principles

1. **Reduce complexity relentlessly.** Every field, screen, and flow is checked against: "does a non-technical brand owner or a first-time creator understand this in 5 seconds?" Hide irrelevant fields contextually rather than adding toggles (digital sellers never see shipping fields).
2. **Don't favor one side.** Merchant flow and creator flow get equal design attention. A feature that helps one side at the other's expense doesn't ship.
3. **No fake momentum.** No fabricated testimonials, no invented "trusted by" numbers, no metrics until they're real. This is a stated constraint ([design.md](http://design.md), Section Design) and a positioning choice ([wesellvia.com](http://wesellvia.com) shows zeroed dashboards deliberately).
4. **Attribution over trust.** Don't ask anyone to trust that a post worked — show both sides the same receipt.
5. **Niche before scale.** Solve the chicken-and-egg problem by winning one category/vertical completely before expanding, not by going wide and thin.
6. **Rules before AI.** Fraud detection and core logic start deterministic (rules, thresholds) and only get an ML/AI layer once there's real data to train on — avoids black-box decisions on money during MVP.

## Source

Synthesized from: raw data doc (Goals section), Case Study doc (Actual Design Objective), [design.md](http://design.md) (Design Philosophy), [wesellvia.com](http://wesellvia.com) copy.

---

# Product Foundation / MVP Scope

> Source: `Product Foundation/MVP Scope.md` · tag: `shared` · last updated: 2026-08-23

# MVP Scope

## Purpose

The single source of truth for what's actually being built right now — every decision below has already been made elsewhere in this documentation; this doc exists so scope doesn't have to be reconstructed by scanning 100+ pages. Updated every time a new MVP-scoping decision is made.

## Checkout & Payments

- **SellVia Checkout REMOVED (reversed 2026-08-07)** — external-site tracking is now the model: customer buys on the merchant's own site, SellVia tracks via redirect + universal onboarding snippet + discount-code fallback. See 02. Architecture Decision Log.
- **Processor: Paddle** (reversed 2026-08-10 from Stripe Connect — see 02. Architecture Decision Log). Handles periodic merchant billing; creator payout mechanism not yet confirmed against Paddle's platform/marketplace product — flagged as an open item below, not solved.
- **Currencies:** USD, EUR, GBP only (PKR dropped for now)
- **Commission:** merchant-set freely, no platform range, no bargaining
- **Platform fee: 2% flat**, no subscription tier
- **Payout:** commission accrues to creator balance after merchant billing succeeds (not instant/live-split, per the Checkout & Payments reversal below); creator bank payout gated at **$50 threshold**; merchant payout NOT threshold-gated
- **Attribution window:** 30 days
- Refund clawback: RESOLVED 2026-08-07 — creator commission is never clawed back; merchant absorbs full refund cost

## Stack

- **Backend:** FastAPI (Python), single **monolithic** service (modular monolith — extractable later, not built as microservices)
- **Frontend:** Next.js + shadcn/ui + Tailwind
- **Database:** **Supabase** (Postgres + pgvector + built-in pooling) — explicit MVP choice, revisit at scale
- **Auth:** Ory Kratos (Ory Network managed hosting for MVP, self-hosted later — updated 2026-08-04, was Clerk)
- **Background jobs:** Celery (Redis broker)
- **Hosting:** VPS (Hetzner vs. DigitalOcean — still open) + Cloudflare (DNS/CDN/WAF)
- **Git:** Monorepo, short-lived service-prefixed feature branches, no long-lived per-service branches

## AI Features (initial level, no training/ML infra)

- Creator ↔ Offer matching (embeddings + pgvector)
- Application screening summaries (LLM, cached per application)
- Campaign copy assist (LLM draft)
- Disclosure nudge: templated, NOT LLM-generated (legal text)
- Fraud detection stays **rules-based**, not AI, for MVP

## Security & Resilience

- WAF (Cloudflare), IP anomaly detection with throttle→ban escalation, documented DDoS response plan
- Tenant isolation enforced across cache, DB, background jobs, file storage, and logs — fail-closed principle
- Cross-tenant automated test suite (built after MVP functionally complete, required before Private Beta)
- Feature flags **mandatory** for any change touching Sales/Commissions/Payouts/Refunds

## Accessibility & Machine-Readability (binding gates, not aspirational)

- Full keyboard navigation, screen reader/ARIA compliance, WCAG AA contrast (verification pending)
- Structured data ([schema.org](http://schema.org)), semantic HTML, OpenAPI spec, llms.txt, deliberate robots.txt

## Cost & Financial Tracking

- AI/token usage tracking per feature
- Unit economics (revenue vs. cost per user, asymmetric by role)
- Automated monthly P&L (Paddle reconciliation + hosting costs + AI costs)

## Roles & Access

- Merchant, Creator, Admin (single flat role, no tiering)
- Dual-role accounts (Merchant + Creator) allowed
- No follower-count floor for creator eligibility

## Explicitly Deferred to Post-MVP

See **Full Product Vision (Post-MVP)** for the complete list — notably: external-site checkout tracking, PKR support, subscription pricing, AI-based fraud detection, microservices extraction, mobile apps.

## Still-Open Items Blocking MVP Completion (not deferred — need a real decision)

- Commission-rate lock timing: "at approval" (State Machines) vs. "at time of sale" (Business Rules) — genuine conflict, unresolved
- Partial refund commission handling
- Chargeback dispute fee allocation — RESOLVED 2026-08-07: SellVia absorbs first 5 lost disputes per merchant, merchant pays from 6th onward
- Sales tax / VAT (founder has deferred this explicitly to end of build)
- Self-dealing block (dual-role account applying to own campaign) — RESOLVED 2026-08-07: blocked outright
- Merchant Paddle-restriction handling
- Beachhead niche/vertical for go-to-market
- Private Beta cohort size/cap
- Supabase Storage vs. separate S3-compatible provider
- VPS provider: Hetzner vs. DigitalOcean
- India IT Rules relevance (founder has deferred this explicitly to end of build)
- **Paddle creator-payout evaluation (added 2026-08-10)** — does Paddle for Platforms actually support per-creator payout (KYC, bank transfer, $50 threshold) the way Stripe Connect did — needs real evaluation before build, see Architecture Decision Log

## Status & Incident Communication (added 2026-08-04, upgraded from earlier "deferred")

- Public status page on a **separate domain, separate infrastructure** from the main SellVia stack — managed status page tool (Instatus/Better Uptime-style), not self-hosted
- Scheduled maintenance announcements with subscriber notifications
- Formal incident communication workflow (Investigating → Identified → Monitoring → Resolved), tied to checkout-pause incidents specifically

## Update (2026-08-07): RESOLVED

Commission-rate lock timing is resolved — locked at approval, confirmed by founder. No longer an open item. See 01. Business Rules and 01. Commission Engine for the correction.

## Update (2026-08-07): MAJOR REVISION — Checkout & Payments Model Reversed

**This section's original "SellVia Checkout only" bullets are superseded.** Current model:

- **External-site tracking** — customer buys on the merchant's own site; SellVia redirect logs the click, a universal onboarding tracking snippet on the merchant's confirmation page reports the sale
- **Paddle** used for periodic merchant billing and creator payouts, not a live per-sale split
- **Money collection: billed periodically** (merchant's card on file, recurring cycle)
- **Creator payout: bill-first-then-pay** (working default) — SellVia doesn't front commission before billing succeeds
- **Refund clawback: creator commission is NEVER clawed back** (RESOLVED) — merchant absorbs full cost via billing-cycle credit adjustment
- **Commission rate: locked at creator approval** (RESOLVED), never changes after
- **Self-dealing: blocked outright** (RESOLVED) — dual-role account cannot apply to own campaign
- **Chargeback dispute fee: SellVia absorbs first 5 lost disputes per merchant, merchant pays from 6th onward** (RESOLVED)
- **Merchant Paddle restriction: auto-pauses all live campaigns immediately** (RESOLVED)
- Currencies, platform fee (2% flat), attribution window (30 days) unchanged

Full detail: 01. Money Flow, 01. Commission Engine, 01. State Machines, 05. Payment Flow, 02. Architecture Decision Log (all updated 2026-08-07).

## Update (2026-08-10): Payments Processor Reversed — Paddle Replaces Stripe

**Founder decision: Paddle instead of Stripe, across the board** (merchant billing, tax, and creator payouts). See 02. Architecture Decision Log for full reasoning. Every doc referencing Stripe/Stripe Connect/Stripe Tax has been updated to Paddle. One real open item this creates, not yet resolved: Paddle's per-creator payout capability (KYC collection, bank transfer, threshold-gated payout) hasn't been evaluated the way Stripe Connect's was — added to Still-Open Items above.

**Superseded by the 2026-08-23 update below — Paddle itself is now removed for MVP.**

## Update (2026-08-23): MAJOR REVISION — Pakistan-Only, No Processor, Shopify-Only, Offer Absorbs Campaign

Four founder decisions. Full reasoning in 02. Architecture Decision Log; this section is the scope-level summary.

**Market:** Pakistan only for MVP. Every merchant is a Pakistani business. This is the resolved "beachhead" — geography, not a product niche.

**Currency:** **PKR only.** USD/EUR/GBP support is not needed for MVP (it moves to the deferred list — see Full Product Vision (Post-MVP)).

**Payments processor: Swich** (swichnow.io) — confirmed 2026-08-23, replacing the earlier "no processor, manual bank transfer" working default. A Pakistani payments infrastructure company, PCI-DSS v4.0.1 certified, covering both legs: recurring billing/invoice-links for merchant collection, and payout/disbursement API (bank, JazzCash, EasyPaisa, Raast) for creator payouts. **Not a Merchant of Record like Paddle was** — Swich moves money, it doesn't absorb tax/compliance obligations, so SellVia itself remains responsible for its own tax posture (see Payments/Tax Considerations). Pricing, onboarding requirements, and exact API shapes are unconfirmed pending an actual signup/integration conversation.

**Merchant integration: Shopify only.** The universal onboarding snippet (any platform) is retired for MVP in favor of **Shopify's native webhook** — more reliable (server-side, not cookie-dependent), at the cost of not supporting other platforms yet. "For now we are just going with Shopify only" — other platforms (WooCommerce, custom sites, the universal snippet) are explicitly deferred, not abandoned.

**Domain model: Offer absorbs Campaign.** There is no separate Campaign entity. "Offer is offer, it is not turning into any campaign at all." An Offer carries commission rate + lifecycle status (draft/live/paused/ended) directly; Applications/AffiliateLinks/Sales attach to an Offer. Every doc and this doc's own earlier language that treats Offer and Campaign as two entities is superseded — see 01. Domain Model (updated same date).

### What this changes in the sections above

- **Checkout & Payments:** currencies row → PKR only, not USD/EUR/GBP. Processor row → **Swich** (confirmed 2026-08-23), not Paddle, not a manual bank-transfer default. Attribution/refund/commission-lock rules (30-day window, locked-at-approval, no clawback) are unchanged in substance — only re-worded from "Campaign" to "Offer."
- **Stack:** remove Paddle as a dependency, add **Swich SDK/API** in its place — a real integration (recurring billing + payout API + webhooks), not the manual admin workflow the interim bank-transfer default implied.
- **AI Features:** unchanged — "Creator ↔ Offer matching" replaces "Creator ↔ Campaign matching," same mechanism.
- **Roles & Access:** unchanged.

### Still-Open Items — updated

- ~~Paddle creator-payout evaluation~~ — **moot, Paddle removed.**
- ~~Merchant Paddle-restriction handling~~ — **moot.**
- ~~Exact local settlement rail~~ — **resolved 2026-08-23: Swich**, covering both billing and payout. Not yet done: the actual signup/integration work.
- **New (2026-08-23):** Swich onboarding requirements for a 10–25-merchant Private Beta volume — pricing, KYC, minimums — not yet confirmed, needs a real vendor conversation before build starts.
- **New (2026-08-23):** exact Swich webhook/reconciliation shape — replaces the earlier "who reconciles bank transfers" open item now that there's an actual processor with its own transaction records to reconcile against, but the specific fields/events aren't yet mapped.
- Shopify app review/approval requirements (Shopify's own app-store or private-app process) for the webhook integration — not yet scoped.
- Sales tax / VAT: narrows to Pakistani tax law (FBR) only, not multi-jurisdiction USD/EUR/GBP — still deferred per founder's existing compliance-review deferral. **Unaffected by the Swich decision** — Swich is a payment processor, not a Merchant of Record, so it doesn't absorb this the way Paddle would have.
- Beachhead niche/vertical: resolved as geography (Pakistan) rather than a product category — whether a category focus is still needed *within* Pakistan is open.
- Private Beta cohort size/cap: unchanged, still 10–25.

---

# Product Foundation / Product Glossary

> Source: `Product Foundation/Product Glossary.md` · tag: `shared` · last updated: 2026-08-23

# Product Glossary

## Purpose

Shared vocabulary so "campaign," "commission," and "payout" mean the same thing in every doc, in code, and in conversations with users.

| Term | Definition |
| --- | --- |
| **Merchant / Brand / Business Owner** | A user selling a product (digital or physical) who lists it with a commission attached. Pakistan-based only for MVP, on Shopify. |
| **Creator** | A user with an audience who promotes a merchant's product in exchange for commission. |
| **Offer / Product** | The thing being sold, **and** its commission listing in one entity. Has a price (PKR), category (digital/physical), a merchant-set commission rate, and a lifecycle status (draft/live/paused/ended). Open for creators to apply to. There is no separate "Campaign" — see Note below. |
| **Application** | A creator's request to promote a specific offer. States: pending, approved, rejected (see State Machines). |
| **Affiliate Link** | The unique, trackable URL generated for an approved creator–offer pair (e.g. `sellvia.link/mia-glow` per the live site example). Carries the creator's attribution "fingerprint." |
| **Click / Attribution Event** | A tracked interaction (click, cart add, purchase) tied to a specific affiliate link, timestamped, visible to both merchant and creator. |
| **Sale / Order** | A verified purchase attributed to an affiliate link. Triggers commission calculation. |
| **Commission** | The creator's share of a sale, set as a percentage by the merchant at offer creation (10–50% typical, per raw data doc; 20% shown in the live site's Glow Serum example). |
| **Payout** | The transfer of a creator's earned commission to them, released once the merchant's billing cycle for that sale settles (bill-first-then-pay via Swich) — not triggered instantly on a verified sale. |
| **Wallet / Balance** | A creator's running total of earned-but-not-yet-paid-out commission. |
| **Receipt** | The shared, identical record of a sale shown to both merchant and creator (amount, commission split, timestamps). |
| **Waitlist** | Pre-launch signup; current validation-stage entry point. |

## Notes

Terms here should be treated as the canonical names used in the database schema (03. Database) and API (07. API) — avoid renaming these mid-build.

## Update (2026-08-23): "Campaign" Retired as a Term

**"Campaign" no longer names a distinct entity** — merged into Offer, per founder decision ("offer is offer, it is not turning into any campaign at all") and 01. Domain Model's 2026-08-23 revision. Any doc still using "Campaign" as a noun for the commission-bearing listing should be read as "Offer." Also: **commission and price are PKR only for MVP** (Pakistan-only market), and the merchant's own checkout is **Shopify specifically**, not any e-commerce platform — see 02. Architecture Decision Log.

---

# Product Foundation / Product Roadmap

> Source: `Product Foundation/Product Roadmap.md` · tag: `shared` · last updated: 2026-08-23

# Product Roadmap

## Purpose

Reconcile the two roadmap framings that already exist — the public-facing 4-stage roadmap on [wesellvia.com](http://wesellvia.com), and the internal 7-phase implementation plan from the raw data doc — into one sequence.

## Public Roadmap ([wesellvia.com](http://wesellvia.com))

1. **Research** — months of conversations with brands and creators about why partnerships die. (Complete / ongoing)
2. **Validation** — *you are here.* Landing page live, waitlist open. Signups move the line to Private Beta.
3. **Private Beta** — first cohort invited from the waitlist, in join order, on founding terms.
4. **Public Launch** — the open marketplace.

## Internal Implementation Phases (raw data doc), mapped onto the public roadmap

| Public stage | Internal phase(s) |
| --- | --- |
| Validation (current) | MVP Definition, Rapid Prototyping |
| → Private Beta | Usability Testing, Design System, Backend & Tracking |
| Private Beta | Beta Launch & Feedback |
| → Public Launch | Iterate and Expand |

## MVP Definition (must-have for Private Beta)

From the raw data doc, confirmed as still current:

- User auth (merchant + creator roles)
- Product/campaign listing
- Affiliate link generation + click tracking
- Basic sales tracking and attribution
- Simple dashboards (merchant + creator)
- ~~Payout mechanism~~ — resolved 2026-08-23: **Swich** (superseding the earlier PayPal/bank-transfer/Paddle framing entirely — see MVP Scope, Architecture Decision Log)

**Explicitly deferred post-MVP:** complex analytics, AI-based matching/screening (see 02. Technical Architecture → AI Services, once written). ~~External-site checkout tracking is deferred; MVP is SellVia Checkout only — every sale happens on SellVia's hosted checkout, no exceptions.~~ — **reversed 2026-08-07: external-site tracking IS the MVP model.** SellVia has no hosted checkout of its own; every sale happens on the merchant's own Shopify store, with SellVia tracking via redirect + webhook/pixel attribution (see MVP Scope, Money Flow).

## Open Questions

- Target date/size for Private Beta cohort ("in join order" — is there a cap?)
- Confirmed beachhead niche/category (referenced as open in Product Vision doc)
- Whether AI matching (creator↔offer) ships in Private Beta or is deferred to Public Launch — MVP Scope lists Creator ↔ Offer matching under in-scope AI Features at an "initial level," but doesn't itself say whether that ships within Private Beta specifically or waits for Public Launch, so this stays open pending that call

## Update (2026-08-07): RESOLVED — 10-25 Merchants/Creators

**Founder-confirmed:** Private Beta cohort is capped at **10-25** merchants/creators combined for the first invited group — small and intentional, matching "in join order, on founding terms" rather than a mass invitation. Waitlist → beta invitation (10. Admin Panel) stops issuing invites once this cap is reached, reassessed once this initial cohort is running smoothly.

## Update (2026-08-07): Invitation Process RESOLVED — Curated First Cohort, Automatic After

**Founder-confirmed:** the first Private Beta cohort (10–25 merchants/creators) is **manually curated**, not strict signup-order — chosen specifically to build a coherent cluster around the eventual beachhead niche (still open, per Product Vision) rather than a scattered group of unrelated signups. This is a deliberate, one-time exception to the "automate everything" principle applied everywhere else in this build (04. Fraud Prevention, 05. Payment Flow, etc.) — justified because it's a single small decision, not a repeating operational burden, and directly supports niche-fit strategy.

**After the first cohort is seated: fully automatic, strict signup order**, zero curation — matches the general automation-first approach for everything that scales beyond a one-time decision.

## Update (2026-08-23): Correction — "MVP Definition" Checkout Claim Never Got the 2026-08-07 Reversal

The "MVP Definition" section above still described "SellVia Checkout only" as the model and listed external-site checkout tracking as deferred — that was superseded by the 2026-08-07 reversal (see MVP Scope, Money Flow) and never got corrected here. Corrected: SellVia has no hosted checkout; external-site tracking (redirect + webhook/pixel attribution on the merchant's own Shopify store) is the current MVP model.

---

# Product Foundation / Product Vision

> Source: `Product Foundation/Product Vision.md` · tag: `shared` · last updated: 2026-08-23

# Product Vision

## Purpose

This document defines what SellVia is, who it's for, and what "winning" looks like, so every downstream decision — business logic, architecture, UX — can be checked against a single source of truth instead of re-litigated per feature.

## Vision Statement

**SellVia gives every product a sales force that only gets paid when it sells.**

Brands list products with a commission attached. Creators pick what fits their audience and apply. Every sale is traced to the exact creator and post, and payout settles automatically once the merchant's billing cycle for that sale closes via Swich — no invoices, no net-60, no spreadsheets.

## The Problem (as validated on [wesellvia.com](http://wesellvia.com))

Two groups have complementary, currently-unsolved problems:

| Side | Current reality |
| --- | --- |
| Small/early-stage brands | Cold DMs with a media kit and a prayer. Campaign tracking across five spreadsheets. "Exposure" offered as payment. No affiliate manager, no ad budget. |
| Early-career creators | Chasing invoices for 60+ days. No proof a post "worked." No structured way to find brands that fit their audience without cold outreach. |

Existing affiliate networks (Amazon Associates, ShareASale, Rakuten, Impact, CJ Affiliate) exist, but are **built for enterprises with dedicated affiliate managers** — not for a small brand and a 9k-follower creator who'd be a perfect match. That gap is the whole opportunity.

## Target Users

**(A) Business Owners / Brands** — e-commerce retailers, SaaS founders, brick-and-mortar stores wanting performance-based marketing (pay-per-sale) with no upfront cost and no contracts.

**(B) Content Creators** — bloggers, YouTubers, Instagram/TikTok creators, affiliate publishers, and anyone monetizing an audience, including small/niche creators (e.g. 9k followers) who are underserved by enterprise-grade networks.

## Value Proposition

- **For brands:** $0 owed until something sells. No ad budget, no sales team, no contract. Set a commission, go live, review applicants, done.
- **For creators:** Discover campaigns instead of cold-pitching. Every click/cart/purchase attributed to the exact creator and post. Commission is agreed *before* anyone posts — no "exposure" as payment.
- **For both:** One shared receipt per sale. Payout settles automatically once the merchant's billing cycle for that sale closes — no invoicing, no chasing, no 60-day wait.

## What Makes This Different

1. **Radical transparency as a trust device.** The current landing page explicitly states "SellVia doesn't exist yet," shows zeroed-out metrics ("0 creators approved, 0 sales, $0 tracked revenue"), and frames signups as votes to build the product. This is a deliberate positioning choice, not a placeholder — it should persist into early product messaging ("you are literally here" / roadmap stage visibility).
2. **Creators apply, brands approve** — not cold outreach in either direction. Everyone in a deal chose to be there.
3. **Attribution is automatic and mutual.** Both sides see the same receipt for the same sale at the same time. No "did this post actually work?" ambiguity.
4. **Niche-first go-to-market**, not "all products, all creators" from day one — addresses the two-sided chicken-and-egg problem directly (see Business Rules / GTM notes in 01. Business Logic).

## Success Metrics (from raw data + case study docs)

- Number of active merchants and active affiliates (both sides of liquidity)
- Click-to-sale conversion rate
- Time from campaign listing → first creator approved
- Time from sale → payout settled (target: near-instant, not net-60)
- User satisfaction / usability scores on both dashboards
- Waitlist → activated user conversion (current validation-stage KPI)

## Guiding Principles

- **Reduce complexity relentlessly.** Contextually hide irrelevant fields (e.g. a digital-goods seller never sees a shipping field). Progressive disclosure over feature-front-loading.
- **Don't favor one side.** Every business rule and UX decision gets checked against both the merchant flow and the creator flow.
- **No fake momentum.** No fabricated testimonials, no invented metrics, no "trusted by 1000+ brands" until it's real — this is a stated design constraint ([design.md](http://design.md) § Section Design), not just a nice-to-have.
- **Design system discipline.** Black/lime, Outfit/Figtree, no gradients, no glassmorphism — see [design.md](http://design.md) and 09. UX for the full system.

## Non-Goals (for MVP)

- Multi-currency support
- Complex analytics/BI dashboards
- Enterprise/large-brand tooling (dedicated affiliate managers, bulk campaign management)
- International tax handling beyond basic W-8/W-9

## Open Questions

- ~~What's the actual niche/category for the "beachhead" cohort~~ — **resolved 2026-08-23 as geography, not category: Pakistan-only for MVP** (see Update below). Whether a category focus is also needed *within* Pakistan is still open.
- What follower/audience-size floor (if any) applies to creator eligibility? The FAQ on [wesellvia.com](http://wesellvia.com) implies "no," but this needs an explicit rule for moderation/quality control.
- Payout rails at launch: ~~PayPal + bank transfer~~ / ~~Paddle~~ — **superseded, see Update below.**
- How does "private beta, in join order, on founding terms" (from the roadmap) map to concrete product rules — is there a cap on beta cohort size? (Resolved elsewhere: 10–25, see Product Roadmap.)

## Update (2026-08-23): Pakistan-Only Market, No Processor, Shopify-Only

Founder decision, full reasoning in 02. Architecture Decision Log:

- **Target market narrows to Pakistan only for MVP** — every Merchant is a Pakistani business. This is the resolved beachhead: geography, not a product niche.
- **Currency: PKR only**, replacing USD/EUR/GBP for MVP.
- **Payment processor: Swich** (swichnow.io), confirmed 2026-08-23 — replaces the interim "no processor, bank transfer" default. Covers both merchant billing (recurring invoice-links) and creator payout (disbursement across bank/JazzCash/EasyPaisa/Raast) under one vendor. Not a Merchant of Record like Paddle was — SellVia keeps its own tax responsibility. Signup/integration details unconfirmed pending a real vendor conversation; see MVP Scope's Still-Open Items.
- **Merchant integration: Shopify only.** Every MVP merchant's own checkout is a Shopify store, tracked via Shopify's native webhook rather than the universal snippet — "for now we are just going with Shopify only." Other platforms are deferred, not abandoned — see Full Product Vision (Post-MVP).
- **Offer absorbs Campaign** — "offer is offer, it is not turning into any campaign at all." No separate Campaign entity; see Product Glossary, Domain Model.

This changes the Value Proposition and Target Users sections above only in scope (Pakistan, Shopify, PKR), not in substance — the core mechanism (commission-only, creators apply, automatic attribution, shared receipt) is unchanged.

**Correction to Vision Statement / Value Proposition wording above:** both sections previously said payout "fires automatically the moment a sale is verified" — that overstated it. The resolved model is bill-first-then-pay: commission accrues to a creator's balance only after the merchant's billing cycle for that sale settles via Swich, not instantly on verification. Reworded above to match.

## Related Docs

-
    1. Business Logic → User Roles, Domain Model, Commission Engine
-
    1. UX → Design System ([design.md](http://design.md))
-
    1. Infrastructure & DevOps → Environment Strategy

---

# Product Foundation / Success Metrics

> Source: `Product Foundation/Success Metrics.md` · tag: `shared`

# Success Metrics

## Purpose

Define what "working" means at each stage, so the team isn't guessing whether a release helped.

## Marketplace Health (both sides of liquidity)

- **Active merchants** — merchants with ≥1 live campaign in the last 30 days
- **Active creators** — creators with ≥1 approved application or active link in the last 30 days
- **Liquidity ratio** — active creators : active merchants. Track to catch one-sided growth early (a known risk per Challenges & Risks in the raw data doc).

## Conversion Funnel

- Waitlist signup → activated account (post-launch)
- Campaign listed → first creator application (time-to-first-application)
- Application submitted → approved (approval rate, time-to-decision)
- Click → cart → purchase (click-to-sale conversion rate, per campaign and per creator)
- Sale verified → payout settled (target: near-instant; this is a core differentiator vs. net-60 invoicing, so track it explicitly, not just as a technical SLA)

## Trust & Quality

- Disputed/flagged sales as % of total
- Fraud/anomaly rate (fake clicks, cookie stuffing) — see 04. Security → Fraud Prevention
- Creator satisfaction / merchant satisfaction (usability scores, per raw data doc's original success metrics)

## Validation-Stage Metrics (current phase)

- Waitlist signups (total, and split business vs. creator)
- "Why do you want to join" response quality/themes (qualitative signal on positioning)
- Roadmap stage: currently Stage 02 — Validation, per [wesellvia.com](http://wesellvia.com)

## Open Questions

- What conversion rate or liquidity ratio triggers a decision to open the niche beyond the initial beachhead category?
- Is there a minimum viable payout SLA (e.g. "under 24 hours") that should become a marketed guarantee, or just an internal target?

---

# Technical Architecture / AI Services

> Source: `Technical Architecture/AI Services.md` · tag: `shared` · last updated: 2026-08-23

# AI Services

## Purpose

How AI/LLM features are actually implemented at initial scale — referenced from several other docs as "not yet written" until now.

## Principle

Nothing here requires custom-trained models or ML infrastructure. "AI" at this stage means calling an embeddings/LLM API from a self-contained module inside the FastAPI monolith (per Backend Architecture's modular-monolith discipline) — extractable into its own service later if it ever needs independent scaling, but not built that way now.

## Module Structure

```text
ai_services/
  embeddings.py   → generates + stores embeddings (pgvector)
  matching.py     → creator↔offer similarity ranking
  screening.py    → LLM application-fit summaries
  copy_assist.py  → LLM offer description drafts
```

## Feature 1: Creator ↔ Offer Matching

- Embed each CreatorProfile's niche/bio and each Offer's product description (one embeddings API call each)
- Store vectors in Postgres via the **pgvector** extension — no separate vector database needed
- Offer discovery (02. Search Strategy) ranks by cosine similarity, layered on top of existing category/commission filters, not replacing them
- Recomputed via a Celery background job on Offer/CreatorProfile create or update — never computed synchronously on a page load

## Feature 2: Application Screening Assist

- One LLM call per application generates a plain-language fit summary for the Merchant reviewing it (audience niche, conversion rate, alignment with the offer)
- Synchronous call, but result is cached per application — never regenerated on repeat views

## Feature 3: Offer Copy Assist

- Merchant provides minimal input (product name, price); LLM drafts an editable offer description
- Directly addresses the original raw data doc's goal of reducing friction for non-marketer merchants

## Feature 4: Disclosure Nudge — Deliberately Templated, Not Generative

- FTC-required affiliate disclosure text is legally sensitive; **this stays a fixed, reviewed template** inserted at link-generation time, not LLM-generated fresh each time. Consistency and legal review matter more than personalization here.

## Explicitly Out of Scope for "Initial Level"

- **Fraud/anomaly detection stays rules-based** (04. Security → Fraud Prevention, Mission & Principles → "rules before AI") — no training data exists yet, and a wrong ML call on someone's real earnings is a worse failure mode than an over-cautious deterministic rule. Revisit only once there's real transaction volume to train against.
- No custom-trained models of any kind at this stage — every feature above is an API call to an existing embeddings/LLM provider, not something SellVia trains itself.

## Cost & Caching Discipline

LLM/embedding API calls cost money per call — every feature above is designed to cache results and only recompute on actual data changes (new/updated Offer or CreatorProfile), not on every request, to keep this affordable at MVP scale.

## Open Questions

- Specific embeddings/LLM provider choice — not decided, reasonable to pick based on cost/quality once ready to implement
- Whether screening summaries and copy drafts need a human-editable "regenerate" option in the UI, or are one-shot suggestions — a UX decision for 09. UX, not blocking this doc

## Update (2026-08-23): Reconciled for the Offer Entity Merge

This doc referred throughout to "Campaign" (module names, feature headers, prose). Campaign was merged into Offer (01. Domain Model) — every such reference above is corrected to "Offer," matching sibling 02. Technical Architecture docs' convention.

---

# Technical Architecture / API Design

> Source: `Technical Architecture/API Design.md` · tag: `shared`

# API Design

## Purpose

The contract between frontend and backend — detailed endpoint specs live in 07. API; this doc covers the design conventions.

## Style

REST over GraphQL for MVP — simpler to reason about, easier to secure per-endpoint with role checks, and the data shapes here (offers, applications, sales) are not deeply nested/graph-like enough to need GraphQL's flexibility.

## Conventions

- Resource-based URLs: `/campaigns`, `/applications`, `/sales`, `/payouts`
- Role-scoped by default: a Merchant's `/campaigns` only returns their own; Admin has a separate `/admin/*` namespace with elevated access, matching the Permission Matrix (01. Business Logic)
- Pagination on all list endpoints (offers, applications, sales — updated 2026-08-23, was "campaigns," no separate entity) — these will grow unbounded over time
- Idempotency keys required on any endpoint that touches Swich (billing-invoice creation, payout triggers — updated 2026-08-23, was Paddle; "checkout creation" is separately stale, predates the 2026-08-07 checkout reversal) to avoid double-charging on retry — critical given this is a payments system, not optional

## Versioning

- Not needed at MVP (single client, single version) — revisit once there's a public API or third-party integrations (e.g. the deferred Shopify webhook integration from v2)

## Authentication

- Every request carries an Ory Kratos session token (updated 2026-08-04, was Clerk); backend verifies it and attaches the resolved User + role(s) to the request context before any business logic runs

## Open Questions

- Whether a public, documented API is ever exposed to merchants directly (e.g. for their own reporting), or if the API stays purely internal to SellVia's own frontend — not needed for MVP either way

---

# Technical Architecture / Architecture Decision Log

> Source: `Technical Architecture/Architecture Decision Log.md` · tag: `shared` · last updated: 2026-08-23

# Architecture Decision Log

## Purpose

A single chronological record of every major architecture decision, why it was made, what alternatives were considered, and where the full detail lives. This doesn't replace the detailed docs — it's the index that answers "why is it this way" without searching through 100+ pages.

## Format

Each entry: Decision — Alternatives considered — Reasoning — Status — Full detail link.

---

### Backend language: FastAPI (Python)

**Alternatives considered:** Node.js via Next.js API routes (original choice)

**Reasoning:** Founder preference for Python/FastAPI over the original Node-unified approach. Real trade-off accepted: two languages, two deploy paths, CORS between services, instead of one unified app.

**Status:** Confirmed. **Detail:** 02. Backend Architecture

### Application structure: Modular monolith, not microservices

**Alternatives considered:** Full microservices (separate Payments/Campaigns/Notifications services)

**Reasoning:** Team size (solo founder) doesn't justify microservices' organizational benefit; financial-chain transaction consistency is simpler within one service boundary. Built with clean internal module boundaries so extraction is possible later without a rewrite.

**Status:** Confirmed. **Revisit trigger:** a specific module demonstrably needs independent scaling/failure isolation under real load. **Detail:** 02. System Architecture, 02. Backend Architecture

### Database: Supabase (MVP) → Neon (production/scale)

**Alternatives considered:** Self-managed Postgres, RDS

**Reasoning:** Supabase for MVP ease (pgvector + built-in pooling out of the box); Neon confirmed as the actual production target for database branching (pairs with Environment Strategy/Git Strategy) and serverless scale-to-zero. Both vanilla Postgres — migration is a data move, not a rearchitecture.

**Status:** Confirmed, staged. **Revisit trigger:** Supabase pricing/connection limits become a real bottleneck. **Detail:** 06. Hosting Strategy

### Auth: Clerk → Ory Kratos

**Alternatives considered:** Clerk (original), Better Auth (rejected — TypeScript-only, incompatible with FastAPI), Authentik (rejected — built for enterprise internal SSO, not consumer CIAM)

**Reasoning:** Cost-at-scale and vendor lock-in were real long-term concerns; Kratos is language-agnostic (pure REST API, no FastAPI friction) and purpose-built for consumer identity. Ory Network (managed) for MVP, self-hosted later — same staged pattern as the database.

**Status:** Confirmed, staged. **Detail:** 04. Authentication

### Checkout: SellVia-hosted only for MVP

**Alternatives considered:** External-site checkout (redirect + webhook/pixel tracking, Shopify-style)

**Reasoning:** Dual-mode roughly doubles MVP engineering surface and reintroduces attribution ambiguity (cookie blocking, webhook reliability, self-reported sales) the product's trust positioning is built to eliminate.

**Status:** Confirmed for MVP. **Deferred:** external-site tracking is a named v2 item. **Detail:** 01. Money Flow

### Payments processor: Stripe Connect (superseded 2026-08-10 — see reversal below)

**Alternatives considered:** Lemon Squeezy / Paddle (Merchant of Record model — rejected at the time)

**Reasoning (original, 2026-08-03):** MoR providers assume a single seller; structurally incompatible with the three-way Merchant/Creator/Platform split this business model requires. Stripe Connect's `application_fee_amount` + `transfer_data` natively supports the split.

**Status:** Superseded. **Detail:** 01. Commission Engine, 05. Payment Flow

### Tax handling: Stripe Tax (superseded 2026-08-10 — see reversal below)

**Alternatives considered:** Merchant of Record (rejected, see above), no tooling (rejected — insufficient for multi-jurisdiction VAT/sales tax)

**Reasoning (original, 2026-08-03):** Plugs into existing Stripe Connect setup without disrupting the split architecture. Marketplace-facilitator-law liability question remains separately open pending real legal review.

**Status:** Superseded. **Detail:** 05. Tax Considerations

### Pricing model: flat 2% fee, no subscription

**Alternatives considered:** $49/mo subscription tier (original idea, dropped)

**Reasoning:** Simpler to explain ("we only make money when you do"), no billing infrastructure needed, consistent with the platform's "$0 owed until something sells" positioning.

**Status:** Confirmed. **Detail:** 05. Platform Business Model & Pricing

### Currency support: USD/EUR/GBP only, PKR dropped

**Reasoning:** Paddle doesn't support direct PKR payouts to connected accounts; would have required a separate local payout partner. Revisit only with real demand.

**Status:** Confirmed. **Detail:** 01. Business Rules

### Data consistency: Event sourcing (financial chain only) + last-write-wins (everything else)

**Alternatives considered:** Universal event sourcing (rejected — over-engineering for low-stakes data), CRDTs/Operational Transformation (rejected — no concurrent-editing feature exists in the product to justify them)

**Reasoning:** Event sourcing's audit/replay value is highest specifically for money; applying it everywhere adds complexity without payoff. LWW is sufficient for single-owner-edited data like Campaigns.

**Status:** Confirmed. **Detail:** 03. Event Sourcing (Financial Chain), 03. Database Design

### Git: Monorepo, short-lived service-prefixed feature branches

**Alternatives considered:** Two separate repos (frontend/backend), long-lived per-service branches (both rejected)

**Reasoning:** Cross-cutting changes (new endpoint + the frontend calling it) stay atomic in one PR. Long-lived branches increase risk via drift, contrary to the actual goal of minimizing risk — short branches + path-scoped CI is the safer pattern.

**Status:** Confirmed. **Detail:** 06. Git Repository Strategy

### Risk mitigation for financial-chain changes: Feature flags, not just branch strategy

**Reasoning:** Branch naming affects code review, not production exposure. A feature flag can be killed in seconds without a redeploy — the actual highest-leverage risk reduction for payments-critical changes.

**Status:** Confirmed, mandatory for any Sales/Commissions/Payouts/Refunds change. **Detail:** 06. Feature Flags Strategy

### Status page: separate domain, separate infrastructure (reversed from earlier "not needed" stance)

**Reasoning:** A status page hosted on the same infrastructure it reports on fails exactly when it's needed most. Managed tool (Instatus/Better Uptime-style), not self-hosted — same "use managed services for undifferentiated infra" pattern as Paddle/Clerk/Supabase.

**Status:** Confirmed, explicit reversal of an earlier deferral. **Detail:** 10. Status Page & Incident Communication

### AI features: API-based only, no custom training; fraud detection stays rules-based

**Reasoning:** No training data exists yet for fraud ML; a wrong ML call on real earnings is a worse failure than an over-cautious rule. All AI features (matching, screening, copy-assist) are embeddings/LLM API calls, not custom models.

**Status:** Confirmed for MVP. **Detail:** 02. AI Services

## Open Questions

None — this log is descriptive, not decision-making. Add a new entry whenever a future prompt resolves or reverses an architectural choice.

## Update (2026-08-07): MAJOR REVERSAL — Checkout Model

### Checkout: External-site tracking (REVERSES the SellVia-hosted-only decision above)

**Alternatives considered:** SellVia-hosted checkout (original MVP decision, now reversed)

**Reasoning:** Founder decided to switch to the affiliate-network model (customer buys on merchant's own site, SellVia tracks via redirect + merchant-reported sales) rather than processing payment directly. This reopens the exact trust/attribution-reliability trade-offs the original hosted-checkout decision was built to avoid (cookie blocking, merchant under-reporting risk, no direct payment witness) — a deliberate, informed trade the founder chose to make.

**New problem this created:** since SellVia never touches the payment, it needed a new money-collection mechanism. **Resolved: periodic billing** (merchant's card on file charged on a recurring cycle for accumulated commissions + platform fee), with creator payouts sequenced *after* successful billing (bill-first-then-pay, lower risk than fronting the money).

**Status:** Confirmed 2026-08-07, supersedes the earlier "SellVia Checkout only for MVP" entry above. **Detail:** 01. Money Flow, 01. Commission Engine, 01. State Machines, 05. Payment Flow — all rewritten same date.

**Still open:** merchant integration mechanism (webhook spec vs. platform-specific like Shopify first), billing cycle length, card-failure retry policy, sale-report acceptance criteria.

## Update (2026-08-10): Payments processor reversed — Paddle replaces Stripe

### Payments processor: Paddle (REVERSES the Stripe Connect / Stripe Tax decisions above)

**Alternatives considered:** Stripe Connect (original choice, now reversed), staying split (Paddle for merchant billing only + Stripe Connect for creator payouts — rejected in favor of one processor)

**Reasoning:** Founder decision to consolidate on Paddle. Under the current external-site-tracking model (reversed 2026-08-07), Stripe was already doing two jobs, not the original one it was chosen for: (1) periodically billing the merchant's card on file for accumulated commissions + platform fee, and (2) paying out individual creators. Paddle covers job (1) natively and well — it's a Merchant of Record, built exactly for billing/subscribing a customer, and Paddle Tax replaces Stripe Tax for the same multi-jurisdiction VAT/sales-tax handling this doc's original Tax entry called for.

**The open gap this creates:** job (2), paying out many independent third-party creators with their own KYC/tax-form collection, is what Stripe Connect specifically solved and what the original 2026-08-03 entry above correctly identified MoR providers as *not* built for. Paddle's marketplace/payout product ("Paddle for Platforms") is the closest fit, but it has not been evaluated against Commission Engine's payout requirements ($50 threshold, bill-first-then-pay sequencing, per-creator bank payout) — this is real, unresolved risk this reversal introduces, not a solved problem. Flagging explicitly rather than assuming parity with what Stripe Connect provided.

**Status:** Confirmed 2026-08-10, supersedes the "Payments processor: Stripe Connect" and "Tax handling: Stripe Tax" entries above. **Detail:** 01. Commission Engine, 01. Money Flow, 05. Payment Flow, 05. Wallet Design, 05. Payout Process, 05. Tax Considerations — all updated same date.

**Open question this reversal creates:** does Paddle for Platforms actually support per-creator payout the way this doc's Payout Process/Wallet Design assume Stripe Connect did — needs real evaluation before this is build-ready, not just a documentation find-and-replace.

## Update (2026-08-23): MAJOR REVISION — Pakistan-Only Market, Paddle Removed, Shopify-Only Integration, Offer Absorbs Campaign

Four founder decisions, all effective immediately, superseding the entries above where they conflict.

### Market scope: Pakistan-only for MVP (REVERSES "USD/EUR/GBP only, PKR dropped")

**Alternatives considered:** Continuing global USD/EUR/GBP scope (original MVP decision, now reversed)

**Reasoning:** Founder decision to launch to the Pakistani market exclusively — every merchant onboarded for MVP is a Pakistani business. This resolves the still-open "beachhead niche" question from Product Roadmap/Product Vision by geography rather than product category, and removes the entire reason PKR was dropped in the first place (Paddle's lack of direct PKR payout support no longer matters if Paddle isn't used at all — see next entry).

**Status:** Confirmed 2026-08-23. **Detail:** 01. Product Vision, 01. MVP Scope, 01. Business Rules.

### Payments processor: Paddle removed, no processor for MVP — local bank transfer (working default) (REVERSES the 2026-08-10 Paddle entry)

**Alternatives considered:** Keeping Paddle (rejected — doesn't support PKR payouts natively, and is the wrong tool for a single-country, non-MoR-dependent model); a local gateway (JazzCash/EasyPaisa/Safepay) API integration (candidate for later, not chosen for MVP)

**Reasoning:** Founder decision: "instead of paddle or anything." With the market narrowed to Pakistan, the three-way MoR/split problem Paddle and Stripe Connect were both solving mostly disappears — there's one country, one currency, and (per the next entry) one sales channel. **Working default, needs founder confirmation before build:** merchant billing and creator payout both happen via **direct bank transfer (IBFT/RAAST)**, admin-initiated and admin-verified, with no third-party payment processor in the loop for MVP. This is the simplest mechanism that requires zero external integration and fits "any merchant from here."

**Status:** Working default, not yet build-confirmed. **Open:** exact rail (manual IBFT vs. RAAST-instant vs. a local gateway API), and who reconciles it (Admin manually vs. an automated feed) — flagged in MVP Scope's Still-Open Items. **Detail:** 05. Payment Flow, 05. Payout Process, 05. Wallet Design, 05. Platform Business Model & Pricing (all updated 2026-08-23).

### Merchant integration: Shopify-only via native webhook (REVERSES the 2026-08-07 "universal onboarding snippet" resolution)

**Alternatives considered:** Universal JS snippet across any platform (the prior resolution, now reversed for MVP scope reasons, not because it was technically wrong); discount-code-only fallback alone (insufficient without a primary signal)

**Reasoning:** Founder decision: "for now we are just going with shopify only." Since MVP explicitly does not need to support arbitrary platforms, the snippet's main advantage (works everywhere, no per-platform engineering) stops mattering, and Shopify's native webhook (already identified in Payment Flow's own 2026-08-07 update as "the obvious first candidate" for a platform-specific upgrade) becomes the primary mechanism instead of a deferred nice-to-have. More reliable than the snippet (server-side, not dependent on the customer's browser/cookies/ad-blocker), at the cost of only working for Shopify merchants — an acceptable trade now that all MVP merchants are pre-scoped to a platform anyway.

**Status:** Confirmed 2026-08-23. Snippet + discount-code-fallback approach is retired for MVP, not deleted from history — becomes the relevant mechanism again only if/when SellVia supports non-Shopify merchants. **Detail:** 05. Payment Flow (updated 2026-08-23).

### Domain model: Campaign entity removed, merged into Offer

**Alternatives considered:** Keeping Offer and Campaign as two entities (the original model — Offer = product, Campaign = a commission-bearing listing of that product); one-campaign-per-offer as a compromise (considered, rejected as still two entities for no real benefit)

**Reasoning:** Founder decision: "offer is offer, it is not turning into any campaign at all." This resolves Domain Model's own open question ("does Offer need its own entity separate from Campaign?") in favor of the simpler answer — **there is no Campaign entity.** An Offer carries its commission rate and lifecycle status (draft/live/paused/ended) directly. Applications, AffiliateLinks, and Sales attach to an Offer, not to a Campaign-wrapping-an-Offer.

**Status:** Confirmed 2026-08-23. **Detail:** 01. Domain Model, 01. Business Rules, 01. State Machines, 01. Commission Engine, 01. Product Glossary (all updated 2026-08-23).

**Not yet updated to match:** the three frontend Scratch docs (FEATURE_LIST.md, SCREEN_INVENTORY.md, SITE_MAP.md) still describe the Campaign-based model throughout — flagged with a banner pointing here rather than fully rewritten, given the scope of ~50 screens referencing "Campaign." Treat "Campaign" in those two docs as "Offer" until they're rewritten.

*(FEATURE_LIST.md was fully rewritten 2026-08-23, same day — see that doc directly. SCREEN_INVENTORY.md and SITE_MAP.md still carry banners rather than full rewrites.)*

## Update (2026-08-23, later same day): Processor Confirmed — Swich Replaces the Manual-Bank-Transfer Working Default

### Payments processor: Swich (RESOLVES the "no processor, manual bank transfer" working default above)

**Alternatives considered:** Payoneer (researched, rejected — built for cross-border flows, doesn't natively hold/move domestic PKR, and its billing-side product (Checkout) requires a Hong Kong entity + $20k/month minimum volume, disqualifying for MVP); plain manual bank transfer, admin-verified (the prior working default, now upgraded); AbhiPay (researched, inconclusive — site access blocked research, appears to be an acquiring/checkout product without confirmed bulk-payout capability); AssanPay (viable for the billing leg alone, but no confirmed payout/disbursement product, so it would've needed pairing with a second vendor anyway).

**Reasoning:** Founder decision: **Swich** (swichnow.io), a Pakistani payments infrastructure company, PCI-DSS v4.0.1 certified. Chosen because it covers **both** legs SellVia needs under one vendor:

- **Merchant billing:** Swich's recurring billing / invoice-link product — SellVia generates a payment request for each BillingCycle's total, merchant pays via card/bank transfer/JazzCash/EasyPaisa through Swich's checkout, webhook confirms → cycle marked `charged`.
- **Creator payout:** Swich's payout/disbursement API — bulk disbursement across bank transfer (1LINK), JazzCash, EasyPaisa, and Raast, explicitly marketed for exactly this use case ("commission payouts" named directly in Swich's own materials).

One vendor, one integration, one reconciliation surface — the reasoning that ruled out needing to pair AssanPay (billing) with a separate Raast-licensed fintech (payouts).

**Genuine, real distinction from Paddle, not a detail to gloss over:** Swich is a **payment processor/gateway — not a Merchant of Record.** Paddle (the original MVP processor) absorbed tax/compliance obligations as the legal seller of record; Swich does not. This means SellVia itself remains legally responsible for its own tax obligations (see 05. Tax Considerations, updated same date) — this was already true under the "no processor, bank transfer" default, and remains true with Swich. Nothing about choosing Swich reduces this responsibility the way choosing Paddle once did.

**Status:** Confirmed 2026-08-23. **Not yet done:** actual signup/integration with Swich — pricing, onboarding/KYC requirements for a 10–25-merchant Private Beta volume, and Swich's exact API field shapes are unconfirmed pending a real vendor conversation. Every schema/field name introduced in the docs below is a working draft, not a verified Swich API contract. **Detail:** 01. Money Flow, 01. Commission Engine, 01. State Machines, 05. Payment Flow, 05. Payout Process, 05. Wallet Design, 05. Platform Business Model & Pricing, 05. Tax Considerations, 03. Table Specifications (all updated same date).

---

# Technical Architecture / Async Job Pattern & Idempotency

> Source: `Technical Architecture/Async Job Pattern & Idempotency.md` · tag: `shared`

# Async Job Pattern & Idempotency

## Purpose

The pattern for any user-initiated operation too heavy to run inline in a request (exports, reports, bulk operations) — distinct from 02. Background Jobs' existing system-triggered jobs (payout batching, webhook processing). This is user-triggered, needs a job entity the user can reference, and completes via notification, not polling.

## The Rule

**No API route ever does heavy processing inline.** When a user clicks something expensive (export, bulk report, anything non-trivial), the route does exactly one thing: create a job record and return its ID immediately with `status=processing`. Actual work happens in a worker, off the request cycle entirely — the request/response is fast regardless of how long the real work takes.

## Flow

```mermaid
flowchart TD
    A[User clicks Export] --> B[Client generates idempotency key]
    B --> C[POST /jobs/export with idempotency key]
    C --> D{Job with this key already exists?}
    D -- Yes --> E[Return existing job id + status, no new job created]
    D -- No --> F[Create job record, status=pending]
    F --> G[Enqueue Celery task, return job id, status=processing]
    G --> H[Worker picks up job, does the real work]
    H --> I[Job status updated to completed, result stored]
    I --> J[Notification sent to user - per 01. Notification Logic]
    J --> K[User clicks notification, retrieves result]
```

## Idempotency Keys — Preventing Duplicate Jobs

**Every job-creation request carries a client-generated idempotency key** (a UUID, generated once per user action — e.g. once when the export button is clicked, not regenerated on a rapid double-click of the same intent). Server checks for an existing job with that key before creating anything:

- **Key already exists → return the existing job's ID and current status.** No new job, no duplicate work, no duplicate notification.
- **Key doesn't exist → create the job, enqueue the work.**

This is the same principle already required for Swich-touching endpoints (updated 2026-08-23, was Paddle — 07. API → REST Standards' existing idempotency requirement), generalized to every job-creation endpoint, not just payment ones — a double-click, a flaky network retry, or an impatient second click all resolve to exactly one job.

## Job Schema

```text
jobs
  id
  type              (e.g. "export_sales_report", "export_creator_earnings")
  status            (pending / processing / completed / failed)
  idempotency_key   (unique, client-generated)
  user_id           (who requested it)
  tenant_id         (per 04. Security → Tenant Isolation Audit — scoped, never cross-tenant visible)
  params            (jsonb — what was requested, e.g. date range, filters)
  result_url        (nullable — signed URL to the output once complete, per 03. Database → File Storage)
  error_message     (nullable — if failed)
  created_at
  completed_at
```

## Worker Pattern

A Celery task registered per job `type`, triggered when the job record is created — same underlying mechanism as every other background job (02. Background Jobs), same retry-with-backoff discipline. On success: write `result_url`, set `status=completed`, trigger notification. On failure: set `status=failed`, `error_message`, still notify the user (a silent failure the user never learns about is worse than a visible one) — logged per 06. Error Handling & Logging Pipeline like any other job failure.

## Completion: Notification, Not Polling

**Users are not shown a spinner waiting on this.** A new notification trigger, `job_completed` (extends 01. Business Logic → Notification Logic's existing trigger list), fires the moment the worker finishes — delivered via the existing notification channels (in-app + email, per 06. Email Infrastructure). A `GET /jobs/:id` endpoint still exists as a fallback for a user who wants to check manually or if a notification is missed, but it's not the primary UX.

## Tenant Scoping

A job's `GET /jobs/:id` (and the list of a user's own jobs) is scoped exactly like everything else per the Permission Matrix and Tenant Isolation Audit — a Merchant can never see another Merchant's export job, even by guessing an ID.

## Open Questions

- None blocking — this is a straightforward extension of already-decided patterns (Background Jobs, Notification Logic, idempotency keys already required for Swich — updated 2026-08-23, was Paddle). Specific job `type`s get added as actual export/report features are built.

---

# Technical Architecture / Backend Architecture

> Source: `Technical Architecture/Backend Architecture.md` · tag: `shared` · last updated: 2026-08-23

# Backend Architecture

## Purpose

How the server-side logic, payments processing, and business rules are implemented.

## Stack (decided 2026-08-03: FastAPI)

- **Framework: FastAPI (Python)** — a separate backend service, not Next.js API routes. This means SellVia is now a **two-service architecture**: Next.js frontend + FastAPI backend, talking over HTTP, rather than one unified Next.js app (see System Architecture for the updated diagram).
- **Database access:** SQLAlchemy (ORM) + Alembic for migrations — replaces the earlier Prisma recommendation (see 03. Database → Database Design, Migration Strategy, both updated accordingly)
- **Async:** FastAPI's native async support is a good fit here given how much of this backend is I/O-bound (Paddle API calls, webhook processing, database queries) — worth actually using `async def` route handlers and an async database driver (e.g. `asyncpg` via SQLAlchemy's async engine) rather than defaulting to sync
- **Payments:** Paddle, repurposed for periodic merchant billing and creator payouts (reversed 2026-08-07, 01. Money Flow) — not a live per-sale split anymore. Paddle's Python SDK, not the Node SDK.

## Why This Is a Real Architectural Fork, Not Just a Swap

Next.js API routes meant the frontend and backend were one deployable unit. FastAPI as a separate service means:

- Two codebases, two languages (TypeScript frontend, Python backend) instead of one
- Frontend calls the backend over HTTP/REST (per 07. API) rather than same-process function calls
- Two separate deploy pipelines, two sets of environment variables, two processes to run and monitor on the VPS (see 06. Infrastructure — CI/CD Pipeline and VPS Setup both need updating for this, flagged separately)
- CORS becomes a real concern between frontend and backend origins (04. Security → API Security already covers this in principle, now it actually applies day one, not just for a hypothetical future public API)

## Core Backend Responsibilities (unchanged from before — implementation language changed, not the logic)

1. **Auth verification** — validate Ory Kratos session tokens on every request, map to Merchant/Creator/Admin role
2. **Campaign/Application lifecycle** — implement the state machines from 01. Business Logic → State Machines
3. **Sale report acceptance** — receive and validate merchant-reported sales from the onboarding snippet (05. Payment Flow, reversed 2026-08-07), run 04. Fraud Prevention's plausibility checks, add accepted sales to the merchant's open Billing Cycle
4. **Periodic billing** — scheduled job charges each merchant's card on file for their Billing Cycle total (05. Payment Flow)
5. **Webhook handling** — FastAPI route receiving Paddle webhooks, verifying signature, enqueuing background work (see Background Jobs — also updated for Python)
6. **Attribution tracking** — record AttributionEvents against the correct AffiliateLink within the 30-day window
7. **Notification triggers** — fire events consumed by the notification worker

## Why Paddle (unchanged reasoning)

Same as before — managed KYC/tax-form collection and money movement via Paddle seller accounts is far less engineering/compliance surface than a custom ledger + manual payouts, regardless of backend language.

## Open Questions

- **Paddle account type:** still recommend Express for MVP (unchanged from before — this decision doesn't depend on backend language)
- Whether frontend and backend deploy independently or are still coordinated as one release (see 06. Infrastructure → CI/CD Pipeline, needs updating for the two-service split)

## Update (2026-08-03): Confirmed monolithic

To be explicit given the question came up directly: this FastAPI backend is **one monolithic service** internally organized into modules (campaigns, applications, payments, notifications), not split into microservices. See System Architecture for the full reasoning. The "Core Backend Responsibilities" listed above (auth, campaign lifecycle, checkout, webhooks, attribution, notifications) all live in this single service, not distributed across separate ones.

## Update (2026-08-03): Built as a Modular Monolith — Extractable Later

Confirmed monolithic for now (see above), but built with a specific discipline so it can evolve into microservices later without a rewrite: **each internal module (campaigns, applications, payments, notifications) is self-contained** — owns its own data access, doesn't reach directly into another module's internals, communicates through clearly-defined internal interfaces rather than shared global state. This is the "modular monolith" pattern — if a specific module (most likely Payments, given it's the most load- and correctness-sensitive) ever needs to become its own service, that boundary already exists and the extraction is a scoped migration, not a redesign.

## Fault Isolation — What's Already True Without Microservices

The concern "if one part breaks, the rest should keep working" is already mostly addressed by the current architecture, independent of the monolith/microservices question:

- **Per-request isolation is automatic in FastAPI:** an unhandled error in one endpoint returns a 500 to that caller only — it does not crash the process or affect other requests.
- **Workers (Celery) already run as a separate process from the API** — if the API crashes, queued jobs keep draining; if a worker crashes, the API keeps serving requests.
- **Database, Redis, Paddle, and Ory Kratos are already separate, independently-managed services** — none of them go down because of a bug in SellVia's own code.

**The real remaining single point of failure:** if the FastAPI process itself crashes entirely (not a single bad request, but a full process crash — e.g. out-of-memory), everything it directly serves goes down together, since it's one process. At current scale, the right mitigation is fast detection and restart (06. Infrastructure → Monitoring, plus a process manager that auto-restarts on crash) rather than splitting into microservices — genuine microservices fault isolation requires deliberate patterns (async messaging, circuit breakers, timeouts) that add real complexity, and naively splitting without them can make things less resilient, not more, by turning function calls into network calls that can also fail.

**Bottom line:** revisit true service extraction only when a specific module demonstrably needs independent scaling or failure isolation under real load — the modular structure means that's a scoped decision when the time comes, not a foundational one to make now.

## Update (2026-08-23): Paddle → Swich, Pakistan/PKR Only, Offer Replaces Campaign

Founder decisions, full reasoning in 02. Architecture Decision Log.

- **"Payments: Paddle" → "Payments: Swich"** — Swich's Python SDK (if one exists) or direct REST API calls, not yet confirmed which pending real integration docs. "Why Paddle" section above (managed KYC/tax-form collection) is **not fully true of Swich** — Swich is a processor, not a Merchant of Record, so it does *not* absorb KYC/tax-form collection the way Paddle's reasoning here assumed; see 05. Tax Considerations for the resulting gap. This section's reasoning is superseded, not just its vendor name.
- **"I/O-bound (Paddle API calls...)" → "I/O-bound (Swich API calls, Shopify webhook processing...)"** — two external integrations now, not one.
- **"Sale report acceptance... from the onboarding snippet"** → from **Shopify's `orders/paid` webhook** (05. Payment Flow, reversed again 2026-08-23 — Shopify-only, not the universal snippet this line still describes).
- **"Campaign/Application lifecycle"** → **"Offer/Application lifecycle"** — no separate Campaign entity (01. Domain Model).
- **"Webhook handling... receiving Paddle webhooks"** → receiving **both** Swich webhooks (`/webhooks/swich`) and Shopify webhooks (`/webhooks/shopify-sales`) — two independent handlers, not one (see 04. Security → Webhook Security, updated same date).
- **"Paddle account type: still recommend Express for MVP"** open question — moot, no Paddle account.
- **Fault Isolation section's "Database, Redis, Paddle, and Ory Kratos"** → "Database, Redis, Swich, Ory Kratos, and Shopify" as the independently-managed external services.
- **Currency: PKR only.**

---

# Technical Architecture / Background Jobs

> Source: `Technical Architecture/Background Jobs.md` · tag: `shared` · last updated: 2026-08-03

# Background Jobs

## Purpose

What runs outside the request/response cycle, and why.

## Jobs

1. **Webhook processing** — as described in Event-Driven Architecture, webhook handlers enqueue work rather than processing inline
2. **Notification delivery** — sending emails/push notifications per 01. Business Logic → Notification Logic's triggers; should never block the request that caused them
3. **Payout batching** — checking which creators have crossed the payout threshold and triggering their Swich payout (updated 2026-08-23, was Paddle; also, "instant split" is stale independent of this update — superseded by the 2026-08-07 billing-cycle reversal, see Money Flow) is a natural periodic job (e.g. runs every few hours) rather than a real-time check on every single sale
4. **Refund/billing-credit processing** — when a Swich billing-credit event is applied (updated 2026-08-23; the original "`charge.refunded` webhook" and "14-day-window clawback" are both stale independent of today's change — commission is never clawed back at all per Commission Engine, and refunds are a merchant-requested billing credit per Refund Handling, not a webhook-driven event)
5. **Attribution window expiry cleanup** — marking AttributionEvents outside the 30-day window as no longer eligible for a Sale, if a click's window lapses without a purchase

## Stack

Redis-backed queue (e.g. BullMQ, given the Node/Next.js stack) — lightweight, well-supported, doesn't require a heavier system like Kafka/RabbitMQ at this scale.

## Reliability Considerations

- Jobs that touch money (payout batching, refund clawback) need retry-with-backoff and dead-letter handling — a silently failed payout job is a real trust problem given the product's whole positioning
- Idempotency keys carried through from the triggering webhook/event, so retries don't double-process

## Open Questions

- Exact payout batch frequency (hourly? every 15 min? daily?) — more frequent is closer to "instant," but adds Swich payout costs and complexity (updated 2026-08-23, was Paddle); recommend starting with a few-times-daily batch and tightening later if needed

## Update (2026-08-03): Celery replaces BullMQ

Following the FastAPI switch, **Celery (with Redis as the broker)** is the background job system, not BullMQ — BullMQ is Node-specific and no longer applies. Same Redis instance, same job list (webhook processing, notification delivery, payout batching, refund clawback, attribution-window expiry cleanup), same reliability requirements (retry-with-backoff, dead-letter handling on money-touching jobs). RQ (Redis Queue) is a lighter-weight Python alternative to Celery worth considering if Celery's operational overhead feels like more than this stage needs — either is a reasonable choice, Celery is more common/battle-tested, RQ is simpler to run.

---

# Technical Architecture / Caching Strategy

> Source: `Technical Architecture/Caching Strategy.md` · tag: `shared` · last updated: 2026-08-04

# Caching Strategy

## Purpose

What gets cached, why, and for how long — kept deliberately light for MVP given the scale involved.

## What NOT to Cache

- Anything related to balances, payouts, or commission amounts — these must always be read live from the database (or Swich directly, updated 2026-08-23 from Paddle), never from a cache, given they're financial figures a user might act on
- Sale/Application state — same reasoning; staleness here is a trust problem, not just a UX inconvenience

## What's Reasonable to Cache

- **Public offer discovery listings** (what a creator browses) — short TTL (e.g. 60 seconds) is fine, since a few seconds of staleness on "which offers are live" isn't a correctness problem
- **Merchant/Creator public profile data** shown on offer/application cards (niche, audience size) — similarly low-stakes to cache briefly
- **Static design-system assets** — handled by CDN (see CDN Strategy), not application-level caching

## Mechanism

Redis, same instance used for background job queues (see Background Jobs) — no need for a separate caching layer at this scale.

## Open Questions

- None blocking — this doc is intentionally conservative (cache little, cache short) given how much of the app is financial data that shouldn't be served stale. Revisit if a specific endpoint becomes a real performance bottleneck under real traffic.

## Update (2026-08-04): Mandatory Tenant Scoping — No Exceptions for Tenant-Private Data

**Tenant definition:** `MerchantProfile.id` or `CreatorProfile.id` (not `User.id`) — matches the actual data-owning boundary in Domain Model. A dual-role user has two separate tenant contexts, not one.

**Rule: every cached query, cache fragment, and cached API response that touches tenant-private data must include the tenant ID in its cache key, with no exceptions.** Examples of correct keying:

```text
merchant:{merchant_profile_id}:offers
creator:{creator_profile_id}:earnings_summary
merchant:{merchant_profile_id}:sales:2026-08
```

A cache key that omits tenant context for private data is a bug, full stop — not a performance shortcut to consider.

## The One Deliberate Exception: Public Data Gets Its Own Explicit Namespace

Public offer discovery listings (visible identically to every Creator, by design — see 01. Business Logic → User Flows) are **not** tenant-private data — there's no tenant boundary being crossed by sharing them. These get a separate, explicitly-named `public:` namespace:

```text
public:offers:discovery:page-1
```

**The point isn't "skip tenant scoping here" — it's that every key must be unambiguous about which bucket it's in.** A key must never be constructed in a way where it's unclear whether it's tenant-scoped or public; `public:` and `merchant:{id}:` / `creator:{id}:` prefixes must never collide or be reused for the wrong purpose. This distinction gets audited explicitly in 04. Security → Tenant Isolation Audit.

## Enforcement, Not Just Convention

Relying on every developer remembering to add the tenant prefix by hand is exactly how this kind of bug slips through. Recommend a thin caching helper/wrapper that **requires** a tenant ID (or an explicit `public` marker) as a parameter to construct any cache key — making it structurally awkward to write an unscoped cache call, rather than just documenting the convention and hoping it's followed.

---

# Technical Architecture / CDN Strategy

> Source: `Technical Architecture/CDN Strategy.md` · tag: `shared`

# CDN Strategy

## Purpose

How static assets and the public marketing site are served quickly and reliably.

## Approach

Cloudflare in front of everything (per the earlier infrastructure conversation) — handles DNS, CDN caching of static assets, HTTPS/SSL, and basic DDoS protection. This was already the recommended setup before any of the payments/checkout decisions were made, and nothing since has changed that.

## What Goes Through the CDN

- Static frontend assets (JS/CSS bundles, fonts — Outfit/Figtree per [design.md](http://design.md), images)
- The public marketing site content ([wesellvia.com](http://wesellvia.com))

## What Does NOT Go Through a Cache Layer

- Authenticated dashboard requests, checkout pages, anything involving live balances/state — these must hit the application directly, not a cached edge response

## Open Questions

- None blocking — this is a fairly standard, low-risk setup already aligned with the earlier infra conversation.

---

# Technical Architecture / Event-Driven Architecture

> Source: `Technical Architecture/Event-Driven Architecture.md` · tag: `shared` · last updated: 2026-08-23

# Event-Driven Architecture

## Purpose

Where SellVia relies on events (mostly from Paddle) rather than direct synchronous calls, and why.

## Why This Matters Here Specifically

Because SellVia processes real payments via Paddle, a lot of the system's state changes are driven by **webhooks**, not by the user's own request finishing. A checkout succeeding, a refund happening, a payout completing — these are all things Paddle tells SellVia about asynchronously, and the backend has to react correctly and idempotently.

## Key Events

| Paddle webhook | SellVia reaction |
| --- | --- |
| `transaction.completed` | Mark Sale as `verified`, credit creator/merchant balances (already split by Paddle), trigger notifications |
| `charge.refunded` | Mark Sale as `refunded`, apply the 14-day clawback rule (01. Business Logic → Commission Engine) |
| `payout.paid` | Mark Payout as `paid`, notify the recipient |
| `payout.failed` | Mark Payout as `failed`, retry per Payout State Machine, alert Admin if repeated |
| `seller.updated` (Connect) | Update a Merchant/Creator's onboarding/KYC status — relevant for gating whether they can receive payouts yet |

## Reliability Requirements

- **Webhook signature verification** on every incoming Paddle webhook — non-negotiable, this is the primary attack surface for someone trying to fake a "sale" or "payout" event (see 04. Security → Webhook Security, not yet written)
- **Idempotent processing** — Paddle can and will redeliver webhooks; handlers must not double-credit a wallet if the same event arrives twice
- **Queue, don't process inline** — webhook handlers should enqueue a job and return 200 quickly, then process asynchronously (see Background Jobs), so Paddle doesn't time out and retry unnecessarily

## Open Questions

- Whether to build a generic internal event bus (for notification triggers, analytics events, etc. beyond just Paddle webhooks) now, or keep it simple and Paddle-webhook-specific for MVP — recommend keeping it simple until there's a second real event source that justifies the abstraction

## Diagram

```mermaid
sequenceDiagram
    participant S as Paddle
    participant API as API Endpoint
    participant Q as Redis Queue
    participant W as Worker

    S->>API: POST /webhooks/paddle
    API->>API: Verify signature
    API->>Q: Enqueue job
    API-->>S: 200 OK (fast, non-blocking)
    Q->>W: Job picked up
    W->>W: Process idempotently (update Sale/Payout state)
```

## Update (2026-08-04): The Full Chain, Explicit u2014 SellVia's Actual Version

Restating this as one explicit sequence, translated from generic payment-webhook language into what actually happens here (no subscriptions exist in SellVia u2014 Platform Business Model & Pricing explicitly rejected that model in favor of a flat 2% fee, so this chain replaces "activate subscription" with what SellVia actually does):

**On `transaction.completed` (one verified event, one action, every time u2014 idempotency key prevents any repeat):**

1. **Verify signature first** u2014 nothing below runs if this fails (04. Webhook Security, unchanged, non-negotiable)
2. **Mark the Sale verified** (not "invoice paid" u2014 SellVia's equivalent record, 01. State Machines)
3. **Credit balances** u2014 commission and platform fee, already split by Paddle in the same transaction (01. Commission Engine)
4. **Update tenant-scoped access/state** u2014 the Creator's wallet balance, the Merchant's sale count, both tenant-isolated per 04. Tenant Isolation Audit
5. **Send the notification** u2014 "sale made," "commission earned" (01. Notification Logic)

**Trust boundary, restated plainly:** the *button* (frontend "pay" click) never triggers any of the above directly u2014 only the verified webhook event does. A user closing their browser right after paying still results in the full chain running, because it's driven by Paddle's event, not by the frontend completing a request. This was already the design (Event-Driven Architecture's whole reason for existing) u2014 restating it here as the explicit trust rule it always was.

**Double-charge prevention, restated:** Paddle redelivers webhooks by design (not a bug to guard against, an expected behavior to design for) — idempotent processing means a redelivered `transaction.completed` for the same Paddle transaction ID is a no-op the second time, never a second credit. Already required (Webhook Security, Event-Driven Architecture); this is the same rule, not a new one.

## Update (2026-08-23): ⚠️ This Doc Predates Two Major Reversals

Same scope of staleness as System Architecture's 2026-08-23 update — this doc's entire "Key Events" table, diagram, and worked "Full Chain" section describe **SellVia-hosted checkout with an instant Paddle split**, both reversed 2026-08-07, on top of Paddle itself being replaced by Swich 2026-08-23. A line-patch would misrepresent how confident this correction is, so flagging the scope instead:

- **`transaction.completed` → "already split by Paddle"** — wrong twice over: there's no live split (periodic billing since 2026-08-07) and no Paddle (Swich since 2026-08-23). The real event chain is: Shopify `orders/paid` webhook → sale accepted → added to open BillingCycle → (later, on cycle close) Swich billing-payment-confirmed webhook → BillingCycle marked `charged` → creator payouts released → Swich payout-confirmed webhook → Payout marked `paid`. Two independent webhook sources (Shopify, Swich), not one (Paddle).
- **`charge.refunded` / "14-day clawback rule"** — both wrong independent of today's update: there is no clawback at all (01. Commission Engine, reversed 2026-08-07); a refund is a merchant-requested billing credit (05. Refund Handling), not a webhook-driven event.
- **`seller.updated`** — was Paddle Connect KYC/onboarding status; needs a Swich equivalent, unconfirmed.
- **Diagram's `POST /webhooks/paddle`** → `POST /webhooks/swich` and, separately, `POST /webhooks/shopify-sales` — two endpoints now, not one.

**Recommended fix, not done here:** redraw the sequence diagram and rewrite the Key Events table against the actual current model (05. Payment Flow, 01. Money Flow, both current as of 2026-08-23) rather than the hosted-checkout model this doc was originally written for.

---

# Technical Architecture / File Storage

> Source: `Technical Architecture/File Storage.md` · tag: `shared`

# File Storage

## Purpose

Where uploaded assets (product images, profile photos, logos) live.

## Approach

S3-compatible object storage (matches the earlier infrastructure conversation's recommendation) — not stored in the database, not stored on the app server's local disk (which wouldn't survive redeploys/scaling anyway).

## Separation by Environment

Per the earlier environment-strategy conversation: separate buckets (or bucket prefixes) for production, staging, and local — so test uploads in staging never appear in production and vice versa.

## What's Stored Here

- Product images (uploaded by merchants when creating an Offer)
- Merchant logos / business branding
- Creator profile photos
- **Not** anything related to Swich/KYC documents (updated 2026-08-23, was Paddle) — **not confirmed** whether these stay inside Swich's own onboarding flow the way Paddle's did (per Backend Architecture's 2026-08-23 update); Swich is a processor, not a Merchant of Record, so this claim needs verifying once real integration begins, not assumed

## Open Questions

- Whether product images are fetched/scraped automatically from a merchant's existing website URL (as discussed in the "owner fetched product from website directly" conversation) or always manually uploaded — auto-fetch is a nice-to-have that reduces merchant friction but adds scraping/parsing complexity; recommend manual upload for MVP with auto-fetch as a v2 convenience feature

---

# Technical Architecture / Frontend Architecture

> Source: `Technical Architecture/Frontend Architecture.md` · tag: `shared` · last updated: 2026-08-23

# Frontend Architecture

## Purpose

How the merchant dashboard, creator dashboard, and public-facing pages are built.

## Stack (proposed, consistent with the [design.md](http://design.md) system and existing tooling choices)

- **Framework:** Next.js (React) — matches the artifact/tooling ecosystem already in use for this project and supports both the marketing site and authenticated app in one codebase
- **Styling:** Tailwind, following [design.md](http://design.md)'s black/lime, Outfit/Figtree, no-gradients system directly
- **Auth integration:** Ory Kratos's SDK/components for sign-in, sign-up, and session state

## Surface Areas

1. **Public marketing site** ([wesellvia.com](http://wesellvia.com)) — already live, per the earlier read-through: hero, concept walkthrough, roadmap, FAQ, waitlist form
2. **Merchant dashboard** — campaign creation/management, application review, sales/analytics view, payout history
3. **Creator dashboard** — campaign discovery/browse, application status, link management, earnings/balance view
4. **Merchant billing card collection** — a Paddle Checkout form for the merchant to add/update their card on file for periodic billing (reversed 2026-08-07, 01. Money Flow) — this is the only Paddle Checkout surface remaining on SellVia's own frontend; there is no follower-facing checkout page, since purchases happen entirely on the merchant's own website
5. **Admin panel** — moderation queue, campaign vetting, refund/dispute handling (see 10. Operations, not yet written, for the operational workflows this supports)

## Design System Constraints (from [design.md](http://design.md) — binding, not optional)

- Colors: black (#000000) background, lime (#BFFF13) used sparingly for CTAs/highlights only
- Typography: Outfit for headlines/CTAs, Figtree for body/labels
- No gradients, no glassmorphism, no glow effects, restrained animation only
- 12-column desktop grid, thin borders over shadows

## Component Reuse Across Dashboards

Merchant and Creator dashboards share a lot structurally (list view, detail view, stats cards, notification feed) even though the content differs — recommend a shared component library (cards, tables, forms) rather than building each dashboard from scratch, so the "don't favor one side" design principle (Mission & Principles) is easier to enforce by construction.

## Open Questions

- Mobile app vs. responsive web only for MVP — raw data doc mentions mobile tracking apps, but responsive web is the leaner MVP scope
- Exact UX for the merchant's onboarding snippet install step (05. Payment Flow) — copy-paste instructions, guided setup, or an automated verification ping — not yet designed

## Stack Confirmed (2026-08-03)

**shadcn/ui + Tailwind CSS** — confirmed as the actual frontend component/styling stack, replacing the earlier "proposed" framing above. shadcn/ui components get themed directly against [design.md](http://design.md)'s tokens (black background, lime accent used sparingly, Outfit/Figtree, 10–12px radii, no shadows) rather than used with their default styling — the design system is binding, shadcn is just the component primitive layer underneath it.

## Diagram

```mermaid
flowchart LR
    subgraph UI Layer
        SHADCN[shadcn/ui components]
        TW[Tailwind CSS]
    end
    SHADCN --> TW
    TW --> DESIGN[design.md tokens: black/lime, Outfit/Figtree, 10-12px radii]
```

## Update (2026-08-23): Paddle → Swich, Offer Replaces Campaign, Shopify-Only, Pakistan/PKR

Founder decisions, full reasoning in 02. Architecture Decision Log. Less deep staleness than System Architecture/Event-Driven Architecture (this doc's UI-layer content ages better), but real corrections:

- **§2/§3 "campaign creation/management," "campaign discovery/browse"** → "offer creation/management," "offer discovery/browse" — no separate Campaign entity (01. Domain Model).
- **§4 "Merchant billing card collection — a Paddle Checkout form"** → **Swich billing connect** — same role (the only payment-widget-adjacent surface on SellVia's own frontend), different vendor, and possibly a different shape entirely: whether Swich even requires an embedded widget (vs. a simple redirect/API flow) is unconfirmed (see 04. CORS, CSP & Security Headers' 2026-08-23 update).
- **§5 "campaign vetting"** → "offer vetting."
- **Open Questions' "merchant's onboarding snippet install step"** → **resolved and superseded**: Shopify OAuth/webhook connect (05. Payment Flow, 2026-08-23), not a snippet at all — this open question no longer applies as stated.
- **Currency: PKR only** throughout any price/commission display.

Design System Constraints section (colors/typography/grid) is unaffected — that's a separate, still-current decision (see UX/Design System's own 2026-08-23 update for the one real change there: Gray 02 corrected for WCAG AA).

---

# Technical Architecture / Search Strategy

> Source: `Technical Architecture/Search Strategy.md` · tag: `shared`

# Search Strategy

## Purpose

How creators find offers to promote, and how merchants find/review creators.

## MVP: Structured Filtering, Not Full-Text Search

Given the raw data doc's original design ("browse/join offers by category: digital vs physical") and the case study's emphasis on reducing complexity, MVP search doesn't need a dedicated search engine (e.g. Elasticsearch) — simple database filtering/sorting on structured fields covers it:

- Filter by category (digital/physical), commission rate range, niche tag
- Sort by newest, highest commission, most applications (popularity signal)

## Post-MVP: AI-Assisted Matching

As discussed in the AI integration conversation, semantic creator↔offer matching (matching a creator's niche/audience against product category/description, not just exact category filters) is a strong post-MVP candidate — see 02. Technical Architecture → AI Services for how that would layer on top of this basic filtering rather than replace it.

## Open Questions

- Whether tags/niches are a fixed taxonomy (simpler, easier to filter) or free-text (more flexible, harder to search cleanly) — recommend a fixed taxonomy of niches for MVP, expand as real usage reveals gaps

---

# Technical Architecture / System Architecture

> Source: `Technical Architecture/System Architecture.md` · tag: `shared` · last updated: 2026-08-23

# System Architecture

## Purpose

The single picture of how all of SellVia's pieces fit together — every other doc in this section zooms into one part of this.

## High-Level Diagram

```text
               Cloudflare (DNS, CDN, DDoS)
                       ↓
               Next.js App (frontend + API routes)
                       ↓
       ────────────────────────────────────
       ↓                   ↓                       ↓
Clerk (auth)         Paddle          Background Workers
       ↓             (checkout, splits,          ↓
PostgreSQL           payouts, webhooks)      Redis (queues, cache)
(source of truth)         ↓                       ↓
       ──────────────→  Notification Service (email/push)
```

## Core Principle

SellVia is a **hosted-checkout marketplace**, not a click-tracking-only affiliate network (see 01. Business Logic → Money Flow). This shapes the whole architecture: the backend isn't just storing links and reading pixels — it's a real payments system built around Paddle, so correctness and auditability of money movement matter more than in a typical CRUD app.

## Major Components

1. **Frontend** — merchant dashboard, creator dashboard, public campaign discovery, hosted checkout pages (see Frontend Architecture)
2. **Backend / API** — auth-gated REST API serving the frontend and handling business logic (see Backend Architecture, API Design)
3. **Auth** — Clerk, handling sign-up/login/session for Merchant/Creator/Admin roles (see 04. Security → Authentication)
4. **Payments** — Paddle for checkout, the three-way split, and payouts (see Backend Architecture, and 01. Business Logic → Commission Engine/Money Flow for the business rules this implements)
5. **Database** — PostgreSQL as the single source of truth for all entities (see 03. Database)
6. **Background workers** — handle anything that shouldn't block a user-facing request: webhook processing, payout batching, notification delivery (see Background Jobs)
7. **Cache/queues** — Redis, for both caching and job queues (see Caching Strategy, Background Jobs)
8. **Notifications** — email/push delivery service triggered by backend events (see 01. Business Logic → Notification Logic for the business rules)

## AI Services Layer (proposed, post-MVP)

Discussed but not yet built: creator↔campaign matching, application screening assist, fraud/anomaly detection, disclosure-compliance assist. These would sit as a separate service the backend calls into (not embedded ad hoc per feature) — see AI Services doc below for the split of what's rule-based vs. ML vs. LLM-based.

## Environments

Per the earlier infrastructure conversation: Local → Staging ([staging.wesellvia.com](http://staging.wesellvia.com)) → Production ([wesellvia.com](http://wesellvia.com)), each with fully separate databases, Paddle modes (test vs. live), and file storage buckets. See 06. Infrastructure & DevOps (not yet written) for the full environment strategy — that conversation already covered most of this in depth and should be ported into Notion next.

## Open Questions

- Monolith (single Next.js app + API routes) vs. separate frontend/backend services — recommend starting monolithic for MVP speed, splitting later only if a specific bottleneck justifies it
- Whether background workers run on the same VPS as the main app or a separate box — depends on load, revisit once there's real traffic

## Diagram

```mermaid
flowchart TD
    CF[Cloudflare: DNS, CDN, HTTPS, DDoS]
    APP[Next.js App: Frontend + API Routes]
    AUTH[Ory Kratos: Auth]
    PADDLE[Paddle: Checkout, Splits, Payouts]
    WORKERS[Background Workers]
    DB[(PostgreSQL)]
    REDIS[(Redis: Queues + Cache)]
    NOTIF[Notification Service]

    CF --> APP
    APP --> AUTH
    APP --> PADDLE
    APP --> DB
    APP --> WORKERS
    WORKERS --> REDIS
    WORKERS --> NOTIF
    PADDLE -.webhooks.-> WORKERS
```

## Update (2026-08-03): FastAPI Backend — Two-Service Architecture

The backend is now **FastAPI (Python)**, not Next.js API routes (see Backend Architecture for full detail). This changes the System Architecture diagram above from "one Next.js app doing everything" to two separate services talking over HTTP:

```mermaid
flowchart TD
    CF[Cloudflare: DNS, CDN, HTTPS, DDoS]
    FE[Next.js Frontend]
    BE[FastAPI Backend]
    AUTH[Ory Kratos: Auth]
    PADDLE[Paddle]
    WORKERS[Background Workers: Celery/RQ]
    DB[(PostgreSQL via SQLAlchemy)]
    REDIS[(Redis: Queues + Cache)]

    CF --> FE
    FE -->|REST API calls| BE
    BE --> AUTH
    BE --> PADDLE
    BE --> DB
    BE --> WORKERS
    WORKERS --> REDIS
    PADDLE -.webhooks.-> BE
```

This means two deployable units, two environment-variable sets, and CORS between frontend and backend origins now genuinely matters (04. Security → API Security). See 06. Infrastructure for VPS/CI-CD updates reflecting this split.

## Update (2026-08-03): Monolithic FastAPI backend, not microservices

**Decided: the FastAPI backend is a single monolithic service**, not split into separate microservices (e.g. no separate Payments service, Campaigns service, Notifications service). Internally organized into clean modules (campaigns, applications, payments, notifications, etc.), but one process, one deploy, one database connection pool.

**Why:** microservices solve organizational problems (independent teams owning independent services) that don't exist at this team size, and they introduce real cost here specifically — this is a financial system where keeping a Sale, its Commission, and balance updates consistent is much simpler within one transaction boundary than coordinated across services. Matches Mission & Principles' "reduce complexity relentlessly."

**The one piece already separate:** background workers (Celery) run as their own process, since that's a natural, low-cost seam — not a step toward microservices, just standard separation of request-handling from background work. This is the one piece that could become independently scaled/deployed later without a redesign, if ever needed.

**Revisit only if:** the team grows enough to need independent ownership of specific domains, or a specific component (e.g. checkout processing under heavy load) demonstrably needs to scale independently from the rest — not before there's real evidence of either.

## Update (2026-08-04): Auth Provider Diagram Note

The "AUTH" node in the diagram above is now Ory Kratos (04. Security → Authentication, superseding the earlier Clerk decision) — Ory Network (managed) for MVP, self-hosted Kratos at scale. Called from the FastAPI backend as a REST API, same as Paddle/Supabase — no change to the overall modular-monolith shape, just a swapped external identity provider.

## Update (2026-08-23): ⚠️ This Doc Predates Two Major Reversals — Diagrams and "Core Principle" Are Stale, Not Just the Paddle Name

**This is the oldest, least-maintained doc in Technical Architecture.** Every diagram above (both mermaid versions) and the "Core Principle" section still describe **SellVia-hosted checkout via Paddle** — a model reversed on 2026-08-07 (01. Money Flow) and further revised on 2026-08-23 (02. Architecture Decision Log). This is deeper staleness than a find-and-replace can fix responsibly, so this update flags the scope rather than papering over it with a partial patch:

- **"Core Principle: hosted-checkout marketplace"** — wrong. SellVia has no hosted checkout at all; customers buy on the merchant's own Shopify store (external-site tracking model).
- **Both diagrams' "PADDLE: Checkout, Splits, Payouts" node** — wrong on all three counts: no checkout (SellVia never hosted one, post-2026-08-07), no live split (billing is periodic, post-2026-08-07), and no Paddle at all (Swich replaces it, 2026-08-23). The correct shape now has **two** external payment-adjacent integrations, not one: Swich (billing/payout) and Shopify (sale reporting via webhook) — plus the redirect/click-tracking mechanism (`GET /go/:slug`) that has no representation in either diagram.
- **"Major Components" §1 "hosted checkout pages"** and §4 "Paddle for checkout, the three-way split" — same staleness.
- **Environments section's "Paddle modes"** → Swich modes.
- **"Campaign" throughout** → "Offer," no separate entity (01. Domain Model, 2026-08-23).

**Recommended fix, not done here:** a full diagram rewrite showing Shopify webhook → sale acceptance → BillingCycle → Swich invoice → Swich payout, replacing the Paddle-centered checkout diagram entirely. Flagging this as a real gap rather than a cosmetic one — anyone building against this doc's diagrams today would build the wrong thing.

---

# UI / FEATURE_LIST

> Source: `UI/FEATURE_LIST.md` · tag: `frontend` · last updated: 2026-08-23

# SellVia — Frontend Feature List

## Purpose

Every product feature the frontend needs to support, derived exclusively from the documentation in `/Docs`. Grouped by product area. Each feature lists: description, user goal, main actions, expected frontend behavior, important states, relevant screens (see `SCREEN_INVENTORY.md`), backend/API dependencies, permissions, edge cases, and source docs.

**How to read this document:** SellVia's docs record decisions chronologically, with later "Update" sections superseding earlier text in the same file. This list reflects the **latest resolved state** as of the most recent updates (through 2026-08-23), not the original/superseded framing. Where a doc contains stale, unresolved, or contradictory statements, this is marked **"Needs clarification"** rather than guessed at. See `SITE_MAP.md` and `SCREEN_INVENTORY.md` for the navigation and screen-level counterparts to this list.

**Critical context for every feature below (updated 2026-08-23):**

1. **Market: Pakistan-only, PKR only.** Every Merchant is a Pakistani business. No USD/EUR/GBP for MVP.
2. **Payment processor: Swich** (swichnow.io), confirmed 2026-08-23. Merchant billing runs through Swich's recurring billing/invoice-link product; creator payout runs through Swich's disbursement API (bank, JazzCash, EasyPaisa, Raast). There is no embedded Paddle Checkout anywhere in the frontend, and no manual admin-run bank-transfer workflow either — both legs are Swich-integrated, webhook-confirmed. **Swich is a payment processor, not a Merchant of Record** — SellVia keeps its own tax responsibility (see [Payments/Tax Considerations]). Swich's exact API shapes/pricing/onboarding requirements are unconfirmed pending real integration — every Swich-specific field/flow below is a working draft.
3. **Merchant integration: Shopify only, via native webhook.** Every MVP merchant connects a Shopify store (OAuth/app-install); Shopify's `orders/paid` webhook reports sales, not a copy-paste JS snippet. The customer still buys on the merchant's own (Shopify) checkout — SellVia has never hosted checkout for this model, unrelated to this change.
4. **No separate Campaign entity.** "Offer is offer, it is not turning into any campaign at all." An Offer carries its commission rate and lifecycle status (draft/live/paused/ended) directly — one creation flow, not a two-step Offer-then-Campaign wrap. Applications, AffiliateLinks, and Sales attach to an Offer.

Full reasoning for all four: `Technical Architecture/Architecture Decision Log`, `Product Foundation/MVP Scope`. Auth provider is **Ory Kratos** (switched from Clerk 2026-08-04) — unaffected by this revision.

---

## 0. Cross-Cutting Platform Features

These apply across every module rather than belonging to one screen.

### 0.1 Unified Authentication (Sign Up / Log In / Session)

- **Description:** Single sign-up form; user selects Merchant or Creator role (a user may hold both roles on one account). Auth handled by Ory Kratos.
- **User goal:** Get into the product with minimal friction, on the correct role-specific path.
- **Main user actions:** Register (email/password, optionally social login — provider TBD), verify email, log in, log out, log out of all other devices, reset password.
- **Expected frontend behavior:** Ory Kratos SDK/components drive the sign-in/sign-up/session UI. Role selection branches the post-signup flow (Merchant onboarding vs. Creator onboarding). Session is server-validated (not a cached JWT) — sensitive actions re-verify live.
- **Important states:** loading (session check), unauthenticated, authenticated (role-resolved), email-unverified (gated), error (invalid credentials, account locked), MFA-challenge (if enabled).
- **Relevant screens:** Login, Register, Forgot/Reset Password, Verify Email, MFA setup/challenge.
- **API/backend dependencies:** Ory Kratos REST API (session issuance, verification, password reset, email verification); backend resolves role(s) from Kratos identity `traits`, never trusts a client-supplied role.
- **Permissions/roles:** N/A (pre-role-gating layer); role is attached to session after auth.
- **Edge cases:** Session expires at 14-day ceiling forcing re-login; 5 concurrent sessions per account (Admin may need a higher cap — unresolved); instant revocation on password change, Admin suspension, or IP-anomaly ban escalation.
- **Source:** [Security/Authentication], [Security/Session Management], [API/API Authentication], [Business Logic/User Roles].

### 0.2 Dual-Role Context Switching

- **Description:** A single account can be both Merchant and Creator. Each role has its own separate profile, data, and nav.
- **User goal:** Operate as either "hat" without the two experiences bleeding together.
- **Main user actions:** Switch active context (Merchant ↔ Creator).
- **Expected frontend behavior:** Explicit role switcher (mechanism not yet designed in docs) rather than a merged nav — Merchant nav (Offers/Applications/Sales/Payouts) and Creator nav (Discover/My Links/Earnings) never combine into one menu.
- **Important states:** active-context indicator; empty state if the user hasn't set up the other role yet.
- **Relevant screens:** Present in the global app shell/header of both dashboards.
- **API/backend dependencies:** Role(s) resolved server-side per request; a Merchant-context request never leaks Creator-context data and vice versa (tested explicitly per Cross-Tenant Isolation Testing).
- **Permissions/roles:** Merchant, Creator (same user, two contexts).
- **Edge cases:** Self-dealing block — a dual-role user's CreatorProfile can never apply to an Offer owned by their own MerchantProfile (hard-blocked server-side, not just hidden in UI).
- **Source:** [Business Logic/User Roles], [UX/Navigation], [Edge Cases/User Edge Cases], [Business Logic/Business Rules].

### 0.3 Notifications

- **Description:** In-app + email notifications for key lifecycle events, kept quiet/informational (no gamification, per design philosophy).
- **User goal:** Know when something requiring attention or worth celebrating has happened, without checking manually.
- **Main user actions:** View notification feed, mark as read, click through to the relevant screen.
- **Expected frontend behavior:** Real-time or near-real-time delivery for high-trust moments (e.g., application approved → link issued) — this is one of the product's "trust moments" and should not feel delayed/black-box. Quiet visual treatment (no confetti/badges).
- **Important states:** unread/read, empty ("no notifications yet"), loading.
- **Relevant screens:** Notification center/feed (shared component across Merchant/Creator/Admin shells).
- **API/backend dependencies:** `notifications` table; triggers per Notification Logic; async job completion also notifies (`job_completed`).
- **Permissions/roles:** Scoped to the recipient `user_id` only.
- **Edge cases:** Application-rejected notification content unspecified (open question — **Needs clarification**); real-time vs. digest cadence for "sale made" unspecified (**Needs clarification**); exact merchant "milestone reached" thresholds undefined (**Needs clarification**).
- **Source:** [Business Logic/Notification Logic], [Technical Architecture/Async Job Pattern & Idempotency], [UX/Interaction Patterns].

### 0.4 Empty / Loading / Error States (Design System Requirement)

- **Description:** A calm, intentional empty-state pattern reused throughout the product, echoing the public site's own "0 creators, 0 sales, ₨0" zeroed-dashboard honesty device.
- **User goal:** Never mistake "nothing here yet" for "something is broken."
- **Expected frontend behavior:** Empty states read as expected/calm, not broken (e.g., "link generated, zero clicks yet"). Loading states are simple fades/skeletons, never playful spinner copy (restrained-animation design rule). Errors are specific and actionable ("You've already applied to this offer," never "Something went wrong").
- **Relevant screens:** Every list/table view across the app (offers, applications, sales, payouts, links).
- **API/backend dependencies:** Structured error shape `{ "error": { "code", "message", "status" } }` from every endpoint; two-layer error handling (safe message to user, full detail to private log) — no raw stack traces ever reach the UI.
- **Source:** [UX/Components], [UX/Copy Guidelines], [API/Error Responses], [Infrastructure/Error Handling & Logging Pipeline].

### 0.5 Accessibility (Binding, Not Aspirational)

- **Description:** Full keyboard navigation, screen-reader compatibility, and verified WCAG AA color contrast across the entire product — explicitly called out as enforced pre-launch gates, not nice-to-haves.
- **Expected frontend behavior:** Logical tab order; visible focus rings (lime accent, consistent with the design system's "active state" use of lime); all custom interactive components (offer discovery filters, the "Get Link" component, status badges with actions) independently verified for keyboard operability — shadcn/ui defaults aren't assumed sufficient; all form fields have real associated labels (no placeholder-only labels); `aria-describedby`/`aria-invalid` on validation errors; all meaningful images have alt text (including product images — schema currently lacks an alt-text field, flagged as a gap); icon-only controls get `aria-label`; async status changes use `aria-live`.
- **Contrast status — RESOLVED 2026-08-23:** Lime (#BFFF13) and Gray 01 (#A1A1AA) both verified compliant against black (17.5:1 and 8.2:1). Gray 02 was the actual failure at 4.35:1 — corrected to **#787882** (4.82:1). See [UX/Accessibility] for the full computed table. No longer an open risk.
- **Source:** [UX/Accessibility], [Security/Security Checklist].

### 0.6 Machine-Readability of Public Content (SEO / AI-Agent Legibility)

- **Description:** Public Offer pages carry `schema.org` `Product`/`Offer` JSON-LD; semantic HTML landmarks; OpenAPI spec auto-generated from the backend; `llms.txt` at the marketing site root; deliberate `robots.txt` allowing known AI crawlers on public pages only.
- **User goal (indirect):** Let AI shopping agents, search AI answers, and link-preview tools understand SellVia's public offers correctly.
- **Expected frontend behavior:** Every public Offer detail page renders JSON-LD with accurate price/currency (PKR)/availability. Standard OG/Twitter-card/canonical-URL meta tags on all public pages, generated from the base page template.
- **What does NOT get this treatment:** Authenticated dashboards, any tenant-private data — same boundary as accessibility, but for machines instead of humans.
- **Source:** [UX/AI Agent & Machine Readability].

### 0.7 AI-Assisted Features

| Feature | What it does | User-facing surface | Notes |
| --- | --- | --- | --- |
| Creator↔Offer matching | Ranks offer discovery results by embedding similarity on top of existing category/commission filters | Creator discovery/browse screen | Post-MVP per some docs, but described as an "initial AI level" item elsewhere — **Needs clarification on MVP vs. post-MVP timing** |
| Application screening assist | One LLM-generated plain-language fit summary per application, cached, shown to the reviewing Merchant | Merchant application review screen | Never shown to any other Merchant (tenant-private) |
| Offer copy assist | Merchant provides product name + price (PKR); LLM drafts an editable offer description | Merchant offer creation form | Draft is editable, not final |
| Disclosure nudge | Fixed, legally-reviewed FTC-style disclosure template shown at link-generation time | Creator "Get Link" moment | Deliberately templated, NOT LLM-generated (legal text) — Pakistan-specific disclosure norms not yet reviewed, **Needs clarification** |

- **Source:** [Technical Architecture/AI Services], [Product Foundation/MVP Scope], [Security/Tenant Isolation Audit].

---

## 1. Public Marketing & Discovery

### 1.1 Marketing Site (wesellvia.com)

- **Description:** The existing live public site — hero, concept walkthrough, roadmap-stage visibility, FAQ, waitlist form. Deliberately shows zeroed real metrics ("0 creators approved, 0 sales, ₨0 tracked revenue") as a radical-transparency positioning device, not a placeholder.
- **User goal:** Understand what SellVia is, see it's early/honest, and join the waitlist.
- **Main user actions:** Read hero/roadmap/FAQ, submit waitlist form (business or creator, "why do you want to join").
- **Expected frontend behavior:** Nav: logo left, links center/right ("How It Works," "For Businesses," "For Creators"), single "Join Waitlist" CTA — no dropdowns, no mega menus. Zeroed-metric device persists into early product messaging. Copy should reflect Pakistan-only scope once product messaging catches up to the 2026-08-23 revision — **Needs clarification** on whether the current live site copy needs an update pass.
- **Important states:** waitlist form submitted (confirmation), roadmap stage indicator ("you are literally here" — currently Stage 02: Validation).
- **Relevant screens:** Public Home / Landing.
- **API/backend dependencies:** Waitlist signup endpoint (implied, not explicitly specified in Endpoint Specifications — **Needs clarification**).
- **Permissions/roles:** Public, unauthenticated.
- **Edge cases:** None documented beyond standard form validation.
- **Source:** [Product Foundation/Product Vision], [Product Foundation/Product Roadmap], [UX/Design System], [UX/Navigation].

### 1.2 Public Offer Discovery / Browse

- **Description:** Browsable, filterable list of live offers — the entry point for creators (and, per the AI-agent doc, for machine consumption of public product data).
- **User goal:** Find an offer/product worth promoting.
- **Main user actions:** Filter by category (digital/physical), commission-rate range, niche; sort by newest, highest commission, most applications (popularity proxy).
- **Expected frontend behavior:** No dedicated search engine for MVP — structured DB filtering/sorting only. Public, no auth required to view. All prices/commissions shown in PKR.
- **Important states:** loading, empty ("no live offers match these filters"), error.
- **Relevant screens:** Public/Creator Offer Discovery list, Public Offer detail page.
- **API/backend dependencies:** `GET /offers` (public, filterable) — renamed from `GET /campaigns` 2026-08-23.
- **Permissions/roles:** Public read; apply action requires Creator auth.
- **Edge cases:** Post-MVP AI-assisted semantic matching layers on top of this same filtering, doesn't replace it.
- **Source:** [Technical Architecture/Search Strategy], [API/Endpoint Specifications], [Business Logic/User Flows].

### 1.3 Public Offer Detail Page

- **Description:** The public page for a single offer/product — name, price (PKR), commission rate, merchant, `schema.org` markup.
- **User goal (creator):** Decide whether to apply. **User goal (buyer via a shared link):** Land here or get redirected onward to the merchant's Shopify product page.
- **Main user actions:** View details; (creator, authenticated) apply.
- **Expected frontend behavior:** Carries `Product`/`Offer` JSON-LD. This page is informational/discovery, not a checkout entry point — a shared AffiliateLink resolves through `GET /go/:slug`, which redirects to the **merchant's own Shopify store**, not to this page's checkout (there is none).
- **Important states:** loading, not-found/ended offer.
- **Relevant screens:** Public Offer detail.
- **API/backend dependencies:** `GET /affiliate-links/:slug` (public), `GET /go/:slug` (public redirect + click logging).
- **Permissions/roles:** Public.
- **Edge cases:** Offer ended — clicks after end date attribute nothing; clicks before end date still honored within the 30-day window.
- **Source:** [UX/AI Agent & Machine Readability], [API/Endpoint Specifications], [Business Logic/State Machines].

---

## 2. Onboarding & Account Setup

### 2.1 Role Selection & Signup Branching

- **Description:** After the unified signup form, the flow branches based on chosen role(s).
- **User goal:** Get to the correct next step (Offer creation vs. offer browsing) without extra clicks.
- **Main user actions:** Choose Merchant, Creator, or both.
- **Expected frontend behavior:** Minimal-field, low-friction form; contextual hiding of irrelevant fields per role (a digital-goods Merchant never sees a shipping field).
- **Relevant screens:** Register, Role Selection (may be same screen).
- **Edge cases:** No follower-count floor for Creator eligibility (merit/fit-based, decided).
- **Source:** [Business Logic/User Flows], [Business Logic/User Roles], [UX/Interaction Patterns].

### 2.2 Merchant Onboarding: Swich Billing Connect

- **Description:** Merchant connects Swich as their billing method before any offer can go live — replaces the Paddle card-on-file step (Paddle removed) and the interim plain-bank-details-form default (superseded same-day by the Swich decision).
- **User goal:** Get set up so periodic billing cycles have a working way to actually charge them.
- **Main user actions:** Complete Swich's own onboarding/checkout-connect flow (likely an embedded widget or redirect, exact mechanism unconfirmed pending real Swich integration docs).
- **Expected frontend behavior:** Gate: an Offer cannot go `draft → live` until this is complete. Whether this is an embedded Swich widget (like the old Paddle Checkout iframe) or a redirect flow is **Needs clarification** — Swich's actual integration pattern isn't confirmed yet.
- **Important states:** not started, connected, payment-failed (Swich-reported reason + prompt to update payment method).
- **Relevant screens:** Merchant Billing Setup / Settings.
- **API/backend dependencies:** `merchant_profiles.swich_customer_id` (new 2026-08-23, replacing both `paddle_customer_id` and the interim `bank_account_*` fields).
- **Permissions/roles:** Merchant only, own account.
- **Edge cases:** **Needs clarification** — the exact retry/escalation policy for a failed billing-cycle charge (the old "3 failed Paddle billing attempts over 3 days" rule needs re-mapping against Swich's own failure-webhook behavior, not yet done). Flagged in MVP Scope's Still-Open Items.
- **Source:** [Product Foundation/MVP Scope], [Business Logic/State Machines], [Database/Table Specifications], [Payments/Money Flow], [Payments/Payment Flow].

### 2.3 Merchant Onboarding: Shopify Store Connect

- **Description:** Merchant connects their Shopify store via OAuth/app-install — this is what reports sales back to SellVia. Replaces the universal tracking-snippet install entirely (Shopify-only for MVP).
- **User goal:** Get attribution working so sales are tracked and commissions calculated.
- **Main user actions:** Click "Connect Shopify Store," authorize the SellVia app in their Shopify admin, confirm the webhook is active.
- **Expected frontend behavior:** Offer cannot go `draft → live` until SellVia verifies the Shopify webhook is registered and active. Also: creating a unique discount code in the merchant's Shopify discount system during offer setup (fallback attribution signal, unchanged in concept from before). This is a **simpler** frontend step than the old snippet flow — no code to copy-paste, just an OAuth redirect and a confirmation state.
- **Important states:** not connected, connecting (mid-OAuth), connected/verified, connection-failed (with troubleshooting copy — e.g. wrong store, permissions declined).
- **Relevant screens:** Merchant Offer Setup (Shopify connect step), possibly a dedicated onboarding screen.
- **API/backend dependencies:** Shopify OAuth flow; `POST /webhooks/shopify-sales` (renamed from `POST /webhooks/merchant-sales` 2026-08-23).
- **Permissions/roles:** Merchant only, own offers.
- **Edge cases:** Exact Shopify app-install path (public Shopify App Store listing vs. a private/custom app SellVia distributes directly) is explicitly **undesigned** — **Needs clarification**, flagged in MVP Scope's Still-Open Items.
- **Source:** [Payments/Payment Flow], [Payments/Money Flow], [Business Logic/State Machines], [Technical Architecture/Frontend Architecture].

### 2.4 Creator Onboarding: Swich Payout Setup

- **Description:** Creator registers as a Swich payout recipient (payee) to receive commission — replaces Paddle seller onboarding entirely, and supersedes the interim plain-bank-details-form default from earlier the same day.
- **User goal:** Be able to actually get paid once commissions accrue.
- **Main user actions:** Complete Swich's payee-registration flow — select payout method (bank account, JazzCash, or EasyPaisa) and provide the corresponding details.
- **Expected frontend behavior:** A Creator approved for an offer but with incomplete Swich payee registration must **not** get an active AffiliateLink yet — block link activation entirely rather than accruing unpayable commission (unchanged principle from the Paddle-era design). Offering a choice of payout method (not just bank account) is new relative to the interim default, and is a real UX improvement — most Pakistani creators are more likely to have JazzCash/EasyPaisa than a bank IBAN handy.
- **Important states:** not started, complete, incomplete-blocking-link.
- **Relevant screens:** Creator Payout Setup / Settings.
- **API/backend dependencies:** `creator_profiles.swich_payee_id`, `creator_profiles.payout_method` (new 2026-08-23, replacing `paddle_seller_id` and the interim `bank_account_*` fields).
- **Permissions/roles:** Creator only, own account.
- **Edge cases:** Onboarding-incomplete gate is resolved as hard-block (unchanged). Whether any Pakistani-tax-equivalent form (an FBR-relevant declaration, if any) is collected here is **Needs clarification** — Swich, like the bank-transfer default before it, is not confirmed to auto-collect this the way Paddle's onboarding did (Swich is a processor, not a Merchant of Record — see [Payments/Tax Considerations]).
- **Source:** [Edge Cases/User Edge Cases], [Technical Architecture/Backend Architecture], [Payments/Tax Considerations], [Payments/Payout Process].

---

## 3. Merchant Module

### 3.1 Offer Creation & Management (merged with the former "Campaign" concept)

- **Description:** A Merchant's product listing **and** its commission-bearing, applicable listing, in one entity — name, price (PKR), category (digital/physical), commission rate, lifecycle status. There is no separate step to "wrap" a product in a campaign; setting the commission rate and publishing are part of the same creation flow.
- **User goal:** List a product with a commission attached and get it in front of creators, in one pass.
- **Main user actions:** Create Offer (name, price, category, commission rate), edit Offer, publish (`draft → live`), pause, resume, end, archive/soft-delete.
- **Expected frontend behavior:** Minimal required fields; category selection hides irrelevant fields (e.g., no shipping field for digital). No multi-page wizard, and — since Offer absorbed Campaign — **one fewer step** than the original two-entity design (create Offer, then separately create a Campaign around it). `draft → live` blocked until **both** gates pass: bank settlement setup complete (§2.2) AND Shopify store connected & verified (§2.3). Editing commission rate mid-flight does not require re-consent from already-approved creators (they keep their locked rate, per approval-time locking); new applicants see the new rate.
- **Important states:** draft, live, paused, ended; empty ("no offers yet"); loading; error; success (created); each gate's pass/fail state visible before publish is attempted.
- **Relevant screens:** Offers list, Create/Edit Offer, Offer detail.
- **API/backend dependencies:** `GET /offers`, `POST /offers`, `PATCH /offers/:id`, `PATCH /offers/:id/status` (all renamed from `/campaigns/...` 2026-08-23).
- **Permissions/roles:** Merchant (own offers only); Admin (any, for moderation/vetting override).
- **Edge cases:** Paused offers keep honoring in-flight attribution within the 30-day window, accept no new applications; ended offers stop attributing new clicks immediately but honor pre-end clicks within the window; high-commission/high-risk offers require Admin vetting before going live (thresholds undefined — **Needs clarification**); product image auto-fetch from a URL is a deferred v2 convenience — manual upload only for MVP.
- **Source:** [Business Logic/Domain Model], [Business Logic/State Machines], [Business Logic/Business Rules], [Database/Table Specifications], [Technical Architecture/File Storage], [Operations/Admin Panel].

### 3.2 *(Retired — merged into §3.1, 2026-08-23)*

The former "Offer Management" and "Campaign Creation & Management" were two separate sections describing a two-entity model. As of the 2026-08-23 revision there is one entity (Offer) and one section (§3.1) — this number is intentionally left as a pointer rather than reused, so cross-references elsewhere in the docs that still say "§3.2" resolve here.

### 3.3 Application Review

- **Description:** Merchant reviews creator applications to their offers and approves/rejects.
- **User goal:** Choose the right creators for the offer.
- **Main user actions:** View applicant's audience/niche/engagement data (+ AI-generated fit summary), approve, reject.
- **Expected frontend behavior:** Approval **immediately** surfaces the generated AffiliateLink to the Creator (real-time-feeling, not delayed-email-only) — described as one of the product's core "trust moments."
- **Important states:** pending, approved, rejected; empty ("no applications yet"); pending-count indicator.
- **Relevant screens:** Applications list (per offer), Application review card/detail.
- **API/backend dependencies:** `POST /offers/:id/applications` (creator-initiated), `GET /offers/:id/applications`, `PATCH /applications/:id` (approve/reject, triggers AffiliateLink creation) — all renamed from `/campaigns/...` 2026-08-23.
- **Permissions/roles:** Merchant (own offers only); Admin (moderation override).
- **Edge cases:** Rejected applicants cannot resurrect the old application, only submit a new one; whether merchants see aggregate creator performance platform-wide or only the applicant's own submitted stats is unresolved (**Needs clarification**); self-dealing applications blocked server-side before reaching this queue.
- **Source:** [Business Logic/State Machines], [Business Logic/Permission Matrix], [UX/Components], [Business Logic/Business Rules].

### 3.4 Sales Visibility

- **Description:** Merchant's view of sales reported and attributed to their offers.
- **User goal:** See what's selling and confirm commissions are calculating correctly.
- **Main user actions:** View sale list, filter/sort, view acceptance status.
- **Expected frontend behavior:** Shows `acceptance_status` (accepted/rejected). Amounts in PKR. Never served from cache — always live.
- **Important states:** accepted, rejected (flagged for Admin review, not silently dropped), empty, loading.
- **Relevant screens:** Sales list (Merchant), Sale detail/receipt.
- **API/backend dependencies:** `GET /sales` (scoped to own).
- **Permissions/roles:** Merchant (own only); Admin (any).
- **Edge cases:** A merchant under-reporting sales is a fraud vector the external-tracking model introduced — flagged/reconciled at the platform level, not something the merchant UI directly exposes.
- **Source:** [API/Endpoint Specifications], [Security/Fraud Prevention], [Technical Architecture/Caching Strategy].

### 3.5 Billing (Periodic Merchant Billing — Swich)

- **Description:** Merchant's billing-cycle history and Swich billing-method management — a Swich-generated invoice/payment-link per cycle, webhook-confirmed, not a Paddle charge and not a manual admin-tracked bank transfer.
- **User goal:** Understand what's owed and confirm payment goes through smoothly each cycle.
- **Main user actions:** View billing cycle history/totals, complete payment through Swich when a cycle closes, update billing method.
- **Expected frontend behavior:** Monthly billing cycles (unchanged cadence). On a cycle closing, SellVia's backend generates a Swich invoice/payment request for the total owed; the merchant completes it through Swich's checkout (card, bank transfer, JazzCash, or EasyPaisa); a Swich webhook confirms it and the cycle flips to `charged` automatically — no admin manually checking a bank statement. **Retry/escalation policy for a failed charge is not yet fully designed** (see §2.2's edge case) — the old "3 failed billing attempts → auto-pause" rule needs re-mapping against Swich's own failure-webhook behavior, which isn't confirmed yet.
- **Important states:** open, pending_charge, charged, failed (with a way to see why, from Swich's reported reason, and what to do).
- **Relevant screens:** Billing Cycles / Billing History, Billing Settings.
- **API/backend dependencies:** `GET /billing-cycles` (scoped to own, now surfacing `swich_invoice_id`/`swich_payment_reference`).
- **Permissions/roles:** Merchant (own only).
- **Edge cases:** Failed billing accumulates rather than losing sales; creator commission for that cycle stays unpaid until resolved (bill-first-then-pay, unchanged principle).
- **Source:** [Payments/Payment Flow], [Payments/Money Flow], [Business Logic/State Machines], [Database/Table Specifications].

### 3.6 Refund Credit Request

- **Description:** Merchant requests a billing credit for a sale that was already tracked/billed/paid out, when their own customer got a refund on the merchant's Shopify store.
- **User goal:** Not be billed for a sale that was refunded to the end customer.
- **Main user actions:** Submit a credit request for a specific sale, optionally partial.
- **Expected frontend behavior:** Capped at 5 credits/calendar month; proportional credit for partial refunds; beyond the cap, no further credit (clear messaging why). Amounts in PKR.
- **Important states:** credits-remaining-this-month counter, submitted, applied-to-next-cycle, cap-reached (disabled state with explanation).
- **Relevant screens:** Sale detail (Merchant) → Request Refund Credit action; possibly a dedicated Refund Requests list.
- **API/backend dependencies:** No explicit endpoint named in Endpoint Specifications — **Needs clarification** ("UI/API for how a merchant actually submits a credit request — not yet designed").
- **Permissions/roles:** Merchant (own sales only).
- **Edge cases:** Creator commission is never clawed back regardless — SellVia absorbs the cost within the cap.
- **Source:** [Payments/Refund Handling], [Payments/Money Flow].

### 3.7 Merchant Profile / Business Settings

- **Description:** Business name, category, and account-level settings.
- **User goal:** Keep business info accurate.
- **Main user actions:** Edit business name/profile fields.
- **Relevant screens:** Merchant Settings / Business Profile.
- **API/backend dependencies:** `merchant_profiles` table (last-write-wins conflict resolution, not event-sourced).
- **Permissions/roles:** Merchant, own profile only.
- **Source:** [Business Logic/Domain Model], [Database/Database Design].

---

## 4. Creator Module

### 4.1 Offer Discovery (Authenticated)

- **Description:** Same underlying discovery/filter system as the public browse page, in-app for logged-in creators applying.
- **User goal:** Find offers that fit their audience/niche.
- **Main user actions:** Filter (category, commission range, niche), sort, apply.
- **Expected frontend behavior:** Post-MVP: AI similarity ranking layered on top of filters (timing unresolved — **Needs clarification**).
- **Important states:** loading, empty, already-applied indicator per offer.
- **Relevant screens:** Creator Discovery/Browse.
- **API/backend dependencies:** `GET /offers` (public endpoint, same as marketing-site browse, called in an authenticated context here).
- **Permissions/roles:** Creator (apply action); public (browse).
- **Source:** [Business Logic/User Flows], [Technical Architecture/Search Strategy].

### 4.2 Application Submission

- **Description:** Creator applies to a specific offer, with audience info attached.
- **User goal:** Get approved to promote a product.
- **Main user actions:** Submit application (audience snippet — niche, audience size, engagement rate).
- **Expected frontend behavior:** One application per (offer, creator) pair — duplicate attempt returns a clear 409 error, not a generic failure. Rate-limited per Creator account to prevent spam-applying.
- **Important states:** submitting, submitted/pending, error (duplicate, self-dealing block, rate-limited).
- **Relevant screens:** Offer detail (Apply action), My Applications list.
- **API/backend dependencies:** `POST /offers/:id/applications` (renamed from `/campaigns/:id/applications` 2026-08-23).
- **Permissions/roles:** Creator only.
- **Edge cases:** Self-dealing block (own offer, if dual-role); audience/niche data is self-reported and currently unverifiable (open fraud-implication question).
- **Source:** [Business Logic/Business Rules], [Database/Constraints], [Security/Rate Limiting], [Edge Cases/Creator Edge Cases].

### 4.3 My Applications

- **Description:** Creator's view of their own application statuses.
- **User goal:** Track where each application stands.
- **Expected frontend behavior:** pending / approved / rejected states clearly shown; whether/how a rejection reason is communicated is unresolved (**Needs clarification**).
- **Relevant screens:** My Applications list.
- **API/backend dependencies:** Scoped read of `applications` (no dedicated endpoint explicitly named beyond the offer-scoped one — **Needs clarification** on a creator-facing "my applications across all offers" endpoint).
- **Permissions/roles:** Creator, own only.
- **Source:** [Business Logic/State Machines], [Business Logic/Notification Logic].

### 4.4 My Links (AffiliateLinks)

- **Description:** The unique trackable link (+ discount code) issued on approval.
- **User goal:** Get and share the promotional link/code.
- **Main user actions:** Copy link, copy discount code, share.
- **Expected frontend behavior:** The "Get Link" moment should feel like a small, clear payoff, given how central it is to the product's trust story. Disclosure-nudge template shown at this moment (fixed legal text, not AI-generated). Only one link per approved application (no regenerating to obscure attribution).
- **Important states:** link generated/zero clicks yet (calm empty state, not "broken"), active, offer-ended (link stops attributing new activity).
- **Relevant screens:** My Links list, Link detail (click/cart/purchase timeline).
- **API/backend dependencies:** `GET /affiliate-links/:slug` (public resolution endpoint); link data scoped to the owning creator for the dashboard view.
- **Permissions/roles:** Creator, own links only.
- **Source:** [UX/Components], [Business Logic/Domain Model], [Business Logic/Business Rules], [Payments/Payment Flow].

### 4.5 Earnings / Wallet

- **Description:** Running balance of accrued-but-not-yet-paid-out commission, plus payout history.
- **User goal:** Know how much they've earned and when they'll get paid.
- **Main user actions:** View balance, view progress toward the payout threshold, view payout history.
- **Expected frontend behavior:** Balance only includes commissions whose BillingCycle has reached `charged` — pre-billing commission is "owed" but not yet in the spendable/displayed wallet balance. Never cached — always live, computed directly from SellVia's own ledger — `wallet_balance_cents` is authoritative, not synced from Swich's account balance (Swich is the execution rail that moves the money once a payout is due; it doesn't hold SellVia's ledger). All figures in PKR only — the multi-currency conversion-display concern from the earlier USD/EUR/GBP design no longer applies.
- **Important states:** accruing (below threshold), threshold-crossed (payout pending), processing, paid, failed-retrying.
- **Relevant screens:** Earnings/Wallet dashboard, Payout history detail.
- **API/backend dependencies:** `GET /payouts` (scoped to own); `creator_profiles.wallet_balance_cents`.
- **Permissions/roles:** Creator, own only.
- **Edge cases:** No client-facing "trigger payout" action (fully automatic/threshold-based, though "automatic" now means an admin-actioned bank transfer rather than a processor API call — see §3.5); one-time manual below-threshold payout allowed only on account closure (proposed default); a refund after a creator has already been paid is a real, accepted platform loss, never clawed back from the creator. **The $50 payout threshold needs re-specifying in PKR — not yet done, flagged as an open item.**
- **Source:** [Payments/Wallet Design], [Payments/Payout Process], [Business Logic/State Machines], [Edge Cases/Payment Edge Cases].

### 4.6 Creator Profile Settings

- **Description:** Niche, audience size, engagement rate, and payout account status.
- **User goal:** Keep profile accurate so merchants can evaluate fit.
- **Main user actions:** Edit niche/audience info.
- **Relevant screens:** Creator Settings / Profile.
- **API/backend dependencies:** `creator_profiles` table.
- **Permissions/roles:** Creator, own profile only.
- **Edge cases:** Whether engagement_rate is self-reported vs. platform-calculated is an open fraud-relevant question — **Needs clarification** before deciding if this field is editable or read-only/derived.
- **Source:** [Business Logic/Domain Model], [Edge Cases/Creator Edge Cases].

---

## 5. Admin Module

*All Admin screens live under a fully separate `/admin/*` namespace, never exposed in regular Merchant/Creator navigation. Single flat Admin role for MVP (no tiering).*

### 5.1 Moderation Queue

- **Description:** Flagged sales/applications/accounts from rules-based fraud detection, awaiting human review.
- **User goal (Admin):** Resolve flags — clear false positives, act on real fraud.
- **Main user actions:** Review flag reason (velocity, self-referral, conversion outlier, device fingerprinting, merchant under-reporting pattern), clear or act (suspend, reverse a sale, ban).
- **Expected frontend behavior:** Every action logged to the Audit Log (who, what, outcome).
- **Important states:** flagged/unreviewed, cleared, actioned.
- **Relevant screens:** Moderation Queue, Flagged Item detail.
- **API/backend dependencies:** `GET /admin/flagged`.
- **Permissions/roles:** Admin only.
- **Source:** [Operations/Moderation], [Security/Fraud Prevention], [Business Logic/Permission Matrix].

### 5.2 Offer Vetting

- **Description:** High-commission or high-risk offers awaiting approval before going live.
- **User goal:** Approve/reject before a risky offer reaches creators.
- **Main user actions:** Approve, reject.
- **Relevant screens:** Offer Vetting Queue.
- **API/backend dependencies:** `POST /admin/offers/:id/vet` (renamed from `/admin/campaigns/:id/vet` 2026-08-23).
- **Permissions/roles:** Admin only.
- **Edge cases:** Exact vetting trigger thresholds undefined — **Needs clarification**.
- **Source:** [Operations/Admin Panel], [Business Logic/Business Rules].

### 5.3 User Management

- **Description:** View/suspend Merchant or Creator accounts; view a user's history for support/moderation purposes only.
- **Main user actions:** View user detail, suspend/ban.
- **Expected frontend behavior:** Suspension instantly revokes the user's sessions.
- **Relevant screens:** User Management list, User detail.
- **API/backend dependencies:** `POST /admin/users/:id/suspend`.
- **Permissions/roles:** Admin only.
- **Edge cases:** No formal appeals process designed yet (handled case-by-case via support) — **Needs clarification**.
- **Source:** [Operations/Admin Panel], [Operations/Moderation], [Security/Session Management].

### 5.4 Refund / Dispute Handling

- **Description:** Manual refund-credit review. Chargeback-evidence submission (originally scoped for Paddle disputes) needs re-scoping against Swich — Swich is a payment processor with its own dispute-handling process for card transactions specifically, but whether it covers bank-transfer/JazzCash/EasyPaisa-settled amounts the same way a card chargeback would is unconfirmed.
- **Main user actions:** Approve/deny refund credit requests.
- **Important states:** SellVia absorbs a merchant's first 5 lost disputes (lifetime counter) — **this rule was written for a Paddle-chargeback world; whether it still applies as-is under Swich is Needs clarification.**
- **Relevant screens:** Refund/Dispute Handling queue.
- **API/backend dependencies:** Not explicitly named in Endpoint Specifications — **Needs clarification**.
- **Permissions/roles:** Admin only.
- **Edge cases:** Who submits any dispute evidence, and to whom (SellVia vs. Swich vs. Swich-on-SellVia's-behalf), is unresolved — **Needs clarification**.
- **Source:** [Payments/Chargebacks], [Payments/Refund Handling], [Operations/Admin Panel].

### 5.5 Reconciliation Review

- **Description:** Surfaces mismatches between internal records and Swich's own transaction records, for manual investigation. (Previously scoped against Paddle's records; the interim bank-transfer default would have had no processor API to reconcile against at all — Swich restores that.)
- **Main user actions:** Review flagged mismatch, investigate, resolve.
- **Important caveat:** Reconciliation can verify the billing/payout legs against Swich's transaction records (once that integration exists), but it can no longer independently confirm the underlying sale happened as reported — that trust still rests entirely on Fraud Prevention's Shopify-webhook-reporting checks, unchanged from the external-tracking model generally.
- **Relevant screens:** Reconciliation Review queue.
- **API/backend dependencies:** Endpoint not explicitly named — **Needs clarification**; likely a scheduled job comparing SellVia's ledger against Swich's transaction-list API once that's integrated.
- **Permissions/roles:** Admin only.
- **Source:** [Payments/Reconciliation], [Operations/Admin Panel].

### 5.6 Waitlist → Beta Invitation Management

- **Description:** Manages the Private Beta cohort (currently capped 10–25, manually curated for the first cohort, fully automatic/signup-order after). Now implicitly scoped to Pakistani applicants only, since the market is Pakistan-only for MVP.
- **Main user actions:** Review waitlist, curate/invite first cohort, monitor automatic invitations thereafter.
- **Relevant screens:** Waitlist Management.
- **Permissions/roles:** Admin only.
- **Edge cases:** Beachhead niche/vertical *within* Pakistan (beyond the geography resolution itself) still undecided — **Needs clarification**.
- **Source:** [Product Foundation/Product Roadmap], [Operations/Admin Panel].

### 5.7 At-Risk New Users View

- **Description:** Accounts that hit the 48-hour churn threshold without completing their core activation action (Merchant: publish first offer; Creator: submit first application).
- **User goal:** Give the founder/Admin visibility into who's stalling, distinct from the fraud queue.
- **Relevant screens:** At-Risk Users view (within Admin Panel).
- **API/backend dependencies:** `activation_nudges` table.
- **Permissions/roles:** Admin only.
- **Source:** [Analytics/Activation, Aha Moment & Churn Signals], [Operations/Admin Panel].

### 5.8 Founder AI Command Console

- **Description:** Founder-only natural-language interface over the entire Admin surface — every tool wraps an existing, already-permission-checked Admin API endpoint; never raw DB access.
- **User goal:** Query/act on admin data without navigating multiple screens.
- **Main user actions:** Ask a question (read, executes directly), issue a write command (requires explicit confirmation, every time, no exceptions), request a product-change spec (drafted only, never auto-executed/deployed).
- **Expected frontend behavior:** Fail-closed on ambiguity — asks for clarification rather than guessing. Every AI-console action logged with `initiated_via: ai_console`.
- **Important states:** answering, awaiting-confirmation (for write actions), executed, clarification-needed.
- **Relevant screens:** AI Command Console (chat-style interface).
- **Permissions/roles:** Admin/Founder only.
- **Edge cases:** Whether this ships MVP or post-MVP is an explicit open call — **Needs clarification**.
- **Source:** [Operations/Founder AI Command Console], [Operations/Live Production Access for Support (Command Console)], [Database/Audit Log Design].

### 5.9 Support Tooling (Console-Assisted)

- **Description:** Ticket context lookup and per-feature playbook retrieval, used by the founder/Admin when handling support requests.
- **Main user actions:** Pull a user's recent activity in one view, retrieve the relevant support playbook.
- **Relevant screens:** Likely part of User Management detail or the AI Command Console, not necessarily a separate screen — **Needs clarification** on whether this needs dedicated UI.
- **Permissions/roles:** Admin only.
- **Source:** [Operations/Live Production Access for Support (Command Console)], [Operations/Per-Feature Support Playbooks], [Operations/Support Tiers].

### 5.10 Admin Analytics (Marketplace Health, P&L, Unit Economics)

- **Description:** Founder/Admin dashboards for marketplace health, funnels, time-to-payout, monthly P&L, unit economics, AI/token cost. All monetary figures in PKR.
- **Main user actions:** View KPI trends, review the automated monthly P&L report, review per-feature AI cost.
- **Important states:** finalized vs. draft P&L report (recommend a "finalized" flag so historical reports don't silently change).
- **Relevant screens:** Admin/Founder Dashboard (marketplace health + funnels), Monthly P&L report, Unit Economics view.
- **API/backend dependencies:** `monthly_pnl_reports`, `ai_usage_events`, `infra_costs` tables; `get_pnl(month)` console tool. `monthly_pnl_reports.paddle_fees_cents` is renamed `swich_fees_cents` — Swich's own transaction fees (rate unconfirmed pending real signup) are a real cost line, unlike the near-zero cost the interim bank-transfer default would have had.
- **Permissions/roles:** Admin only.
- **Source:** [Analytics/Dashboards], [Analytics/KPIs], [Analytics/Automated Monthly P&L], [Analytics/Unit Economics (Revenue vs Cost per User)], [Analytics/AI Token Usage Tracking].

---

## 6. Shared Dashboard Analytics (Merchant & Creator)

### 6.1 Merchant Analytics

- **Description:** Per-merchant offer performance: clicks, conversion, sales, spend; exportable reports. All figures in PKR.
- **Expected frontend behavior:** Simple charts, no data-viz flourishes ("clarity over excitement"); heavy/slow exports run as async jobs (notification on completion, not a spinner).
- **Relevant screens:** Merchant Dashboard home, Offer performance detail, Export flow.
- **API/backend dependencies:** `POST /jobs/export`, `GET /jobs/:id`.
- **Permissions/roles:** Merchant, own data only.
- **Source:** [Analytics/Dashboards], [Technical Architecture/Async Job Pattern & Idempotency].

### 6.2 Creator Analytics

- **Description:** Per-creator link performance: impressions/clicks/sales, earnings trend toward the payout threshold.
- **Relevant screens:** Creator Dashboard home.
- **Permissions/roles:** Creator, own data only.
- **Source:** [Analytics/Dashboards].

---

## 7. Customer Support (User-Facing)

### 7.1 Support Contact

- **Description:** In-app support link/contact form for logged-in Merchants/Creators; email support otherwise.
- **User goal:** Get help when something's wrong (payout delay, rejected application, suspected double charge, disputed clawback).
- **Expected frontend behavior:** Clear, specific error/status copy so common cases (e.g., normal Swich settlement/payout processing window) don't look like failures. Exact expected window under Swich is **Needs clarification** — the original "2–7 day" figure assumed Paddle's own rail and hasn't been re-confirmed against Swich's.
- **Relevant screens:** Support/Help contact form or link (likely footer/nav-level, not a full dashboard section).
- **Permissions/roles:** Any authenticated user.
- **Edge cases:** No formal SLA — founder-handled through Private Beta.
- **Source:** [Operations/Customer Support Flows], [Operations/Support Tiers].

### 7.2 Account Deletion Request

- **Description:** User-initiated account deletion with a 14-day cancellable grace period.
- **User goal:** Delete their account/data.
- **Main user actions:** Request deletion, cancel within grace period.
- **Expected frontend behavior:** Clear confirmation step naming what happens (PII anonymized, financial-chain skeleton retained, sessions revoked, product images removed immediately with placeholders shown on any referencing offer).
- **Important states:** requested/counting-down, cancelled, processing, completed.
- **Relevant screens:** Account Settings → Delete Account flow.
- **API/backend dependencies:** Async deletion job (Async Job Pattern).
- **Permissions/roles:** Any authenticated user, own account only.
- **Source:** [Security/Data Retention Policy Engine].

### 7.3 Data Disclosure Notices

- **Description:** Plain-language notices at the point of data collection (signup, before bank details are collected, before AI-matching use of profile data) — not buried in a ToS.
- **Expected frontend behavior:** Short, contextual, timed notices woven into the relevant flow (signup form, bank-details step, profile-completion step for creators).
- **Relevant screens:** Embedded in Signup, Bank Details Setup, Creator Profile Settings — not a standalone screen.
- **Source:** [Security/Data Inventory & Disclosure].

---

## Feature-Level "Needs Clarification" Summary

For quick reference, every open item flagged above:

1. Admin role's full formal scope (used broadly throughout but never explicitly ratified).
2. Waitlist signup endpoint not explicitly specified.
3. Exact retry/escalation policy for a failed Swich billing-cycle charge (§2.2/§3.5) — the old Paddle "3 failed attempts" rule needs re-mapping against Swich's own failure-webhook shape, not yet confirmed.
4. Exact Shopify app-install path — public App Store listing vs. private/custom app (§2.3).
5. Application-rejection notification content/whether a reason is shown.
6. Real-time vs. digest cadence for "sale made" / merchant notifications.
7. Exact merchant "milestone reached" thresholds.
8. Whether merchants see platform-wide aggregate creator performance or only per-applicant stats.
9. High-commission/high-risk offer vetting thresholds.
10. UI/API for merchant refund-credit request submission.
11. Whether the Paddle-era "SellVia absorbs first 5 lost disputes" rule still applies as-is under Swich, and who submits any dispute evidence to whom (§5.4).
12. Formal account-suspension appeals process.
13. Timing of AI-based creator↔offer matching (MVP vs. post-MVP).
14. Whether engagement_rate is self-reported or platform-calculated.
15. Whether Founder AI Command Console ships MVP or post-MVP.
16. Whether support-ticket-context tooling needs dedicated UI or lives inside User Management/AI Console.
17. The $50 payout threshold needs re-specifying in PKR (§4.5).
18. Swich's exact API shapes, pricing, and MVP-scale onboarding requirements — nothing here is confirmed, it's all a working draft pending a real signup/integration conversation (§2.2, §2.4, §3.5, §5.10 — every Swich mention across this doc).
19. Expected Swich settlement/payout window to communicate in support copy (§7.1).
20. Whether an FBR-relevant tax form/declaration needs collecting at Creator onboarding (§2.4) — Swich, like the interim bank-transfer default before it, is not a Merchant of Record and doesn't auto-collect this.
21. Beachhead niche/vertical *within* Pakistan (geography itself is resolved; category focus within it is not).

**Resolved since the last revision (2026-08-07), no longer open:**

- ~~Whether Offer needs its own entity vs. one-campaign-per-offer~~ — resolved: no Campaign entity at all.
- ~~Merchant Paddle requirement post-reversal~~ — moot, Paddle removed.
- ~~Exact UX for the tracking-snippet install step~~ — resolved: Shopify OAuth/webhook connect, not a snippet.
- ~~Lime-as-text WCAG AA contrast~~ — resolved: verified compliant, and Gray 02 (the actual failure) corrected.
- ~~Payment processor~~ — resolved same day: Swich, replacing the brief interim manual-bank-transfer default.

**Still outstanding, unrelated to the 2026-08-23 revision:**

- Alt-text field missing from product image schema.

---

## Cross-References

- Screens implementing these features: `SCREEN_INVENTORY.md`
- Navigation/routes these features live at: `SITE_MAP.md`

---

# UI / SCREEN_INVENTORY

> Source: `UI/SCREEN_INVENTORY.md` · tag: `frontend` · last updated: 2026-08-23

# SellVia — Frontend Screen Inventory

## Purpose

Every screen the frontend needs to design/implement, derived from `/Docs`. Cross-references `FEATURE_LIST.md` (feature detail) and `SITE_MAP.md` (route source/[Explicit] vs. [Inferred] status — routes are not repeated in full here, see that doc).

## Global Notes That Apply to Every Screen Below

> **⚠️ Update (2026-08-23):** Pakistan-only market, PKR only, payment processor is **Swich** (swichnow.io — confirmed same day, replacing Paddle and a brief interim manual-bank-transfer default), Shopify-only merchant integration (OAuth, not snippet), and no separate Campaign entity (merged into Offer — every "Campaign"/"campaign" screen below means "Offer"). Full reasoning: `Technical Architecture/Architecture Decision Log`. **`FEATURE_LIST.md` has been fully rewritten to match (2026-08-23) — read that for the resolved feature-level detail.** This screen inventory has now been reconciled to match: C2/C3/C4 onboarding rewritten to Swich/Shopify-OAuth, and the former D2–D6 duplicate Offer/Campaign screens collapsed into a single D2–D4 Offer screen set (see note below).

- **Design system (binding):** black `#000000` background, lime `#BFFF13` accent used sparingly (primary CTA/highlights/focus only, never large blocks), white/gray text hierarchy — **Gray 02 muted text is `#787882`, corrected 2026-08-23 from the original `#71717A`, which measured 4.35:1 and failed WCAG AA** (see `UX/Accessibility`) — Outfit (headlines/CTAs) + Figtree (body/labels), no gradients/glassmorphism/glow, thin borders not shadows, 10–12px radii, restrained animation (fades/opacity/2–4px movement only). Source: `UX/Design System`.
- **Responsive:** Web-responsive only for MVP (no native mobile app). 12-column desktop grid per `Technical Architecture/Frontend Architecture`; specific breakpoints not documented — **Needs clarification**.
- **Accessibility (every screen):** full keyboard operability, visible focus rings (lime), proper form labels + ARIA, `aria-live` on async status changes, WCAG AA contrast — **verified 2026-08-23, resolved, see `UX/Accessibility`**. See `FEATURE_LIST.md` §0.5.
- **Loading states:** simple fades/skeletons, never a playful spinner.
- **Error states:** structured, specific, actionable copy; never a raw stack trace or generic "something went wrong."
- **Empty states:** calm and expected-looking, consistent with the product's own "0 creators, 0 sales, $0" honesty device — never look broken.
- **UI is not a trust boundary:** every action shown/hidden per role in these screens is a UX convenience only — the backend independently re-checks permissions on every request regardless of what a screen renders.

---

## A. Public Marketing & Discovery

### A1. Public Home (Marketing Landing)

- **Purpose:** Explain SellVia, show radical-transparency positioning (zeroed real metrics), collect waitlist signups.
- **Route:** `/` — [Explicit]
- **Access:** Public.
- **Entry points:** Direct visit, all external links/ads, nav logo click from anywhere.
- **Main UI sections:** Hero, concept walkthrough ("One Arrow, Two Wins"), zeroed live-metrics display, roadmap-stage indicator, FAQ, waitlist form, nav (logo, How It Works / For Businesses / For Creators, Join Waitlist CTA), footer.
- **Primary actions:** Submit waitlist form (role: business/creator).
- **Secondary actions:** Navigate to How It Works / For Businesses / For Creators.
- **Data required:** Live counts for the zeroed-metrics device (0 until real, per design intent — must genuinely reflect real numbers once non-zero, never fabricated).
- **API dependencies:** Waitlist submission endpoint — **Needs clarification** (not explicitly named in `API/Endpoint Specifications`).
- **Loading/Empty/Error/Success states:** Success = waitlist confirmation message/state; Error = form validation.
- **Responsive:** Primary public-facing page — must work well on mobile.
- **Related features:** §1.1 Marketing Site.
- **Related docs:** `Product Foundation/Product Vision`, `Product Foundation/Product Roadmap`, `UX/Design System`, `UX/Navigation`, `UX/Copy Guidelines`.

### A2. How It Works / For Businesses / For Creators (Informational Pages)

- **Purpose:** Explain the value proposition per audience.
- **Route:** `/how-it-works`, `/for-businesses`, `/for-creators` — [Inferred]
- **Access:** Public.
- **Entry points:** Top nav from any public page.
- **Main UI sections:** Explainer content, CTA to waitlist or discovery.
- **Primary actions:** Navigate to waitlist/discovery.
- **Related features:** §1.1.
- **Related docs:** `Product Foundation/Product Vision`, `UX/Navigation`.

### A3. Public Campaign Discovery / Browse

- **Purpose:** Let anyone (and AI shopping agents/crawlers) browse live campaigns.
- **Route:** `/campaigns` — [Inferred]
- **Access:** Public read; Apply action requires Creator auth (redirects to login/register).
- **Entry points:** Marketing nav, direct link, search-engine/AI-agent discovery.
- **Main UI sections:** Filter bar (category: digital/physical, commission range, niche), sort control (newest, highest commission, most applications), campaign card grid.
- **Primary actions:** Filter, sort, open a campaign.
- **Secondary actions:** (if authenticated as Creator) Apply directly from the card.
- **Data required:** `GET /campaigns` (public, filterable, paginated).
- **Loading state:** skeleton grid. **Empty state:** "no live campaigns match these filters." **Error state:** standard error shape. **Success state:** N/A (list view).
- **Responsive:** Card grid reflows to single column on mobile.
- **Related features:** §1.2 Public Campaign Discovery.
- **Related docs:** `Technical Architecture/Search Strategy`, `API/Endpoint Specifications`, `Technical Architecture/Caching Strategy` (public campaigns cached under a `public:` namespace, short TTL).

### A4. Public Campaign / Offer Detail

- **Purpose:** Show one campaign/product's details; the page an AffiliateLink's `schema.org` markup lives on.
- **Route:** `/campaigns/:slug` — [Inferred]
- **Access:** Public.
- **Entry points:** Discovery grid, direct link, AI-agent/search preview.
- **Main UI sections:** Product name/price/currency, commission rate, merchant name, `Product`/`Offer` JSON-LD (non-visual), Apply CTA (Creator-gated).
- **Primary actions:** Apply (if authenticated Creator, not self-owned campaign).
- **Secondary actions:** Share.
- **Data required:** Campaign + Offer + Merchant public fields.
- **States:** loading; not-found/ended (campaign no longer live — still show honestly rather than 404 if within attribution-relevant history); error.
- **Related features:** §1.3.
- **Related docs:** `UX/AI Agent & Machine Readability`, `API/Endpoint Specifications`, `Business Logic/State Machines`.

---

## B. Authentication

### B1. Login

- **Purpose:** Authenticate an existing user.
- **Route:** `/login` — [Inferred]
- **Access:** Public (unauthenticated only — redirect away if already logged in).
- **Entry points:** Nav "Log In," expired-session redirect, direct link.
- **Main UI sections:** Ory Kratos-driven login form (email/password, optional social login), "forgot password" link, MFA challenge step if enabled.
- **Primary actions:** Submit credentials.
- **Data required:** N/A (delegated to Kratos).
- **States:** loading (session check), error (invalid credentials, locked account), MFA-challenge.
- **Related features:** §0.1 Unified Authentication.
- **Related docs:** `Security/Authentication`, `Security/Session Management`, `API/API Authentication`.

### B2. Register (with Role Selection)

- **Purpose:** Create an account and choose Merchant/Creator (or both).
- **Route:** `/register` — [Inferred]
- **Access:** Public.
- **Entry points:** Nav "Join," waitlist-invitation email link, direct link.
- **Main UI sections:** Signup form, role selector, plain-language data-disclosure notice (what's collected/why, per Disclosure Principle).
- **Primary actions:** Submit, select role(s).
- **Secondary actions:** Switch to login.
- **States:** loading, error (email taken, weak password), success → routes into onboarding.
- **Related features:** §2.1 Role Selection & Signup Branching, §7.3 Data Disclosure Notices.
- **Related docs:** `Business Logic/User Flows`, `Security/Data Inventory & Disclosure`.

### B3. Forgot / Reset Password

- **Purpose:** Recover account access.
- **Route:** `/forgot-password`, `/reset-password` — [Inferred]
- **Access:** Public.
- **Main UI sections:** Email entry, reset-token form.
- **States:** submitted, error (invalid/expired token), success.
- **Note:** Password change should trigger instant revocation of all other sessions (security requirement, worth surfacing to the user as "you've been logged out everywhere else").
- **Related docs:** `Security/Password Policy`, `Security/Session Management`.

### B4. Verify Email

- **Purpose:** Confirm email ownership.
- **Route:** `/verify-email` — [Inferred]
- **Access:** Authenticated-but-unverified.
- **States:** pending, verified, expired-link (resend action).
- **Related docs:** `Security/Authentication`.

### B5. MFA Setup / Challenge

- **Purpose:** Optional (Creator) / recommended (Merchant) / possibly mandatory (Admin) multi-factor auth.
- **Route:** `/mfa` — [Inferred]
- **Access:** Authenticated.
- **States:** not-enabled, setup-in-progress, enabled, challenge-on-login.
- **Related docs:** `Security/Password Policy` (open question: mandatory for Merchants before launch — **Needs clarification**).

---

## C. Onboarding

### C1. Role Selection (if separate from Register)

- **Purpose:** Confirm/adjust role after signup.
- **Route:** `/onboarding/role` — [Inferred]
- **Access:** Authenticated, first-run.
- **Related docs:** `Business Logic/User Roles`.

### C2. Merchant Onboarding — Swich Billing Connect

- **Purpose:** Connect Swich as the merchant's billing method before any Offer can go live.
- **Route:** `/onboarding/merchant/swich` (also reachable from `/settings/billing`) — [Inferred]
- **Access:** Merchant only.
- **Entry points:** Post-role-selection first-run flow; blocked-publish prompt from Offer creation.
- **Main UI sections:** Swich billing-connect flow (embedded widget or redirect — exact mechanism unconfirmed pending real Swich integration docs), gate-status indicator.
- **Primary actions:** Complete Swich billing connect.
- **Data required:** Swich connect session/credentials from backend.
- **States:** not-started, in-progress, complete, payment-failed (Swich-reported reason + prompt to update payment method).
- **API dependencies:** Swich billing-connect API; `merchant_profiles.swich_customer_id`.
- **Related features:** §2.2, §3.5 Billing.
- **Related docs:** `Product Foundation/MVP Scope`, `Business Logic/State Machines`, `Security/CORS, CSP & Security Headers` (CSP must allow Swich's connect domain, once confirmed).
- **Open item:** Whether this is an embedded Swich widget or a redirect flow is **Needs clarification** — Swich's actual integration pattern isn't confirmed yet (see `FEATURE_LIST.md` §2.2).

### C3. Merchant Onboarding — Shopify Store Connect

- **Purpose:** Connect the merchant's Shopify store via OAuth so sales report back to SellVia before an Offer can go live. Replaces the retired copy-paste tracking-snippet flow entirely (Shopify-only for MVP).
- **Route:** `/onboarding/merchant/shopify` (likely also embedded in Offer creation) — [Inferred]
- **Access:** Merchant only.
- **Main UI sections:** "Connect Shopify Store" action, OAuth authorization redirect into the merchant's Shopify admin, webhook-active confirmation state, discount-code creation guidance (fallback attribution signal).
- **Primary actions:** Click "Connect Shopify Store," authorize the SellVia app in Shopify admin.
- **States:** not-connected, connecting (mid-OAuth), connected/verified, connection-failed (with troubleshooting copy — e.g. wrong store, permissions declined).
- **Related features:** §2.3.
- **Related docs:** `Payments/Payment Flow`, `Business Logic/State Machines` (second required gate on draft→live).
- **Open item:** Exact Shopify app-install path (public App Store listing vs. private/custom app SellVia distributes directly) is undesigned — **Needs clarification**.

### C4. Creator Onboarding — Swich Payout Setup

- **Purpose:** Register as a Swich payout recipient (payee) so approved links can activate.
- **Route:** `/onboarding/creator/payout` (also `/settings/payout`) — [Inferred]
- **Access:** Creator only.
- **Main UI sections:** Swich payee-registration flow — payout method selection (bank account, JazzCash, or EasyPaisa) plus the corresponding details, completion status.
- **States:** not-started, in-progress, complete, blocking-link-activation (explicit message: "finish this to activate your link").
- **Related features:** §2.4.
- **Related docs:** `Edge Cases/User Edge Cases`, `Payments/Tax Considerations` (whether any Pakistani-tax-equivalent declaration is collected here is **Needs clarification** — Swich is a payments processor, not a Merchant of Record like Paddle was, so it isn't confirmed to auto-collect this).

---

## D. Merchant Dashboard

> **Update (2026-08-23): D2–D6 collapsed.** The five screens below previously described two separate entities — "Offer" (D2/D3) and "Campaign" (D4/D5/D6) — for what is now one entity per the Offer/Campaign merge (see global note above). Collapsed into three screens: D2 Offers List, D3 Create/Edit Offer, D4 Offer Detail, each merging the more complete/current details from both duplicate specs. Everything downstream (formerly D7–D14) is renumbered D5–D12 accordingly.

### D1. Merchant Dashboard Home / Overview

- **Purpose:** At-a-glance offer performance across all the merchant's offers.
- **Route:** `/dashboard` (Merchant context) — [Inferred]
- **Access:** Merchant.
- **Entry points:** Post-login landing (if single-role Merchant), nav.
- **Main UI sections:** Stat cards (clicks, conversion, sales, spend), recent activity, pending-applications count, onboarding-gate status banner if incomplete.
- **Primary actions:** Navigate to Offers/Applications/Sales/Payouts.
- **Data required:** Aggregate offer metrics, own only.
- **States:** loading, empty ("no offers yet" — with a clear "create your first offer" CTA), error.
- **Related features:** §6.1 Merchant Analytics.
- **Related docs:** `Analytics/Dashboards`, `Analytics/KPIs`.

### D2. Offers List

- **Purpose:** Manage the merchant's product listings — one list, one entity (Offer absorbed the former "Campaign," see `Business Logic/Domain Model`).
- **Route:** `/offers` — [Explicit nav section]
- **Access:** Merchant, own only.
- **Main UI sections:** List/table (name, price, category, status badge: draft/live/paused/ended, commission rate, applications count, sales count), create action, filter by status.
- **Primary actions:** Create Offer.
- **Secondary actions:** Edit, pause/resume/end a live Offer inline, soft-delete (archive).
- **States:** loading, empty ("no offers yet"), error.
- **Related features:** §3.1 Offer Creation & Management.
- **Related docs:** `Business Logic/Domain Model`, `Database/Table Specifications`, `UX/Navigation`, `Business Logic/State Machines`.

### D3. Create / Edit Offer

- **Purpose:** Build an Offer — product listing and its commission-bearing, applicable listing, in one flow. No separate step to "wrap" a product in a campaign; setting the commission rate and publishing are part of the same creation flow.
- **Route:** `/offers/new`, `/offers/:id/edit` — [Inferred]
- **Access:** Merchant, own only.
- **Main UI sections:** Name, price (PKR), category (digital/physical — contextually hides shipping-relevant fields when digital), product image upload, commission rate input (no platform bounds, sanity-checked 0–100%), AI copy-assist draft-description action, publish gate checklist (Swich billing connect ✓/✗, Shopify store connected & verified ✓/✗).
- **Primary actions:** Save as draft, Publish (only enabled once both gates pass).
- **Secondary actions:** Request AI-drafted description.
- **States:** editing, saving, gate-incomplete (publish disabled with explanation), error (validation), success/live.
- **Related features:** §3.1, §0.7 AI-Assisted Features (copy assist).
- **Related docs:** `Business Logic/Business Rules`, `Business Logic/State Machines`, `Technical Architecture/AI Services`, `Technical Architecture/File Storage`, `Database/Constraints`.

### D4. Offer Detail

- **Purpose:** Single Offer's status, performance, and management actions.
- **Route:** `/offers/:id` — [Inferred]
- **Access:** Merchant, own only.
- **Main UI sections:** Status + commission rate, performance stats, applications summary (link to full list), sales summary, pause/resume/end/edit actions.
- **Primary actions:** Pause, resume, end, edit.
- **States:** per Offer state machine (draft/live/paused/ended); Swich-restricted-auto-paused banner if applicable.
- **Related docs:** `Business Logic/State Machines`, `Edge Cases/Business Edge Cases`.

### D5. Applications List (per Offer or All)

- **Purpose:** Review incoming creator applications.
- **Route:** `/applications` (all) or `/offers/:id` → applications tab — [Explicit nav section, exact structure Inferred]
- **Access:** Merchant, own offers only.
- **Main UI sections:** List (creator name, niche, audience size, engagement rate, AI fit-summary snippet, status), filter by status/offer.
- **Primary actions:** Open an application to review.
- **States:** loading, empty ("no applications yet"), error.
- **Related features:** §3.3 Application Review.
- **Related docs:** `Business Logic/Permission Matrix`, `Technical Architecture/AI Services`.

### D6. Application Review Detail

- **Purpose:** Approve or reject one application.
- **Route:** `/applications/:id` — [Inferred]
- **Access:** Merchant, own offer's applications only.
- **Main UI sections:** Creator audience/niche/engagement data, AI-generated fit summary, Approve / Reject actions.
- **Primary actions:** Approve (triggers AffiliateLink creation + immediate Creator notification), Reject.
- **States:** pending, approved (link-issued confirmation), rejected, error.
- **Related docs:** `Business Logic/State Machines`, `UX/Interaction Patterns` (approval is a "trust moment," must feel immediate).

### D7. Sales List

- **Purpose:** See sales attributed to the merchant's offers.
- **Route:** `/sales` — [Explicit nav section]
- **Access:** Merchant, own only.
- **Main UI sections:** List (date, offer, creator, amount, `acceptance_status`: accepted/rejected), filter/sort, export action.
- **Primary actions:** Export report (async job).
- **Secondary actions:** Open a sale detail, request refund credit.
- **States:** loading, empty, error.
- **Related features:** §3.4 Sales Visibility, §6.1 Merchant Analytics (export).
- **Related docs:** `API/Endpoint Specifications`, `Technical Architecture/Async Job Pattern & Idempotency`.

### D8. Sale Detail / Receipt

- **Purpose:** Single sale's shared receipt (identical to what the creator sees).
- **Route:** `/sales/:id` — [Inferred]
- **Access:** Merchant, own only.
- **Main UI sections:** Amount, commission split, platform fee, timestamps, billing-cycle link, "Request Refund Credit" action.
- **Primary actions:** Request refund credit (if within monthly cap).
- **States:** accepted, rejected, credit-requested, credit-cap-reached (disabled with explanation).
- **Related features:** §3.6 Refund Credit Request.
- **Related docs:** `Payments/Refund Handling`, `Business Logic/Business Rules` (symmetric receipt is a trust mechanism).

### D9. Billing / Payouts (Billing Cycles)

- **Purpose:** View billing-cycle history and totals owed/charged.
- **Route:** `/payouts` or `/billing` (Merchant context) — [Explicit nav section, exact label Inferred]
- **Access:** Merchant, own only.
- **Main UI sections:** List of billing cycles (period, status: open/pending_charge/charged/failed, total owed, retry count).
- **States:** open (accruing), pending_charge, charged, failed (with retry status + payment-method-update CTA).
- **Related features:** §3.5.
- **Related docs:** `Payments/Payment Flow`, `Business Logic/State Machines`.

### D10. Merchant Settings — Business Profile

- **Route:** `/settings/business` — [Inferred]
- **Access:** Merchant, own only.
- **Main UI sections:** Business name, category.
- **Related docs:** `Business Logic/Domain Model`.

### D11. Merchant Settings — Billing Card

- **Route:** `/settings/billing` — [Inferred] (same as C2, reachable post-onboarding too)
- **Access:** Merchant, own only.
- **Related docs:** `Payments/Payment Flow`.

### D12. Merchant Settings — Security

- **Route:** `/settings/security` — [Inferred]
- **Access:** Merchant, own only.
- **Main UI sections:** Active sessions list (+ "log out all other devices"), MFA setup, password change.
- **Related docs:** `Security/Session Management`.

---

## E. Creator Dashboard

### E1. Creator Dashboard Home / Overview

- **Purpose:** At-a-glance earnings/links performance.
- **Route:** `/dashboard` (Creator context) — [Inferred]
- **Access:** Creator.
- **Main UI sections:** Stat cards (clicks, sales, earnings trend toward the payout threshold — amount TBD in PKR), recent activity, application-status summary.
- **States:** loading, empty ("apply to your first offer" CTA), error.
- **Related features:** §6.2 Creator Analytics.
- **Related docs:** `Analytics/Dashboards`.

### E2. Discover / Browse Campaigns

- **Purpose:** Authenticated campaign discovery + apply.
- **Route:** `/discover` — [Explicit nav section]
- **Access:** Creator.
- **Main UI sections:** Same filter/sort as public discovery, plus "already applied" indicators and (post-MVP) AI-matched ranking.
- **Primary actions:** Apply.
- **States:** loading, empty, error.
- **Related features:** §4.1.
- **Related docs:** `UX/Navigation`, `Technical Architecture/Search Strategy`.

### E3. Campaign Detail (Creator View) / Apply

- **Purpose:** View a campaign and submit an application.
- **Route:** `/discover/:slug` — [Inferred]
- **Access:** Creator.
- **Main UI sections:** Product/commission info, application form (audience snippet), disclosure-requirement note.
- **Primary actions:** Submit application.
- **States:** not-applied, submitting, submitted/pending, error (duplicate — 409, self-dealing block, rate-limited).
- **Related features:** §4.2 Application Submission.
- **Related docs:** `Database/Constraints`, `Security/Rate Limiting`.

### E4. My Applications

- **Purpose:** Track application statuses.
- **Route:** `/applications` (Creator context) — [Inferred]
- **Access:** Creator, own only.
- **Main UI sections:** List (campaign, status: pending/approved/rejected, date).
- **States:** loading, empty, error.
- **Related features:** §4.3.
- **Related docs:** `Business Logic/State Machines`.
- **Open item:** Rejection-reason display — **Needs clarification**.

### E5. My Links

- **Purpose:** Manage and view performance of issued AffiliateLinks.
- **Route:** `/links` — [Explicit nav section]
- **Access:** Creator, own only.
- **Main UI sections:** List (campaign, slug/URL, discount code, clicks/sales summary), copy actions.
- **Primary actions:** Copy link, copy discount code.
- **States:** loading, empty ("no links yet — apply to a campaign to get started"), zero-clicks-yet (calm, not broken), error.
- **Related features:** §4.4.
- **Related docs:** `UX/Components` ("Get Link" moment), `Business Logic/Domain Model`.

### E6. Link Detail

- **Purpose:** One link's full attribution timeline.
- **Route:** `/links/:id` — [Inferred]
- **Access:** Creator, own only.
- **Main UI sections:** Click/cart-add/purchase timeline (per the traced live-site example: post → click → cart → purchase), commission earned from this link.
- **States:** loading, empty (zero clicks), error.
- **Related docs:** `Business Logic/User Flows` (traced example, good QA basis).

### E7. Earnings / Wallet

- **Purpose:** View balance, threshold progress, payout history.
- **Route:** `/earnings` — [Explicit nav section]
- **Access:** Creator, own only.
- **Main UI sections:** Current balance (billed-and-charged commissions only), progress bar/indicator toward the payout threshold (amount TBD in PKR), payout history list.
- **States:** accruing, threshold-crossed, processing, paid, failed-retrying, empty (no earnings yet).
- **Related features:** §4.5.
- **Related docs:** `Payments/Wallet Design`, `Payments/Payout Process`.

### E8. Creator Settings — Profile

- **Route:** `/settings/profile` — [Inferred]
- **Access:** Creator, own only.
- **Main UI sections:** Niche, audience size, engagement rate (editability depends on self-reported-vs-calculated resolution — **Needs clarification**).
- **Related docs:** `Business Logic/Domain Model`, `Edge Cases/Creator Edge Cases`.

### E9. Creator Settings — Payout

- **Route:** `/settings/payout` — [Inferred] (same as C4, reachable post-onboarding)
- **Access:** Creator, own only.
- **Related docs:** `Payments/Payout Process`.

### E10. Creator Settings — Security

- **Route:** `/settings/security` — [Inferred]
- **Access:** Creator, own only.
- **Related docs:** `Security/Session Management`.

---

## F. Shared Cross-Role Screens

### F1. Notification Center

- **Purpose:** View all notifications.
- **Route:** `/notifications` — [Inferred]
- **Access:** Any authenticated user, own only.
- **Main UI sections:** Feed (read/unread), type-based icon/label, click-through to relevant screen.
- **States:** loading, empty ("no notifications yet"), error.
- **Related features:** §0.3.
- **Related docs:** `Business Logic/Notification Logic`.

### F2. Account Deletion Flow

- **Purpose:** Request/cancel account deletion.
- **Route:** Inside `/settings/security` or a dedicated `/settings/delete-account` — [Inferred]
- **Access:** Any authenticated user, own account only.
- **Main UI sections:** Confirmation dialog explaining consequences, 14-day countdown, cancel action.
- **States:** requested/counting-down, cancelled, processing, completed.
- **Related features:** §7.2.
- **Related docs:** `Security/Data Retention Policy Engine`.

### F3. Support / Help Contact

- **Purpose:** Reach support.
- **Route:** Footer/nav link or `/support` — [Inferred]
- **Access:** Any user (authenticated context adds account-specific info).
- **Main UI sections:** Contact form or email link.
- **Related features:** §7.1.
- **Related docs:** `Operations/Customer Support Flows`.

---

## G. Admin Panel

*All screens below: Access = Admin only, `/admin/*` namespace, fully separate nav.*

### G1. Admin Dashboard

- **Route:** `/admin/dashboard` — [Inferred]
- **Purpose:** Marketplace health at a glance (the most important view during Validation/Private Beta).
- **Main UI sections:** Active merchants/creators, liquidity ratio, time-to-payout trend, funnel views (Merchant + Creator).
- **Data required:** KPI aggregates.
- **Related docs:** `Analytics/Dashboards`, `Analytics/KPIs`, `Product Foundation/Success Metrics`.

### G2. Moderation Queue

- **Route:** `/admin/moderation` — [Inferred]
- **Purpose:** Review flagged sales/applications/accounts.
- **Main UI sections:** Flag list (trigger rule, entity, date), detail view with clear/act actions.
- **Data required:** `GET /admin/flagged`.
- **States:** unreviewed, cleared, actioned.
- **Related docs:** `Operations/Moderation`, `Security/Fraud Prevention`.

### G3. Offer Vetting Queue

- **Route:** `/admin/offers/vetting` — [Inferred] (renamed from `/admin/campaigns/vetting` 2026-08-23, per the Offer/Campaign merge)
- **Purpose:** Approve/reject high-commission or high-risk offers before they go live.
- **Data required:** `POST /admin/offers/:id/vet` (renamed from `/admin/campaigns/:id/vet` 2026-08-23).
- **Related docs:** `Operations/Admin Panel`.

### G4. User Management

- **Route:** `/admin/users`, `/admin/users/:id` — [Inferred]
- **Purpose:** View/suspend accounts; support-purpose data lookup.
- **Main UI sections:** Search/list, user detail (activity, roles, suspend action), `get_ticket_context`-style aggregated view.
- **Data required:** `POST /admin/users/:id/suspend`.
- **Related docs:** `Operations/Admin Panel`, `Operations/Live Production Access for Support (Command Console)`.

### G5. Refund / Dispute Handling

- **Route:** `/admin/refunds-disputes` — [Inferred]
- **Purpose:** Review refund-credit requests, handle chargeback evidence.
- **Main UI sections:** Request list, cap-counter per merchant, evidence-submission form.
- **Related docs:** `Payments/Refund Handling`, `Payments/Chargebacks`.

### G6. Reconciliation Review

- **Route:** `/admin/reconciliation` — [Inferred]
- **Purpose:** Investigate Swich-vs-internal mismatches.
- **Related docs:** `Payments/Reconciliation`.

### G7. Waitlist / Beta Invitation Management

- **Route:** `/admin/waitlist` — [Inferred]
- **Purpose:** Curate/invite the Private Beta cohort (10–25, manual first cohort, automatic thereafter).
- **Related docs:** `Product Foundation/Product Roadmap`.

### G8. At-Risk New Users

- **Route:** `/admin/at-risk-users` — [Inferred]
- **Purpose:** Surface 48h churn-signal accounts.
- **Related docs:** `Analytics/Activation, Aha Moment & Churn Signals`.

### G9. Founder AI Command Console

- **Route:** `/admin/console` — [Inferred]
- **Purpose:** Natural-language admin interface.
- **Main UI sections:** Chat input, response stream, confirmation prompts for write actions, clarification prompts on ambiguity.
- **States:** answering (read), awaiting-confirmation (write), executed, clarification-needed.
- **Related docs:** `Operations/Founder AI Command Console`.

### G10. Admin Analytics (P&L, Unit Economics, AI Costs)

- **Route:** `/admin/analytics/*` — [Inferred]
- **Purpose:** Monthly P&L, per-user unit economics, AI/token cost dashboards.
- **Data required:** `monthly_pnl_reports`, `ai_usage_events`.
- **States:** finalized vs. draft report.
- **Related docs:** `Analytics/Automated Monthly P&L`, `Analytics/Unit Economics (Revenue vs Cost per User)`, `Analytics/AI Token Usage Tracking`.

---

## Screen Count Summary

| Module | Screens |
| --- | --- |
| Public Marketing & Discovery | 4 |
| Authentication | 5 |
| Onboarding | 4 |
| Merchant Dashboard | 12 |
| Creator Dashboard | 10 |
| Shared Cross-Role | 3 |
| Admin Panel | 10 |
| **Total** | **~48** |

## What This Inventory Deliberately Excludes

- A checkout/payment screen for buyers — no longer exists (external-site tracking model).
- Native mobile screens — responsive web only for MVP.
- A public API developer portal — post-MVP.

## Cross-References

- Feature detail per screen's function: `FEATURE_LIST.md`
- Route hierarchy and [Explicit]/[Inferred] source basis: `SITE_MAP.md`

---

# UI / SITE_MAP

> Source: `UI/SITE_MAP.md` · tag: `frontend` · last updated: 2026-08-23

# SellVia — Frontend Site Map

## Purpose

Hierarchical navigation structure for the SellVia frontend, derived from `/Docs`. Cross-references `FEATURE_LIST.md` (what each area does) and `SCREEN_INVENTORY.md` (per-screen detail).

## How to Read the Route Labels

Every route below is tagged:

- **[Explicit]** — a route or navigation grouping the documentation names directly (e.g., `UX/Navigation`'s Merchant nav items, `API/Endpoint Specifications`'s resource paths, `/admin/*`).
- **[Inferred]** — a route this document proposes to implement an explicitly-described feature/screen, following the app's own naming conventions (plural nouns, resource-based) and REST Standards (`/api/v1/*` is API-only, not a frontend route pattern — frontend routes below are conventional Next.js App Router paths, not literally specified anywhere in the docs).

No routes are invented beyond what's needed to reach a feature already documented in `FEATURE_LIST.md`.

> **⚠️ Update (2026-08-23):** Pakistan-only market, payment processor is **Swich** (swichnow.io — confirmed same day, for the `/onboarding/merchant/paddle`, `/onboarding/creator/payout`, `/settings/billing` routes below — same routes, but they now render a Swich billing-connect / payee-registration flow, not embedded Paddle Checkout), Shopify-only merchant integration (`/onboarding/merchant/tracking-snippet` becomes a Shopify connect/OAuth step, not a copy-paste snippet), and no separate Campaign entity (merged into Offer). Full reasoning: `Technical Architecture/Architecture Decision Log`. **`FEATURE_LIST.md` has been fully rewritten to match (2026-08-23).** The Merchant Dashboard (§4) and Admin (§6) route trees below have been reconciled to the collapsed Offer structure and the `/admin/offers/vetting` rename; §1 (public discovery) and §3 (onboarding path names) still use the old naming pending a fuller pass — treat "campaign" in any remaining route name as "offer," and `/onboarding/merchant/paddle` as `/onboarding/merchant/swich`.

## Two Frontend Surfaces

Per `Technical Architecture/Frontend Architecture`, SellVia's frontend has two logically distinct parts sharing one Next.js codebase:

1. **Public marketing/discovery site** — `wesellvia.com`, unauthenticated.
2. **Authenticated application** — Merchant dashboard, Creator dashboard, Admin panel — role-gated, separate navigation per role per `UX/Navigation` ("role-based, not a single shared nav").

---

## 1. Public Site

```text
/                                          [Explicit — wesellvia.com home: hero, concept walkthrough,
                                             roadmap, FAQ, waitlist form]
├── /how-it-works                          [Inferred — nav link named "How It Works" in design.md/Navigation]
├── /for-businesses                        [Inferred — nav link named "For Businesses"]
├── /for-creators                          [Inferred — nav link named "For Creators"]
├── /waitlist                              [Inferred — "Join Waitlist" CTA target; may instead be an
                                             in-page form on "/" rather than a separate route — Needs
                                             clarification]
├── /campaigns                             [Inferred — public campaign discovery/browse list;
                                             GET /campaigns is explicitly public per API docs]
│   └── /campaigns/:slug                   [Inferred — public Campaign/Offer detail page carrying
                                             schema.org Product/Offer JSON-LD, per UX/AI Agent doc]
├── /go/:slug                              [Explicit — GET /go/:slug, the AffiliateLink redirect
                                             endpoint; logs the click, bounces to the merchant's own
                                             site. Not a rendered page a user lingers on.]
├── /llms.txt                              [Explicit — plain-markdown file for LLM consumption]
├── /robots.txt                            [Explicit — deliberately allows known AI crawlers on public
                                             pages, disallows authenticated routes]
└── /legal (privacy, terms)                [Inferred — required by Data Inventory & Disclosure's
                                             disclosure principle; exact legal pages/copy not specified
                                             — Needs clarification]
```

**Note on the removed checkout route:** earlier documentation versions describe `POST /checkout/:slug/session` and a SellVia-hosted checkout page. This is **superseded** (2026-08-07 reversal) — there is no SellVia-hosted checkout route. A shared link goes `/go/:slug` → redirect to the merchant's own external site.

---

## 2. Authentication Routes (Shared Shell, Pre-Role)

```text
/login                                     [Inferred — conventional route; Ory Kratos-driven]
/register                                  [Inferred — unified signup form, role selection inside]
/forgot-password                           [Inferred]
/reset-password                            [Inferred]
/verify-email                              [Inferred]
/mfa (setup / challenge)                   [Inferred — MFA policy exists (optional for Creators,
                                             recommended for Merchants, mandatory-under-consideration
                                             for Admin) but no route/screen name is specified]
/logout                                    [Inferred — action, not necessarily a standalone page]
```

**Source:** `Security/Authentication`, `Security/Session Management`, `Security/Password Policy`, `API/API Authentication`.

---

## 3. Onboarding (Post-Signup, Pre-Dashboard)

```text
/onboarding/role                           [Inferred — if role selection isn't inline on /register]
/onboarding/merchant/paddle                [Inferred — Paddle card-on-file / billing setup step,
                                             gates Campaign draft→live]
/onboarding/merchant/tracking-snippet      [Inferred — snippet install + verification step, gates
                                             Campaign draft→live]
/onboarding/creator/payout                 [Inferred — Paddle seller onboarding, gates
                                             AffiliateLink activation]
```

**Source:** `Business Logic/State Machines`, `Payments/Payment Flow`, `Edge Cases/User Edge Cases`, `Technical Architecture/Frontend Architecture`.

**Needs clarification:** whether onboarding is a dedicated route sequence or embedded as steps inside the Merchant/Creator dashboard's first-run state — not specified in the docs.

---

## 4. Merchant Dashboard (Authenticated, Role = Merchant)

Per `UX/Navigation`: **Offers / Applications / Sales / Payouts** are the named top-level nav sections for the Merchant role. Flat, no deep nesting, no mega-menus.

```text
/dashboard                                 [Inferred — Merchant home/overview if role = merchant;
                                             see §7 for dual-role landing behavior]
├── /offers                                [Explicit — named nav section (UX/Navigation); Offer
                                             absorbed the former Campaign entity — one list/detail/
                                             create flow, no separate /campaigns route — see
                                             SCREEN_INVENTORY.md D2–D4]
│   ├── /offers/new
│   ├── /offers/:id
│   └── /offers/:id/edit
├── /applications                          [Explicit — named nav section]
│   └── /applications/:id                  [Inferred — application review detail, or inline expansion]
├── /sales                                 [Explicit — named nav section; GET /sales]
│   └── /sales/:id                         [Inferred — sale/receipt detail]
├── /payouts                               [Explicit — named nav section — for Merchant this now
                                             surfaces Billing Cycles, not a live-split payout log,
                                             per the 2026-08-07 reversal; may be relabeled
                                             "Billing" in the actual UI — Needs clarification]
│   └── /payouts/:id  (or /billing/:id)    [Inferred — billing cycle detail]
├── /settings
│   ├── /settings/business                 [Inferred — MerchantProfile edit]
│   ├── /settings/billing                  [Inferred — Swich billing-connect management]
│   └── /settings/security                 [Inferred — sessions, MFA, password]
└── /notifications                         [Inferred — shared notification feed]
```

**Source:** `UX/Navigation`, `API/Endpoint Specifications`, `Business Logic/User Flows`, `Payments/Payment Flow`.

---

## 5. Creator Dashboard (Authenticated, Role = Creator)

Per `UX/Navigation`: **Discover / My Links / Earnings** are the named top-level nav sections for the Creator role.

```text
/dashboard                                 [Inferred — Creator home/overview if role = creator]
├── /discover                              [Explicit — named nav section; campaign browse/apply,
                                             authenticated version of the public /campaigns list]
│   └── /discover/:slug                    [Inferred — campaign detail + Apply action]
├── /applications                          [Inferred — "My Applications" status list; not named
                                             explicitly in Navigation but required by the Application
                                             state machine + Notification Logic]
├── /links                                 [Explicit — "My Links" nav section]
│   └── /links/:id                         [Inferred — link detail: click/cart/purchase timeline]
├── /earnings                              [Explicit — "Earnings" nav section — wallet balance,
                                             $50-threshold progress, payout history]
├── /settings
│   ├── /settings/profile                  [Inferred — CreatorProfile edit: niche, audience, rate]
│   ├── /settings/payout                   [Inferred — Paddle seller management]
│   └── /settings/security                 [Inferred]
└── /notifications                         [Inferred — shared component]
```

**Source:** `UX/Navigation`, `Business Logic/User Flows`, `Payments/Wallet Design`.

---

## 6. Admin Panel (Authenticated, Role = Admin)

Per `UX/Navigation`: "**Admin nav is entirely separate (`/admin/*`), not exposed to regular Merchant/Creator navigation at all.**" — [Explicit] namespace, [Inferred] sub-routes based on `Operations/Admin Panel`'s named screens.

```text
/admin
├── /admin/dashboard                       [Inferred — marketplace health, funnels, time-to-payout]
├── /admin/moderation                      [Inferred — flagged Sales/Applications queue;
                                             GET /admin/flagged]
│   └── /admin/moderation/:id
├── /admin/offers/vetting                  [Inferred — high-commission/high-risk offer approval;
                                             POST /admin/offers/:id/vet (renamed from
                                             /admin/campaigns/... 2026-08-23)]
├── /admin/users                           [Inferred — user management list]
│   └── /admin/users/:id                   [Inferred — user detail, suspend action;
                                             POST /admin/users/:id/suspend]
├── /admin/refunds-disputes                [Inferred — refund credit review + chargeback evidence]
├── /admin/reconciliation                  [Inferred — Swich-vs-internal-records mismatch review]
├── /admin/waitlist                        [Inferred — waitlist → beta invitation management]
├── /admin/at-risk-users                   [Inferred — 48h churn-signal view]
├── /admin/console                         [Inferred — Founder AI Command Console, chat-style
                                             natural-language interface]
├── /admin/analytics
│   ├── /admin/analytics/pnl               [Inferred — Automated Monthly P&L report]
│   ├── /admin/analytics/unit-economics    [Inferred]
│   └── /admin/analytics/ai-costs          [Inferred — AI/Token Usage Tracking]
└── /admin/settings                        [Inferred — feature flags visibility, etc. — not detailed
                                             in the docs as a screen; flagged for completeness only]
```

**Source:** `UX/Navigation`, `Operations/Admin Panel`, `Operations/Founder AI Command Console`, `Analytics/Dashboards`, `Analytics/Automated Monthly P&L`.

**Note:** The Admin role is single/flat for MVP — no sub-navigation for tiered admin permissions.

---

## 7. Dual-Role Navigation Behavior

For an account holding both Merchant and Creator roles, `UX/Navigation` recommends **"a role switcher rather than merging both role's navigation into one confusing menu."** No specific route pattern is given. Two reasonable implementations, neither confirmed in the docs — **Needs clarification**:

- **Option A [Inferred]:** Shared base path with a context switch, e.g. `/dashboard?as=merchant` / `/dashboard?as=creator`, swapping the entire nav/shell.
- **Option B [Inferred]:** Fully separate path namespaces, e.g. `/merchant/*` and `/creator/*`, with a switcher link between them.

This site map does not prescribe which; `SCREEN_INVENTORY.md` treats Merchant and Creator dashboards as separate screen sets regardless of final URL structure.

---

## 8. Full Hierarchy (Condensed Tree)

```text
/
├── / (marketing home)
├── /how-it-works
├── /for-businesses
├── /for-creators
├── /waitlist
├── /campaigns (public browse)
│   └── /campaigns/:slug (public detail)
├── /go/:slug (redirect, not a page)
├── /legal/*
│
├── /login
├── /register
├── /forgot-password
├── /reset-password
├── /verify-email
├── /mfa
│
├── /onboarding/*
│
├── /dashboard  (role-resolved landing: Merchant or Creator shell)
│
├── Merchant shell
│   ├── /offers[/new|/:id|/:id/edit]
│   ├── /applications[/:id]
│   ├── /sales[/:id]
│   ├── /payouts (billing cycles)[/:id]
│   ├── /settings/{business,billing,security}
│   └── /notifications
│
├── Creator shell
│   ├── /discover[/:slug]
│   ├── /applications
│   ├── /links[/:id]
│   ├── /earnings
│   ├── /settings/{profile,payout,security}
│   └── /notifications
│
└── /admin/*
    ├── /admin/dashboard
    ├── /admin/moderation[/:id]
    ├── /admin/offers/vetting
    ├── /admin/users[/:id]
    ├── /admin/refunds-disputes
    ├── /admin/reconciliation
    ├── /admin/waitlist
    ├── /admin/at-risk-users
    ├── /admin/console
    ├── /admin/analytics/{pnl,unit-economics,ai-costs}
    └── /admin/settings
```

---

## What This Site Map Deliberately Excludes

- **A SellVia-hosted checkout route** — removed by the 2026-08-07 reversal; do not build one.
- **A public API developer portal** — explicitly not needed until there's demand beyond SellVia's own frontend (deferred, post-MVP).
- **Mobile app navigation** — MVP is responsive web only; native app nav is out of scope (`Product Foundation/Full Product Vision (Post-MVP)`).
- **Status page** — lives on a separate domain/infrastructure by design, not part of this app's route tree at all.

## Cross-References

- What each route's screen actually contains: `SCREEN_INVENTORY.md`
- What feature each route serves: `FEATURE_LIST.md`

---

# UX / Accessibility

> Source: `UX/Accessibility.md` · tag: `frontend` · last updated: 2026-08-23

# Accessibility

## Purpose

WCAG-level considerations — not covered at all in [design.md](http://design.md), a genuine gap this doc fills.

## Color Contrast

- **Flagging a real tension:** [design.md](http://design.md)'s black background + lime accent is striking, but lime-on-black and lime-on-white both need actual contrast-ratio verification against WCAG AA (4.5:1 for body text, 3:1 for large text/UI components) before this ships broadly — not verified anywhere yet, and lime green in particular can have contrast issues depending on exact shade and text size. This should be checked with a real contrast tool against the specific hex values in [design.md](http://design.md), not assumed fine because it looks fine.
- White-on-black body text should be comfortably compliant — the main risk is specifically the lime accent used for text (vs. just borders/backgrounds), per [design.md](http://design.md)'s own guidance that lime should be "used sparingly."

## Other Baseline Requirements (not in [design.md](http://design.md), standard practice)

- Keyboard navigability for all interactive elements (forms, buttons, campaign discovery filters)
- Focus states visible or use the lime accent, and lime accent doubling here fits [design.md](http://design.md)'s "active states" allowance
- Alt text on all product images (03. Database → File Storage doesn't currently include an alt-text field — worth adding)
- Form labels properly associated with inputs, not just placeholder text (placeholder-only labels are a common accessibility failure and an easy one to avoid from the start)

## Open Questions

- Actual contrast ratio verification against the specific hex values in [design.md](http://design.md) — genuinely not done, should happen before broad launch, not guessed at here

## Update (2026-08-04): Three Binding Requirements — Not Aspirational, Enforced

These upgrade the earlier "flagged, not verified" contrast note into three concrete, testable requirements for every screen shipped.

### 1. Full Keyboard Navigation — No Exceptions

Every interactive element in the application must be reachable and operable by keyboard alone — no mouse-only interactions anywhere, across both dashboards, the hosted checkout, and the public marketing site.

- Logical tab order following visual layout (not DOM-order accidents from CSS positioning)
- All shadcn/ui components (09. UX → Design System) come with reasonable keyboard support by default — but every **custom** interactive element built on top (offer discovery filters, the "Get Link" component, status badges with actions) must be explicitly verified, not assumed
- Visible focus indicators on every focusable element — use the lime accent for focus rings, consistent with [design.md](http://design.md)'s existing "active states" allowance for lime, so this reinforces the design system rather than fighting it
- Modals/dialogs trap focus correctly and return focus to the triggering element on close
- **Especially the merchant billing-connect flow:** SellVia has no hosted checkout of its own — customers buy on the merchant's own Shopify store (reversed 2026-08-07, 01. Money Flow) — so the one remaining embedded-payment-shaped surface is merchant billing setup via the **Swich billing-connect widget**, embedded in a SellVia-branded shell. Verify keyboard operability end-to-end through that Swich billing-connect flow, not just assume Swich's widget components handle it — the surrounding page chrome is still SellVia's responsibility

### 2. Screen Reader Compatibility

- **All form fields** have properly associated labels (already required per this doc's earlier "no placeholder-only labels" note) plus correct ARIA attributes where native HTML semantics aren't enough (e.g. `aria-describedby` linking a field to its error message, `aria-invalid` on validation failure)
- **All images** have meaningful alt text — product images (03. Database → File Storage doesn't currently have an alt-text field; add one), profile photos, any icons that convey meaning rather than being purely decorative (decorative icons get `alt=""` / `aria-hidden`, not a missing attribute)
- **All buttons and icon-only controls** have accessible labels (`aria-label` where there's no visible text) — particularly relevant given [design.md](http://design.md)'s "no icons above headings" minimalism (09. UX → Components) often means icon-only actions in compact UI (status badges, table row actions)
- **Status/state changes** (e.g. "application approved," "payout sent" per 09. UX → Copy Guidelines) use `aria-live` regions where they update without a page reload, so a screen reader user isn't left unaware something changed

### 3. WCAG AA Color Contrast — Verified, Not Assumed

This closes the exact gap flagged above ("not verified anywhere yet"): every text/background combination in [design.md](http://design.md)'s palette must be checked against a real contrast tool before broad rollout, specifically:

- White (#FFFFFF) on black (#000000): comfortably compliant, low risk
- **Lime (#BFFF13) as text color: the actual risk.** Must hit 4.5:1 for body text / 3:1 for large text (18pt+/14pt+bold) or UI components. If lime-as-text fails on either background, the fix is restricting lime to backgrounds/borders/icons only (never body copy) rather than changing the brand color itself — preserves [design.md](http://design.md)'s "used sparingly" intent while staying compliant
- Gray 01 (#A1A1AA) and Gray 02 (#71717A) on black: both need verification, since muted text is exactly where contrast tends to quietly fail

## Where This Gets Enforced

Add to 04. Security → Security Checklist as a pre-launch gate item, alongside the existing tenant-isolation and payments-testing requirements — accessibility shouldn't be the one category that's "nice to have" while everything else is a hard gate.

## Update (2026-08-23): Contrast Verification RESOLVED

**Actually computed against the relative-luminance WCAG formula (not guessed):**

| Text color | On black (#000000) | Result |
| --- | --- | --- |
| White #FFFFFF | ~21:1 | Pass (AAA) |
| Lime #BFFF13 | **17.5:1** | Pass (AAA) — lime was never the actual risk; it's high-luminance and safe as text too, though still used sparingly per Design System's intent |
| Gray 01 #A1A1AA | 8.2:1 | Pass (AAA) |
| Gray 02 #71717A (original) | **4.35:1** | **Fail** — under the 4.5:1 AA minimum for normal text |

**Fix applied:** Gray 02 changed to **#787882** (same hue, channels raised ~7 points) → **4.82:1**, passes AA with margin. Updated in UX/Design System. No other palette values needed changing — lime and Gray 01 were already compliant, so this was a one-color fix, not a broader palette rework.

This closes the "Needs clarification" item carried in FEATURE_LIST.md §0.5 and SCREEN_INVENTORY.md's global notes — both should be read as resolved now.

**Separate note, unrelated to color:** this file previously had literal stray "n" characters where line breaks should be in the section above (a pre-existing source-export artifact, not something this update introduced) — cleaned up 2026-08-23, along with reworking the "Especially the checkout flow" keyboard requirement, which described a Paddle-Checkout-in-SellVia-shell flow that no longer exists (checkout moved to merchants' own Shopify stores, reversed 2026-08-07; Paddle replaced by Swich 2026-08-23) — now points at verifying the Swich billing-connect widget instead.

---

# UX / AI Agent & Machine Readability

> Source: `UX/AI Agent & Machine Readability.md` · tag: `frontend` · last updated: 2026-08-23

# AI Agent & Machine Readability

## Purpose

Make SellVia's public-facing content and API legible to AI agents — shopping/browsing agents acting on a user's behalf, LLM crawlers indexing for AI search, and third parties integrating programmatically — not just human visitors and screen readers (09. UX → Accessibility covers that adjacent but distinct concern).

## 1. Structured Data ([schema.org](http://schema.org) JSON-LD)

Every public Offer page gets `Product` and `Offer` [schema.org](http://schema.org) markup embedded as JSON-LD:

```json
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Glow Serum",
  "offers": {
    "@type": "Offer",
    "price": "68.00",
    "priceCurrency": "PKR",
    "availability": "https://schema.org/InStock"
  }
}
```

This is what lets an AI shopping agent (or a search engine's AI answer) correctly understand price, availability, and product identity without guessing from rendered text — directly relevant given SellVia has **no checkout of its own**: the actual purchase happens on the merchant's own Shopify store (01. Business Logic → Money Flow), so SellVia's public Offer page can't rely on the merchant's checkout page for this markup — SellVia's own markup is the *only* structured signal it controls for these products.

## 2. Semantic HTML — Shared Payoff With Accessibility

Proper landmark elements (`<nav>`, `<main>`, `<article>`, correct heading hierarchy) and meaningful element structure benefit screen readers (09. UX → Accessibility) and AI agents/crawlers identically — both are parsing structure, not just rendered pixels. This isn't duplicate work; building it once correctly serves both audiences.

## 3. OpenAPI Spec — Already Free With FastAPI, Just Needs Using

FastAPI auto-generates an OpenAPI/Swagger spec (`/openapi.json`, human-viewable at `/docs`) from the route definitions in 07. API → Endpoint Specifications, with zero extra work beyond writing clean type hints and docstrings on route handlers. This is exactly the machine-readable contract an AI agent needs to understand and call SellVia's API programmatically — worth treating route documentation quality as a real requirement, not an afterthought, specifically because it's this cheap to get right.

## 4. llms.txt for the Public Marketing Site

An emerging convention: a plain-markdown `/llms.txt` file at the site root, summarizing what SellVia is and linking to key pages, written for an LLM to parse quickly rather than crawl the full rendered site. Low effort, direct benefit for AI-search visibility (ties to the earlier GEO/AEO conversation, if that skill gets used later) — recommend adding this alongside the marketing site, not the authenticated app.

## 5. Metadata: Open Graph, Twitter Cards, Canonical URLs

Standard social/SEO meta tags on every public page (title, description, `og:image`, canonical URL) — also consumed by AI agents previewing or summarizing a link, not just social platforms. Low effort, should be part of the base page template, not per-page manual work.

## 6. robots.txt — Deliberate, Not Default-Blocked

Explicitly allow known AI crawler user-agents (e.g. GPTBot, ClaudeBot, and similar) on public marketing/campaign-discovery pages — blocking them by default (a common template default) would defeat the entire point of this doc. **Authenticated dashboard routes stay disallowed**, same as any crawler — this only applies to intentionally public content, consistent with 02. Caching Strategy's public/private namespace distinction.

## What Does NOT Get This Treatment

Authenticated Merchant/Creator dashboards, checkout session details, anything tenant-private (04. Security → Tenant Isolation Audit) — machine-readability applies only to intentionally public content and the documented API surface, never to private data. An AI agent should be able to understand what a product is and how to call the public API; it should have zero visibility into any tenant's private data, same boundary as a human without credentials.

## Open Questions

- Whether llms.txt is worth maintaining before Public Launch or is a Private-Beta-era nice-to-have — reasonable to add cheaply now alongside the marketing site rather than as a separate future task, given how little effort it takes

## Update (2026-08-23): Corrected Checkout/Currency/Entity Assumptions

Section 1 previously assumed SellVia has its own hosted checkout page and used stale "Campaign" naming and USD pricing. Corrected: SellVia has no checkout of its own (checkout happens on the merchant's Shopify store, reversed 2026-08-07); the entity is "Offer," not "Campaign" (Campaign retired, merged into Offer); and MVP currency is PKR only.

---

# UX / Components

> Source: `UX/Components.md` · tag: `frontend` · last updated: 2026-08-23

# Components

## Purpose

How [design.md](http://design.md)'s card/section patterns translate into the specific components the Merchant and Creator dashboards actually need — [design.md](http://design.md) covers marketing-page components; this doc covers the authenticated-app components it doesn't.

## Shared Components (used by both dashboards, per 02. Frontend Architecture's recommendation to build these once)

- **List/table view** — offers, applications, sales, payouts. Per [design.md](http://design.md): content blocks, not marketing components; thin borders, no icon-per-row decoration.
- **Stat card** — a single number + label (e.g. "Balance: ₨11,850", "Clicks: 128") — follows [design.md](http://design.md)'s card structure (title, description, optional number, optional border), no icons above headings.
- **Status badge** — pending/approved/rejected, live/paused/ended, verified/refunded — small, text-based, colored via border/text color per state rather than filled color blocks (consistent with "green should feel earned" — lime reserved for genuinely primary actions, not routine status indicators).
- **Empty state** — needed for "no offers yet," "no applications yet," "link generated but zero clicks yet" (per 08. Edge Cases → Creator Edge Cases) — should read as calm and expected, not broken, consistent with the landing page's own zeroed-dashboard framing ("0 creators, 0 sales, $0" is a deliberate design choice already, not just an MVP placeholder).

## Merchant-Specific

- Offer creation form (name, price, currency, commission rate — no bargaining UI per 01. Business Logic)
- Application review card (creator's audience/niche/engagement data + approve/reject actions)

## Creator-Specific

- Offer discovery card (product, commission rate, merchant name, apply action)
- "Get Link" component — the moment a creator receives their AffiliateLink, should feel like a small, clear payoff given how central this moment is to the whole product

## Open Questions

- None blocking — direct extension of [design.md](http://design.md)'s stated patterns into the specific screens 01. Business Logic's User Flows already describe.

## Update (2026-08-23): Reconciled to Offer Entity Model and PKR Currency

This file was never revised for the Offer/Campaign merge or the PKR-only currency decision. "Campaign" terminology throughout (list/table view, creation form, discovery card, empty states) is now "Offer," and the Stat Card example uses a PKR figure instead of USD.

---

# UX / Copy Guidelines

> Source: `UX/Copy Guidelines.md` · tag: `frontend`

# Copy Guidelines

## Purpose

How SellVia's actual product copy sounds — [design.md](http://design.md) covers visual design; this doc covers voice, filling a real gap.

## Voice, Derived From What's Already Written

The live [wesellvia.com](http://wesellvia.com) copy ("One Arrow, Two Wins," "SellVia doesn't exist yet," the zeroed-dashboard honesty beat) establishes a voice that's direct, a little wry, and radically transparent — confident without being hypey. This should extend into the actual product, not just the marketing page:

- Prefer plain statements over exclamation points or false urgency ("Your link is ready" not "Your link is ready!!")
- Numbers speak for themselves — the landing page's zeroed-dashboard device ("0 creators, 0 sales, $0") is a pattern worth reusing in-product for genuine empty states (see Components → Empty State)
- Avoid marketing-speak in transactional copy: "Payout sent" not "You've been rewarded!"

## Error Messages

- Specific and actionable, not generic ("You've already applied to this campaign" not "Something went wrong") — matches 07. API → Error Responses' structured error codes, which should map to genuinely helpful copy, not just technical codes surfaced directly to users

## Open Questions

- None blocking — this doc establishes a direction consistent with existing live copy; specific microcopy gets written per-screen as they're built.

---

# UX / Design System

> Source: `UX/Design System.md` · tag: `frontend` · last updated: 2026-08-23

# Design System

## Purpose

The binding visual language for everything SellVia builds — this doc summarizes [design.md](http://design.md); [design.md](http://design.md) itself remains the detailed source, not duplicated in full here.

## Philosophy

Feel like a serious early-stage product, not a marketing campaign. Reference points: HackerRank, Paddle Docs, Linear, Vercel — confident, clear, product-first. Explicitly avoid: gradients, glassmorphism, glow effects, animated blobs, generic SaaS screenshots, buzzword sections.

## Color

- Background: **#000000** (black) — page, hero, nav, footer
- Accent: **#BFFF13** (lime) — used sparingly: primary CTA, small highlights, active/focus states only. Never large color blocks or green backgrounds behind content.
- Text: White (#FFFFFF) primary, Gray 01 (#A1A1AA) secondary, Gray 02 (#787882) muted
- Borders: #27272A default, #3F3F46 hover

## Update (2026-08-23): Gray 02 Corrected for WCAG AA — RESOLVED

**Gray 02 changed from `#71717A` to `#787882`.** Actual contrast verification (see UX/Accessibility) found the original value — Tailwind's zinc-500 — measured **4.35:1 against black, failing the 4.5:1 AA minimum for body text** by a small margin. `#787882` keeps the same hue/blue-gray tint and measures **4.82:1**, comfortably compliant. Lime and Gray 01 were also verified and need no change — see Accessibility doc for full numbers.

## Typography

- **Outfit** — headlines, hero copy, nav, CTA buttons, section titles
- **Figtree** — paragraphs, labels, form fields, cards, metadata

## Layout

- Max container width 1280px, content width 1200px, readable text 640–720px
- Section padding: 120px top/bottom (hero: 160px top, 120px bottom)
- Whitespace is a deliberate design element, not empty space to fill

## Shadows & Radius

- Avoid shadows almost entirely; borders instead of elevation
- Small border radius: buttons 10px, cards 12px, inputs 10px, never exceeding 16px

## Animation

Very restrained: fade-ins, opacity transitions, border-color transitions, 2–4px subtle movement only. No parallax, no floating effects, no infinite motion.

## Full Detail

See [design.md](http://design.md) for the complete specification (hero structure, navigation, card patterns, CTA styling, grid system) — this doc is a working summary, not a replacement.

---

# UX / Interaction Patterns

> Source: `UX/Interaction Patterns.md` · tag: `frontend`

# Interaction Patterns

## Purpose

How the two dashboards behave, not just how they look — [design.md](http://design.md) is visual-only, this doc covers interaction.

## Core Interaction Principle (from Mission & Principles)

"Reduce complexity relentlessly" — applies to interaction, not just layout: contextually hide irrelevant fields/actions rather than showing everyone everything with some parts disabled. A digital-goods Merchant never sees a shipping-related field at all, rather than seeing it grayed out.

## Key Interaction Moments

- **Campaign creation → live:** should feel like very few steps (per the raw data doc's original "minimize form fields, add defaults" goal) — commission rate, product info, publish. No multi-page wizard for MVP.
- **Application → approval → link generation:** the approval action should immediately surface the generated AffiliateLink to the Creator (real-time or near-real-time notification, not a delayed email only) — this is one of the product's core "trust moments" and shouldn't feel like a black box.
- **Checkout:** stale — SellVia has no hosted checkout page (reversed 2026-08-07, 01. Money Flow); customers buy on the merchant's own Shopify store. The one remaining Paddle-Checkout-shaped surface — merchant billing setup — now uses Swich instead (updated 2026-08-23), and per [design.md](http://design.md)'s restrained-animation rule should still feel calm and fast, not gamified, whatever Swich's actual widget/flow turns out to be.

## Loading/Transition States

- Per [design.md](http://design.md)'s animation restraint ("fade in, slight opacity transitions... nothing beyond that"), loading states should be simple and quiet — skeleton screens or fades, not spinners with playful copy.

## Open Questions

- None blocking — this doc translates already-stated design and product principles into interaction guidance; specific micro-interactions get refined during actual screen design.

---

# UX / Navigation

> Source: `UX/Navigation.md` · tag: `frontend`

# Navigation

## Purpose

How someone moves around the authenticated app — [design.md](http://design.md)'s Navigation section covers the public marketing site only; this doc covers the dashboard.

## Marketing Site Nav (from [design.md](http://design.md), unchanged)

Logo left, links center/right ("How It Works," "For Businesses," "For Creators"), single "Join Waitlist" CTA. No dropdowns, no mega menus.

## Dashboard Navigation (new — not covered by [design.md](http://design.md))

- **Role-based, not a single shared nav:** a Merchant sees Offers / Applications / Sales / Payouts; a Creator sees Discover / My Links / Earnings. A user with both roles (per User Roles' default) needs a way to switch context — recommend a role switcher rather than merging both role's navigation into one confusing menu, consistent with "don't favor one side" but also don't blend the two experiences into one.
- **Admin nav** is entirely separate (`/admin/*`), not exposed to regular Merchant/Creator navigation at all.

## Minimalism Carries Over

[Design.md](http://Design.md)'s "avoid dropdowns, avoid mega menus" principle should extend to the dashboard — flat, few top-level sections per role, not deep nested menus.

## Open Questions

- Exact mechanism for switching between Merchant/Creator context on a dual-role account — not designed yet, reasonable to resolve during actual screen design rather than guess here

---
