openapi: 3.1.0
info:
  title: SOT Platform API
  version: "1.0.0"
  description: >
    Stock Ownership Transfer Platform — REST API (v1). CMA-supervised, bilingual
    share-transfer platform for Saudi private companies (SJSC/CJSC). Scope: v1.0,
    Use Case A + C, transfer-only.

    Conventions:
    - URL-versioned under /v1. JSON only. All times ISO-8601 UTC.
    - Auth: Bearer JWT (15-min access, 7-day rotating refresh). High-value actions
      (signing, payment confirmation) require a fresh Nafath re-auth
      reference supplied in the X-Nafath-Reauth header.
    - Money as decimal strings (e.g. "2000000.00") to preserve DECIMAL precision — never floats.
    - Idempotency: mutating payment endpoints require an Idempotency-Key header (UUID).
    - Errors: RFC-7807-style problem objects (see Error schema).
    - Bilingual: name fields carry _ar and _en variants; documents generated AR+EN.
  contact: { name: SOT Engineering, email: Yasmin@numuangels.net }

servers:
  - url: https://api.sot.numuangels.net/v1
    description: Production
  - url: https://api.staging.sot.numuangels.net/v1
    description: Staging
  - url: http://localhost:3000/v1
    description: Local / demo (MOCK integrations)

tags:
  - { name: Auth, description: "OTP, Nafath, tokens" }
  - { name: Users, description: "Profile & verification status" }
  - { name: Companies, description: "Wathq lookup & registry (Use Case C)" }
  - { name: Transfers, description: "Core transfer lifecycle & state actions" }
  - { name: CompanyApproval, description: "Company notification & approval before signing" }
  - { name: Documents, description: "Deed, ZATCA declaration, vault" }
  - { name: Signatures, description: "Three-party e-signing" }
  - { name: Payments, description: "Moyasar, escrow & seller wallet" }
  - { name: MoCI, description: "Filing guidance & Wathq verification" }
  - { name: Dashboards, description: "Seller & buyer views" }
  - { name: Admin, description: "Platform operations & CMA reporting" }
  - { name: Webhooks, description: "Inbound provider callbacks (HMAC-validated)" }

security:
  - bearerAuth: []

paths:

  # ---------------- Auth ----------------
  /auth/otp/request:
    post:
      tags: [Auth]
      summary: Request OTP to a Saudi mobile number (AUTH-01, AUTH-05)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone_number]
              properties:
                phone_number: { type: string, example: "+9665XXXXXXXX", description: E.164 }
      responses:
        "202": { description: OTP dispatched (expires in 5 min; 30 s delivery target) }
        "409": { description: Number already registered, $ref: "#/components/responses/Problem" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /auth/otp/verify:
    post:
      tags: [Auth]
      summary: Verify OTP; returns tokens (account pending until Nafath)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone_number, code]
              properties:
                phone_number: { type: string }
                code: { type: string, example: "482913" }
      responses:
        "200":
          description: Authenticated
          content: { application/json: { schema: { $ref: "#/components/schemas/TokenPair" } } }
        "401": { $ref: "#/components/responses/Problem" }
  /auth/nafath/initiate:
    post:
      tags: [Auth]
      summary: Start Nafath identity verification (AUTH-02, AUTH-03)
      responses:
        "200":
          description: Nafath session started; show 2-digit code to confirm in Nafath app
          content:
            application/json:
              schema:
                type: object
                properties:
                  nafath_session_ref: { type: string }
                  display_code: { type: string, example: "27", description: 2-digit confirm code }
                  expires_at: { type: string, format: date-time }
  /auth/nafath/reauth:
    post:
      tags: [Auth]
      summary: High-value action re-authentication; returns a short-lived reauth ref
      responses:
        "200":
          description: Re-auth reference for use in X-Nafath-Reauth
          content:
            application/json:
              schema: { type: object, properties: { nafath_reauth_ref: { type: string }, expires_at: { type: string, format: date-time } } }
  /auth/token/refresh:
    post:
      tags: [Auth]
      summary: Rotate refresh token, issue new access token
      security: []
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, required: [refresh_token], properties: { refresh_token: { type: string } } } } }
      responses:
        "200": { description: New pair, content: { application/json: { schema: { $ref: "#/components/schemas/TokenPair" } } } }
        "401": { $ref: "#/components/responses/Problem" }

  # ---------------- Users ----------------
  /users/me:
    get:
      tags: [Users]
      summary: Current user profile & verification status (AUTH-07)
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/User" } } } }
        "401": { $ref: "#/components/responses/Problem" }

  # ---------------- Companies / Registry (Use Case C) ----------------
  /companies/lookup:
    post:
      tags: [Companies]
      summary: Verify CR via Wathq; return shareholding (REG-01, REG-02)
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, required: [cr_number], properties: { cr_number: { type: string, example: "7012345678" } } } } }
      responses:
        "200":
          description: Wathq-verified company + seller shareholding
          content: { application/json: { schema: { $ref: "#/components/schemas/CompanyLookupResult" } } }
        "404": { description: CR not found — fall back to document upload, $ref: "#/components/responses/Problem" }
        "502": { description: Wathq unavailable — retry or document upload, $ref: "#/components/responses/Problem" }
  /companies/{companyId}:
    get:
      tags: [Companies]
      parameters: [{ $ref: "#/components/parameters/companyId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/Company" } } } }
        "404": { $ref: "#/components/responses/Problem" }
  /companies/{companyId}/shareholders:
    get:
      tags: [Companies]
      summary: Cap table (shareholder register)
      parameters: [{ $ref: "#/components/parameters/companyId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Shareholder" } } } } }
  /companies/{companyId}/registry/reconcile:
    post:
      tags: [Companies]
      summary: Resolve Wathq vs claimed discrepancy; route to company admin (REG-04)
      parameters: [{ $ref: "#/components/parameters/companyId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/RegistryReconcileRequest" } } }
      responses:
        "202": { description: Discrepancy logged; awaiting board ratification }
  /companies/{companyId}/registry/ratify:
    post:
      tags: [Companies]
      summary: Board ratification of baseline register via emdha (REG-05)
      parameters: [{ $ref: "#/components/parameters/companyId" }]
      responses:
        "202": { description: Ratification signing session created }

  # ---------------- Transfers ----------------
  /transfers:
    get:
      tags: [Transfers]
      summary: List transfers for current user (paginated)
      parameters:
        - { $ref: "#/components/parameters/status" }
        - { $ref: "#/components/parameters/page" }
        - { $ref: "#/components/parameters/pageSize" }
      responses:
        "200":
          description: OK
          content: { application/json: { schema: { $ref: "#/components/schemas/TransferPage" } } }
    post:
      tags: [Transfers]
      summary: Initiate a transfer (TRF-01…TRF-05) → DRAFT
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/TransferCreate" } } }
      responses:
        "201":
          description: Created (fee computed, reference TXN-YYYY-NNNNN)
          content: { application/json: { schema: { $ref: "#/components/schemas/Transfer" } } }
        "422": { $ref: "#/components/responses/Problem" }
  /transfers/{transferId}:
    get:
      tags: [Transfers]
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/Transfer" } } } }
        "404": { $ref: "#/components/responses/Problem" }
  /transfers/{transferId}/events:
    get:
      tags: [Transfers]
      summary: Immutable event log (CMA audit trail)
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/TransferEvent" } } } } }
  /transfers/{transferId}/submit-pricing:
    post:
      tags: [Transfers]
      summary: DRAFT → PRICE_VALIDATION (triggers FO Engine)
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "202": { description: FO calculation started }
        "409": { $ref: "#/components/responses/StateConflict" }
  /transfers/{transferId}/buyer-confirm:
    post:
      tags: [Transfers]
      summary: Buyer confirms price after KYC → BUYER_CONFIRMED (TRF-06)
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "200": { description: Confirmed }
        "409": { $ref: "#/components/responses/StateConflict" }
  /transfers/{transferId}/notify-company:
    post:
      tags: [Transfers]
      summary: Send one-time SMS link to company manager → COMPANY_NOTIFIED (TRF-08)
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "202": { description: SMS dispatched }
  /transfers/{transferId}/cancel:
    post:
      tags: [Transfers]
      summary: Cancel (non-terminal states); applies refund rules
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      requestBody:
        content: { application/json: { schema: { type: object, properties: { reason: { type: string } } } } }
      responses:
        "200": { description: Cancelled }
        "409": { $ref: "#/components/responses/StateConflict" }

  # ---------------- Fairness Opinion ----------------
  /transfers/{transferId}/fairness-opinion:
    get:
      tags: [FairnessOpinion]
      summary: Retrieve the FO for a transfer (FO-06)
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/FairnessOpinion" } } } }
        "404": { $ref: "#/components/responses/Problem" }
  /transfers/{transferId}/fairness-opinion/reprice:
    post:
      tags: [FairnessOpinion]
      summary: Submit a new price for a FO_FLAGGED transfer → recompute
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, required: [price_per_share], properties: { price_per_share: { $ref: "#/components/schemas/Money" } } } } }
      responses:
        "202": { description: Recalculating }
  /transfers/{transferId}/fairness-opinion/justify:
    post:
      tags: [FairnessOpinion]
      summary: Provide written justification for out-of-range price
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, required: [justification], properties: { justification: { type: string } } } } }
      responses:
        "202": { description: Justification stored; awaiting acceptance }

  # ---------------- Company approval (ROFR removed — DEC-006) ----------------
  /transfers/{transferId}/company-approval:
    post:
      tags: [CompanyApproval]
      summary: Company approves the transfer → COMPANY_APPROVED (TRF-09)
      description: Company admin (emdha) approves after review. No shareholder pre-emption window.
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "202": { description: Approval signing session created }
        "409": { $ref: "#/components/responses/StateConflict" }
  /transfers/{transferId}/company-reject:
    post:
      tags: [CompanyApproval]
      summary: Company rejects the transfer → CANCELLED
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      requestBody:
        content: { application/json: { schema: { type: object, properties: { reason: { type: string } } } } }
      responses:
        "200": { description: Rejected/cancelled }
        "409": { $ref: "#/components/responses/StateConflict" }

  # ---------------- Documents ----------------
  /transfers/{transferId}/documents:
    get:
      tags: [Documents]
      summary: List documents in the vault for a transfer
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Document" } } } } }
  /documents/{documentId}/download:
    get:
      tags: [Documents]
      summary: Time-limited signed OSS URL (checksum-validated)
      parameters: [{ name: documentId, in: path, required: true, schema: { type: string, format: uuid } }]
      responses:
        "200":
          description: OK
          content: { application/json: { schema: { type: object, properties: { url: { type: string, format: uri }, expires_at: { type: string, format: date-time }, checksum_sha256: { type: string } } } } }

  # ---------------- Signatures ----------------
  /transfers/{transferId}/signatures:
    get:
      tags: [Signatures]
      summary: "Signature status per role (order: seller→company→buyer)"
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Signature" } } } } }
  /transfers/{transferId}/signatures/initiate:
    post:
      tags: [Signatures]
      summary: Start emdha signing session for the next signer (SIGN-04)
      description: Requires fresh Nafath re-auth (X-Nafath-Reauth).
      parameters:
        - { $ref: "#/components/parameters/transferId" }
        - { $ref: "#/components/parameters/nafathReauth" }
      responses:
        "202": { description: emdha session created; redirect signer }
        "412": { description: Nafath re-auth missing/expired, $ref: "#/components/responses/Problem" }
        "409": { $ref: "#/components/responses/StateConflict" }

  # ---------------- Payments / Escrow / Wallet ----------------
  /transfers/{transferId}/escrow/fund:
    post:
      tags: [Payments]
      summary: Buyer funds escrow after all signed → ESCROW_FUNDED (PAY-01)
      description: Idempotent. Only permitted from BUYER_SIGNED.
      parameters:
        - { $ref: "#/components/parameters/transferId" }
        - { $ref: "#/components/parameters/idempotencyKey" }
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, required: [payment_method], properties: { payment_method: { $ref: "#/components/schemas/PaymentMethod" } } } } }
      responses:
        "201": { description: Payment intent created, content: { application/json: { schema: { $ref: "#/components/schemas/EscrowTransaction" } } } }
        "409": { $ref: "#/components/responses/StateConflict" }
  /transfers/{transferId}/payment/confirm:
    post:
      tags: [Payments]
      summary: Seller confirms receipt → PAYMENT_CONFIRMED; fee deducted at release (PAY-04)
      parameters:
        - { $ref: "#/components/parameters/transferId" }
        - { $ref: "#/components/parameters/nafathReauth" }
      responses:
        "200": { description: Confirmed }
        "409": { $ref: "#/components/responses/StateConflict" }
  /wallet:
    get:
      tags: [Payments]
      summary: Seller wallet balance (PAY-05)
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/Wallet" } } } }
  /wallet/transactions:
    get:
      tags: [Payments]
      summary: Immutable wallet ledger
      parameters: [{ $ref: "#/components/parameters/page" }, { $ref: "#/components/parameters/pageSize" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/WalletTransaction" } } } } }
  /wallet/withdraw:
    post:
      tags: [Payments]
      summary: Withdraw to bank (mada/IBAN) (PAY-05)
      parameters: [{ $ref: "#/components/parameters/idempotencyKey" }, { $ref: "#/components/parameters/nafathReauth" }]
      requestBody:
        required: true
        content: { application/json: { schema: { type: object, required: [amount_sar, iban], properties: { amount_sar: { $ref: "#/components/schemas/Money" }, iban: { type: string } } } } }
      responses:
        "202": { description: Withdrawal initiated }

  # ---------------- MoCI ----------------
  /transfers/{transferId}/moci/guidance:
    get:
      tags: [MoCI]
      summary: Step-by-step SBC portal filing guide (MOCI-01)
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/MociGuidance" } } } }
  /transfers/{transferId}/moci/confirm-filing:
    post:
      tags: [MoCI]
      summary: Company confirms MoCI filing → MOCI_FILED (starts Wathq poll)
      parameters: [{ $ref: "#/components/parameters/transferId" }]
      responses:
        "202": { description: Wathq verification polling started }

  # ---------------- Dashboards ----------------
  /dashboard/seller:
    get:
      tags: [Dashboards]
      summary: Seller portfolio, transfers, fee history (DASH-01…03)
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/SellerDashboard" } } } }
  /dashboard/buyer:
    get:
      tags: [Dashboards]
      summary: Buyer offers, holdings, performance (DASH-04…06)
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/BuyerDashboard" } } } }

  # ---------------- Admin ----------------
  /admin/transfers:
    get:
      tags: [Admin]
      summary: Monitor all transfers (platform admin)
      parameters: [{ $ref: "#/components/parameters/status" }, { $ref: "#/components/parameters/page" }, { $ref: "#/components/parameters/pageSize" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/TransferPage" } } } }
        "403": { $ref: "#/components/responses/Problem" }
  /admin/reports/cma-sandbox:
    get:
      tags: [Admin]
      summary: Generate monthly CMA sandbox report
      parameters: [{ name: month, in: query, required: true, schema: { type: string, example: "2026-07" } }]
      responses:
        "200": { description: Report (JSON or signed export URL) }
  /admin/audit-logs:
    get:
      tags: [Admin]
      summary: Export immutable audit logs (entity/actor filters)
      parameters:
        - { name: entity_type, in: query, schema: { type: string } }
        - { name: entity_id, in: query, schema: { type: string, format: uuid } }
      responses:
        "200": { description: OK }

  # ---------------- Webhooks (inbound) ----------------
  /webhooks/nafath:
    post:
      tags: [Webhooks]
      summary: Nafath verification callback (HMAC-validated)
      security: []
      parameters: [{ $ref: "#/components/parameters/signature" }]
      requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
      responses: { "204": { description: Processed }, "401": { description: Invalid signature } }
  /webhooks/emdha:
    post:
      tags: [Webhooks]
      summary: emdha signing events (per-signer)
      security: []
      parameters: [{ $ref: "#/components/parameters/signature" }]
      requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
      responses: { "204": { description: Processed }, "401": { description: Invalid signature } }
  /webhooks/moyasar:
    post:
      tags: [Webhooks]
      summary: Moyasar payment events (idempotency-checked)
      security: []
      parameters: [{ $ref: "#/components/parameters/signature" }]
      requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
      responses: { "204": { description: Processed }, "401": { description: Invalid signature } }
  /webhooks/4jawaly:
    post:
      tags: [Webhooks]
      summary: SMS delivery receipts
      security: []
      parameters: [{ $ref: "#/components/parameters/signature" }]
      requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
      responses: { "204": { description: Processed } }

components:

  securitySchemes:
    bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }

  parameters:
    transferId: { name: transferId, in: path, required: true, schema: { type: string, format: uuid } }
    companyId: { name: companyId, in: path, required: true, schema: { type: string, format: uuid } }
    page: { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
    pageSize: { name: pageSize, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
    status: { name: status, in: query, schema: { $ref: "#/components/schemas/TransferStatus" } }
    idempotencyKey: { name: Idempotency-Key, in: header, required: true, schema: { type: string, format: uuid } }
    nafathReauth: { name: X-Nafath-Reauth, in: header, required: true, schema: { type: string }, description: Fresh Nafath re-auth reference for high-value actions }
    signature: { name: X-Signature, in: header, required: true, schema: { type: string }, description: HMAC signature of the raw body }

  responses:
    Problem:
      description: Error (problem+json)
      content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } }
    RateLimited:
      description: Too many requests
      content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } }
    StateConflict:
      description: Action not allowed from the current transfer state
      content: { application/problem+json: { schema: { $ref: "#/components/schemas/Error" } } }

  schemas:
    Money:
      type: string
      pattern: '^-?\d+(\.\d{1,4})?$'
      example: "2000000.00"
      description: Decimal string; preserves DECIMAL precision. Never a JSON number.
    Error:
      type: object
      required: [type, title, status]
      properties:
        type: { type: string, example: "https://api.sot.numuangels.net/errors/state-conflict" }
        title: { type: string, example: "State conflict" }
        status: { type: integer, example: 409 }
        detail: { type: string }
        code: { type: string, example: "TRF_INVALID_TRANSITION" }
        instance: { type: string }
    TokenPair:
      type: object
      properties:
        access_token: { type: string }
        refresh_token: { type: string }
        token_type: { type: string, default: Bearer }
        expires_in: { type: integer, example: 900 }
    UserType: { type: string, enum: [SELLER, BUYER, BOTH, PLATFORM_ADMIN] }
    User:
      type: object
      properties:
        id: { type: string, format: uuid }
        phone_number: { type: string }
        email: { type: string, nullable: true }
        full_name_ar: { type: string, nullable: true }
        full_name_en: { type: string, nullable: true }
        user_type: { $ref: "#/components/schemas/UserType" }
        nafath_verified: { type: boolean }
        nafath_verified_at: { type: string, format: date-time, nullable: true }
        pdpl_consent_at: { type: string, format: date-time, nullable: true }
    CompanyType: { type: string, enum: [CJSC, SJSC, LLC] }
    Company:
      type: object
      properties:
        id: { type: string, format: uuid }
        cr_number: { type: string }
        company_name_ar: { type: string }
        company_name_en: { type: string, nullable: true }
        company_type: { $ref: "#/components/schemas/CompanyType" }
        sector: { type: string, nullable: true }
        total_issued_shares: { type: integer }
        total_assets_sar: { $ref: "#/components/schemas/Money" }
        total_liabilities_sar: { $ref: "#/components/schemas/Money" }
        is_registry_established: { type: boolean }
        wathq_verified_at: { type: string, format: date-time, nullable: true }
    CompanyLookupResult:
      type: object
      properties:
        company: { $ref: "#/components/schemas/Company" }
        seller_found_in_wathq: { type: boolean }
        ownership_percentage: { type: string, nullable: true, example: "12.500000" }
        implied_share_count: { type: integer, nullable: true }
        source: { type: string, example: WATHQ }
        last_verified_at: { type: string, format: date-time }
        fallback_required: { type: boolean, description: true if seller not found → document upload }
    ShareClass: { type: string, enum: [ORDINARY, PREFERRED, FOUNDER] }
    DataSource: { type: string, enum: [WATHQ, DOCUMENT_UPLOAD, BOARD_RATIFICATION] }
    Shareholder:
      type: object
      properties:
        id: { type: string, format: uuid }
        company_id: { type: string, format: uuid }
        full_name_ar: { type: string }
        shares_held: { type: integer }
        ownership_percentage: { type: string }
        share_class: { $ref: "#/components/schemas/ShareClass" }
        data_source: { $ref: "#/components/schemas/DataSource" }
        is_active: { type: boolean }
    RegistryReconcileRequest:
      type: object
      required: [authoritative_source]
      properties:
        authoritative_source: { $ref: "#/components/schemas/DataSource" }
        notes: { type: string }
    MarketCondition: { type: string, enum: [NORMAL, STRESS, HOT] }
    TransferStatus:
      type: string
      enum: [DRAFT, PRICE_VALIDATION, FO_APPROVED, FO_FLAGGED, BUYER_NOTIFIED, BUYER_CONFIRMED,
             COMPANY_NOTIFIED, COMPANY_APPROVED,
             SIGNING_PENDING, SELLER_SIGNED, COMPANY_SIGNED, BUYER_SIGNED, ESCROW_FUNDED,
             PAYMENT_CONFIRMED, MOCI_PENDING, MOCI_FILED, WATHQ_VERIFIED, COMPLETED,
             CANCELLED, DISPUTED]
    TransferCreate:
      type: object
      required: [company_id, shares_count, price_per_share, market_condition]
      properties:
        company_id: { type: string, format: uuid }
        buyer_national_id: { type: string, description: NID entered by seller before buyer registers }
        buyer_phone: { type: string }
        shares_count: { type: integer, minimum: 1 }
        price_per_share: { $ref: "#/components/schemas/Money" }
        market_condition: { $ref: "#/components/schemas/MarketCondition" }
        is_related_party: { type: boolean, default: false }
    Transfer:
      type: object
      properties:
        id: { type: string, format: uuid }
        reference: { type: string, example: "TXN-2026-00847" }
        company_id: { type: string, format: uuid }
        seller_id: { type: string, format: uuid }
        buyer_id: { type: string, format: uuid, nullable: true }
        shares_count: { type: integer }
        price_per_share: { $ref: "#/components/schemas/Money" }
        total_deal_value: { $ref: "#/components/schemas/Money" }
        platform_fee: { $ref: "#/components/schemas/Money", description: "2%, floor 2000, cap 20000" }
        market_condition: { $ref: "#/components/schemas/MarketCondition" }
        is_related_party: { type: boolean }
        status: { $ref: "#/components/schemas/TransferStatus" }
        company_approval_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    TransferPage:
      type: object
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/Transfer" } }
        page: { type: integer }
        page_size: { type: integer }
        total: { type: integer }
    TransferEvent:
      type: object
      properties:
        id: { type: string, format: uuid }
        event_type: { type: string, example: FO_APPROVED }
        actor_type: { type: string, enum: [SELLER, BUYER, COMPANY_ADMIN, PLATFORM_SYSTEM, EXTERNAL_API] }
        event_data: { type: object }
        created_at: { type: string, format: date-time }
    FoVerdict: { type: string, enum: [WITHIN_RANGE, BELOW_FLOOR, ABOVE_CEILING] }
    JcurveFlag: { type: string, enum: [BLIND_POOL, SWEET_SPOT, APPROACHING_TAIL, TAIL_END] }
    FairnessOpinion:
      type: object
      properties:
        reference: { type: string, example: "FO-2026-00123" }
        transfer_id: { type: string, format: uuid }
        nav_per_share: { $ref: "#/components/schemas/Money" }
        market_condition: { $ref: "#/components/schemas/MarketCondition" }
        discount_min_pct: { type: string, example: "10.00" }
        discount_avg_pct: { type: string, example: "14.00" }
        discount_max_pct: { type: string, example: "20.00" }
        floor_price: { $ref: "#/components/schemas/Money" }
        midpoint_price: { $ref: "#/components/schemas/Money" }
        ceiling_price: { $ref: "#/components/schemas/Money" }
        agreed_price: { $ref: "#/components/schemas/Money" }
        discount_to_nav_pct: { type: string }
        verdict: { $ref: "#/components/schemas/FoVerdict" }
        company_age_years: { type: integer }
        jcurve_flag: { $ref: "#/components/schemas/JcurveFlag" }
        source_citation: { type: string }
        document_url: { type: string, nullable: true }
        issued_at: { type: string, format: date-time, nullable: true }
    DocumentType: { type: string, enum: [TRANSFER_DEED, FAIRNESS_OPINION, ZATCA_DECLARATION, BOARD_RATIFICATION, COMPANY_APPROVAL, PRICE_JUSTIFICATION] }
    Document:
      type: object
      properties:
        id: { type: string, format: uuid }
        document_type: { $ref: "#/components/schemas/DocumentType" }
        language: { type: string, enum: [BILINGUAL, AR, EN], default: BILINGUAL }
        version: { type: integer }
        status: { type: string, enum: [DRAFT, PENDING_SIGNATURES, PARTIALLY_SIGNED, FULLY_SIGNED, SUPERSEDED] }
        checksum_sha256: { type: string, nullable: true }
        generated_at: { type: string, format: date-time }
    SignerRole: { type: string, enum: [SELLER, BUYER, COMPANY_ADMIN] }
    Signature:
      type: object
      properties:
        id: { type: string, format: uuid }
        signer_role: { $ref: "#/components/schemas/SignerRole" }
        signature_status: { type: string, enum: [PENDING, SIGNED, DECLINED, EXPIRED] }
        emdha_session_id: { type: string, nullable: true }
        signed_at: { type: string, format: date-time, nullable: true }
    PaymentMethod: { type: string, enum: [MADA, VISA, MASTERCARD, APPLE_PAY, STCPAY] }
    EscrowTransaction:
      type: object
      properties:
        id: { type: string, format: uuid }
        transfer_id: { type: string, format: uuid }
        transaction_type: { type: string, enum: [ESCROW_FUNDING, ESCROW_RELEASE_TO_SELLER, ESCROW_REFUND_TO_BUYER, PLATFORM_FEE_DEDUCTION] }
        amount_sar: { $ref: "#/components/schemas/Money" }
        status: { type: string, enum: [PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED] }
        payment_method: { $ref: "#/components/schemas/PaymentMethod" }
    Wallet:
      type: object
      properties:
        balance_sar: { $ref: "#/components/schemas/Money" }
        currency: { type: string, default: SAR }
    WalletTransaction:
      type: object
      properties:
        id: { type: string, format: uuid }
        transaction_type: { type: string, enum: [CREDIT_ESCROW_RELEASE, CREDIT_REFUND, DEBIT_WITHDRAWAL, DEBIT_PLATFORM_FEE] }
        amount_sar: { $ref: "#/components/schemas/Money" }
        balance_after_sar: { $ref: "#/components/schemas/Money" }
        description: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
    MociGuidance:
      type: object
      properties:
        company_type: { $ref: "#/components/schemas/CompanyType" }
        steps: { type: array, items: { type: object, properties: { order: { type: integer }, title_ar: { type: string }, title_en: { type: string }, detail_ar: { type: string }, detail_en: { type: string } } } }
        prefilled_summary: { type: object, description: "New shareholder name, ID, share count, transfer date" }
    SellerDashboard:
      type: object
      properties:
        holdings: { type: array, items: { $ref: "#/components/schemas/Shareholder" } }
        transfers: { type: array, items: { $ref: "#/components/schemas/Transfer" } }
        fee_history: { type: array, items: { type: object } }
    BuyerDashboard:
      type: object
      properties:
        pending_offers: { type: array, items: { $ref: "#/components/schemas/Transfer" } }
        holdings: { type: array, items: { type: object } }
        total_invested_sar: { $ref: "#/components/schemas/Money" }
        current_value_sar: { $ref: "#/components/schemas/Money" }
