dealspace/API-SPEC.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)