# Phase 5 Backend Contract — Seller Portal

Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md) Phase 5 (Sprints 5.1–5.3). Depends on [Phase 3](PHASE-3-CATALOG-OFFER-FULFILLMENT-CONTRACT.md) (Offer) and [Phase 2](PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md) (unified Order + Fulfillment).

**Status: ready to build behind the launch gate.** Frontend note: Seller Management is currently a static placeholder, feature-flagged off by default, with **zero backend bytes and zero `HttpClient` reference** — this contract is a from-scratch build, not a gateway swap.

---

## 1. Multi-seller model reminder

Per the Phase 2 unified-orders decision: a seller never owns a separate `Order`. They see the `Fulfillment` group(s) that belong to them within shared orders, and the `OrderLine`s scoped to their `sellerId`. All endpoints below are pre-filtered server-side to the authenticated seller — never trust a frontend-supplied `sellerId` filter.

## 2. Entities

```ts
interface SellerOrganization {
  id: string;
  marketplaceId: string;
  legalName: string;
  status: 'pending' | 'approved' | 'suspended' | 'rejected';
  bankDetailsRef: string;  // pointer into secret storage, never raw account numbers over the wire
  createdAt: string;
}

interface SellerUser {
  id: string;
  sellerOrganizationId: string;
  role: 'SELLER_OWNER' | 'SELLER_CATALOG_MANAGER' | 'SELLER_ORDER_MANAGER' | 'SELLER_FINANCE_VIEWER' | 'SELLER_VIEWER';
  email: string;
  status: 'active' | 'invited' | 'suspended';
}

interface SellerMarketplaceMembership {
  sellerOrganizationId: string;
  marketplaceId: string;
  status: 'pending' | 'approved' | 'suspended';
}

interface SellerIntegration {
  sellerOrganizationId: string;
  apiCredentialRef: string;
  webhookUrl?: string;
  lastSyncAt?: string;
  lastSyncError?: string;
}
```

## 3. Endpoints (all scoped server-side to the authenticated seller's org)

```
POST /api/seller/v1/onboarding                 { legalName, contacts, marketplaceId }
GET  /api/seller/v1/profile
GET  /api/seller/v1/offers?status=&page=
POST /api/seller/v1/offers
PATCH /api/seller/v1/offers/{id}
POST /api/seller/v1/offers/bulk-price-update    -- mass price/stock edit, see Phase 3 §6 for the shared bulk-import pattern
GET  /api/seller/v1/orders?fulfillmentStatus=
PATCH /api/seller/v1/orders/{orderId}/fulfillment/{fulfillmentId}   { status, evidence }
GET  /api/seller/v1/finance/accruals
GET  /api/seller/v1/finance/settlements
POST /api/seller/v1/finance/bank-details        -- step-up auth + audit event required, see §5
GET  /api/seller/v1/team
POST /api/seller/v1/team/invite                 { email, role }
GET  /api/seller/v1/integrations
```

## 4. Roles (fixed set, enforced backend-side)

```
SELLER_OWNER            - full access within the org
SELLER_CATALOG_MANAGER  - offers/catalog only
SELLER_ORDER_MANAGER    - orders/fulfillment only
SELLER_FINANCE_VIEWER   - read-only finance
SELLER_VIEWER           - read-only everything
```

No UI-only gating. Every endpoint above checks `SellerUser.role` server-side regardless of what the frontend renders — this is the same principle as [Track S](TRACK-S-SECURITY-RBAC-CONTRACT.md), scoped to the seller domain specifically.

## 5. Sensitive-action rules

- Bank/payment detail changes (`POST .../finance/bank-details`) require step-up authentication, produce an audit event, and — if maker/checker mode is enabled for the tenant — require a second approver before taking effect.
- A seller can never query, by any endpoint or parameter manipulation, another seller's products, orders, customers, finance data, or API keys. This must be enforced at the query layer (implicit `WHERE sellerOrganizationId = :authenticatedSeller`), not left to the frontend to "not ask for it."

## 6. What the frontend will start doing once this ships

- Replace the static Seller Management placeholder with real screens: Onboarding, Catalog, Prices & Stock, Orders, Finance, Team, Integrations (per plan §2.2).
- Resolve the two competing seller type shapes flagged in `GAPS-AND-IMPROVEMENTS.md` (`SellerConfig` in bootstrap models vs. `Seller`/`SellerBranding` in the domain layer) against this contract's `SellerOrganization`/`SellerUser` shapes.
- First-ever exercise of the `sellerManagement.enabled` flag at `true` — write a fixture test, since it has never been tested at its real-world-eventual value.
