2910 lines
83 KiB
YAML
2910 lines
83 KiB
YAML
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)
|