From a8379a2a0c92a1e770b5c6235b97ef5f74e1e249 Mon Sep 17 00:00:00 2001
From: James
Date: Sat, 28 Feb 2026 04:39:38 -0500
Subject: [PATCH] Add WebMCP + agent-friendly enhancements
- llms.txt: LLM-readable site description (llmstxt.org spec)
- mcp-manifest.json: WebMCP declarative API manifest
- robots.txt: Allow all AI crawlers + sitemap reference
- sitemap.xml: All 7 pages with priority/lastmod
- Schema.org SoftwareApplication markup in index.html
- OpenGraph + Twitter meta tags on all pages
- WebMCP form annotations on waitlist form
- aria-label on mobile menu buttons
---
API-SPEC.yaml | 2909 +++++++++++++++++++++++++++++++++++++
ONBOARDING-SPEC.md | 1500 +++++++++++++++++++
website/dpa.html | 11 +
website/features.html | 13 +-
website/index.html | 39 +-
website/llms.txt | 21 +
website/mcp-manifest.json | 30 +
website/pricing.html | 13 +-
website/privacy.html | 11 +
website/robots.txt | 12 +
website/security.html | 13 +-
website/sitemap.xml | 45 +
website/terms.html | 11 +
13 files changed, 4622 insertions(+), 6 deletions(-)
create mode 100644 API-SPEC.yaml
create mode 100644 ONBOARDING-SPEC.md
create mode 100644 website/llms.txt
create mode 100644 website/mcp-manifest.json
create mode 100644 website/robots.txt
create mode 100644 website/sitemap.xml
diff --git a/API-SPEC.yaml b/API-SPEC.yaml
new file mode 100644
index 0000000..c58d47c
--- /dev/null
+++ b/API-SPEC.yaml
@@ -0,0 +1,2909 @@
+openapi: 3.0.3
+info:
+ title: Dealspace API
+ description: |
+ M&A deal management platform API. Investment Banks, Sellers, and Buyers collaborate
+ on a structured request-and-answer workflow system.
+
+ ## Authentication
+ All endpoints except `/health` and `/api/auth/*` require a valid Bearer token.
+ Tokens are JWT (HS256) with 1-hour access token lifetime and 7-day refresh token lifetime.
+
+ ## RBAC Model
+ Access is scoped to projects and workstreams. Roles determine permissions:
+ - **ib_admin** — Full control over project and all workstreams
+ - **ib_member** — Manage requests and vet answers in assigned workstreams
+ - **seller_admin** — Manage seller team, see all seller-directed requests
+ - **seller_member** — Answer requests in assigned workstreams
+ - **buyer_admin** — Manage buyer team, access data room
+ - **buyer_member** — Submit requests, view published answers
+ - **observer** — Read-only access to assigned workstreams
+
+ ## Error Responses
+ All errors follow a consistent format:
+ ```json
+ {"error": "Human-readable message", "code": "ERROR_CODE", "details": {}}
+ ```
+
+ ## Rate Limiting
+ Endpoints are rate-limited per user, IP, and project. Headers `X-RateLimit-Limit`,
+ `X-RateLimit-Remaining`, and `Retry-After` are included in responses.
+ version: 1.0.0
+ contact:
+ name: Dealspace Support
+ email: support@muskepo.com
+ license:
+ name: Proprietary
+
+servers:
+ - url: https://muskepo.com/api
+ description: Production
+ - url: http://localhost:8080/api
+ description: Development
+
+tags:
+ - name: Auth
+ description: Authentication and MFA
+ - name: Projects
+ description: Top-level deal containers
+ - name: Workstreams
+ description: RBAC-anchored workflow containers (Finance, Legal, IT, etc.)
+ - name: Request Lists
+ description: Named collections of requests within a workstream
+ - name: Requests
+ description: Individual request items
+ - name: Answers
+ description: Responses to requests
+ - name: Matches
+ description: AI-suggested answer-to-request matching
+ - name: Objects
+ description: File upload/download with watermarking
+ - name: Tasks
+ description: Personal task inbox
+ - name: Access
+ description: RBAC grants
+ - name: Invites
+ description: Project invitations
+ - name: Audit
+ description: Security audit log
+ - name: Health
+ description: Service health check
+
+paths:
+ # ============================================================================
+ # AUTH ENDPOINTS
+ # ============================================================================
+
+ /auth/login:
+ post:
+ tags: [Auth]
+ summary: Authenticate with email and password
+ description: |
+ Returns access and refresh tokens. If MFA is required but not yet verified,
+ the token will have `mfa: false` and can only be used on `/auth/mfa/*` endpoints.
+
+ **Rate limit:** 5 requests/minute per IP
+ operationId: login
+ security: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [email, password]
+ properties:
+ email:
+ type: string
+ format: email
+ example: user@example.com
+ password:
+ type: string
+ format: password
+ minLength: 8
+ fingerprint:
+ type: object
+ description: Device fingerprint for session binding
+ properties:
+ userAgent:
+ type: string
+ acceptLanguage:
+ type: string
+ timezone:
+ type: string
+ responses:
+ '200':
+ description: Authentication successful
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AuthTokens'
+ '401':
+ description: Invalid credentials
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: Rate limit exceeded
+ headers:
+ Retry-After:
+ schema:
+ type: integer
+ description: Seconds until rate limit resets
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /auth/refresh:
+ post:
+ tags: [Auth]
+ summary: Rotate refresh token
+ description: |
+ Exchange a valid refresh token for new access and refresh tokens.
+ The old refresh token is invalidated (rotation). Fingerprint must match
+ the original session.
+
+ **Rate limit:** 60 requests/minute per user
+ operationId: refreshToken
+ security: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [refresh_token]
+ properties:
+ refresh_token:
+ type: string
+ fingerprint:
+ type: object
+ properties:
+ userAgent:
+ type: string
+ acceptLanguage:
+ type: string
+ timezone:
+ type: string
+ responses:
+ '200':
+ description: Tokens rotated successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AuthTokens'
+ '401':
+ description: Invalid or expired refresh token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /auth/logout:
+ post:
+ tags: [Auth]
+ summary: Revoke current session
+ description: |
+ Invalidates the current access token and all associated refresh tokens.
+
+ **Rate limit:** 60 requests/minute per user
+ operationId: logout
+ security:
+ - bearerAuth: []
+ responses:
+ '204':
+ description: Session revoked successfully
+ '401':
+ description: Invalid or expired token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /auth/mfa/setup:
+ post:
+ tags: [Auth]
+ summary: Initiate TOTP MFA setup
+ description: |
+ Generates a new TOTP secret and returns a QR code URI for authenticator apps.
+ Also returns recovery codes (store securely, shown only once).
+
+ **RBAC:** Any authenticated user
+ **Rate limit:** 5 requests/minute per user
+ operationId: mfaSetup
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: MFA setup initiated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ secret:
+ type: string
+ description: Base32-encoded TOTP secret (for manual entry)
+ qr_uri:
+ type: string
+ format: uri
+ description: otpauth:// URI for QR code generation
+ recovery_codes:
+ type: array
+ items:
+ type: string
+ description: 10 single-use recovery codes
+ '400':
+ description: MFA already enabled
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /auth/mfa/verify:
+ post:
+ tags: [Auth]
+ summary: Verify TOTP code
+ description: |
+ Completes MFA setup (if pending) or verifies MFA during login.
+ On successful verification, returns a new token with `mfa: true`.
+
+ **Rate limit:** 5 requests/minute per user (prevents brute force)
+ operationId: mfaVerify
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [code]
+ properties:
+ code:
+ type: string
+ pattern: '^\d{6}$'
+ description: 6-digit TOTP code from authenticator app
+ responses:
+ '200':
+ description: MFA verified
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AuthTokens'
+ '401':
+ description: Invalid TOTP code
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /invites/accept:
+ post:
+ tags: [Auth]
+ summary: Accept invitation and create account
+ description: |
+ Accepts an invite token. If the user does not exist, creates a new account.
+ If the user exists (by email), grants access to the invited project/workstream.
+
+ **Rate limit:** 10 requests/minute per IP
+ operationId: acceptInvite
+ security: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [token]
+ properties:
+ token:
+ type: string
+ description: Invite token from email
+ name:
+ type: string
+ description: User's full name (required if creating new account)
+ password:
+ type: string
+ format: password
+ minLength: 8
+ description: Password (required if creating new account)
+ responses:
+ '200':
+ description: Invitation accepted
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ user_id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ role:
+ type: string
+ tokens:
+ $ref: '#/components/schemas/AuthTokens'
+ '400':
+ description: Invalid, expired, or already-used invite
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ # ============================================================================
+ # PROJECT ENDPOINTS
+ # ============================================================================
+
+ /projects:
+ get:
+ tags: [Projects]
+ summary: List all projects for the authenticated user
+ description: |
+ Returns all projects the user has access to, based on their role grants.
+
+ **RBAC:** Any authenticated user
+ **Rate limit:** 300 requests/minute per user
+ operationId: listProjects
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: stage
+ in: query
+ schema:
+ type: string
+ enum: [pre_dataroom, dataroom, closed]
+ description: Filter by deal stage
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ default: 50
+ maximum: 100
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ default: 0
+ responses:
+ '200':
+ description: List of projects
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ projects:
+ type: array
+ items:
+ $ref: '#/components/schemas/Project'
+ total:
+ type: integer
+ limit:
+ type: integer
+ offset:
+ type: integer
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+
+ post:
+ tags: [Projects]
+ summary: Create a new project
+ description: |
+ Creates a new project. The creator automatically becomes `ib_admin`.
+
+ **RBAC:** Any authenticated user (typically IB initiates deals)
+ **Rate limit:** 10 requests/minute per user
+ operationId: createProject
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectCreate'
+ responses:
+ '201':
+ description: Project created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ '400':
+ description: Invalid input
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+
+ /projects/{id}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+
+ get:
+ tags: [Projects]
+ summary: Get project details
+ description: |
+ Returns project metadata and summary statistics.
+
+ **RBAC:** Any role with access to the project
+ **Rate limit:** 300 requests/minute per user
+ operationId: getProject
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: Project details
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ patch:
+ tags: [Projects]
+ summary: Update project
+ description: |
+ Updates project metadata (name, stage, theme, etc.).
+
+ **RBAC:** `ib_admin` only
+ **Rate limit:** 60 requests/minute per user
+ operationId: updateProject
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectUpdate'
+ responses:
+ '200':
+ description: Project updated
+ headers:
+ ETag:
+ schema:
+ type: string
+ description: Updated version tag for concurrency control
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ '400':
+ description: Invalid input
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '409':
+ $ref: '#/components/responses/Conflict'
+ '412':
+ $ref: '#/components/responses/PreconditionFailed'
+
+ delete:
+ tags: [Projects]
+ summary: Soft delete project
+ description: |
+ Marks the project as deleted. Data is retained for compliance but
+ project becomes inaccessible to all users.
+
+ **RBAC:** `ib_admin` only
+ **Rate limit:** 10 requests/minute per user
+ operationId: deleteProject
+ security:
+ - bearerAuth: []
+ responses:
+ '204':
+ description: Project deleted
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ # ============================================================================
+ # WORKSTREAM ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/workstreams:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+
+ get:
+ tags: [Workstreams]
+ summary: List workstreams in project
+ description: |
+ Returns all workstreams the user has access to within the project.
+
+ **RBAC:** Any role with project access (filtered by workstream-level access)
+ **Rate limit:** 300 requests/minute per user
+ operationId: listWorkstreams
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: List of workstreams
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ workstreams:
+ type: array
+ items:
+ $ref: '#/components/schemas/Workstream'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ post:
+ tags: [Workstreams]
+ summary: Create workstream
+ description: |
+ Creates a new workstream within the project. Workstreams are RBAC anchors.
+
+ **RBAC:** `ib_admin` only
+ **Rate limit:** 60 requests/minute per user
+ operationId: createWorkstream
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkstreamCreate'
+ responses:
+ '201':
+ description: Workstream created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Workstream'
+ '400':
+ description: Invalid input
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+
+ patch:
+ tags: [Workstreams]
+ summary: Update workstream
+ description: |
+ Updates workstream metadata (name, description).
+
+ **RBAC:** `ib_admin` or `ib_member` with access to this workstream
+ **Rate limit:** 60 requests/minute per user
+ operationId: updateWorkstream
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkstreamUpdate'
+ responses:
+ '200':
+ description: Workstream updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Workstream'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ # ============================================================================
+ # REQUEST LIST ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/workstreams/{wsId}/lists:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+
+ get:
+ tags: [Request Lists]
+ summary: List request lists in workstream
+ description: |
+ Returns all request lists within the workstream.
+
+ **RBAC:** Any role with workstream access
+ **Rate limit:** 300 requests/minute per user
+ operationId: listRequestLists
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: List of request lists
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ lists:
+ type: array
+ items:
+ $ref: '#/components/schemas/RequestList'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ post:
+ tags: [Request Lists]
+ summary: Create request list
+ description: |
+ Creates a new named collection of requests within the workstream.
+
+ **RBAC:** `ib_admin`, `ib_member`
+ **Rate limit:** 60 requests/minute per user
+ operationId: createRequestList
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RequestListCreate'
+ responses:
+ '201':
+ description: Request list created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RequestList'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ # ============================================================================
+ # REQUEST ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/workstreams/{wsId}/requests:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+
+ get:
+ tags: [Requests]
+ summary: List requests in workstream
+ description: |
+ Returns requests in the workstream. Supports filtering by assignee, status, and stage.
+
+ **RBAC:** Any role with workstream access. Pre-dataroom entries invisible to buyers.
+ **Rate limit:** 300 requests/minute per user
+ operationId: listRequests
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: assignee
+ in: query
+ schema:
+ type: string
+ description: Filter by assignee. Use "me" for current user.
+ - name: status
+ in: query
+ schema:
+ type: string
+ enum: [open, assigned, answered, vetted, published, closed]
+ description: Filter by status
+ - name: stage
+ in: query
+ schema:
+ type: string
+ enum: [pre_dataroom, dataroom, closed]
+ description: Filter by deal stage
+ - name: list_id
+ in: query
+ schema:
+ type: string
+ format: uuid
+ description: Filter by request list
+ - name: priority
+ in: query
+ schema:
+ type: string
+ enum: [high, normal, low]
+ - name: overdue
+ in: query
+ schema:
+ type: boolean
+ description: Only return requests past due date
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ default: 50
+ maximum: 100
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ default: 0
+ responses:
+ '200':
+ description: List of requests
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ requests:
+ type: array
+ items:
+ $ref: '#/components/schemas/Request'
+ total:
+ type: integer
+ limit:
+ type: integer
+ offset:
+ type: integer
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ post:
+ tags: [Requests]
+ summary: Create request
+ description: |
+ Creates a new request in the workstream. IB/Seller create for seller action,
+ Buyers create via data room (routed to IB).
+
+ **RBAC:** `ib_admin`, `ib_member`, `buyer_admin`, `buyer_member`
+ **Rate limit:** 60 requests/minute per user
+ operationId: createRequest
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RequestCreate'
+ responses:
+ '201':
+ description: Request created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Request'
+ '400':
+ description: Invalid input
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/requests/{reqId}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/RequestId'
+
+ get:
+ tags: [Requests]
+ summary: Get request details
+ description: |
+ Returns full request details including routing chain (visible to IB only).
+
+ **RBAC:** Any role with workstream access
+ **Rate limit:** 300 requests/minute per user
+ operationId: getRequest
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: Request details
+ headers:
+ ETag:
+ schema:
+ type: string
+ description: Version tag for concurrency control
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Request'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ patch:
+ tags: [Requests]
+ summary: Update request
+ description: |
+ Updates request metadata (title, body, priority, due_date, assigned_to, status).
+ Use `If-Match` header with ETag for optimistic concurrency control.
+
+ **RBAC:** `ib_admin`, `ib_member`, `seller_admin`, `seller_member` (own assignments)
+ **Rate limit:** 60 requests/minute per user
+ operationId: updateRequest
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: If-Match
+ in: header
+ schema:
+ type: string
+ description: ETag from previous GET for concurrency control
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RequestUpdate'
+ responses:
+ '200':
+ description: Request updated
+ headers:
+ ETag:
+ schema:
+ type: string
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Request'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '409':
+ $ref: '#/components/responses/Conflict'
+ '412':
+ $ref: '#/components/responses/PreconditionFailed'
+
+ /projects/{id}/workstreams/{wsId}/requests/{reqId}/forward:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/RequestId'
+
+ post:
+ tags: [Requests]
+ summary: Forward request in routing chain
+ description: |
+ Forwards the request to another user. Creates a routing chain hop.
+ The `return_to_id` is set automatically so completion returns to the forwarder.
+
+ **RBAC:** Current assignee or `ib_admin`
+ **Rate limit:** 60 requests/minute per user
+ operationId: forwardRequest
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [to_user_id]
+ properties:
+ to_user_id:
+ type: string
+ format: uuid
+ description: User to forward the request to
+ message:
+ type: string
+ description: Optional message for the recipient
+ responses:
+ '200':
+ description: Request forwarded
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Request'
+ '400':
+ description: Invalid recipient
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/requests/{reqId}/complete:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/RequestId'
+
+ post:
+ tags: [Requests]
+ summary: Mark request as complete
+ description: |
+ Marks the request as completed by the current assignee. If `return_to_id` is set,
+ the request is automatically routed back to that user's inbox. Otherwise, it moves
+ to the final status.
+
+ **RBAC:** Current assignee only
+ **Rate limit:** 60 requests/minute per user
+ operationId: completeRequest
+ security:
+ - bearerAuth: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ description: Optional completion note
+ responses:
+ '200':
+ description: Request completed and routed
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Request'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ # ============================================================================
+ # ANSWER ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/workstreams/{wsId}/answers:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+
+ post:
+ tags: [Answers]
+ summary: Create an answer
+ description: |
+ Creates a new answer in the workstream. Answers can be linked to one or more
+ requests. Initially created in `draft` status.
+
+ **RBAC:** `seller_admin`, `seller_member`, `ib_admin`, `ib_member`
+ **Rate limit:** 60 requests/minute per user
+ operationId: createAnswer
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AnswerCreate'
+ responses:
+ '201':
+ description: Answer created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Answer'
+ '400':
+ description: Invalid input
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/answers/{ansId}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/AnswerId'
+
+ get:
+ tags: [Answers]
+ summary: Get answer details
+ description: |
+ Returns full answer details including linked requests and files.
+
+ **RBAC:** Depends on answer status. Draft/submitted visible to IB+seller.
+ Published visible to all with workstream access.
+ **Rate limit:** 300 requests/minute per user
+ operationId: getAnswer
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: Answer details
+ headers:
+ ETag:
+ schema:
+ type: string
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Answer'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ patch:
+ tags: [Answers]
+ summary: Update answer
+ description: |
+ Updates answer content. Only allowed in `draft` or `rejected` status.
+
+ **RBAC:** Answer creator, `seller_admin`, or `ib_admin`
+ **Rate limit:** 60 requests/minute per user
+ operationId: updateAnswer
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: If-Match
+ in: header
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AnswerUpdate'
+ responses:
+ '200':
+ description: Answer updated
+ headers:
+ ETag:
+ schema:
+ type: string
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Answer'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '409':
+ $ref: '#/components/responses/Conflict'
+
+ /projects/{id}/workstreams/{wsId}/answers/{ansId}/submit:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/AnswerId'
+
+ post:
+ tags: [Answers]
+ summary: Submit answer for vetting
+ description: |
+ Submits the answer for IB review. Transitions status from `draft` to `submitted`.
+
+ **RBAC:** Answer creator, `seller_admin`
+ **Rate limit:** 60 requests/minute per user
+ operationId: submitAnswer
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: Answer submitted for vetting
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Answer'
+ '400':
+ description: Answer not in draft status
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/answers/{ansId}/approve:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/AnswerId'
+
+ post:
+ tags: [Answers]
+ summary: Approve answer
+ description: |
+ IB approves the answer. Transitions status from `submitted` to `approved`.
+ Answer is now ready for publishing to data room.
+
+ **RBAC:** `ib_admin`, `ib_member` with workstream access
+ **Rate limit:** 60 requests/minute per user
+ operationId: approveAnswer
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: Answer approved
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Answer'
+ '400':
+ description: Answer not in submitted status
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/answers/{ansId}/reject:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/AnswerId'
+
+ post:
+ tags: [Answers]
+ summary: Reject answer
+ description: |
+ IB rejects the answer with a reason. Transitions status from `submitted` to `rejected`.
+ Seller can then edit and resubmit.
+
+ **RBAC:** `ib_admin`, `ib_member` with workstream access
+ **Rate limit:** 60 requests/minute per user
+ operationId: rejectAnswer
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [reason]
+ properties:
+ reason:
+ type: string
+ minLength: 1
+ description: Explanation for why the answer was rejected
+ responses:
+ '200':
+ description: Answer rejected
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Answer'
+ '400':
+ description: Answer not in submitted status or missing reason
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/answers/{ansId}/publish:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/AnswerId'
+
+ post:
+ tags: [Answers]
+ summary: Publish answer to data room
+ description: |
+ Publishes an approved answer to the data room. All linked requests are updated
+ and broadcast notifications sent to all requesters who asked equivalent questions.
+
+ **RBAC:** `ib_admin`, `ib_member` with workstream access
+ **Rate limit:** 60 requests/minute per user
+ operationId: publishAnswer
+ security:
+ - bearerAuth: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ broadcast_to:
+ type: string
+ enum: [linked_requesters, all_workstream, all_dataroom]
+ default: linked_requesters
+ description: |
+ Broadcast scope:
+ - `linked_requesters`: Only users who submitted linked requests
+ - `all_workstream`: All users with workstream access
+ - `all_dataroom`: All users with any data room access
+ responses:
+ '200':
+ description: Answer published
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Answer'
+ '400':
+ description: Answer not in approved status
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ # ============================================================================
+ # AI MATCHING ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/workstreams/{wsId}/requests/{reqId}/matches:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/RequestId'
+
+ get:
+ tags: [Matches]
+ summary: Get AI-suggested answer matches
+ description: |
+ Returns published answers that the AI has identified as potentially satisfying
+ this request (cosine similarity >= 0.72). Human confirmation is required before
+ linking.
+
+ **RBAC:** `ib_admin`, `ib_member`
+ **Rate limit:** 20 requests/minute per user (embedding cost control)
+ operationId: getMatches
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: min_score
+ in: query
+ schema:
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 0.72
+ description: Minimum similarity score threshold
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ default: 10
+ maximum: 25
+ responses:
+ '200':
+ description: List of suggested matches
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ matches:
+ type: array
+ items:
+ $ref: '#/components/schemas/AnswerMatch'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/requests/{reqId}/matches/{answerId}/confirm:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/RequestId'
+ - $ref: '#/components/parameters/AnswerIdPath'
+
+ post:
+ tags: [Matches]
+ summary: Confirm AI match
+ description: |
+ Human confirms that the suggested answer satisfies this request.
+ Creates a confirmed link in `answer_links` and updates request status.
+ Triggers broadcast to the requester.
+
+ **RBAC:** `ib_admin`, `ib_member`
+ **Rate limit:** 60 requests/minute per user
+ operationId: confirmMatch
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: Match confirmed
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ request:
+ $ref: '#/components/schemas/Request'
+ answer:
+ $ref: '#/components/schemas/Answer'
+ link_id:
+ type: string
+ format: uuid
+ '400':
+ description: Answer not published or already linked
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/workstreams/{wsId}/requests/{reqId}/matches/{answerId}/reject:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/WorkstreamId'
+ - $ref: '#/components/parameters/RequestId'
+ - $ref: '#/components/parameters/AnswerIdPath'
+
+ post:
+ tags: [Matches]
+ summary: Reject AI match
+ description: |
+ Human rejects the AI suggestion. The answer will not be suggested again for
+ this request (stored in `answer_links` with rejected flag).
+
+ **RBAC:** `ib_admin`, `ib_member`
+ **Rate limit:** 60 requests/minute per user
+ operationId: rejectMatch
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: Match rejected
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: Match rejected and will not be suggested again
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ # ============================================================================
+ # FILE/OBJECT ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/objects:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+
+ post:
+ tags: [Objects]
+ summary: Upload file
+ description: |
+ Uploads a file to the project's object store. Files are encrypted at rest.
+ Returns an objectId that can be referenced in answers.
+
+ **RBAC:** `ib_admin`, `ib_member`, `seller_admin`, `seller_member`
+ **Rate limit:** 10 uploads/minute per user
+ **Max size:** 100MB per file
+ operationId: uploadObject
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ required: [file]
+ properties:
+ file:
+ type: string
+ format: binary
+ filename:
+ type: string
+ description: Override filename (defaults to uploaded name)
+ description:
+ type: string
+ description: File description for search
+ responses:
+ '201':
+ description: File uploaded
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ object_id:
+ type: string
+ pattern: '^[a-f0-9]{16}$'
+ description: Content-addressable object ID
+ filename:
+ type: string
+ size:
+ type: integer
+ description: File size in bytes
+ mime_type:
+ type: string
+ uploaded_at:
+ type: string
+ format: date-time
+ '400':
+ description: Invalid file or missing data
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '413':
+ description: File too large
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /projects/{id}/objects/{objectId}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/ObjectId'
+
+ get:
+ tags: [Objects]
+ summary: Download file with watermark
+ description: |
+ Downloads a file with dynamic watermark applied. Watermarks include:
+ - User name and organization
+ - Timestamp
+ - CONFIDENTIAL marking
+
+ PDF, Word, Excel, and images have visible watermarks.
+ Other files are encrypted downloads only.
+
+ **RBAC:** Any role with access to the answer containing this file
+ **Rate limit:** 50 downloads/minute per user (exfiltration protection)
+ operationId: downloadObject
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: preview
+ in: query
+ schema:
+ type: boolean
+ default: false
+ description: Return preview (lower quality) instead of full file
+ responses:
+ '200':
+ description: File content with watermark
+ headers:
+ Content-Disposition:
+ schema:
+ type: string
+ example: 'attachment; filename="document.pdf"'
+ Content-Type:
+ schema:
+ type: string
+ X-Watermark-Applied:
+ schema:
+ type: string
+ enum: [visible, encrypted, none]
+ content:
+ '*/*':
+ schema:
+ type: string
+ format: binary
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ delete:
+ tags: [Objects]
+ summary: Delete file
+ description: |
+ Deletes a file from the object store. File must not be referenced by any
+ published answer.
+
+ **RBAC:** File uploader, `ib_admin`, `seller_admin`
+ **Rate limit:** 60 requests/minute per user
+ operationId: deleteObject
+ security:
+ - bearerAuth: []
+ responses:
+ '204':
+ description: File deleted
+ '400':
+ description: File is referenced by published answer
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ # ============================================================================
+ # TASK ENDPOINTS
+ # ============================================================================
+
+ /tasks:
+ get:
+ tags: [Tasks]
+ summary: List personal tasks
+ description: |
+ Returns all tasks (requests) assigned to the current user across all projects.
+ This is the user's personal inbox.
+
+ **RBAC:** Any authenticated user
+ **Rate limit:** 300 requests/minute per user
+ operationId: listTasks
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: status
+ in: query
+ schema:
+ type: string
+ enum: [open, assigned, answered, vetted, published, closed]
+ description: Filter by status
+ - name: overdue
+ in: query
+ schema:
+ type: boolean
+ description: Only return tasks past due date
+ - name: project_id
+ in: query
+ schema:
+ type: string
+ format: uuid
+ description: Filter by project
+ - name: priority
+ in: query
+ schema:
+ type: string
+ enum: [high, normal, low]
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ default: 50
+ maximum: 100
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ default: 0
+ responses:
+ '200':
+ description: List of tasks
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ tasks:
+ type: array
+ items:
+ $ref: '#/components/schemas/Task'
+ total:
+ type: integer
+ limit:
+ type: integer
+ offset:
+ type: integer
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+
+ # ============================================================================
+ # ACCESS ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/access:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+
+ get:
+ tags: [Access]
+ summary: List access grants
+ description: |
+ Returns all access grants for the project.
+
+ **RBAC:** `ib_admin`, `seller_admin` (own org), `buyer_admin` (own org)
+ **Rate limit:** 300 requests/minute per user
+ operationId: listAccess
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: role
+ in: query
+ schema:
+ type: string
+ enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer]
+ description: Filter by role
+ - name: workstream_id
+ in: query
+ schema:
+ type: string
+ format: uuid
+ description: Filter by workstream
+ responses:
+ '200':
+ description: List of access grants
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ grants:
+ type: array
+ items:
+ $ref: '#/components/schemas/AccessGrant'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ post:
+ tags: [Access]
+ summary: Grant access
+ description: |
+ Grants a role to a user for the project and/or specific workstream.
+ Users can only grant roles at or below their level.
+ IB can grant any role. Seller/Buyer can only grant within their org type.
+
+ **RBAC:** `ib_admin`, `seller_admin`, `buyer_admin` (with hierarchy constraints)
+ **Rate limit:** 60 requests/minute per user
+ operationId: grantAccess
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AccessGrantCreate'
+ responses:
+ '201':
+ description: Access granted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AccessGrant'
+ '400':
+ description: Invalid input or role hierarchy violation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/access/{grantId}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/GrantId'
+
+ delete:
+ tags: [Access]
+ summary: Revoke access
+ description: |
+ Revokes an access grant. All active sessions for the affected user are immediately
+ invalidated for this project.
+
+ **RBAC:** `ib_admin`, or admin of same org type who granted the access
+ **Rate limit:** 60 requests/minute per user
+ operationId: revokeAccess
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: reason
+ in: query
+ schema:
+ type: string
+ description: Audit trail reason for revocation
+ responses:
+ '204':
+ description: Access revoked
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ # ============================================================================
+ # INVITE ENDPOINTS
+ # ============================================================================
+
+ /projects/{id}/invites:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+
+ get:
+ tags: [Invites]
+ summary: List pending invites
+ description: |
+ Returns all pending invitations for the project.
+
+ **RBAC:** `ib_admin`, `seller_admin`, `buyer_admin`
+ **Rate limit:** 300 requests/minute per user
+ operationId: listInvites
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: status
+ in: query
+ schema:
+ type: string
+ enum: [pending, accepted, expired, revoked]
+ responses:
+ '200':
+ description: List of invites
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invites:
+ type: array
+ items:
+ $ref: '#/components/schemas/Invite'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ post:
+ tags: [Invites]
+ summary: Create invite
+ description: |
+ Creates an invitation for a user to join the project with a specific role.
+ Invites are scoped to a project and optionally to specific workstreams.
+ Invite token expires in 72 hours.
+
+ **RBAC:** `ib_admin`, `seller_admin`, `buyer_admin` (with role hierarchy constraints)
+ **Rate limit:** 30 requests/minute per user
+ operationId: createInvite
+ security:
+ - bearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/InviteCreate'
+ responses:
+ '201':
+ description: Invite created and sent
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Invite'
+ '400':
+ description: Invalid input or role hierarchy violation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ /projects/{id}/invites/{inviteId}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/InviteId'
+
+ delete:
+ tags: [Invites]
+ summary: Revoke invite
+ description: |
+ Revokes a pending invitation. If already accepted, use access revocation instead.
+
+ **RBAC:** Invite creator or `ib_admin`
+ **Rate limit:** 60 requests/minute per user
+ operationId: revokeInvite
+ security:
+ - bearerAuth: []
+ responses:
+ '204':
+ description: Invite revoked
+ '400':
+ description: Invite already accepted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '404':
+ $ref: '#/components/responses/NotFound'
+
+ # ============================================================================
+ # AUDIT ENDPOINT
+ # ============================================================================
+
+ /projects/{id}/audit:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+
+ get:
+ tags: [Audit]
+ summary: Get audit log
+ description: |
+ Returns paginated audit log entries for the project. Includes all security-relevant
+ events: access changes, file downloads, status transitions, logins.
+
+ Audit entries are hash-chained for tamper evidence.
+
+ **RBAC:** `ib_admin` only
+ **Rate limit:** 30 requests/minute per user
+ operationId: getAuditLog
+ security:
+ - bearerAuth: []
+ parameters:
+ - name: action
+ in: query
+ schema:
+ type: string
+ description: Filter by action type (e.g., "file.downloaded", "access.granted")
+ - name: actor_id
+ in: query
+ schema:
+ type: string
+ format: uuid
+ description: Filter by actor
+ - name: target_id
+ in: query
+ schema:
+ type: string
+ format: uuid
+ description: Filter by target entry/user/file
+ - name: from
+ in: query
+ schema:
+ type: string
+ format: date-time
+ description: Start of time range
+ - name: to
+ in: query
+ schema:
+ type: string
+ format: date-time
+ description: End of time range
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ default: 100
+ maximum: 500
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ default: 0
+ responses:
+ '200':
+ description: Audit log entries
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ entries:
+ type: array
+ items:
+ $ref: '#/components/schemas/AuditEntry'
+ total:
+ type: integer
+ limit:
+ type: integer
+ offset:
+ type: integer
+ chain_verified:
+ type: boolean
+ description: Whether hash chain integrity was verified
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+
+ # ============================================================================
+ # HEALTH ENDPOINT
+ # ============================================================================
+
+ /health:
+ get:
+ tags: [Health]
+ summary: Service health check
+ description: |
+ Returns service health status. No authentication required.
+ Suitable for load balancer and monitoring probes.
+ operationId: healthCheck
+ security: []
+ responses:
+ '200':
+ description: Service healthy
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: [healthy, degraded]
+ version:
+ type: string
+ example: "1.0.0"
+ uptime:
+ type: integer
+ description: Uptime in seconds
+ checks:
+ type: object
+ properties:
+ database:
+ type: string
+ enum: [ok, error]
+ object_store:
+ type: string
+ enum: [ok, error]
+ redis:
+ type: string
+ enum: [ok, error]
+ '503':
+ description: Service unhealthy
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: [unhealthy]
+ errors:
+ type: array
+ items:
+ type: string
+
+# ==============================================================================
+# COMPONENTS
+# ==============================================================================
+
+components:
+ securitySchemes:
+ bearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
+ description: |
+ JWT token from `/auth/login` or `/auth/refresh`.
+ Token contains: sub (user_id), org, roles[], mfa, sid, fp, exp.
+
+ parameters:
+ ProjectId:
+ name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Project ID
+
+ WorkstreamId:
+ name: wsId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Workstream ID
+
+ RequestId:
+ name: reqId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Request ID
+
+ AnswerId:
+ name: ansId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Answer ID
+
+ AnswerIdPath:
+ name: answerId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Answer ID for matching
+
+ ObjectId:
+ name: objectId
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: '^[a-f0-9]{16}$'
+ description: Content-addressable object ID
+
+ GrantId:
+ name: grantId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Access grant ID
+
+ InviteId:
+ name: inviteId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Invite ID
+
+ responses:
+ Unauthorized:
+ description: Authentication required or token invalid
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ example:
+ error: "Invalid or expired authentication token"
+ code: "UNAUTHORIZED"
+ details: {}
+
+ Forbidden:
+ description: Insufficient permissions for this operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ example:
+ error: "You do not have permission to perform this action"
+ code: "FORBIDDEN"
+ details:
+ required_role: "ib_admin"
+
+ NotFound:
+ description: Resource not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ example:
+ error: "Resource not found"
+ code: "NOT_FOUND"
+ details: {}
+
+ Conflict:
+ description: Concurrent modification conflict
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ example:
+ error: "Resource was modified by another user"
+ code: "CONFLICT"
+ details:
+ current_version: 5
+
+ PreconditionFailed:
+ description: ETag mismatch - resource changed since last fetch
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ example:
+ error: "Resource has been modified. Fetch the latest version and retry."
+ code: "PRECONDITION_FAILED"
+ details:
+ current_etag: '"abc123"'
+
+ schemas:
+ Error:
+ type: object
+ required: [error, code]
+ properties:
+ error:
+ type: string
+ description: Human-readable error message
+ code:
+ type: string
+ description: Machine-readable error code
+ enum:
+ - BAD_REQUEST
+ - UNAUTHORIZED
+ - FORBIDDEN
+ - NOT_FOUND
+ - CONFLICT
+ - PRECONDITION_FAILED
+ - RATE_LIMIT_EXCEEDED
+ - INTERNAL_ERROR
+ - INVALID_INVITE
+ - INVITE_EXPIRED
+ - INVITE_ALREADY_USED
+ - EMAIL_MISMATCH
+ - MFA_REQUIRED
+ - INVALID_TOTP
+ - CONCURRENT_MODIFICATION
+ details:
+ type: object
+ additionalProperties: true
+ description: Additional error context
+
+ AuthTokens:
+ type: object
+ properties:
+ access_token:
+ type: string
+ description: JWT access token (1 hour lifetime)
+ refresh_token:
+ type: string
+ description: Refresh token (7 day lifetime, rotates on use)
+ token_type:
+ type: string
+ enum: [Bearer]
+ expires_in:
+ type: integer
+ description: Access token lifetime in seconds
+ mfa_required:
+ type: boolean
+ description: Whether MFA verification is pending
+ user:
+ $ref: '#/components/schemas/User'
+
+ User:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ email:
+ type: string
+ format: email
+ name:
+ type: string
+ organization_id:
+ type: string
+ format: uuid
+ organization_name:
+ type: string
+ mfa_enabled:
+ type: boolean
+ created_at:
+ type: string
+ format: date-time
+
+ Project:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ example: "Project Falcon"
+ description:
+ type: string
+ stage:
+ type: string
+ enum: [pre_dataroom, dataroom, closed]
+ theme_id:
+ type: string
+ format: uuid
+ nullable: true
+ created_by:
+ type: string
+ format: uuid
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+ stats:
+ type: object
+ properties:
+ workstreams:
+ type: integer
+ requests_total:
+ type: integer
+ requests_open:
+ type: integer
+ requests_completed:
+ type: integer
+ answers_published:
+ type: integer
+ my_role:
+ type: string
+ enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer]
+ description: Current user's role in this project
+
+ ProjectCreate:
+ type: object
+ required: [name]
+ properties:
+ name:
+ type: string
+ minLength: 1
+ maxLength: 255
+ description:
+ type: string
+ maxLength: 2000
+ theme_id:
+ type: string
+ format: uuid
+
+ ProjectUpdate:
+ type: object
+ properties:
+ name:
+ type: string
+ minLength: 1
+ maxLength: 255
+ description:
+ type: string
+ maxLength: 2000
+ stage:
+ type: string
+ enum: [pre_dataroom, dataroom, closed]
+ theme_id:
+ type: string
+ format: uuid
+ nullable: true
+
+ Workstream:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ example: "Finance"
+ slug:
+ type: string
+ example: "finance"
+ description:
+ type: string
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+ stats:
+ type: object
+ properties:
+ requests_total:
+ type: integer
+ requests_open:
+ type: integer
+ answers_published:
+ type: integer
+
+ WorkstreamCreate:
+ type: object
+ required: [name]
+ properties:
+ name:
+ type: string
+ minLength: 1
+ maxLength: 100
+ slug:
+ type: string
+ pattern: '^[a-z0-9-]+$'
+ maxLength: 50
+ description: URL-safe identifier (auto-generated from name if not provided)
+ description:
+ type: string
+ maxLength: 1000
+
+ WorkstreamUpdate:
+ type: object
+ properties:
+ name:
+ type: string
+ minLength: 1
+ maxLength: 100
+ description:
+ type: string
+ maxLength: 1000
+
+ RequestList:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ workstream_id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ example: "Initial Due Diligence"
+ description:
+ type: string
+ created_by:
+ type: string
+ format: uuid
+ created_at:
+ type: string
+ format: date-time
+ request_count:
+ type: integer
+
+ RequestListCreate:
+ type: object
+ required: [name]
+ properties:
+ name:
+ type: string
+ minLength: 1
+ maxLength: 255
+ description:
+ type: string
+ maxLength: 1000
+
+ Request:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ workstream_id:
+ type: string
+ format: uuid
+ list_id:
+ type: string
+ format: uuid
+ nullable: true
+ ref:
+ type: string
+ example: "FIN-042"
+ description: Human-readable reference number
+ title:
+ type: string
+ example: "Provide audited financials FY2024"
+ body:
+ type: string
+ priority:
+ type: string
+ enum: [high, normal, low]
+ due_date:
+ type: string
+ format: date
+ nullable: true
+ status:
+ type: string
+ enum: [open, assigned, answered, vetted, published, closed]
+ stage:
+ type: string
+ enum: [pre_dataroom, dataroom, closed]
+ assignee_id:
+ type: string
+ format: uuid
+ nullable: true
+ description: Current assignee (who has it RIGHT NOW)
+ assignee:
+ $ref: '#/components/schemas/User'
+ return_to_id:
+ type: string
+ format: uuid
+ nullable: true
+ description: Who it returns to when completed
+ origin_id:
+ type: string
+ format: uuid
+ nullable: true
+ description: Ultimate requester who started the chain
+ created_by:
+ type: string
+ format: uuid
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+ routing_chain:
+ type: array
+ description: Full routing history (visible to IB only)
+ items:
+ type: object
+ properties:
+ actor_id:
+ type: string
+ format: uuid
+ action:
+ type: string
+ enum: [created, forwarded, completed]
+ timestamp:
+ type: string
+ format: date-time
+ message:
+ type: string
+ linked_answers:
+ type: array
+ items:
+ type: object
+ properties:
+ answer_id:
+ type: string
+ format: uuid
+ confirmed:
+ type: boolean
+ linked_at:
+ type: string
+ format: date-time
+
+ RequestCreate:
+ type: object
+ required: [title]
+ properties:
+ title:
+ type: string
+ minLength: 1
+ maxLength: 500
+ body:
+ type: string
+ maxLength: 10000
+ list_id:
+ type: string
+ format: uuid
+ description: Optional request list to add to
+ priority:
+ type: string
+ enum: [high, normal, low]
+ default: normal
+ due_date:
+ type: string
+ format: date
+ assigned_to:
+ type: array
+ items:
+ type: string
+ format: uuid
+ description: Initial assignees
+
+ RequestUpdate:
+ type: object
+ properties:
+ title:
+ type: string
+ minLength: 1
+ maxLength: 500
+ body:
+ type: string
+ maxLength: 10000
+ priority:
+ type: string
+ enum: [high, normal, low]
+ due_date:
+ type: string
+ format: date
+ nullable: true
+ assigned_to:
+ type: array
+ items:
+ type: string
+ format: uuid
+ status:
+ type: string
+ enum: [open, assigned, answered, vetted, published, closed]
+
+ Answer:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ workstream_id:
+ type: string
+ format: uuid
+ title:
+ type: string
+ body:
+ type: string
+ status:
+ type: string
+ enum: [draft, submitted, approved, rejected, published]
+ rejection_reason:
+ type: string
+ nullable: true
+ files:
+ type: array
+ items:
+ type: object
+ properties:
+ object_id:
+ type: string
+ pattern: '^[a-f0-9]{16}$'
+ filename:
+ type: string
+ size:
+ type: integer
+ mime_type:
+ type: string
+ linked_requests:
+ type: array
+ items:
+ type: object
+ properties:
+ request_id:
+ type: string
+ format: uuid
+ ref:
+ type: string
+ title:
+ type: string
+ confirmed:
+ type: boolean
+ broadcast_to:
+ type: string
+ enum: [linked_requesters, all_workstream, all_dataroom]
+ nullable: true
+ created_by:
+ type: string
+ format: uuid
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+ submitted_at:
+ type: string
+ format: date-time
+ nullable: true
+ approved_at:
+ type: string
+ format: date-time
+ nullable: true
+ published_at:
+ type: string
+ format: date-time
+ nullable: true
+
+ AnswerCreate:
+ type: object
+ required: [title]
+ properties:
+ title:
+ type: string
+ minLength: 1
+ maxLength: 500
+ body:
+ type: string
+ maxLength: 50000
+ file_ids:
+ type: array
+ items:
+ type: string
+ pattern: '^[a-f0-9]{16}$'
+ description: Object IDs of uploaded files
+ request_ids:
+ type: array
+ items:
+ type: string
+ format: uuid
+ description: Requests this answer is linked to
+
+ AnswerUpdate:
+ type: object
+ properties:
+ title:
+ type: string
+ minLength: 1
+ maxLength: 500
+ body:
+ type: string
+ maxLength: 50000
+ file_ids:
+ type: array
+ items:
+ type: string
+ pattern: '^[a-f0-9]{16}$'
+ request_ids:
+ type: array
+ items:
+ type: string
+ format: uuid
+
+ AnswerMatch:
+ type: object
+ properties:
+ answer_id:
+ type: string
+ format: uuid
+ answer_title:
+ type: string
+ answer_status:
+ type: string
+ enum: [published]
+ similarity_score:
+ type: number
+ format: float
+ minimum: 0
+ maximum: 1
+ description: Cosine similarity score (AI-computed)
+ matched_text:
+ type: string
+ description: Snippet of answer that matched the request
+ linked_requests:
+ type: integer
+ description: Number of other requests already linked to this answer
+
+ Task:
+ type: object
+ description: A request in the user's personal inbox
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ project_name:
+ type: string
+ workstream_id:
+ type: string
+ format: uuid
+ workstream_name:
+ type: string
+ ref:
+ type: string
+ title:
+ type: string
+ priority:
+ type: string
+ enum: [high, normal, low]
+ due_date:
+ type: string
+ format: date
+ nullable: true
+ status:
+ type: string
+ enum: [open, assigned, answered, vetted, published, closed]
+ is_overdue:
+ type: boolean
+ return_to:
+ $ref: '#/components/schemas/User'
+ forwarded_by:
+ $ref: '#/components/schemas/User'
+ assigned_at:
+ type: string
+ format: date-time
+ created_at:
+ type: string
+ format: date-time
+
+ AccessGrant:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ workstream_id:
+ type: string
+ format: uuid
+ nullable: true
+ description: If null, access applies to all workstreams
+ user_id:
+ type: string
+ format: uuid
+ user:
+ $ref: '#/components/schemas/User'
+ role:
+ type: string
+ enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer]
+ ops:
+ type: string
+ description: "Operation flags: r=read, w=write, d=delete, m=manage"
+ example: "rw"
+ granted_by:
+ type: string
+ format: uuid
+ granted_at:
+ type: string
+ format: date-time
+
+ AccessGrantCreate:
+ type: object
+ required: [user_id, role]
+ properties:
+ user_id:
+ type: string
+ format: uuid
+ role:
+ type: string
+ enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer]
+ workstream_id:
+ type: string
+ format: uuid
+ nullable: true
+ description: Scope to specific workstream (null = all workstreams)
+ ops:
+ type: string
+ default: "rw"
+ description: "Operation flags: r=read, w=write, d=delete, m=manage"
+
+ Invite:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ workstream_id:
+ type: string
+ format: uuid
+ nullable: true
+ email:
+ type: string
+ format: email
+ role:
+ type: string
+ enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer]
+ status:
+ type: string
+ enum: [pending, accepted, expired, revoked]
+ invited_by:
+ type: string
+ format: uuid
+ invited_by_name:
+ type: string
+ expires_at:
+ type: string
+ format: date-time
+ accepted_at:
+ type: string
+ format: date-time
+ nullable: true
+ created_at:
+ type: string
+ format: date-time
+
+ InviteCreate:
+ type: object
+ required: [email, role]
+ properties:
+ email:
+ type: string
+ format: email
+ role:
+ type: string
+ enum: [ib_admin, ib_member, seller_admin, seller_member, buyer_admin, buyer_member, observer]
+ workstream_id:
+ type: string
+ format: uuid
+ nullable: true
+ description: Scope to specific workstream
+ message:
+ type: string
+ maxLength: 1000
+ description: Personal message to include in invite email
+
+ AuditEntry:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ actor_id:
+ type: string
+ format: uuid
+ actor_email:
+ type: string
+ format: email
+ action:
+ type: string
+ description: |
+ Event type. Examples:
+ - auth.login, auth.logout, auth.mfa_enabled
+ - access.granted, access.revoked
+ - entry.created, entry.updated, entry.published
+ - file.uploaded, file.downloaded, file.deleted
+ - session.created, session.revoked
+ target_type:
+ type: string
+ enum: [entry, user, access, session, file]
+ nullable: true
+ target_id:
+ type: string
+ format: uuid
+ nullable: true
+ details:
+ type: object
+ additionalProperties: true
+ description: Action-specific details
+ ip_address:
+ type: string
+ user_agent:
+ type: string
+ timestamp:
+ type: string
+ format: date-time
+ hash:
+ type: string
+ description: SHA-256 hash for tamper detection (chain integrity)
diff --git a/ONBOARDING-SPEC.md b/ONBOARDING-SPEC.md
new file mode 100644
index 0000000..60f5f0b
--- /dev/null
+++ b/ONBOARDING-SPEC.md
@@ -0,0 +1,1500 @@
+# Dealspace — Onboarding Flow Specification
+
+**Version:** 0.1 — 2026-02-28
+**Status:** Pre-implementation specification
+
+---
+
+## 1. Account Creation (IB Admin)
+
+### 1.1 Registration Form
+
+**URL:** `https://app.muskepo.com/register`
+
+**Form Fields:**
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Create Your Account │
+├─────────────────────────────────────────────────────────────┤
+│ Organization Name [Goldman Sachs ] │
+│ Your Name [Sarah Mitchell ] │
+│ Work Email [sarah.mitchell@gs.com ] │
+│ Password [•••••••••••••• ] │
+│ Confirm Password [•••••••••••••• ] │
+│ │
+│ □ I agree to the Terms of Service and Privacy Policy │
+│ │
+│ [ Create Account ] │
+│ │
+│ Already have an account? Sign in │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**Validation:**
+- Email must be corporate domain (no gmail.com, yahoo.com, etc.) — soft warning, not hard block
+- Password: 12+ characters, at least one uppercase, one number, one special
+- Organization name: required, 2–100 characters
+
+**Data Created:**
+```sql
+-- User record (pending verification)
+INSERT INTO users (id, email, name, password_hash, org_name, status, created_at)
+VALUES ('usr_abc123', 'sarah.mitchell@gs.com', 'Sarah Mitchell', '$argon2id$...',
+ 'Goldman Sachs', 'pending_verification', 1709136000000);
+```
+
+### 1.2 Email Verification Flow
+
+**Email sent immediately after form submission:**
+
+```
+Subject: Verify your Dealspace account
+
+Hi Sarah,
+
+Click below to verify your email and activate your Dealspace account:
+
+[Verify Email Address]
+https://app.muskepo.com/verify?token=abc123def456
+
+This link expires in 24 hours.
+
+If you didn't create this account, you can safely ignore this email.
+
+—
+The Dealspace Team
+```
+
+**On click:**
+1. Token validated (single-use, 24h expiry)
+2. User status → `pending_mfa`
+3. Redirect to `/setup/mfa`
+
+### 1.3 MFA Setup (Mandatory for IB Admin)
+
+**URL:** `https://app.muskepo.com/setup/mfa`
+
+**Step-by-step flow:**
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Set Up Two-Factor Authentication │
+│ │
+│ Step 1 of 3: Install Authenticator App │
+│ ───────────────────────────────────────────────────────── │
+│ │
+│ Download one of these apps on your phone: │
+│ │
+│ • Google Authenticator (iOS / Android) │
+│ • Microsoft Authenticator (iOS / Android) │
+│ • 1Password, Authy, or any TOTP app │
+│ │
+│ Already have one? [Continue →] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Set Up Two-Factor Authentication │
+│ │
+│ Step 2 of 3: Scan QR Code │
+│ ───────────────────────────────────────────────────────── │
+│ │
+│ ┌───────────────┐ │
+│ │ [QR CODE] │ │
+│ │ │ │
+│ └───────────────┘ │
+│ │
+│ Can't scan? Enter this code manually: │
+│ JBSWY3DPEHPK3PXP (tap to copy) │
+│ │
+│ [Continue →] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Set Up Two-Factor Authentication │
+│ │
+│ Step 3 of 3: Verify Setup │
+│ ───────────────────────────────────────────────────────── │
+│ │
+│ Enter the 6-digit code from your authenticator app: │
+│ │
+│ [ 4 ] [ 7 ] [ 2 ] [ 9 ] [ 1 ] [ 5 ] │
+│ │
+│ [ Verify & Complete Setup ] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**On successful verification:**
+1. TOTP secret stored (encrypted) in user record
+2. Recovery codes generated (10 codes, 8 characters each)
+3. User status → `active`
+4. Show recovery codes screen:
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Save Your Recovery Codes │
+│ │
+│ Store these in a safe place. You'll need them if you │
+│ lose access to your authenticator app. │
+│ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 1. XKCD-7843 6. BEEF-2947 │ │
+│ │ 2. PASS-9126 7. DORK-5182 │ │
+│ │ 3. MOON-4521 8. JAZZ-7394 │ │
+│ │ 4. TREE-8734 9. FORK-6215 │ │
+│ │ 5. WAVE-3069 10. LAMP-9847 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ [Download as PDF] [Copy to clipboard] │
+│ │
+│ □ I have saved my recovery codes │
+│ │
+│ [ Continue to Dealspace ] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 1.4 First Login Experience
+
+**URL:** `https://app.muskepo.com/app`
+
+User lands on an empty dashboard with a prominent call to action:
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE Goldman Sachs | Sarah Mitchell ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ │
+│ ┌──────────────────────────┐ │
+│ │ 📁 │ │
+│ │ │ │
+│ │ No projects yet │ │
+│ │ │ │
+│ │ Create your first │ │
+│ │ deal to get started │ │
+│ │ │ │
+│ │ [+ Create Project] │ │
+│ │ │ │
+│ └──────────────────────────┘ │
+│ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 2. First Project Setup Wizard
+
+Clicking "Create Project" launches a 5-step wizard. Each step is a focused screen. Progress bar at top.
+
+### 2.1 Step 1: Project Name + Deal Type
+
+**URL:** `https://app.muskepo.com/app/projects/new/basics`
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Create Project Step 1 of 5 ━━━○○○○ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Project Name │
+│ [TechCorp Acquisition - Project Phoenix ] │
+│ │
+│ Deal Type │
+│ ┌───────────────────┐ ┌───────────────────┐ │
+│ │ ○ Sell-side │ │ ○ Buy-side │ │
+│ │ M&A Advisory │ │ M&A Advisory │ │
+│ └───────────────────┘ └───────────────────┘ │
+│ ┌───────────────────┐ ┌───────────────────┐ │
+│ │ ○ Merger │ │ ○ Other │ │
+│ │ │ │ │ │
+│ └───────────────────┘ └───────────────────┘ │
+│ │
+│ Code Name (optional, shown to buyers instead of project name) │
+│ [Project Phoenix ] │
+│ │
+│ [Cancel] [Next: Workstreams] │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+**Data Created (in memory, not persisted until Step 5):**
+```json
+{
+ "name": "TechCorp Acquisition - Project Phoenix",
+ "deal_type": "sell_side",
+ "code_name": "Project Phoenix",
+ "workstreams": [],
+ "team": [],
+ "seller_contacts": []
+}
+```
+
+### 2.2 Step 2: Configure Workstreams
+
+**URL:** `https://app.muskepo.com/app/projects/new/workstreams`
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Create Project Step 2 of 5 ━━━━○○○ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Configure Workstreams │
+│ │
+│ These are the areas your deal will cover. You can add, remove, or │
+│ rename them. Each workstream has its own access controls. │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────┐ │
+│ │ ☑ Finance [Edit name] [Remove] │ │
+│ │ ☑ Legal [Edit name] [Remove] │ │
+│ │ ☑ IT [Edit name] [Remove] │ │
+│ │ ☑ HR [Edit name] [Remove] │ │
+│ │ ☑ Operations [Edit name] [Remove] │ │
+│ │ ☐ Tax [Edit name] [Remove] │ │
+│ │ ☐ Commercial [Edit name] [Remove] │ │
+│ │ ☐ Environmental [Edit name] [Remove] │ │
+│ └─────────────────────────────────────────────────────────────────┘ │
+│ │
+│ [+ Add Custom Workstream] │
+│ │
+│ [Back] [Next: Invite Team] │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+**Default workstreams (pre-checked):**
+- Finance
+- Legal
+- IT
+- HR
+- Operations
+
+**Additional suggestions (unchecked):**
+- Tax
+- Commercial
+- Environmental
+- Regulatory
+- Insurance
+
+**Custom workstream modal:**
+```
+┌────────────────────────────────────┐
+│ Add Workstream │
+│ │
+│ Name: [IP & Patents ] │
+│ │
+│ [Cancel] [Add Workstream] │
+└────────────────────────────────────┘
+```
+
+### 2.3 Step 3: Invite IB Team Members
+
+**URL:** `https://app.muskepo.com/app/projects/new/team`
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Create Project Step 3 of 5 ━━━━━○○ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Invite Your Team │
+│ Add team members and assign them to workstreams. │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Email Name Role Workstreams │ │
+│ ├─────────────────────────────────────────────────────────────────────┤ │
+│ │ mike@gs.com Mike Chen ib_member Finance, Tax │ │
+│ │ [Edit] [Remove] │ │
+│ ├─────────────────────────────────────────────────────────────────────┤ │
+│ │ jen@gs.com Jen Park ib_member Legal │ │
+│ │ [Edit] [Remove] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ [+ Add Team Member] │
+│ │
+│ ────────────────────────────────────────────────────────────────────── │
+│ Or paste multiple emails (one per line): │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ alex@gs.com │ │
+│ │ david@gs.com │ │
+│ │ lisa@gs.com │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ [Parse & Add] │
+│ │
+│ [Back] [Next: Invite Seller] │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+**Add team member modal:**
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Add Team Member │
+│ │
+│ Email Address │
+│ [mike.chen@gs.com ] │
+│ │
+│ Full Name │
+│ [Mike Chen ] │
+│ │
+│ Role │
+│ ○ IB Admin (full access, can manage project) │
+│ ● IB Member (manage requests + vet answers in workstreams) │
+│ │
+│ Workstream Access │
+│ ☑ Finance │
+│ ☐ Legal │
+│ ☐ IT │
+│ ☐ HR │
+│ ☐ Operations │
+│ ☑ Tax │
+│ │
+│ [Cancel] [Add to Team] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 2.4 Step 4: Invite Seller Contacts
+
+**URL:** `https://app.muskepo.com/app/projects/new/seller`
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Create Project Step 4 of 5 ━━━━━━○ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Invite Seller Team │
+│ Add the sell-side company's contacts. They'll receive access invites. │
+│ │
+│ Seller Organization Name │
+│ [TechCorp Inc. ] │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Email Name Title Role │ │
+│ ├─────────────────────────────────────────────────────────────────────┤ │
+│ │ john.smith@techcorp. John Smith CFO seller_ │ │
+│ │ com admin │ │
+│ │ [Edit] [Remove] │ │
+│ ├─────────────────────────────────────────────────────────────────────┤ │
+│ │ mary.johnson@techcorp Mary Johnson Controller seller_ │ │
+│ │ .com member │ │
+│ │ [Edit] [Remove] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ [+ Add Seller Contact] │
+│ │
+│ ⓘ Seller Admin can manage their team and see all workstreams. │
+│ ⓘ Seller Members only see assigned workstreams. │
+│ │
+│ [Back] [Next: Review & Launch] │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+**Add seller contact modal:**
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Add Seller Contact │
+│ │
+│ Email Address │
+│ [john.smith@techcorp.com ] │
+│ │
+│ Full Name │
+│ [John Smith ] │
+│ │
+│ Title │
+│ [CFO ] │
+│ │
+│ Role │
+│ ● Seller Admin (manage seller team, all workstreams) │
+│ ○ Seller Member (answer requests in assigned workstreams) │
+│ │
+│ Workstream Access (for Seller Member only) │
+│ ☑ Finance │
+│ ☐ Legal │
+│ ... │
+│ │
+│ [Cancel] [Add Contact] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 2.5 Step 5: Review & Launch
+
+**URL:** `https://app.muskepo.com/app/projects/new/review`
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Create Project Step 5 of 5 ━━━━━━━ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Review & Launch │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ PROJECT DETAILS │ │
+│ │ Name: TechCorp Acquisition - Project Phoenix │ │
+│ │ Code Name: Project Phoenix (shown to buyers) │ │
+│ │ Type: Sell-side M&A Advisory [Edit] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ WORKSTREAMS (5) │ │
+│ │ Finance · Legal · IT · HR · Operations [Edit] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ YOUR TEAM (3 invites will be sent) │ │
+│ │ • Mike Chen (mike@gs.com) — IB Member, Finance + Tax │ │
+│ │ • Jen Park (jen@gs.com) — IB Member, Legal │ │
+│ │ • Alex Wong (alex@gs.com) — IB Member, IT [Edit] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ SELLER TEAM (2 invites will be sent) │ │
+│ │ • John Smith (john.smith@techcorp.com) — Seller Admin │ │
+│ │ • Mary Johnson (mary.johnson@techcorp.com) — Seller Member │ │
+│ │ [Edit] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ⓘ 5 invitation emails will be sent when you launch. │
+│ │
+│ [Back] [Launch Project →] │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 2.6 Data Created on Launch
+
+**Database operations (single transaction):**
+
+```sql
+-- Project entry
+INSERT INTO entries (entry_id, project_id, parent_id, type, depth, search_key, summary, data, stage)
+VALUES ('prj_abc123', 'prj_abc123', '', 'project', 0, 'techcorp-acquisition',
+ 'TechCorp Acquisition - Project Phoenix',
+ PACK('{"name":"...","code_name":"...","deal_type":"sell_side","seller_org":"TechCorp Inc."}'),
+ 'pre_dataroom');
+
+-- Workstream entries (5x)
+INSERT INTO entries (entry_id, project_id, parent_id, type, depth, search_key, summary, data, stage)
+VALUES
+ ('ws_fin', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'finance', 'Finance', PACK('{}'), 'pre_dataroom'),
+ ('ws_leg', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'legal', 'Legal', PACK('{}'), 'pre_dataroom'),
+ ('ws_it', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'it', 'IT', PACK('{}'), 'pre_dataroom'),
+ ('ws_hr', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'hr', 'HR', PACK('{}'), 'pre_dataroom'),
+ ('ws_ops', 'prj_abc123', 'prj_abc123', 'workstream', 1, 'operations', 'Operations', PACK('{}'), 'pre_dataroom');
+
+-- Access records (for creating IB admin)
+INSERT INTO access (id, project_id, workstream_id, user_id, role, ops, granted_by, granted_at)
+VALUES ('acc_001', 'prj_abc123', NULL, 'usr_sarah', 'ib_admin', 'rwdm', 'usr_sarah', 1709136000000);
+
+-- Pending invitations (stored until accepted)
+INSERT INTO invitations (id, project_id, email, name, role, workstream_ids, invited_by, expires_at)
+VALUES
+ ('inv_001', 'prj_abc123', 'mike@gs.com', 'Mike Chen', 'ib_member', '["ws_fin"]', 'usr_sarah', ...),
+ ('inv_002', 'prj_abc123', 'jen@gs.com', 'Jen Park', 'ib_member', '["ws_leg"]', 'usr_sarah', ...),
+ ('inv_003', 'prj_abc123', 'john.smith@techcorp.com', 'John Smith', 'seller_admin', NULL, 'usr_sarah', ...),
+ ('inv_004', 'prj_abc123', 'mary.johnson@techcorp.com', 'Mary Johnson', 'seller_member', '["ws_fin"]', 'usr_sarah', ...);
+```
+
+### 2.7 Emails Sent on Launch
+
+**To IB team members (3x):**
+See [Section 6.1 — IB Team Invite Email](#61-ib-team-invite-email)
+
+**To Seller contacts (2x):**
+See [Section 6.2 — Seller Invite Email](#62-seller-invite-email)
+
+### 2.8 Post-Launch Screen
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE [TechCorp Acquisition ▼] Goldman Sachs | Sarah ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ Finance | Legal | IT | HR | Operations │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ✓ Project Created │
+│ │
+│ 5 invitations sent. Here's what happens next: │
+│ │
+│ 1. Your team accepts their invitations │
+│ 2. Seller contacts accept and set up their accounts │
+│ 3. Create your first request list to start the due diligence │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Ready to start? │ │
+│ │ │ │
+│ │ [+ Create Request List] [View Team Status] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 3. Invite Flow (Seller Side)
+
+### 3.1 Seller Invite Email
+
+**The actual email the Seller CFO receives:**
+
+```
+From: TechCorp Acquisition via Dealspace
+Reply-To: sarah.mitchell@gs.com
+To: john.smith@techcorp.com
+Subject: You've been invited to Project Phoenix
+
+───────────────────────────────────────────────────────────────────────
+
+Hi John,
+
+Sarah Mitchell from Goldman Sachs has invited you to join the
+due diligence process for Project Phoenix.
+
+You've been assigned as Seller Admin, which means you can:
+• See all requests from the deal team
+• Assign tasks to your colleagues
+• Manage your team's access and responses
+
+[Accept Invitation]
+https://app.muskepo.com/invite/abc123def456
+
+This invitation expires in 7 days. If you have questions, reply
+to this email to reach Sarah directly.
+
+───────────────────────────────────────────────────────────────────────
+
+What is Dealspace?
+Dealspace is a secure platform for managing M&A due diligence.
+All documents are encrypted and watermarked. You control what
+gets shared and when.
+
+Questions? Contact support@muskepo.com
+```
+
+### 3.2 Invitation Landing Page
+
+**URL:** `https://app.muskepo.com/invite/abc123def456`
+
+**For new users (no existing account):**
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ │
+│ ┌───────────────────────────────────────────────────────────────────────┐ │
+│ │ │ │
+│ │ You're invited to │ │
+│ │ PROJECT PHOENIX │ │
+│ │ │ │
+│ │ Goldman Sachs has invited you to participate in this deal. │ │
+│ │ │ │
+│ │ Your Role: Seller Admin │ │
+│ │ Organization: TechCorp Inc. │ │
+│ │ │ │
+│ └───────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ Create Your Account │
+│ │
+│ Email (pre-filled, read-only) │
+│ [john.smith@techcorp.com ] │
+│ │
+│ Your Name │
+│ [John Smith ] │
+│ │
+│ Create Password │
+│ [•••••••••••• ] │
+│ 12+ characters, including uppercase, number, and special character │
+│ │
+│ Confirm Password │
+│ [•••••••••••• ] │
+│ │
+│ □ I agree to the Terms of Service and Privacy Policy │
+│ │
+│ [ Create Account & Join Project ] │
+│ │
+│ Already have an account? Sign in │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 3.3 MFA Setup (Seller)
+
+**For seller_admin: strongly recommended (shown after account creation)**
+**For seller_member: optional (can skip)**
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ │
+│ Secure Your Account │
+│ │
+│ Two-factor authentication adds an extra layer of security. │
+│ Goldman Sachs recommends enabling this for all deal participants. │
+│ │
+│ [Set Up Two-Factor Authentication] │
+│ │
+│ [Skip for now — I'll do this later] │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 3.4 First Login: Seller Task Inbox
+
+**URL:** `https://app.muskepo.com/app` (after accepting invite)
+
+On first login, the Seller Admin sees their task inbox — which is likely empty until the IB team creates request lists.
+
+**Empty state (no requests yet):**
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE [Project Phoenix ▼] TechCorp | John ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ MY TASKS [All Workstreams ▼] │
+│ │
+│ ┌──────────────────────────┐ │
+│ │ ✓ │ │
+│ │ │ │
+│ │ No tasks yet │ │
+│ │ │ │
+│ │ Goldman Sachs hasn't │ │
+│ │ sent any requests yet. │ │
+│ │ We'll notify you when │ │
+│ │ they do. │ │
+│ │ │ │
+│ └──────────────────────────┘ │
+│ │
+│ ────────────────────────────────────────────────────────────────────── │
+│ While you wait, you can: │
+│ • [Invite your team members] │
+│ • [Set up your profile] │
+│ • [Review the quick start guide] │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+**When requests exist (typical first login):**
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE [Project Phoenix ▼] TechCorp | John ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ MY TASKS [Filter ▼] [Sort: Due Date ▼] 🔍 │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ You have 12 new requests │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ FINANCE (8) │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ ⚡ FIN-001 Audited financial statements FY2023-2024 │ │
+│ │ Due: Mar 15 • High Priority • Assigned to: You [→] │
+│ ├─────────────────────────────────────────────────────────────────────┤ │
+│ │ FIN-002 Revenue breakdown by product line │ │
+│ │ Due: Mar 18 • Normal • Assigned to: You [→] │
+│ ├─────────────────────────────────────────────────────────────────────┤ │
+│ │ FIN-003 Accounts receivable aging report │ │
+│ │ Due: Mar 18 • Normal • Assigned to: Mary Johnson [→] │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ [Show 5 more...] │
+│ │
+│ LEGAL (4) │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ LEG-001 Material contracts list │ │
+│ │ Due: Mar 20 • High Priority • Assigned to: You [→] │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ [Show 3 more...] │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 4. First Request List
+
+### 4.1 Creating a Request List
+
+**IB member navigates to a workstream and clicks "New Request List"**
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE [TechCorp Acquisition ▼] Goldman Sachs | Mike ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ Finance | Legal | IT | HR | Operations │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ FINANCE — Request Lists │
+│ │
+│ [+ New Request List] [Import from Excel] │
+│ │
+│ (No request lists yet) │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 4.2 New Request List Modal
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Create Request List │
+│ │
+│ List Name │
+│ [Initial Finance DD Requests ] │
+│ │
+│ Description (optional) │
+│ [First round of financial due diligence requests for Project Phoenix ] │
+│ │
+│ Default Due Date │
+│ [2026-03-15 ] (can be overridden per request) │
+│ │
+│ Default Assignee │
+│ [John Smith (Seller Admin) ▼ ] (can be overridden per request) │
+│ │
+│ ────────────────────────────────────────────────────────────────────── │
+│ │
+│ How would you like to add requests? │
+│ │
+│ ○ Add requests manually (one at a time) │
+│ ● Import from Excel template │
+│ [Download Template] │
+│ │
+│ [Cancel] [Create List] │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 4.3 Excel Import Template
+
+**File: `dealspace-request-template.xlsx`**
+
+| Column | Header | Required | Description | Valid Values |
+|--------|--------|----------|-------------|--------------|
+| A | ref | No | Reference number (auto-generated if blank) | e.g., FIN-001 |
+| B | title | Yes | Request title | Max 200 chars |
+| C | description | No | Detailed description | Max 5000 chars |
+| D | priority | No | Priority level | high, normal, low (default: normal) |
+| E | due_date | No | Due date | YYYY-MM-DD (default: list default) |
+| F | assigned_to | No | Assignee email | Must be valid seller email |
+
+**Sample template content:**
+
+```
+ref title description priority due_date assigned_to
+FIN-001 Audited financial statements FY2023-2024 Complete audited financials for the last two fiscal... high 2026-03-15 john.smith@techcorp.com
+FIN-002 Revenue breakdown by product line Monthly revenue by product line for last 24 months normal 2026-03-18 john.smith@techcorp.com
+FIN-003 Accounts receivable aging report Current AR aging as of latest month-end normal 2026-03-18 mary.johnson@techcorp.com
+FIN-004 Accounts payable aging report Current AP aging as of latest month-end normal 2026-03-18 mary.johnson@techcorp.com
+FIN-005 Cash flow projections FY2025-2027 3-year projected cash flows high 2026-03-20 john.smith@techcorp.com
+FIN-006 Bank statements (all accounts) Last 12 months of bank statements for all accounts normal 2026-03-22
+FIN-007 Debt schedule Current debt obligations with terms and covenants high 2026-03-18 john.smith@techcorp.com
+FIN-008 Capital expenditure history CapEx by category for last 3 years normal 2026-03-25 mary.johnson@techcorp.com
+```
+
+### 4.4 Import Preview Screen
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ Import Requests — Preview │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ✓ 8 requests parsed successfully │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ Ref Title Priority Due To │ │
+│ ├─────────────────────────────────────────────────────────────────────┤ │
+│ │ FIN-001 Audited financial statements... ⚡ High Mar 15 JS │ │
+│ │ FIN-002 Revenue breakdown by product... — Mar 18 JS │ │
+│ │ FIN-003 Accounts receivable aging... — Mar 18 MJ │ │
+│ │ FIN-004 Accounts payable aging... — Mar 18 MJ │ │
+│ │ FIN-005 Cash flow projections... ⚡ High Mar 20 JS │ │
+│ │ FIN-006 Bank statements (all accounts) — Mar 22 — │ │
+│ │ FIN-007 Debt schedule ⚡ High Mar 18 JS │ │
+│ │ FIN-008 Capital expenditure history — Mar 25 MJ │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ⚠ 1 request has no assignee (FIN-006) — will be assigned to John Smith │
+│ │
+│ □ Send notification emails to assignees now │
+│ □ Send a single summary email per assignee (instead of per-request) │
+│ │
+│ [Cancel] [Back to Edit] [Import 8 Requests →] │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 4.5 Notifications Fired
+
+**On import with "Send notification emails" checked:**
+
+1. **John Smith receives:** "You have 6 new requests in Project Phoenix"
+2. **Mary Johnson receives:** "You have 2 new requests in Project Phoenix"
+
+See [Section 6.3 — New Tasks Notification](#63-new-tasks-notification) for email copy.
+
+---
+
+## 5. The "Aha Moment"
+
+### 5.1 For the Investment Bank
+
+**The moment:** When the first answer comes back with documents attached, automatically indexed and searchable, and they click "Approve" to publish it to the data room — all in 3 clicks.
+
+**The comparison:** In the old workflow, this would involve:
+1. Receiving email with attachment
+2. Downloading attachment
+3. Opening VDR
+4. Navigating to correct folder
+5. Uploading document
+6. Renaming to match convention
+7. Setting permissions
+8. Updating tracking spreadsheet
+9. Emailing confirmation
+
+**Design toward this:**
+- Answer review screen shows document preview inline
+- One-click approve → "Published to Data Room" confirmation
+- Activity feed shows real-time: "FIN-001 answered → vetted → published"
+- Buyer count badge: "3 buyers can now see this"
+
+### 5.2 For the Seller
+
+**The moment:** When they upload their first answer and immediately see exactly what the bank is waiting on — a clear task list with priorities, due dates, and progress tracking, instead of hunting through email threads.
+
+**The comparison:** In the old workflow:
+1. Receive request list via email (usually PDF or Word)
+2. Print it out / copy to spreadsheet
+3. Manually track who's doing what
+4. Hunt through email for clarifications
+5. Reply-all with attachments
+6. No visibility into whether IB received/reviewed
+
+**Design toward this:**
+- Clear task inbox with filters (by workstream, priority, due date, assignee)
+- Progress bar per workstream: "Finance: 3/12 complete"
+- Real-time status: "Submitted → Under Review → Approved ✓"
+- Comments thread inline — no email back-and-forth
+- Mobile-friendly for on-the-go approvals
+
+### 5.3 For the Buyer
+
+**The moment:** When they submit a question and the AI matches it to an existing answer instantly — no waiting, no IB intermediary, just immediate access to relevant documents.
+
+**Design toward this:**
+- Search bar prominent in data room
+- "Ask a question" that triggers AI matching
+- Instant match: "This might answer your question" with preview
+- If no match: "Your question has been submitted" with expected response time
+- Broadcast notification: "New answer matches your previous question"
+
+---
+
+## 6. Email Templates
+
+### 6.1 IB Team Invite Email
+
+```
+From: Dealspace
+Reply-To: sarah.mitchell@gs.com
+To: mike.chen@gs.com
+Subject: Join Project Phoenix on Dealspace
+
+───────────────────────────────────────────────────────────────────────
+
+Hi Mike,
+
+Sarah Mitchell has invited you to join the deal team for
+Project Phoenix on Dealspace.
+
+Your Role: IB Member
+Workstreams: Finance, Tax
+
+[Accept Invitation]
+https://app.muskepo.com/invite/xyz789
+
+This invitation expires in 7 days.
+
+───────────────────────────────────────────────────────────────────────
+
+Dealspace is Goldman Sachs' secure platform for managing M&A
+due diligence. If you have questions about this invitation,
+contact Sarah directly.
+```
+
+### 6.2 Seller Invite Email
+
+```
+From: Project Phoenix via Dealspace
+Reply-To: sarah.mitchell@gs.com
+To: john.smith@techcorp.com
+Subject: You've been invited to Project Phoenix
+
+───────────────────────────────────────────────────────────────────────
+
+Hi John,
+
+Sarah Mitchell from Goldman Sachs has invited you to join the
+due diligence process for Project Phoenix.
+
+You've been assigned as Seller Admin, which means you can:
+• View and respond to all due diligence requests
+• Assign tasks to your colleagues
+• Track your team's progress across all workstreams
+
+[Accept Invitation]
+https://app.muskepo.com/invite/abc123
+
+This invitation expires in 7 days. If you have questions,
+reply to this email to reach Sarah directly.
+
+───────────────────────────────────────────────────────────────────────
+
+What is Dealspace?
+
+Dealspace is a secure platform for managing M&A due diligence.
+All documents are encrypted and dynamically watermarked. You
+maintain full control over what gets shared and when.
+
+Need help? Contact support@muskepo.com
+```
+
+### 6.3 New Tasks Notification
+
+```
+From: Project Phoenix via Dealspace
+To: john.smith@techcorp.com
+Subject: You have 6 new requests in Project Phoenix
+
+───────────────────────────────────────────────────────────────────────
+
+Hi John,
+
+Goldman Sachs has sent you 6 new requests for Project Phoenix.
+
+HIGH PRIORITY (3):
+• FIN-001: Audited financial statements FY2023-2024 — Due Mar 15
+• FIN-005: Cash flow projections FY2025-2027 — Due Mar 20
+• FIN-007: Debt schedule — Due Mar 18
+
+NORMAL PRIORITY (3):
+• FIN-002: Revenue breakdown by product line — Due Mar 18
+• FIN-006: Bank statements (all accounts) — Due Mar 22
+• Plus 1 more
+
+[View Your Tasks]
+https://app.muskepo.com/app/tasks
+
+───────────────────────────────────────────────────────────────────────
+
+You're receiving this because you're assigned to requests in
+Project Phoenix. Manage notification preferences in your settings.
+```
+
+### 6.4 Answer Submitted — Needs Vetting
+
+```
+From: Project Phoenix via Dealspace
+To: mike.chen@gs.com
+Subject: [Action Required] Answer submitted: FIN-001 Audited financials
+
+───────────────────────────────────────────────────────────────────────
+
+Hi Mike,
+
+John Smith from TechCorp has submitted an answer that needs
+your review.
+
+REQUEST: FIN-001 — Audited financial statements FY2023-2024
+SUBMITTED: 2 files attached
+• TechCorp_Audited_Financials_2023.pdf (2.4 MB)
+• TechCorp_Audited_Financials_2024.pdf (2.8 MB)
+
+COMMENT FROM SELLER:
+"Attached are the audited financials for FY2023 and FY2024.
+The 2024 audit was completed last month. Let me know if you
+need the management letters as well."
+
+[Review & Approve]
+https://app.muskepo.com/app/review/ans_xyz123
+
+───────────────────────────────────────────────────────────────────────
+
+Once approved, this answer will be published to the data room
+and visible to all authorized buyers.
+```
+
+### 6.5 Answer Approved / Rejected (Seller Notification)
+
+**Approved:**
+```
+From: Project Phoenix via Dealspace
+To: john.smith@techcorp.com
+Subject: ✓ Answer approved: FIN-001 Audited financials
+
+───────────────────────────────────────────────────────────────────────
+
+Hi John,
+
+Your answer to FIN-001 has been approved and published to
+the data room.
+
+REQUEST: FIN-001 — Audited financial statements FY2023-2024
+STATUS: ✓ Published to Data Room
+APPROVED BY: Mike Chen (Goldman Sachs)
+
+[View in Data Room]
+https://app.muskepo.com/app/dataroom/ans_xyz123
+
+───────────────────────────────────────────────────────────────────────
+```
+
+**Rejected:**
+```
+From: Project Phoenix via Dealspace
+To: john.smith@techcorp.com
+Subject: ⚠ Revision requested: FIN-001 Audited financials
+
+───────────────────────────────────────────────────────────────────────
+
+Hi John,
+
+Your answer to FIN-001 requires revision before it can be
+published.
+
+REQUEST: FIN-001 — Audited financial statements FY2023-2024
+STATUS: Revision Requested
+
+FEEDBACK FROM MIKE CHEN:
+"The 2024 financials are draft — we need the final audited
+version with the auditor's opinion letter. Please also include
+the management representation letters for both years."
+
+[Update Your Answer]
+https://app.muskepo.com/app/tasks/req_001
+
+───────────────────────────────────────────────────────────────────────
+
+Reply to this request directly in Dealspace to keep the
+conversation in one place.
+```
+
+### 6.6 New Answer Available in Data Room (Buyer Notification)
+
+```
+From: Project Phoenix via Dealspace
+To: analyst@buyside-pe.com
+Subject: New document available: Audited Financials
+
+───────────────────────────────────────────────────────────────────────
+
+A new document matching your interests is now available in the
+Project Phoenix data room.
+
+DOCUMENT: Audited Financial Statements FY2023-2024
+CATEGORY: Finance
+ADDED: February 28, 2026
+
+This document may be relevant to your previous question about
+"historical financial performance."
+
+[View in Data Room]
+https://app.muskepo.com/app/dataroom/ans_xyz123
+
+───────────────────────────────────────────────────────────────────────
+
+You're receiving this because you requested information about
+financial statements. Manage notification preferences in settings.
+```
+
+---
+
+## 7. Empty States
+
+### 7.1 IB Admin — No Projects
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE Goldman Sachs | Sarah Mitchell ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ │
+│ ┌──────────────────────────┐ │
+│ │ 📁 │ │
+│ │ │ │
+│ │ No projects yet │ │
+│ │ │ │
+│ │ Dealspace helps you │ │
+│ │ run M&A due diligence │ │
+│ │ 10x faster. │ │
+│ │ │ │
+│ │ [+ Create Your First │ │
+│ │ Project] │ │
+│ │ │ │
+│ └──────────────────────────┘ │
+│ │
+│ ────────────────────────────────────────────────────────────────────── │
+│ │
+│ Or explore with sample data: │
+│ [Load Demo Project] — See how a typical deal flows │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 7.2 Seller Member — Empty Task Inbox
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE [Project Phoenix ▼] TechCorp | Mary ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ MY TASKS │
+│ │
+│ ┌──────────────────────────┐ │
+│ │ ✓ │ │
+│ │ │ │
+│ │ You're all caught up! │ │
+│ │ │ │
+│ │ No tasks assigned to │ │
+│ │ you right now. │ │
+│ │ │ │
+│ └──────────────────────────┘ │
+│ │
+│ ────────────────────────────────────────────────────────────────────── │
+│ │
+│ Your completed tasks (3): │
+│ • FIN-003: Accounts receivable aging — Approved ✓ │
+│ • FIN-004: Accounts payable aging — Approved ✓ │
+│ • FIN-008: Capital expenditure history — Under Review │
+│ │
+│ [View Completed Tasks] │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 7.3 Buyer — Empty Data Room
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ DEALSPACE [Project Phoenix ▼] Blackstone | James ▼ │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ Data Room | My Questions │
+├─────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ DATA ROOM 🔍 Search documents... │
+│ │
+│ ┌──────────────────────────┐ │
+│ │ 📂 │ │
+│ │ │ │
+│ │ Data room is being │ │
+│ │ prepared │ │
+│ │ │ │
+│ │ The sell-side team is │ │
+│ │ gathering documents. │ │
+│ │ We'll notify you when │ │
+│ │ materials are ready. │ │
+│ │ │ │
+│ └──────────────────────────┘ │
+│ │
+│ ────────────────────────────────────────────────────────────────────── │
+│ │
+│ In the meantime: │
+│ [Submit Questions] — Tell us what you're looking for │
+│ [View Deal Summary] — Overview of Project Phoenix │
+│ │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 8. Demo Data
+
+### 8.1 Seed Script Concept
+
+```go
+// cmd/seed/main.go
+// Run: go run ./cmd/seed --demo
+
+package main
+
+func seedDemoProject(db *sql.DB) {
+ // 1. Create organizations
+ gsOrg := createOrg("Goldman Sachs", "gs.com")
+ techCorpOrg := createOrg("TechCorp Inc.", "techcorp.com")
+ blackstoneOrg := createOrg("Blackstone", "blackstone.com")
+ baincapOrg := createOrg("Bain Capital", "baincapital.com")
+
+ // 2. Create users
+ // Goldman Sachs (IB)
+ sarahIB := createUser("sarah.mitchell@gs.com", "Sarah Mitchell", gsOrg, "ib_admin")
+ mikeIB := createUser("mike.chen@gs.com", "Mike Chen", gsOrg, "ib_member")
+ jenIB := createUser("jen.park@gs.com", "Jen Park", gsOrg, "ib_member")
+ alexIB := createUser("alex.wong@gs.com", "Alex Wong", gsOrg, "ib_member")
+
+ // TechCorp (Seller)
+ johnSeller := createUser("john.smith@techcorp.com", "John Smith", techCorpOrg, "seller_admin")
+ marySeller := createUser("mary.johnson@techcorp.com", "Mary Johnson", techCorpOrg, "seller_member")
+ tomSeller := createUser("tom.wilson@techcorp.com", "Tom Wilson", techCorpOrg, "seller_member")
+ lisaSeller := createUser("lisa.chen@techcorp.com", "Lisa Chen", techCorpOrg, "seller_member")
+
+ // Buyers
+ jamesBuyer := createUser("james.lee@blackstone.com", "James Lee", blackstoneOrg, "buyer_admin")
+ emilaBuyer := createUser("emily.davis@baincapital.com", "Emily Davis", baincapOrg, "buyer_admin")
+
+ // 3. Create project
+ project := createProject(ProjectInput{
+ Name: "TechCorp Acquisition - Project Phoenix",
+ CodeName: "Project Phoenix",
+ DealType: "sell_side",
+ SellerOrg: techCorpOrg,
+ CreatedBy: sarahIB,
+ })
+
+ // 4. Create workstreams
+ financeWS := createWorkstream(project, "Finance", "finance")
+ legalWS := createWorkstream(project, "Legal", "legal")
+ itWS := createWorkstream(project, "IT", "it")
+
+ // 5. Assign access
+ // IB team
+ grantAccess(sarahIB, project, nil, "ib_admin")
+ grantAccess(mikeIB, project, financeWS, "ib_member")
+ grantAccess(jenIB, project, legalWS, "ib_member")
+ grantAccess(alexIB, project, itWS, "ib_member")
+
+ // Seller team
+ grantAccess(johnSeller, project, nil, "seller_admin")
+ grantAccess(marySeller, project, financeWS, "seller_member")
+ grantAccess(tomSeller, project, legalWS, "seller_member")
+ grantAccess(lisaSeller, project, itWS, "seller_member")
+
+ // Buyers (data room only)
+ grantAccess(jamesBuyer, project, nil, "buyer_admin")
+ grantAccess(emilaBuyer, project, nil, "buyer_admin")
+
+ // 6. Create Finance request list
+ financeRL := createRequestList(RequestListInput{
+ Name: "Initial Finance DD",
+ Workstream: financeWS,
+ CreatedBy: mikeIB,
+ DefaultDue: time.Now().Add(14 * 24 * time.Hour),
+ })
+
+ // Finance requests (mix of statuses for realism)
+ createRequest(financeRL, RequestInput{
+ Ref: "FIN-001",
+ Title: "Audited financial statements FY2023-2024",
+ Priority: "high",
+ AssignedTo: johnSeller,
+ Status: "published", // Already in data room
+ })
+ createRequest(financeRL, RequestInput{
+ Ref: "FIN-002",
+ Title: "Revenue breakdown by product line",
+ Priority: "normal",
+ AssignedTo: marySeller,
+ Status: "vetted", // Approved, pending publish
+ })
+ createRequest(financeRL, RequestInput{
+ Ref: "FIN-003",
+ Title: "Accounts receivable aging report",
+ Priority: "normal",
+ AssignedTo: marySeller,
+ Status: "submitted", // Awaiting IB review
+ })
+ createRequest(financeRL, RequestInput{
+ Ref: "FIN-004",
+ Title: "Accounts payable aging report",
+ Priority: "normal",
+ AssignedTo: marySeller,
+ Status: "assigned", // Seller working on it
+ })
+ createRequest(financeRL, RequestInput{
+ Ref: "FIN-005",
+ Title: "Cash flow projections FY2025-2027",
+ Priority: "high",
+ AssignedTo: johnSeller,
+ Status: "open", // Not started
+ })
+
+ // 7. Create Legal request list
+ legalRL := createRequestList(RequestListInput{
+ Name: "Legal DD - Contracts & IP",
+ Workstream: legalWS,
+ CreatedBy: jenIB,
+ })
+
+ createRequest(legalRL, RequestInput{
+ Ref: "LEG-001",
+ Title: "Material contracts list",
+ Priority: "high",
+ AssignedTo: tomSeller,
+ Status: "submitted",
+ })
+ createRequest(legalRL, RequestInput{
+ Ref: "LEG-002",
+ Title: "Customer contracts (top 10 by revenue)",
+ Priority: "high",
+ AssignedTo: tomSeller,
+ Status: "assigned",
+ })
+ createRequest(legalRL, RequestInput{
+ Ref: "LEG-003",
+ Title: "IP portfolio summary",
+ Priority: "normal",
+ AssignedTo: tomSeller,
+ Status: "open",
+ })
+ createRequest(legalRL, RequestInput{
+ Ref: "LEG-004",
+ Title: "Pending litigation summary",
+ Priority: "high",
+ AssignedTo: tomSeller,
+ Status: "open",
+ })
+
+ // 8. Create IT request list
+ itRL := createRequestList(RequestListInput{
+ Name: "IT Infrastructure & Security",
+ Workstream: itWS,
+ CreatedBy: alexIB,
+ })
+
+ createRequest(itRL, RequestInput{
+ Ref: "IT-001",
+ Title: "System architecture diagram",
+ Priority: "high",
+ AssignedTo: lisaSeller,
+ Status: "published",
+ })
+ createRequest(itRL, RequestInput{
+ Ref: "IT-002",
+ Title: "Cloud infrastructure costs",
+ Priority: "normal",
+ AssignedTo: lisaSeller,
+ Status: "submitted",
+ })
+ createRequest(itRL, RequestInput{
+ Ref: "IT-003",
+ Title: "Security audit report (SOC2)",
+ Priority: "high",
+ AssignedTo: lisaSeller,
+ Status: "assigned",
+ })
+
+ // 9. Create sample answers for published requests
+ createAnswer(AnswerInput{
+ RequestRef: "FIN-001",
+ Title: "Audited Financial Statements FY2023-2024",
+ Files: []string{"TechCorp_Audited_FY2023.pdf", "TechCorp_Audited_FY2024.pdf"},
+ SubmittedBy: johnSeller,
+ ApprovedBy: mikeIB,
+ Status: "published",
+ })
+
+ createAnswer(AnswerInput{
+ RequestRef: "IT-001",
+ Title: "TechCorp System Architecture",
+ Files: []string{"TechCorp_Architecture_2026.pdf"},
+ SubmittedBy: lisaSeller,
+ ApprovedBy: alexIB,
+ Status: "published",
+ })
+
+ // 10. Create sample buyer questions
+ createBuyerQuestion(BuyerQuestionInput{
+ Workstream: financeWS,
+ AskedBy: jamesBuyer,
+ Question: "Can you provide customer concentration analysis?",
+ Status: "pending", // No match, routed to IB
+ })
+
+ createBuyerQuestion(BuyerQuestionInput{
+ Workstream: itWS,
+ AskedBy: emilaBuyer,
+ Question: "What's the tech stack?",
+ Status: "matched", // AI matched to IT-001
+ MatchedTo: "IT-001",
+ })
+
+ // 11. Create activity events for timeline
+ createEvent(project, "Sarah Mitchell created Project Phoenix", sarahIB, -7*24*time.Hour)
+ createEvent(project, "Initial Finance DD requests sent to TechCorp (12 items)", mikeIB, -6*24*time.Hour)
+ createEvent(project, "John Smith accepted invitation", johnSeller, -5*24*time.Hour)
+ createEvent(project, "FIN-001 submitted by John Smith", johnSeller, -3*24*time.Hour)
+ createEvent(project, "FIN-001 approved and published to data room", mikeIB, -2*24*time.Hour)
+ createEvent(project, "Blackstone joined as buyer", jamesBuyer, -1*24*time.Hour)
+ createEvent(project, "Bain Capital joined as buyer", emilaBuyer, -12*time.Hour)
+}
+```
+
+### 8.2 Demo Login Credentials
+
+| Role | Email | Password | What they see |
+|------|-------|----------|---------------|
+| IB Admin | sarah.mitchell@gs.com | Demo2026! | Full project dashboard, all workstreams |
+| IB Member | mike.chen@gs.com | Demo2026! | Finance workstream, vetting queue |
+| Seller Admin | john.smith@techcorp.com | Demo2026! | Task inbox with 12 requests |
+| Seller Member | mary.johnson@techcorp.com | Demo2026! | Task inbox with 4 Finance requests |
+| Buyer Admin | james.lee@blackstone.com | Demo2026! | Data room with 2 published docs |
+
+### 8.3 Demo Scenario Flow
+
+**For a live demo, walk through this sequence:**
+
+1. **Login as Sarah (IB Admin)** — Show project overview, team status, request progress
+2. **Switch to Mike (IB Member)** — Show vetting queue, approve FIN-003, publish to data room
+3. **Switch to John (Seller Admin)** — Show task inbox, respond to FIN-004
+4. **Switch to James (Buyer)** — Show data room, new document notification, ask a question
+5. **Back to Mike** — Show buyer question routed, AI match suggestion
+
+**Demo data realism:**
+- Mix of statuses (open → assigned → submitted → vetted → published)
+- Some high-priority items
+- Some overdue items (creates urgency)
+- Comment threads on submitted items
+- Activity timeline with realistic timestamps
+
+---
+
+## 9. Implementation Notes
+
+### 9.1 API Endpoints Required
+
+```
+POST /api/register — Account creation
+POST /api/verify — Email verification
+POST /api/mfa/setup — Start MFA enrollment
+POST /api/mfa/verify — Complete MFA enrollment
+POST /api/login — Authenticate
+POST /api/invitations/accept — Accept invitation
+POST /api/projects — Create project (wizard)
+POST /api/projects/:id/invites — Send invitations
+POST /api/projects/:id/requests/import — Excel import
+GET /api/tasks — My task inbox
+PATCH /api/requests/:id — Update request status
+POST /api/answers — Submit answer
+PATCH /api/answers/:id/approve — Approve answer
+PATCH /api/answers/:id/reject — Reject answer
+```
+
+### 9.2 Email Service
+
+- Use SendGrid or AWS SES
+- All emails from: `noreply@muskepo.com`
+- Reply-To: set to inviting user's email
+- Unsubscribe links required (CAN-SPAM compliance)
+- Track open/click rates for onboarding optimization
+
+### 9.3 Metrics to Track
+
+**Onboarding funnel:**
+- Registration → Email Verified (%)
+- Email Verified → MFA Complete (%)
+- MFA Complete → First Project Created (%)
+- First Project → First Request List (%)
+- First Request → First Seller Response (time)
+
+**Activation:**
+- Time to first request list created
+- Time to first seller login
+- Time to first answer submitted
+- Time to first data room publication
+
+---
+
+*This document defines the complete first-run experience. Implementation should follow these flows exactly — deviations require spec update first.*
diff --git a/website/dpa.html b/website/dpa.html
index 2d79b1e..e200f57 100644
--- a/website/dpa.html
+++ b/website/dpa.html
@@ -5,6 +5,17 @@
Data Processing Agreement — Dealspace
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/features.html b/website/features.html
index 4991ff6..ab8287f 100644
--- a/website/features.html
+++ b/website/features.html
@@ -5,6 +5,17 @@
Features — Dealspace
+
+
+
+
+
+
+
+
+
+
+
@@ -53,7 +64,7 @@
Sign In
Request Demo
-
-