openapi: "3.0.3"
info:
  title: Invisible Cap Table API
  description: |
    API-first, AI-native cap table management system. Part of the Invisible suite at invisible.app.

    ## Authentication

    All endpoints except `/health` require authentication via one of two schemes:

    - **ApiKey**: A bearer token prefixed with `icap_` passed in the `Authorization` header.
    - **JWT**: A Cognito JWT token in the `Authorization` header, combined with an `X-Organization-Id` header to scope requests to a specific organization.

    ## Write Endpoint Confirmation

    Write endpoints that mutate cap table data support a two-step confirmation flow. Send `confirm: false` (or omit it) to receive a preview of the changes. Send `confirm: true` to execute the mutation.

    ## Roles

    Users have one of: `owner`, `admin`, `member`, `viewer`, `employee`. Write endpoints require at least `member` role. Organization management endpoints require `owner` or `admin`.
  version: "1.0.0"
  contact:
    name: Real Magic
    url: https://invisible.app

servers:
  - url: https://api.invisible.app
    description: Production

tags:
  - name: Health
    description: Service health check
  - name: Cap Table
    description: Full cap table retrieval
  - name: Stakeholders
    description: Manage stakeholders (founders, employees, investors, etc.)
  - name: Security Classes
    description: Manage security classes (common, preferred, options, etc.)
  - name: Equity Plans
    description: Manage equity incentive plans
  - name: Vesting Schedules
    description: Manage vesting schedules
  - name: Transactions
    description: Record and query equity transactions
  - name: Option Grants
    description: Manage option and RSU grants
  - name: Grant Status
    description: Batch update grant statuses
  - name: Investor Groups
    description: Manage investor groups
  - name: Bulk Operations
    description: Bulk create stakeholders, transactions, and grants
  - name: Organization Management
    description: Manage organizations, members, and invites
  - name: User
    description: Current user info
  - name: 409A Valuations
    description: Manage 409A valuation reports and workflows
  - name: SBC Expensing
    description: Stock-based compensation valuation and expense reporting

security:
  - ApiKey: []
  - JWT: []

paths:
  # ── Health ──────────────────────────────────────────────────────────────
  /health:
    get:
      operationId: getHealth
      tags: [Health]
      summary: Health check
      description: Returns service health status. No authentication required.
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  timestamp:
                    type: string
                    format: date-time
                    example: "2026-03-31T12:00:00Z"
              example:
                status: healthy
                timestamp: "2026-03-31T12:00:00Z"

  # ── Cap Table ───────────────────────────────────────────────────────────
  /api/cap-table:
    get:
      operationId: getCapTable
      tags: [Cap Table]
      summary: Get full cap table
      description: Returns the complete cap table including all stakeholders, summary metrics, security classes, and investor groups.
      responses:
        "200":
          description: Full cap table
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FullCapTableResponse"
              example:
                stakeholders:
                  - id: "stk_abc123"
                    name: "Jane Doe"
                    type: individual
                    shares: 1000000
                    ownershipPercent: 25.0
                summary:
                  totalSharesOutstanding: 4000000
                  totalSharesAuthorized: 10000000
                  totalOptionsOutstanding: 500000
                  fullyDilutedShares: 4500000
                securityClasses:
                  - id: "sc_001"
                    name: "Common Stock"
                    type: common
                    authorizedShares: 10000000
                    issuedShares: 4000000
                investorGroups:
                  - id: "grp_001"
                    name: "Series A Investors"
                    stakeholderIds: ["stk_abc123"]
        "401":
          $ref: "#/components/responses/Unauthorized"

  # ── Stakeholders ────────────────────────────────────────────────────────
  /api/stakeholders:
    get:
      operationId: listStakeholders
      tags: [Stakeholders]
      summary: List stakeholders
      description: Returns a list of stakeholders, optionally filtered by search term.
      parameters:
        - name: search
          in: query
          required: false
          schema:
            type: string
          description: Search term to filter stakeholders by name or email
          example: "Jane"
      responses:
        "200":
          description: List of stakeholders
          content:
            application/json:
              schema:
                type: object
                properties:
                  stakeholders:
                    type: array
                    items:
                      $ref: "#/components/schemas/StakeholderListItem"
              example:
                stakeholders:
                  - id: "stk_abc123"
                    name: "Jane Doe"
                    type: individual
                    email: "jane@example.com"
                    jobTitle: "CTO"
                    department: "Engineering"
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      operationId: createStakeholder
      tags: [Stakeholders]
      summary: Create a stakeholder
      description: Creates a new stakeholder. Requires member role or above.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateStakeholderRequest"
            example:
              name: "Jane Doe"
              type: individual
              email: "jane@example.com"
              jobTitle: "CTO"
              department: "Engineering"
              employeeId: "EMP-001"
      responses:
        "201":
          description: Stakeholder created
          content:
            application/json:
              schema:
                type: object
                properties:
                  stakeholder_id:
                    type: string
              example:
                stakeholder_id: "stk_abc123"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/stakeholders/{id}:
    get:
      operationId: getStakeholder
      tags: [Stakeholders]
      summary: Get stakeholder detail
      description: Returns detailed information about a stakeholder including holdings, transactions, and option grants.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: "stk_abc123"
      responses:
        "200":
          description: Stakeholder detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StakeholderDetail"
              example:
                stakeholder:
                  id: "stk_abc123"
                  name: "Jane Doe"
                  type: individual
                  email: "jane@example.com"
                  jobTitle: "CTO"
                  department: "Engineering"
                holdings:
                  - securityClassId: "sc_001"
                    securityClassName: "Common Stock"
                    shares: 1000000
                    type: common
                transactions:
                  - id: "txn_001"
                    type: issuance
                    shares: 1000000
                    pricePerShare: 0.001
                    transactionDate: "2024-01-15"
                optionGrants:
                  - id: "grant_001"
                    optionType: iso
                    sharesGranted: 50000
                    exercisePrice: 1.25
                    grantDate: "2024-06-01"
                    status: active
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  # ── Option Grants ───────────────────────────────────────────────────────
  /api/option-grants:
    get:
      operationId: listOptionGrants
      tags: [Option Grants]
      summary: List option grants
      description: Returns option grants filtered by optional query parameters.
      parameters:
        - name: stakeholderId
          in: query
          required: false
          schema:
            type: string
          description: Filter by stakeholder ID
        - name: department
          in: query
          required: false
          schema:
            type: string
          description: Filter by department
        - name: status
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/GrantStatus"
          description: Filter by grant status
        - name: planName
          in: query
          required: false
          schema:
            type: string
          description: Filter by equity plan name
        - name: asOfDate
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Calculate vesting as of this date
      responses:
        "200":
          description: List of option grants
          content:
            application/json:
              schema:
                type: object
                properties:
                  grants:
                    type: array
                    items:
                      $ref: "#/components/schemas/OptionGrantDetail"
              example:
                grants:
                  - id: "grant_001"
                    stakeholderId: "stk_abc123"
                    stakeholderName: "Jane Doe"
                    equityPlanId: "plan_001"
                    optionType: iso
                    sharesGranted: 50000
                    exercisePrice: 1.25
                    grantDate: "2024-06-01"
                    vestingStartDate: "2024-06-01"
                    expirationDate: "2034-06-01"
                    status: active
                    sharesVested: 12500
                    sharesExercised: 0
                    sharesUnvested: 37500
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      operationId: createOptionGrant
      tags: [Option Grants]
      summary: Create an option grant
      description: Creates a new option grant. Send `confirm: true` to execute, or omit/set false for a preview.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOptionGrantRequest"
            example:
              stakeholderId: "stk_abc123"
              equityPlanId: "plan_001"
              optionType: iso
              sharesGranted: 50000
              exercisePrice: 1.25
              grantDate: "2024-06-01"
              vestingStartDate: "2024-06-01"
              expirationDate: "2034-06-01"
              vestingScheduleId: "vs_001"
              status: active
              category: new_hire
              notes: "Initial hire grant"
              grantNumber: "G-001"
              confirm: true
      responses:
        "200":
          description: Preview of grant (when confirm is false or omitted)
          content:
            application/json:
              schema:
                type: object
                properties:
                  grant_id:
                    type: string
                    nullable: true
                  preview:
                    type: object
              example:
                grant_id: null
                preview:
                  stakeholderName: "Jane Doe"
                  sharesGranted: 50000
                  exercisePrice: 1.25
                  planAvailableShares: 450000
        "201":
          description: Grant created (when confirm is true)
          content:
            application/json:
              schema:
                type: object
                properties:
                  grant_id:
                    type: string
                  preview:
                    type: object
                    nullable: true
              example:
                grant_id: "grant_001"
                preview: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

    put:
      operationId: updateOptionGrant
      tags: [Option Grants]
      summary: Update an option grant
      description: Updates fields on an existing option grant. Only provided fields are changed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOptionGrantRequest"
            example:
              grantId: "grant_001"
              sharesGranted: 60000
              notes: "Increased grant per board approval"
      responses:
        "200":
          description: Grant updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: boolean
              example:
                updated: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/option-grants/{grantId}:
    delete:
      operationId: deleteOptionGrant
      tags: [Option Grants]
      summary: Delete an option grant
      description: Permanently deletes an option grant.
      parameters:
        - name: grantId
          in: path
          required: true
          schema:
            type: string
          example: "grant_001"
      responses:
        "200":
          description: Grant deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
              example:
                deleted: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/option-grants/exercise:
    post:
      operationId: exerciseOptionGrant
      tags: [Option Grants]
      summary: Exercise options from a grant
      description: Exercises shares from an existing option grant. Send `confirm: true` to execute.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExerciseOptionGrantRequest"
            example:
              grantId: "grant_001"
              sharesToExercise: 10000
              exerciseDate: "2026-01-15"
              notes: "Early exercise"
              confirm: true
      responses:
        "200":
          description: Preview of exercise (when confirm is false or omitted)
          content:
            application/json:
              schema:
                type: object
                properties:
                  exercise_id:
                    type: string
                    nullable: true
                  preview:
                    type: object
              example:
                exercise_id: null
                preview:
                  grantId: "grant_001"
                  sharesToExercise: 10000
                  sharesVestedAvailable: 12500
                  totalCost: 12500.00
        "201":
          description: Exercise recorded (when confirm is true)
          content:
            application/json:
              schema:
                type: object
                properties:
                  exercise_id:
                    type: string
                  preview:
                    type: object
                    nullable: true
              example:
                exercise_id: "ex_001"
                preview: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  # ── Grant Status ────────────────────────────────────────────────────────
  /api/grant-status:
    post:
      operationId: updateGrantStatus
      tags: [Grant Status]
      summary: Batch update grant statuses
      description: Updates the status of one or more grants. Send `confirm: true` to execute.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateGrantStatusRequest"
            example:
              grantIds: ["grant_001", "grant_002"]
              newStatus: approved
              notes: "Board approved on 2026-03-15"
              confirm: true
      responses:
        "200":
          description: Status update result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  grants_affected:
                    type: integer
              example:
                success: true
                grants_affected: 2
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  # ── Security Classes ────────────────────────────────────────────────────
  /api/security-classes:
    get:
      operationId: listSecurityClasses
      tags: [Security Classes]
      summary: List security classes
      description: Returns all security classes for the organization.
      responses:
        "200":
          description: List of security classes
          content:
            application/json:
              schema:
                type: object
                properties:
                  classes:
                    type: array
                    items:
                      $ref: "#/components/schemas/SecurityClassDetail"
              example:
                classes:
                  - id: "sc_001"
                    name: "Common Stock"
                    type: common
                    authorizedShares: 10000000
                    issuedShares: 4000000
                    originalIssuePrice: 0.001
                  - id: "sc_002"
                    name: "Series A Preferred"
                    type: preferred
                    authorizedShares: 2000000
                    issuedShares: 1500000
                    originalIssuePrice: 1.50
                    liquidationPreference: 1.0
                    conversionRatio: 1.0
                    isParticipating: false
                    seniorityRank: 1
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      operationId: createSecurityClass
      tags: [Security Classes]
      summary: Create a security class
      description: Creates a new security class.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSecurityClassRequest"
            example:
              name: "Series A Preferred"
              type: preferred
              originalIssuePrice: 1.50
              liquidationPreference: 1.0
              conversionRatio: 1.0
              isParticipating: false
              seniorityRank: 1
              authorizedShares: 2000000
      responses:
        "201":
          description: Security class created
          content:
            application/json:
              schema:
                type: object
                properties:
                  security_class_id:
                    type: string
              example:
                security_class_id: "sc_002"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

    put:
      operationId: updateSecurityClass
      tags: [Security Classes]
      summary: Update a security class
      description: Updates fields on an existing security class.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSecurityClassRequest"
            example:
              securityClassId: "sc_002"
              authorizedShares: 3000000
      responses:
        "200":
          description: Security class updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: boolean
              example:
                updated: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  # ── Equity Plans ────────────────────────────────────────────────────────
  /api/equity-plans:
    get:
      operationId: listEquityPlans
      tags: [Equity Plans]
      summary: List equity plans
      description: Returns all equity incentive plans for the organization.
      responses:
        "200":
          description: List of equity plans
          content:
            application/json:
              schema:
                type: object
                properties:
                  plans:
                    type: array
                    items:
                      $ref: "#/components/schemas/EquityPlan"
              example:
                plans:
                  - id: "plan_001"
                    name: "2024 Equity Incentive Plan"
                    year: 2024
                    authorizedShares: 1000000
                    sharesGranted: 500000
                    sharesAvailable: 500000
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      operationId: createEquityPlan
      tags: [Equity Plans]
      summary: Create an equity plan
      description: Creates a new equity incentive plan.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEquityPlanRequest"
            example:
              name: "2024 Equity Incentive Plan"
              year: 2024
              authorizedShares: 1000000
      responses:
        "201":
          description: Equity plan created
          content:
            application/json:
              schema:
                type: object
                properties:
                  equity_plan_id:
                    type: string
              example:
                equity_plan_id: "plan_001"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

    put:
      operationId: updateEquityPlan
      tags: [Equity Plans]
      summary: Update an equity plan
      description: Updates fields on an existing equity plan.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEquityPlanRequest"
            example:
              equityPlanId: "plan_001"
              authorizedShares: 1500000
      responses:
        "200":
          description: Equity plan updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: boolean
              example:
                updated: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  # ── Vesting Schedules ───────────────────────────────────────────────────
  /api/vesting-schedules:
    get:
      operationId: listVestingSchedules
      tags: [Vesting Schedules]
      summary: List vesting schedules
      description: Returns all vesting schedule templates.
      responses:
        "200":
          description: List of vesting schedules
          content:
            application/json:
              schema:
                type: object
                properties:
                  schedules:
                    type: array
                    items:
                      $ref: "#/components/schemas/VestingSchedule"
              example:
                schedules:
                  - id: "vs_001"
                    name: "4-Year with 1-Year Cliff"
                    totalMonths: 48
                    cliffMonths: 12
                    frequency: monthly
                  - id: "vs_002"
                    name: "3-Year Quarterly"
                    totalMonths: 36
                    cliffMonths: 0
                    frequency: quarterly
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      operationId: createVestingSchedule
      tags: [Vesting Schedules]
      summary: Create a vesting schedule
      description: Creates a new vesting schedule template.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateVestingScheduleRequest"
            example:
              name: "4-Year with 1-Year Cliff"
              totalMonths: 48
              cliffMonths: 12
              frequency: monthly
      responses:
        "201":
          description: Vesting schedule created
          content:
            application/json:
              schema:
                type: object
                properties:
                  vesting_schedule_id:
                    type: string
              example:
                vesting_schedule_id: "vs_001"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  # ── Transactions ────────────────────────────────────────────────────────
  /api/transactions:
    get:
      operationId: listTransactions
      tags: [Transactions]
      summary: List transactions
      description: Returns transactions filtered by optional query parameters.
      parameters:
        - name: stakeholderId
          in: query
          required: false
          schema:
            type: string
          description: Filter by stakeholder ID
        - name: securityClassId
          in: query
          required: false
          schema:
            type: string
          description: Filter by security class ID
        - name: type
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/TransactionType"
          description: Filter by transaction type
        - name: startDate
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Filter transactions on or after this date
        - name: endDate
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Filter transactions on or before this date
      responses:
        "200":
          description: List of transactions
          content:
            application/json:
              schema:
                type: object
                properties:
                  transactions:
                    type: array
                    items:
                      $ref: "#/components/schemas/Transaction"
              example:
                transactions:
                  - id: "txn_001"
                    securityId: "sec_001"
                    stakeholderId: "stk_abc123"
                    securityClassId: "sc_001"
                    type: issuance
                    shares: 1000000
                    pricePerShare: 0.001
                    transactionDate: "2024-01-15"
                    notes: "Founder shares issuance"
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      operationId: createTransaction
      tags: [Transactions]
      summary: Create a transaction
      description: Records a new equity transaction. Send `confirm: true` to execute.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTransactionRequest"
            example:
              securityId: "sec_001"
              stakeholderId: "stk_abc123"
              securityClassId: "sc_001"
              type: issuance
              shares: 1000000
              pricePerShare: 0.001
              transactionDate: "2024-01-15"
              notes: "Founder shares issuance"
              confirm: true
      responses:
        "200":
          description: Preview of transaction (when confirm is false or omitted)
          content:
            application/json:
              schema:
                type: object
                properties:
                  transaction_id:
                    type: string
                    nullable: true
                  preview:
                    type: object
              example:
                transaction_id: null
                preview:
                  stakeholderName: "Jane Doe"
                  securityClassName: "Common Stock"
                  type: issuance
                  shares: 1000000
                  totalCost: 1000.00
        "201":
          description: Transaction created (when confirm is true)
          content:
            application/json:
              schema:
                type: object
                properties:
                  transaction_id:
                    type: string
                  preview:
                    type: object
                    nullable: true
              example:
                transaction_id: "txn_001"
                preview: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  # ── Investor Groups ─────────────────────────────────────────────────────
  /api/investor-groups:
    post:
      operationId: createInvestorGroup
      tags: [Investor Groups]
      summary: Create an investor group
      description: Creates or updates an investor group. If `groupId` is provided, updates the existing group.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateInvestorGroupRequest"
            example:
              name: "Series A Investors"
              stakeholderIds: ["stk_abc123", "stk_def456"]
      responses:
        "201":
          description: Investor group created
          content:
            application/json:
              schema:
                type: object
                properties:
                  group_id:
                    type: string
              example:
                group_id: "grp_001"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  # ── Bulk Operations ─────────────────────────────────────────────────────
  /api/bulk/stakeholders:
    post:
      operationId: bulkCreateStakeholders
      tags: [Bulk Operations]
      summary: Bulk create stakeholders
      description: Creates multiple stakeholders in a single request.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [stakeholders]
              properties:
                stakeholders:
                  type: array
                  items:
                    $ref: "#/components/schemas/CreateStakeholderRequest"
            example:
              stakeholders:
                - name: "Alice Smith"
                  type: individual
                  email: "alice@example.com"
                  jobTitle: "Engineer"
                  department: "Engineering"
                - name: "Acme Ventures"
                  type: entity
                  email: "contact@acme.vc"
      responses:
        "200":
          description: Bulk creation results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkResult"
              example:
                results:
                  - stakeholder_id: "stk_new001"
                    name: "Alice Smith"
                    success: true
                  - stakeholder_id: "stk_new002"
                    name: "Acme Ventures"
                    success: true
                created: 2
                failed: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/bulk/transactions:
    post:
      operationId: bulkCreateTransactions
      tags: [Bulk Operations]
      summary: Bulk create transactions
      description: Creates multiple transactions in a single request.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [transactions]
              properties:
                transactions:
                  type: array
                  items:
                    $ref: "#/components/schemas/CreateTransactionRequest"
            example:
              transactions:
                - securityId: "sec_001"
                  stakeholderId: "stk_abc123"
                  securityClassId: "sc_001"
                  type: issuance
                  shares: 1000000
                  pricePerShare: 0.001
                  transactionDate: "2024-01-15"
                  confirm: true
      responses:
        "200":
          description: Bulk creation results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkResult"
              example:
                results:
                  - transaction_id: "txn_new001"
                    success: true
                created: 1
                failed: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/bulk/option-grants:
    post:
      operationId: bulkCreateOptionGrants
      tags: [Bulk Operations]
      summary: Bulk create option grants
      description: Creates multiple option grants in a single request.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [grants]
              properties:
                grants:
                  type: array
                  items:
                    $ref: "#/components/schemas/CreateOptionGrantRequest"
            example:
              grants:
                - stakeholderId: "stk_abc123"
                  equityPlanId: "plan_001"
                  optionType: iso
                  sharesGranted: 50000
                  exercisePrice: 1.25
                  grantDate: "2024-06-01"
                  vestingStartDate: "2024-06-01"
                  expirationDate: "2034-06-01"
                  confirm: true
      responses:
        "200":
          description: Bulk creation results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkResult"
              example:
                results:
                  - grant_id: "grant_new001"
                    success: true
                created: 1
                failed: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  # ── User ────────────────────────────────────────────────────────────────
  /api/me:
    get:
      operationId: getMe
      tags: [User]
      summary: Get current user info
      description: Returns information about the authenticated user, including organizations and linked stakeholders.
      responses:
        "200":
          description: Current user info
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeResponse"
              example:
                userId: "usr_001"
                email: "jane@example.com"
                name: "Jane Doe"
                organizations:
                  - id: "org_001"
                    name: "Acme Corp"
                    slug: "acme-corp"
                    role: owner
                linkedStakeholders:
                  - organizationId: "org_001"
                    stakeholderId: "stk_abc123"
                    stakeholderName: "Jane Doe"
        "401":
          $ref: "#/components/responses/Unauthorized"

  # ── Organization Management ─────────────────────────────────────────────
  /api/organizations:
    post:
      operationId: createOrganization
      tags: [Organization Management]
      summary: Create an organization
      description: Creates a new organization. The authenticated user becomes the owner.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrganizationRequest"
            example:
              name: "Acme Corp"
              slug: "acme-corp"
              domain: "acme.com"
      responses:
        "201":
          description: Organization created
          content:
            application/json:
              schema:
                type: object
                properties:
                  organization_id:
                    type: string
              example:
                organization_id: "org_001"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/organizations/{id}/members:
    get:
      operationId: listOrganizationMembers
      tags: [Organization Management]
      summary: List organization members
      description: Returns all members of the organization. Requires owner or admin role.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: "org_001"
      responses:
        "200":
          description: List of members
          content:
            application/json:
              schema:
                type: object
                properties:
                  members:
                    type: array
                    items:
                      $ref: "#/components/schemas/OrganizationMember"
              example:
                members:
                  - userId: "usr_001"
                    email: "jane@example.com"
                    name: "Jane Doe"
                    role: owner
                    canViewFullCapTable: true
                  - userId: "usr_002"
                    email: "bob@example.com"
                    name: "Bob Smith"
                    role: member
                    canViewFullCapTable: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

    post:
      operationId: addOrganizationMember
      tags: [Organization Management]
      summary: Add a member to the organization
      description: Adds a user to the organization by email. If the user does not have an account, an invite code is generated.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: "org_001"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddMemberRequest"
            example:
              email: "newuser@example.com"
              role: member
              expiresInDays: 7
      responses:
        "200":
          description: Member added or invite sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  added:
                    type: boolean
                  invite_code:
                    type: string
                    nullable: true
                  message:
                    type: string
              example:
                added: true
                invite_code: "inv_abc123"
                message: "Invite sent to newuser@example.com"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/organizations/{id}/members/{userId}:
    put:
      operationId: updateOrganizationMember
      tags: [Organization Management]
      summary: Update a member's role or permissions
      description: Updates the role or cap table visibility for an organization member.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: "org_001"
        - name: userId
          in: path
          required: true
          schema:
            type: string
          example: "usr_002"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateMemberRequest"
            example:
              role: admin
              canViewFullCapTable: true
      responses:
        "200":
          description: Member updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: boolean
              example:
                updated: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/organizations/{id}/invite:
    post:
      operationId: inviteStakeholder
      tags: [Organization Management]
      summary: Invite a stakeholder to link their account
      description: Generates an invite code for an existing stakeholder to link to a user account.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          example: "org_001"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InviteStakeholderRequest"
            example:
              stakeholderId: "stk_abc123"
              email: "jane@example.com"
              expiresInDays: 14
      responses:
        "200":
          description: Invite created
          content:
            application/json:
              schema:
                type: object
                properties:
                  invite_code:
                    type: string
                  expires_in_days:
                    type: integer
              example:
                invite_code: "sinv_xyz789"
                expires_in_days: 14
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/invites/redeem:
    post:
      operationId: redeemStakeholderInvite
      tags: [Organization Management]
      summary: Redeem a stakeholder invite
      description: Links the authenticated user's account to a stakeholder record via an invite code.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [inviteCode]
              properties:
                inviteCode:
                  type: string
            example:
              inviteCode: "sinv_xyz789"
      responses:
        "200":
          description: Invite redeemed
          content:
            application/json:
              schema:
                type: object
                properties:
                  redeemed:
                    type: boolean
              example:
                redeemed: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/org-invites/redeem:
    post:
      operationId: redeemOrgInvite
      tags: [Organization Management]
      summary: Redeem an organization invite
      description: Adds the authenticated user to an organization via an invite code.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [inviteCode]
              properties:
                inviteCode:
                  type: string
            example:
              inviteCode: "inv_abc123"
      responses:
        "200":
          description: Invite redeemed
          content:
            application/json:
              schema:
                type: object
                properties:
                  redeemed:
                    type: boolean
              example:
                redeemed: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  # ── 409A Valuations ─────────────────────────────────────────────────────
  /api/409a/start:
    post:
      operationId: start409AValuation
      tags: [409A Valuations]
      summary: Start a new 409A valuation
      description: Initiates a new 409A valuation report. Requires admin role or above.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Start409ARequest"
            example:
              valuationDate: "2026-03-31"
              provider: "internal"
              notes: "Annual 409A valuation"
      responses:
        "201":
          description: 409A valuation report created
          content:
            application/json:
              schema:
                type: object
                properties:
                  report:
                    $ref: "#/components/schemas/Report409A"
              example:
                report:
                  id: "409a_abc123"
                  status: draft
                  valuationDate: "2026-03-31"
                  provider: "internal"
                  createdAt: "2026-03-31T12:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/409a/reports:
    get:
      operationId: list409AReports
      tags: [409A Valuations]
      summary: List 409A valuation reports
      description: Returns all 409A valuation reports for the organization.
      responses:
        "200":
          description: List of 409A reports
          content:
            application/json:
              schema:
                type: object
                properties:
                  reports:
                    type: array
                    items:
                      $ref: "#/components/schemas/Report409A"
              example:
                reports:
                  - id: "409a_abc123"
                    status: finalized
                    valuationDate: "2025-12-31"
                    fairMarketValue: 1.25
                    provider: "internal"
                    createdAt: "2025-12-15T12:00:00Z"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/409a/reports/{id}:
    get:
      operationId: get409AReport
      tags: [409A Valuations]
      summary: Get a 409A valuation report
      description: Returns a single 409A valuation report by ID, including inputs and computed results.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The 409A report ID
          example: "409a_abc123"
      responses:
        "200":
          description: 409A report details
          content:
            application/json:
              schema:
                type: object
                properties:
                  report:
                    $ref: "#/components/schemas/Report409ADetail"
              example:
                report:
                  id: "409a_abc123"
                  status: finalized
                  valuationDate: "2025-12-31"
                  fairMarketValue: 1.25
                  provider: "internal"
                  inputs:
                    totalEquityValue: 5000000
                    dlom: 0.25
                    dloc: 0.10
                    allocationMethod: "opm"
                  createdAt: "2025-12-15T12:00:00Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/409a/reports/{id}/inputs:
    put:
      operationId: update409AInputs
      tags: [409A Valuations]
      summary: Update 409A report inputs
      description: Updates the valuation inputs for a 409A report in draft status. Requires admin role or above.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The 409A report ID
          example: "409a_abc123"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Update409AInputsRequest"
            example:
              totalEquityValue: 5000000
              dlom: 0.25
              dloc: 0.10
              allocationMethod: "opm"
              volatility: 0.55
              riskFreeRate: 0.045
              termYears: 5
      responses:
        "200":
          description: Inputs updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  report:
                    $ref: "#/components/schemas/Report409ADetail"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/409a/reports/{id}/compute:
    post:
      operationId: compute409AValuation
      tags: [409A Valuations]
      summary: Compute 409A valuation
      description: Runs the valuation computation using the provided inputs. Transitions the report to "computed" status.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The 409A report ID
          example: "409a_abc123"
      responses:
        "200":
          description: Valuation computed
          content:
            application/json:
              schema:
                type: object
                properties:
                  report:
                    $ref: "#/components/schemas/Report409ADetail"
              example:
                report:
                  id: "409a_abc123"
                  status: computed
                  valuationDate: "2025-12-31"
                  fairMarketValue: 1.25
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/409a/reports/{id}/assign-reviewer:
    post:
      operationId: assign409AReviewer
      tags: [409A Valuations]
      summary: Assign a reviewer to a 409A report
      description: Assigns a user as the reviewer for a 409A valuation report. Requires admin role or above.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The 409A report ID
          example: "409a_abc123"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssignReviewerRequest"
            example:
              reviewerUserId: "usr_def456"
      responses:
        "200":
          description: Reviewer assigned
          content:
            application/json:
              schema:
                type: object
                properties:
                  report:
                    $ref: "#/components/schemas/Report409A"
              example:
                report:
                  id: "409a_abc123"
                  status: computed
                  reviewerUserId: "usr_def456"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/409a/reports/{id}/review:
    post:
      operationId: review409AReport
      tags: [409A Valuations]
      summary: Submit review for a 409A report
      description: Submits a reviewer's decision (approve or request changes) for a 409A report.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The 409A report ID
          example: "409a_abc123"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Review409ARequest"
            example:
              decision: "approve"
              comments: "Valuation methodology and inputs look correct."
      responses:
        "200":
          description: Review submitted
          content:
            application/json:
              schema:
                type: object
                properties:
                  report:
                    $ref: "#/components/schemas/Report409A"
              example:
                report:
                  id: "409a_abc123"
                  status: reviewed
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/409a/reports/{id}/finalize:
    post:
      operationId: finalize409AReport
      tags: [409A Valuations]
      summary: Finalize a 409A report
      description: Finalizes a reviewed 409A report, locking the fair market value. Requires admin role or above.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The 409A report ID
          example: "409a_abc123"
      responses:
        "200":
          description: Report finalized
          content:
            application/json:
              schema:
                type: object
                properties:
                  report:
                    $ref: "#/components/schemas/Report409A"
              example:
                report:
                  id: "409a_abc123"
                  status: finalized
                  valuationDate: "2025-12-31"
                  fairMarketValue: 1.25
                  finalizedAt: "2026-01-15T12:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  # ── SBC Expensing ──────────────────────────────────────────────────────
  /api/sbc/valuations:
    post:
      operationId: createSbcValuation
      tags: [SBC Expensing]
      summary: Create an SBC grant valuation
      description: Records a fair-value measurement for a specific grant using Black-Scholes or another accepted model.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSbcValuationRequest"
            example:
              grantId: "grant_abc123"
              model: "black-scholes"
              inputs:
                stockPrice: 1.25
                strikePrice: 1.00
                volatility: 0.55
                riskFreeRate: 0.045
                termYears: 6.08
                dividendYield: 0.0
              valuationDate: "2026-03-31"
      responses:
        "201":
          description: SBC valuation created
          content:
            application/json:
              schema:
                type: object
                properties:
                  valuation:
                    $ref: "#/components/schemas/SbcValuation"
              example:
                valuation:
                  id: "sbcv_abc123"
                  grantId: "grant_abc123"
                  model: "black-scholes"
                  fairValue: 0.58
                  valuationDate: "2026-03-31"
                  createdAt: "2026-03-31T12:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

    get:
      operationId: listSbcValuations
      tags: [SBC Expensing]
      summary: List SBC grant valuations
      description: Returns all stock-based compensation valuations for the organization.
      responses:
        "200":
          description: List of SBC valuations
          content:
            application/json:
              schema:
                type: object
                properties:
                  valuations:
                    type: array
                    items:
                      $ref: "#/components/schemas/SbcValuation"
              example:
                valuations:
                  - id: "sbcv_abc123"
                    grantId: "grant_abc123"
                    model: "black-scholes"
                    fairValue: 0.58
                    valuationDate: "2026-03-31"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/sbc/compute:
    post:
      operationId: computeSbcExpense
      tags: [SBC Expensing]
      summary: Compute SBC expense for grants
      description: Computes stock-based compensation expense amounts for specified grants or the entire organization over a period.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ComputeSbcExpenseRequest"
            example:
              startDate: "2026-01-01"
              endDate: "2026-03-31"
              grantIds: ["grant_abc123", "grant_def456"]
      responses:
        "200":
          description: SBC expense computed
          content:
            application/json:
              schema:
                type: object
                properties:
                  expenses:
                    type: array
                    items:
                      $ref: "#/components/schemas/SbcExpenseItem"
                  totalExpense:
                    type: number
                    format: double
              example:
                expenses:
                  - grantId: "grant_abc123"
                    stakeholderName: "Jane Doe"
                    department: "Engineering"
                    periodExpense: 14500.00
                    cumulativeExpense: 43500.00
                    remainingExpense: 116500.00
                totalExpense: 29000.00
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/sbc/expense-schedule:
    get:
      operationId: getSbcExpenseSchedule
      tags: [SBC Expensing]
      summary: Get SBC expense schedule
      description: Returns a period-by-period expense schedule for stock-based compensation, optionally filtered by department or grant.
      parameters:
        - name: startDate
          in: query
          required: true
          schema:
            type: string
            format: date
          description: Start date of the schedule period
          example: "2026-01-01"
        - name: endDate
          in: query
          required: true
          schema:
            type: string
            format: date
          description: End date of the schedule period
          example: "2026-12-31"
        - name: department
          in: query
          required: false
          schema:
            type: string
          description: Filter by department
          example: "Engineering"
        - name: grantId
          in: query
          required: false
          schema:
            type: string
          description: Filter by specific grant ID
          example: "grant_abc123"
      responses:
        "200":
          description: Expense schedule
          content:
            application/json:
              schema:
                type: object
                properties:
                  schedule:
                    type: array
                    items:
                      $ref: "#/components/schemas/SbcExpenseScheduleEntry"
                  summary:
                    $ref: "#/components/schemas/SbcExpenseScheduleSummary"
              example:
                schedule:
                  - period: "2026-Q1"
                    startDate: "2026-01-01"
                    endDate: "2026-03-31"
                    expense: 29000.00
                    cumulativeExpense: 87000.00
                  - period: "2026-Q2"
                    startDate: "2026-04-01"
                    endDate: "2026-06-30"
                    expense: 29000.00
                    cumulativeExpense: 116000.00
                summary:
                  totalExpense: 116000.00
                  periodCount: 4
                  grantCount: 12
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/sbc/disclosure:
    get:
      operationId: getSbcDisclosure
      tags: [SBC Expensing]
      summary: Get SBC disclosure report
      description: Returns stock-based compensation disclosure data for financial statement footnotes, covering a fiscal year.
      parameters:
        - name: fiscalYearStart
          in: query
          required: true
          schema:
            type: string
            format: date
          description: Start date of the fiscal year
          example: "2025-01-01"
        - name: fiscalYearEnd
          in: query
          required: true
          schema:
            type: string
            format: date
          description: End date of the fiscal year
          example: "2025-12-31"
      responses:
        "200":
          description: SBC disclosure data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SbcDisclosureReport"
              example:
                fiscalYearStart: "2025-01-01"
                fiscalYearEnd: "2025-12-31"
                totalCompensationExpense: 580000.00
                weightedAverageAssumptions:
                  volatility: 0.55
                  riskFreeRate: 0.045
                  expectedTerm: 6.08
                  dividendYield: 0.0
                activitySummary:
                  optionsOutstandingStart: 500000
                  granted: 150000
                  exercised: 25000
                  forfeited: 10000
                  optionsOutstandingEnd: 615000
                  weightedAverageExercisePrice: 1.10
                intrinsicValueOutstanding: 153750.00
                unrecognizedExpense: 320000.00
                weightedAverageRemainingVestingPeriod: 2.5
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  # ── Visibility & Org Hierarchy ──

  /api/stakeholders/{id}/manager:
    put:
      summary: Set stakeholder manager
      description: Set or clear a stakeholder's reporting manager. Pass managerId=null to clear.
      tags: [Visibility]
      security: [{ ApiKey: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                managerId:
                  type: string
                  format: uuid
                  nullable: true
      responses:
        "200":
          description: Manager updated
        "403":
          $ref: "#/components/responses/Forbidden"

  /api/stakeholders/bulk-set-managers:
    post:
      summary: Bulk set manager relationships
      tags: [Visibility]
      security: [{ ApiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                assignments:
                  type: array
                  items:
                    type: object
                    properties:
                      stakeholderId:
                        type: string
                        format: uuid
                      managerId:
                        type: string
                        format: uuid
                        nullable: true
                    required: [stakeholderId]
              required: [assignments]
      responses:
        "200":
          description: Results with updated/failed counts

  /api/department-access:
    get:
      summary: List department access grants
      tags: [Visibility]
      security: [{ ApiKey: [] }]
      parameters:
        - name: userId
          in: query
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Department access entries
    post:
      summary: Grant department access
      tags: [Visibility]
      security: [{ ApiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                userId:
                  type: string
                  format: uuid
                departments:
                  type: array
                  items: { type: string }
              required: [userId, departments]
      responses:
        "200":
          description: Number of grants created
  /api/department-access/revoke:
    post:
      summary: Revoke department access
      tags: [Visibility]
      security: [{ ApiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                userId:
                  type: string
                  format: uuid
                departments:
                  type: array
                  items: { type: string }
              required: [userId, departments]
      responses:
        "200":
          description: Number of grants revoked

# ══════════════════════════════════════════════════════════════════════════
# Components
# ══════════════════════════════════════════════════════════════════════════
components:
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: "API key with `icap_` prefix. Pass as: `Authorization: Bearer icap_...`"
    JWT:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: "Cognito JWT token. Must also include `X-Organization-Id` header."

  responses:
    BadRequest:
      description: Bad request - invalid or missing parameters
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: "Validation failed"
            details: "Field 'name' is required"
    Unauthorized:
      description: Missing or invalid authentication
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: "Unauthorized"
            details: "Invalid or expired token"
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: "Forbidden"
            details: "Viewer and Employee roles cannot perform write operations"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: "Not found"
            details: "Resource with the specified ID does not exist"

  schemas:
    # ── Enums ─────────────────────────────────────────────────────────────
    StakeholderType:
      type: string
      enum: [individual, entity, trust, fund, spv]

    SecurityType:
      type: string
      enum: [common, preferred, option, warrant, safe, convertible_note]

    TransactionType:
      type: string
      enum: [issuance, grant, exercise, transfer, cancellation, repurchase, conversion]

    OptionType:
      type: string
      enum: [iso, nqso, rsu]

    GrantStatus:
      type: string
      enum: [proposed, pending_approval, approved, active, fully_vested, exercised, partially_exercised, cancelled, expired]

    GrantCategory:
      type: string
      enum: [new_hire, promotion, retention, performance, refresh, advisory, other]

    VestingFrequency:
      type: string
      enum: [monthly, quarterly, annually]

    UserRole:
      type: string
      enum: [owner, admin, member, viewer, employee]

    # ── Error ─────────────────────────────────────────────────────────────
    Error:
      type: object
      properties:
        error:
          type: string
        details:
          type: string

    # ── Cap Table ─────────────────────────────────────────────────────────
    FullCapTableResponse:
      type: object
      properties:
        stakeholders:
          type: array
          items:
            $ref: "#/components/schemas/CapTableStakeholder"
        summary:
          $ref: "#/components/schemas/CapTableSummary"
        securityClasses:
          type: array
          items:
            $ref: "#/components/schemas/SecurityClassDetail"
        investorGroups:
          type: array
          items:
            $ref: "#/components/schemas/InvestorGroup"

    CapTableStakeholder:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          $ref: "#/components/schemas/StakeholderType"
        shares:
          type: number
        ownershipPercent:
          type: number
          format: double

    CapTableSummary:
      type: object
      properties:
        totalSharesOutstanding:
          type: integer
        totalSharesAuthorized:
          type: integer
        totalOptionsOutstanding:
          type: integer
        fullyDilutedShares:
          type: integer

    # ── Stakeholders ──────────────────────────────────────────────────────
    StakeholderListItem:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          $ref: "#/components/schemas/StakeholderType"
        email:
          type: string
          format: email
        jobTitle:
          type: string
        department:
          type: string
        managerId:
          type: string
          nullable: true
          description: UUID of the manager stakeholder
        managerName:
          type: string
          nullable: true
          description: Name of the manager stakeholder
        isActive:
          type: boolean

    StakeholderDetail:
      type: object
      properties:
        stakeholder:
          $ref: "#/components/schemas/StakeholderListItem"
        holdings:
          type: array
          items:
            $ref: "#/components/schemas/Holding"
        transactions:
          type: array
          items:
            $ref: "#/components/schemas/Transaction"
        optionGrants:
          type: array
          items:
            $ref: "#/components/schemas/OptionGrantDetail"

    Holding:
      type: object
      properties:
        securityClassId:
          type: string
        securityClassName:
          type: string
        shares:
          type: number
        type:
          $ref: "#/components/schemas/SecurityType"

    CreateStakeholderRequest:
      type: object
      required: [name, type]
      properties:
        name:
          type: string
        type:
          $ref: "#/components/schemas/StakeholderType"
        email:
          type: string
          format: email
        jobTitle:
          type: string
        department:
          type: string
        employeeId:
          type: string
        managerId:
          type: string
          format: uuid
          nullable: true
          description: Manager stakeholder UUID for org hierarchy

    # ── Security Classes ──────────────────────────────────────────────────
    SecurityClassDetail:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          $ref: "#/components/schemas/SecurityType"
        authorizedShares:
          type: integer
        issuedShares:
          type: integer
        originalIssuePrice:
          type: number
          format: double
        liquidationPreference:
          type: number
          format: double
        conversionRatio:
          type: number
          format: double
        isParticipating:
          type: boolean
        participationCap:
          type: number
          format: double
        seniorityRank:
          type: integer

    CreateSecurityClassRequest:
      type: object
      required: [name, type]
      properties:
        name:
          type: string
        type:
          $ref: "#/components/schemas/SecurityType"
        originalIssuePrice:
          type: number
          format: double
        liquidationPreference:
          type: number
          format: double
        conversionRatio:
          type: number
          format: double
        isParticipating:
          type: boolean
        participationCap:
          type: number
          format: double
        seniorityRank:
          type: integer
        authorizedShares:
          type: integer

    UpdateSecurityClassRequest:
      type: object
      required: [securityClassId]
      properties:
        securityClassId:
          type: string
        conversionRatio:
          type: number
          format: double
        authorizedShares:
          type: integer
        originalIssuePrice:
          type: number
          format: double
        liquidationPreference:
          type: number
          format: double
        isParticipating:
          type: boolean
        participationCap:
          type: number
          format: double
        seniorityRank:
          type: integer

    # ── Equity Plans ──────────────────────────────────────────────────────
    EquityPlan:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        year:
          type: integer
        authorizedShares:
          type: integer
        sharesGranted:
          type: integer
        sharesAvailable:
          type: integer

    CreateEquityPlanRequest:
      type: object
      required: [name, year, authorizedShares]
      properties:
        name:
          type: string
        year:
          type: integer
        authorizedShares:
          type: integer

    UpdateEquityPlanRequest:
      type: object
      required: [equityPlanId]
      properties:
        equityPlanId:
          type: string
        authorizedShares:
          type: integer
        name:
          type: string

    # ── Vesting Schedules ─────────────────────────────────────────────────
    VestingSchedule:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        totalMonths:
          type: integer
        cliffMonths:
          type: integer
        frequency:
          $ref: "#/components/schemas/VestingFrequency"

    CreateVestingScheduleRequest:
      type: object
      required: [totalMonths, cliffMonths]
      properties:
        name:
          type: string
        totalMonths:
          type: integer
        cliffMonths:
          type: integer
        frequency:
          $ref: "#/components/schemas/VestingFrequency"

    # ── Transactions ──────────────────────────────────────────────────────
    Transaction:
      type: object
      properties:
        id:
          type: string
        securityId:
          type: string
        stakeholderId:
          type: string
        securityClassId:
          type: string
        type:
          $ref: "#/components/schemas/TransactionType"
        shares:
          type: number
        pricePerShare:
          type: number
          format: double
        transactionDate:
          type: string
          format: date
        notes:
          type: string

    CreateTransactionRequest:
      type: object
      required: [securityId, stakeholderId, securityClassId, type, shares, transactionDate]
      properties:
        securityId:
          type: string
        stakeholderId:
          type: string
        securityClassId:
          type: string
        type:
          $ref: "#/components/schemas/TransactionType"
        shares:
          type: number
        pricePerShare:
          type: number
          format: double
        transactionDate:
          type: string
          format: date
        notes:
          type: string
        confirm:
          type: boolean
          default: false
          description: Set to true to execute the transaction. When false or omitted, returns a preview.

    # ── Option Grants ─────────────────────────────────────────────────────
    OptionGrantDetail:
      type: object
      properties:
        id:
          type: string
        stakeholderId:
          type: string
        stakeholderName:
          type: string
        equityPlanId:
          type: string
        optionType:
          $ref: "#/components/schemas/OptionType"
        sharesGranted:
          type: integer
        exercisePrice:
          type: number
          format: double
        grantDate:
          type: string
          format: date
        vestingStartDate:
          type: string
          format: date
        expirationDate:
          type: string
          format: date
        vestingScheduleId:
          type: string
        status:
          $ref: "#/components/schemas/GrantStatus"
        category:
          $ref: "#/components/schemas/GrantCategory"
        grantNumber:
          type: string
        notes:
          type: string
        sharesVested:
          type: integer
        sharesExercised:
          type: integer
        sharesUnvested:
          type: integer

    CreateOptionGrantRequest:
      type: object
      required: [stakeholderId, equityPlanId, optionType, sharesGranted, exercisePrice, grantDate, vestingStartDate, expirationDate]
      properties:
        stakeholderId:
          type: string
        equityPlanId:
          type: string
        optionType:
          $ref: "#/components/schemas/OptionType"
        sharesGranted:
          type: integer
        exercisePrice:
          type: number
          format: double
        grantDate:
          type: string
          format: date
        vestingStartDate:
          type: string
          format: date
        expirationDate:
          type: string
          format: date
        vestingScheduleId:
          type: string
        status:
          $ref: "#/components/schemas/GrantStatus"
        category:
          $ref: "#/components/schemas/GrantCategory"
        notes:
          type: string
        grantNumber:
          type: string
        confirm:
          type: boolean
          default: false
          description: Set to true to create the grant. When false or omitted, returns a preview.

    UpdateOptionGrantRequest:
      type: object
      required: [grantId]
      properties:
        grantId:
          type: string
        sharesGranted:
          type: integer
        exercisePrice:
          type: number
          format: double
        grantDate:
          type: string
          format: date
        vestingStartDate:
          type: string
          format: date
        expirationDate:
          type: string
          format: date
        grantNumber:
          type: string
        category:
          $ref: "#/components/schemas/GrantCategory"
        notes:
          type: string
        status:
          $ref: "#/components/schemas/GrantStatus"

    ExerciseOptionGrantRequest:
      type: object
      required: [grantId, sharesToExercise, exerciseDate]
      properties:
        grantId:
          type: string
        sharesToExercise:
          type: integer
        exerciseDate:
          type: string
          format: date
        notes:
          type: string
        confirm:
          type: boolean
          default: false
          description: Set to true to execute the exercise. When false or omitted, returns a preview.

    # ── Grant Status ──────────────────────────────────────────────────────
    UpdateGrantStatusRequest:
      type: object
      required: [grantIds, newStatus]
      properties:
        grantIds:
          type: array
          items:
            type: string
        newStatus:
          type: string
          enum: [proposed, pending_approval, approved, active, cancelled, expired]
        notes:
          type: string
        confirm:
          type: boolean
          default: false
          description: Set to true to execute the status change. When false or omitted, returns a preview.

    # ── Investor Groups ───────────────────────────────────────────────────
    InvestorGroup:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        stakeholderIds:
          type: array
          items:
            type: string

    CreateInvestorGroupRequest:
      type: object
      required: [name, stakeholderIds]
      properties:
        name:
          type: string
        stakeholderIds:
          type: array
          items:
            type: string
        groupId:
          type: string
          description: If provided, updates the existing group instead of creating a new one.

    # ── Bulk ──────────────────────────────────────────────────────────────
    BulkResult:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            additionalProperties: true
        created:
          type: integer
        failed:
          type: integer

    # ── Organization Management ───────────────────────────────────────────
    MeResponse:
      type: object
      properties:
        userId:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
        organizations:
          type: array
          items:
            $ref: "#/components/schemas/UserOrganization"
        linkedStakeholders:
          type: array
          items:
            $ref: "#/components/schemas/LinkedStakeholder"

    UserOrganization:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        slug:
          type: string
        role:
          $ref: "#/components/schemas/UserRole"

    LinkedStakeholder:
      type: object
      properties:
        organizationId:
          type: string
        stakeholderId:
          type: string
        stakeholderName:
          type: string

    CreateOrganizationRequest:
      type: object
      required: [name, slug]
      properties:
        name:
          type: string
        slug:
          type: string
        domain:
          type: string

    OrganizationMember:
      type: object
      properties:
        userId:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
        role:
          $ref: "#/components/schemas/UserRole"
        canViewFullCapTable:
          type: boolean

    AddMemberRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        role:
          $ref: "#/components/schemas/UserRole"
        expiresInDays:
          type: integer

    UpdateMemberRequest:
      type: object
      properties:
        role:
          $ref: "#/components/schemas/UserRole"
        canViewFullCapTable:
          type: boolean

    InviteStakeholderRequest:
      type: object
      required: [stakeholderId, email]
      properties:
        stakeholderId:
          type: string
        email:
          type: string
          format: email
        expiresInDays:
          type: integer

    # ── 409A Valuation Schemas ────────────────────────────────────────────
    Report409AStatus:
      type: string
      enum: [draft, computed, reviewed, finalized]

    AllocationMethod:
      type: string
      enum: [opm, pwerm, cvm, hybrid]

    Start409ARequest:
      type: object
      required: [valuationDate]
      properties:
        valuationDate:
          type: string
          format: date
        provider:
          type: string
          enum: [internal, external]
          default: internal
        notes:
          type: string

    Report409A:
      type: object
      properties:
        id:
          type: string
        status:
          $ref: "#/components/schemas/Report409AStatus"
        valuationDate:
          type: string
          format: date
        fairMarketValue:
          type: number
          format: double
          nullable: true
        provider:
          type: string
        reviewerUserId:
          type: string
          nullable: true
        finalizedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time

    Report409AInputs:
      type: object
      properties:
        totalEquityValue:
          type: number
          format: double
        dlom:
          type: number
          format: double
          description: Discount for lack of marketability (0-1)
        dloc:
          type: number
          format: double
          description: Discount for lack of control (0-1)
        allocationMethod:
          $ref: "#/components/schemas/AllocationMethod"
        volatility:
          type: number
          format: double
        riskFreeRate:
          type: number
          format: double
        termYears:
          type: number
          format: double

    Report409ADetail:
      allOf:
        - $ref: "#/components/schemas/Report409A"
        - type: object
          properties:
            inputs:
              $ref: "#/components/schemas/Report409AInputs"

    Update409AInputsRequest:
      type: object
      properties:
        totalEquityValue:
          type: number
          format: double
        dlom:
          type: number
          format: double
        dloc:
          type: number
          format: double
        allocationMethod:
          $ref: "#/components/schemas/AllocationMethod"
        volatility:
          type: number
          format: double
        riskFreeRate:
          type: number
          format: double
        termYears:
          type: number
          format: double

    AssignReviewerRequest:
      type: object
      required: [reviewerUserId]
      properties:
        reviewerUserId:
          type: string

    Review409ARequest:
      type: object
      required: [decision]
      properties:
        decision:
          type: string
          enum: [approve, request_changes]
        comments:
          type: string

    # ── SBC Expensing Schemas ─────────────────────────────────────────────
    SbcValuationModel:
      type: string
      enum: [black-scholes, binomial, monte-carlo]

    SbcValuationInputs:
      type: object
      properties:
        stockPrice:
          type: number
          format: double
        strikePrice:
          type: number
          format: double
        volatility:
          type: number
          format: double
        riskFreeRate:
          type: number
          format: double
        termYears:
          type: number
          format: double
        dividendYield:
          type: number
          format: double

    CreateSbcValuationRequest:
      type: object
      required: [grantId, model, inputs, valuationDate]
      properties:
        grantId:
          type: string
        model:
          $ref: "#/components/schemas/SbcValuationModel"
        inputs:
          $ref: "#/components/schemas/SbcValuationInputs"
        valuationDate:
          type: string
          format: date

    SbcValuation:
      type: object
      properties:
        id:
          type: string
        grantId:
          type: string
        model:
          $ref: "#/components/schemas/SbcValuationModel"
        fairValue:
          type: number
          format: double
        inputs:
          $ref: "#/components/schemas/SbcValuationInputs"
        valuationDate:
          type: string
          format: date
        createdAt:
          type: string
          format: date-time

    ComputeSbcExpenseRequest:
      type: object
      required: [startDate, endDate]
      properties:
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
        grantIds:
          type: array
          items:
            type: string
          description: Optional list of grant IDs. If omitted, computes for all grants.

    SbcExpenseItem:
      type: object
      properties:
        grantId:
          type: string
        stakeholderName:
          type: string
        department:
          type: string
        periodExpense:
          type: number
          format: double
        cumulativeExpense:
          type: number
          format: double
        remainingExpense:
          type: number
          format: double

    SbcExpenseScheduleEntry:
      type: object
      properties:
        period:
          type: string
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
        expense:
          type: number
          format: double
        cumulativeExpense:
          type: number
          format: double

    SbcExpenseScheduleSummary:
      type: object
      properties:
        totalExpense:
          type: number
          format: double
        periodCount:
          type: integer
        grantCount:
          type: integer

    SbcDisclosureReport:
      type: object
      properties:
        fiscalYearStart:
          type: string
          format: date
        fiscalYearEnd:
          type: string
          format: date
        totalCompensationExpense:
          type: number
          format: double
        weightedAverageAssumptions:
          type: object
          properties:
            volatility:
              type: number
              format: double
            riskFreeRate:
              type: number
              format: double
            expectedTerm:
              type: number
              format: double
            dividendYield:
              type: number
              format: double
        activitySummary:
          type: object
          properties:
            optionsOutstandingStart:
              type: integer
            granted:
              type: integer
            exercised:
              type: integer
            forfeited:
              type: integer
            optionsOutstandingEnd:
              type: integer
            weightedAverageExercisePrice:
              type: number
              format: double
        intrinsicValueOutstanding:
          type: number
          format: double
        unrecognizedExpense:
          type: number
          format: double
        weightedAverageRemainingVestingPeriod:
          type: number
          format: double
