openapi: 3.1.0
info:
  title: MyStocks Africa Partner API
  version: '1'
  description: |
    REST API for embedding African and global stock trading into your product. Get real-time and historical market data, stock prices, and tickers for major African exchanges including:
    - Nairobi Stock Exchange (NSE)
    - Nigerian Exchange Group (NGX)
    - Johannesburg Stock Exchange (JSE)
    - Ghana Stock Exchange (GSE)
    - Bourse Régionale des Valeurs Mobilières (BRVM)
    - Lusaka Securities Exchange (LuSE)
    - Uganda Securities Exchange (USE)
    - Dar es Salaam Stock Exchange (DSE)

    Easily fetch real-time stock prices, quote trade fees, execute orders, create multi-currency USD wallets, handle partner-asserted KYC, subscribe to treasury bills, corporate/government bonds, money market/yield funds, and pre-IPO deals, and subscribe to webhooks for automated dividend and transaction processing. Ideal for B2B fintechs, neobanks, wealth managers, and AI agents.


    ## Environments
    | Environment | Base URL | Key Prefix |
    |---|---|---|
    | Sandbox | https://mystocks.africa/api/sandbox/v1/partner | sk_sandbox_ |
    | Production | https://mystocks.africa/api/v1/partner | pk_live_ |

    > **Note:** `POST /register` and `POST /reset` are sandbox-only utilities that live at
    > `https://mystocks.africa/api/sandbox/v1` (without the `/partner` segment).
    > All other sandbox endpoints use the `/partner` base above.

    ## Authentication
    Pass your key via **either** header:
    `Authorization: Bearer sk_sandbox_KEY`  or  `x-api-key: sk_sandbox_KEY`

    ## Idempotency
    Pass `Idempotency-Key` on deposit, withdraw, and trade calls.
    Keys are deduplicated for 24 h. HTTP 409 if a concurrent duplicate is in-flight.

    ## Rate Limits
    | Tier | req/min |
    |---|---|
    | Sandbox / Starter | 100 |
    | Growth | 500 |
    | Enterprise | 2,000 |

    Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.
    On a `429` response, `Retry-After` (seconds until reset) is also set.

    Rate limiting uses a **1-minute sliding window**. Exceeding the limit returns `429` immediately —
    requests are not queued. Use `Retry-After` or `X-RateLimit-Reset` to determine when to retry.

    ## Errors
    All errors return a structured JSON response of the form `{"error": {"code": "ERROR_CODE", "message": "readable message"}}`.

    Common HTTP statuses:
    `400` bad params · `401` invalid key · `403` forbidden · `404` not found ·
    `409` idempotency conflict · `422` business rule · `429` rate limit · `500` server error.

    ## Sandbox Scenario Simulation
    When developing against the **Sandbox environment** (`sk_sandbox_...`), you can programmatically force mock status codes and specific sub-error scenarios on demand. This allows you to test your application's error-handling and resilience mechanisms under edge cases.

    Pass either of these custom headers in your Sandbox requests:
    *   `X-Sandbox-Force-Status`: Force a specific HTTP status code (e.g., `429`, `400`, `403`, `500`).
    *   `X-Sandbox-Force-Error`: Force a machine-readable sub-code returned in the `code` field (e.g., `INSUFFICIENT_FUNDS`, `RATE_LIMITED`, `KYC_REQUIRED`).
  contact:
    name: MyStocks Africa Partner Support
    email: partnerships@mystocks.africa
    url: https://mystocks.africa/partners
  license:
    name: Proprietary
    url: https://mystocks.africa/terms
servers:
  - url: https://mystocks.africa/api/v1/partner
    description: Production
  - url: https://mystocks.africa/api/sandbox/v1/partner
    description: Sandbox
tags:
  - name: Registration
    description: Sandbox account creation, partner status, and tier upgrades.
  - name: Market Data
    description: Stocks, companies, prices, charts, news, and pulse events across 8+ African exchanges. Supports real-time and historical stock price feeds for the Nairobi Stock Exchange (NSE), Nigerian Exchange Group (NGX), Johannesburg Stock Exchange (JSE), Ghana Stock Exchange (GSE), BRVM, LuSE, USE, and DSE.
  - name: Trading
    description: Quote, place, list and cancel BUY/SELL orders on your own partner account.
  - name: Asset Classes
    description: Bonds, treasury bills, funds, ETFs, private credit and pre-IPO opportunities.
  - name: Sub-Accounts
    description: Provision, fund, trade on behalf of, and report on your end-users. Each sub-account has an isolated USD wallet, portfolio, and order history.
  - name: Reports
    description: AUM, positions, fees, revenue, invoices and dividends across all sub-accounts.
  - name: Fund Flow
    description: Top-up and payout requests for your master wallet.
  - name: Webhooks
    description: |
      Register HTTPS endpoints for real-time event delivery.

      ### Webhook Security & Verification
      Every payload sent to your webhook is signed with an `HMAC-SHA256` signature using your endpoint's configured signing secret. The signature is sent via the `x-mystocks-signature` header.

      You **must** verify the signature on your server to guarantee the request originated from MyStocks Africa and prevent body tampering. Use a constant-time comparison to mitigate timing attacks.

      #### Express / Node.js Verification Example
      ```javascript
      const crypto = require('crypto');

      app.post('/webhooks/mystocks', (req, res) => {
        const signature = req.headers['x-mystocks-signature'];
        const payload = JSON.stringify(req.body);
        const expectedSignature = crypto
          .createHmac('sha256', process.env.MYSTOCKS_WEBHOOK_SECRET)
          .update(payload)
          .digest('hex');

        // Prevent timing attacks using constant-time comparison
        const verified = crypto.timingSafeEqual(
          Buffer.from(signature, 'utf8'),
          Buffer.from(expectedSignature, 'utf8')
        );

        if (!verified) return res.status(401).send('Invalid signature');
        // Handle event safely...
        res.status(200).send('OK');
      });
      ```

      #### FastAPI / Python Verification Example
      ```python
      import hmac
      import hashlib
      import os
      from fastapi import FastAPI, Header, HTTPException, Request

      app = FastAPI()

      @app.post("/webhooks/mystocks")
      async def handle_webhook(request: Request, x_mystocks_signature: str = Header(...)):
          payload = await request.body()
          expected_sig = hmac.new(
              key=bytes(os.getenv("MYSTOCKS_WEBHOOK_SECRET"), "utf-8"),
              msg=payload,
              digestmod=hashlib.sha256
          ).hexdigest()

          if not hmac.compare_digest(x_mystocks_signature, expected_sig):
              raise HTTPException(status_code=401, detail="Invalid signature")
          # Handle event safely...
          return {"status": "ok"}
      ```
  - name: Market Intelligence
    description: Editorial market news and exchange announcements feed.
  - name: Dividends
    description: Dividend calendar and per-account distribution history.
  - name: Key Management
    description: Rotate, revoke, and issue read-only data API keys.
  - name: Observability
    description: Audit log and daily API usage analytics.
  - name: SLA
    description: Real-time service health and partner SLA tier commitments.
  - name: Settings
    description: Partner configuration including logo and custom SMTP email.
  - name: Sandbox Testing
    description: Live-key (pk_live_) endpoints for end-to-end webhook integration testing.
security:
  - BearerAuth: []
  - ApiKeyHeader: []
webhooks:
  order.pending:
    post:
      summary: Order submitted — pending admin review
      description: |
        Fired immediately when a trade is submitted via POST /users/{userId}/trade or POST /trade.
        Funds are reserved in the sub-account wallet but execution has not yet occurred.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: order.pending
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    orderId:          { type: string }
                    subAccountId:     { type: string, description: 'Present for sub-account orders. Omitted for master-account orders.' }
                    externalId:       { type: string, nullable: true }
                    accountType:      { type: string, example: MASTER, description: 'Present only for master-account orders.' }
                    type:             { type: string, enum: [BUY, SELL] }
                    symbol:           { type: string, example: SCOM.KE }
                    quantity:         { type: number }
                    priceAtOrder:     { type: number, description: Local-currency price at order time. }
                    usdPriceAtOrder:  { type: number }
                    gross:            { type: number }
                    baseFee:          { type: number }
                    partnerMarkupFee: { type: number }
                    fee:              { type: number, description: Total fee (baseFee + partnerMarkupFee). }
                    totalCost:        { type: number, description: 'BUY only: gross + fee, deducted from wallet.' }
                    status:           { type: string, example: PENDING }
      responses:
        '200':
          description: Webhook received successfully.
  order.filled:
    post:
      summary: Order executed — shares/proceeds credited
      description: |
        Fired when MyStocks ops approves and settles an order.
        Also fires the legacy alias `trade.settled` with the same payload for backwards compatibility.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: order.filled
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    orderId:            { type: string }
                    subAccountId:       { type: string, nullable: true }
                    externalId:         { type: string, nullable: true }
                    accountType:        { type: string, example: MASTER, description: 'Present only for master-account orders.' }
                    type:               { type: string, enum: [BUY, SELL] }
                    symbol:             { type: string, example: SCOM.KE }
                    exchange:           { type: string, nullable: true }
                    quantity:           { type: number }
                    priceAtOrder:       { type: number }
                    usdPriceAtOrder:    { type: number }
                    feeAmount:          { type: number }
                    status:             { type: string, example: FILLED }
                    settledAt:          { type: string, format: date-time }
                    settlementUsdPrice: { type: number }
                    totalCost:          { type: number, description: 'BUY only.' }
                    proceeds:           { type: number, description: 'SELL only.' }
      responses:
        '200':
          description: Webhook received successfully.
  order.rejected:
    post:
      summary: Order declined by MyStocks ops
      description: |
        Fired when an order is rejected. BUY order funds are returned to the sub-account wallet before this fires.
        Also fires the legacy alias `trade.rejected` with the same payload.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: order.rejected
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    orderId:          { type: string }
                    subAccountId:     { type: string, nullable: true }
                    externalId:       { type: string, nullable: true }
                    type:             { type: string, enum: [BUY, SELL] }
                    symbol:           { type: string }
                    exchange:         { type: string, nullable: true }
                    quantity:         { type: number }
                    priceAtOrder:     { type: number }
                    usdPriceAtOrder:  { type: number }
                    feeAmount:        { type: number }
                    status:           { type: string, example: REJECTED }
                    settledAt:        { type: string, format: date-time }
                    rejectionCode:    { type: string, nullable: true, example: LIQUIDITY_UNAVAILABLE }
                    rejectionReason:  { type: string, nullable: true }
      responses:
        '200':
          description: Webhook received successfully.
  order.cancelled:
    post:
      summary: Order cancelled by partner
      description: |
        Fired when a partner cancels a PENDING order via DELETE /orders/{orderId}.
        BUY cancellations include `refunded` (amount returned to sub-account wallet).
        SELL cancellations have no wallet impact and omit `refunded`.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: order.cancelled
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    orderId:      { type: string }
                    subAccountId: { type: string }
                    externalId:   { type: string, nullable: true }
                    type:         { type: string, enum: [BUY, SELL] }
                    symbol:       { type: string }
                    quantity:     { type: number }
                    status:       { type: string, example: CANCELLED }
                    refunded:     { type: number, description: 'BUY only: amount returned to sub-account wallet.' }
                    currency:     { type: string, example: USD }
      responses:
        '200':
          description: Webhook received successfully.
  deposit.confirmed:
    post:
      summary: Sub-account deposit recorded
      description: Fired after funds are moved from the partner master wallet to a sub-account wallet.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: deposit.confirmed
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    subAccountId:   { type: string }
                    externalId:     { type: string, nullable: true }
                    amount:         { type: number }
                    currency:       { type: string, example: USD }
                    newBalance:     { type: number }
                    localAmount:    { type: number, description: 'Optional: amount in user local currency.' }
                    localCurrency:  { type: string, description: 'Optional: ISO currency code, e.g. KES.' }
                    fxRate:         { type: number, description: 'Optional: exchange rate applied.' }
      responses:
        '200':
          description: Webhook received successfully.
  withdraw.confirmed:
    post:
      summary: Sub-account withdrawal processed
      description: Fired after a sub-account withdrawal moves funds back to the partner master wallet.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: withdraw.confirmed
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    subAccountId:   { type: string }
                    externalId:     { type: string, nullable: true }
                    amount:         { type: number }
                    currency:       { type: string, example: USD }
                    newBalance:     { type: number }
                    localAmount:    { type: number, description: 'Optional.' }
                    localCurrency:  { type: string, description: 'Optional.' }
                    fxRate:         { type: number, description: 'Optional.' }
      responses:
        '200':
          description: Webhook received successfully.
  wallet.credited:
    post:
      summary: Partner master wallet top-up confirmed
      description: Fired when MyStocks ops credits the partner master wallet after a top-up request is fulfilled.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: wallet.credited
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    amount:     { type: number }
                    newBalance: { type: number }
                    currency:   { type: string, example: USD }
      responses:
        '200':
          description: Webhook received successfully.
  kyc.updated:
    post:
      summary: Sub-account KYC status changed
      description: |
        Fired when a sub-account's KYC status changes — either via POST /users/{userId}/kyc
        or when overridden by MyStocks compliance. `overriddenByAdmin: true` appears only on admin overrides.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: kyc.updated
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    subAccountId:      { type: string }
                    externalId:        { type: string, nullable: true }
                    kycStatus:         { type: string, enum: [NONE, PENDING, VERIFIED] }
                    kycLevel:          { type: string, enum: [NONE, BASIC, FULL] }
                    reference:         { type: string, nullable: true, description: 'Partner-supplied KYC reference, if provided.' }
                    overriddenByAdmin:  { type: boolean, description: 'Present and true when overridden by MyStocks compliance.' }
      responses:
        '200':
          description: Webhook received successfully.
  account.frozen:
    post:
      summary: Sub-account frozen or unfrozen
      description: |
        Fired on both freeze and unfreeze of a sub-account, and when a partner key is revoked.
        Use the `frozen` boolean to determine direction. Key-revocation payloads omit
        `subAccountId`/`externalId` and include `revokedBy` and `reason` instead.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: account.frozen
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    subAccountId:  { type: string, description: 'Present for sub-account events. Omitted for key-revocation events.' }
                    externalId:    { type: string, nullable: true }
                    frozen:        { type: boolean }
                    frozenBy:      { type: string, example: platform_admin, description: 'Present when frozen.' }
                    unfrozenBy:    { type: string, example: platform_admin, description: 'Present when unfrozen.' }
                    reason:        { type: string, description: 'Key-revocation only.' }
                    revokedBy:     { type: string, description: 'Key-revocation only.' }
                    revokedAt:     { type: string, format: date-time, description: 'Key-revocation only.' }
      responses:
        '200':
          description: Webhook received successfully.
  dividend.paid:
    post:
      summary: Dividend distributed to sub-accounts
      description: |
        Fired once per dividend distribution batch, grouped by symbol.
        The `distributions` array lists every sub-account that received a payout.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: dividend.paid
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    symbol:             { type: string, example: SCOM.KE }
                    name:               { type: string }
                    dividendPerShare:   { type: number }
                    currency:           { type: string, example: KES }
                    totalUsdPaid:       { type: number }
                    distributionCount:  { type: integer }
                    distributions:
                      type: array
                      items:
                        type: object
                        properties:
                          subAccountId: { type: string }
                          externalId:   { type: string, nullable: true }
                          units:        { type: number }
                          usdYield:     { type: number }
      responses:
        '200':
          description: Webhook received successfully.
  incident.declared:
    post:
      summary: Platform incident declared
      description: Broadcast to all active partners when MyStocks declares an incident. Severity levels P0 (critical) to P3 (minor).
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: incident.declared
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    id:               { type: string }
                    title:            { type: string }
                    severity:         { type: string, enum: [P0, P1, P2, P3] }
                    affectedServices: { type: array, items: { type: string } }
                    status:           { type: string, example: investigating }
                    startedAt:        { type: string, format: date-time }
      responses:
        '200':
          description: Webhook received successfully.
  incident.resolved:
    post:
      summary: Platform incident resolved
      description: Broadcast to all active partners when a declared incident is closed.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event:
                  type: string
                  example: incident.resolved
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    id:         { type: string }
                    title:      { type: string }
                    severity:   { type: string, enum: [P0, P1, P2, P3] }
                    status:     { type: string, example: resolved }
                    resolvedAt: { type: string, format: date-time }
                    duration:   { type: string, example: '2h 30m', description: Human-readable resolution time. }
      responses:
        '200':
          description: Webhook received successfully.
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: 'Authorization: Bearer sk_sandbox_KEY (sandbox) or pk_live_KEY (production)'
    ApiKeyHeader:
      type: apiKey
      in: header
      name: x-api-key
      description: Alternative to the Authorization header. Same key value.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Unique string that makes money-movement calls safe to retry on network failure.
        Provide on every deposit, withdraw, and trade request.
        Deduplicated for 24 hours -- duplicate keys return the cached response.
        HTTP 409 if a concurrent request with the same key is still in progress.
      schema:
        type: string
        example: dep_user42_1743152580
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable application sub-error code.
              example: INSUFFICIENT_FUNDS
            message:
              type: string
              description: Human-readable error message.
              example: 'Insufficient funds. Required: $9250.00, Available: $3200.00'
    RegisterRequest:
      type: object
      required:
        - businessName
        - email
      properties:
        businessName:
          type: string
          minLength: 2
          description: Your company or project name.
          example: Acme Corp
        email:
          type: string
          format: email
          description: Contact email. Used to prevent duplicate registrations.
          example: dev@acme.com
    AutoRegisterRequest:
      type: object
      required:
        - uid
      properties:
        uid:
          type: string
          description: Your internal user identifier. Idempotency key -- calling twice with the same uid returns the same sub-account.
          example: user_42
        email:
          type: string
          format: email
          description: Email address for the sub-account user.
          example: alice@yourapp.com
        name:
          type: string
          description: Full name shown in the MyStocks admin console.
          example: Alice K.
        phone:
          type: string
          description: User phone number in E.164 format.
          example: '+254712345678'
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code.
          example: KE
    CreateSubAccountRequest:
      type: object
      required:
        - externalId
      properties:
        externalId:
          type: string
          description: Your internal user identifier. Must be unique per partner.
          example: usr_8821
        displayName:
          type: string
          description: Full name shown in the MyStocks admin console.
          example: Alice K.
        email:
          type: string
          format: email
          description: Email address for the sub-account user.
          example: alice@yourapp.com
    UpdateSubAccountRequest:
      type: object
      properties:
        displayName:
          type: string
          description: New display name for the sub-account.
          example: Alice Kamau
        email:
          type: string
          format: email
          description: New email address.
        status:
          type: string
          enum:
            - active
            - frozen
          description: frozen suspends all trading, deposits and withdrawals. Set back to active to restore access.
    DepositRequest:
      type: object
      required:
        - amount
      properties:
        amount:
          type: number
          description: USD amount to deposit into the sub-account. Deducted from your master wallet.
          example: 500
        note:
          type: string
          description: Optional description shown in the transaction history.
          example: Mpesa STK push ref KE2482
        localAmount:
          type: number
          description: Equivalent amount in the user local currency. Stored for audit and compliance only.
          example: 65000
        localCurrency:
          type: string
          description: ISO 4217 code for the local amount.
          example: KES
        fxRate:
          type: number
          description: Exchange rate you applied (local units per USD). Stored for audit -- does not affect wallet credit.
          example: 130
    WithdrawRequest:
      type: object
      required:
        - amount
      properties:
        amount:
          type: number
          description: USD amount to move from the sub-account back to your master wallet.
          example: 200
        note:
          type: string
          description: Optional description shown in transaction history.
        localAmount:
          type: number
          description: Equivalent in local currency for the audit trail.
          example: 26000
        localCurrency:
          type: string
          description: ISO 4217 code.
          example: KES
        fxRate:
          type: number
          description: Exchange rate applied for this withdrawal.
          example: 130
    TradeRequest:
      type: object
      required:
        - symbol
        - type
        - quantity
      properties:
        symbol:
          type: string
          description: Stock symbol. Accepts exchange-qualified (SCOM.KE, DANGCEM.NG) or bare ticker (SCOM) -- auto-resolved when unambiguous. Returns 422 with match list if ambiguous.
          example: SCOM.KE
        type:
          type: string
          enum:
            - BUY
            - SELL
          description: Order direction.
          example: BUY
        quantity:
          type: integer
          minimum: 1
          description: Number of whole shares. Must be a positive integer.
          example: 500
        stopLoss:
          type: number
          description: Optional. Order auto-cancels if the stock falls below this USD price before settlement.
          example: 0.11
        takeProfit:
          type: number
          description: Optional. Order auto-settles if the stock rises above this USD price before settlement.
          example: 0.16
    KycRequest:
      type: object
      required:
        - status
        - level
      properties:
        status:
          type: string
          enum:
            - NONE
            - PENDING
            - VERIFIED
          description: Overall KYC status of the user.
          example: VERIFIED
        level:
          type: string
          enum:
            - NONE
            - BASIC
            - FULL
          description: BASIC = government ID verified (sufficient for trading). FULL = address + enhanced due diligence.
          example: BASIC
        provider:
          type: string
          description: Name of the KYC provider that performed the verification (e.g. sumsub, smile_identity).
          example: sumsub
        reference:
          type: string
          description: Your internal KYC session reference for audit correlation.
          example: kyc_session_88721
    SubscribeRequest:
      type: object
      required:
        - assetType
        - assetId
      properties:
        assetType:
          type: string
          enum:
            - BOND
            - FUND
            - OPPORTUNITY
            - PRE_IPO
          description: Asset class to subscribe to.
          example: FUND
        assetId:
          type: string
          description: Firestore document ID. Obtain from the list endpoints (/bonds, /funds, /opportunities).
          example: fund_mmf_africa
        units:
          type: number
          description: Whole units to subscribe. Required for BOND and FUND.
          example: 500
        amount:
          type: number
          description: USD commitment amount. Required for OPPORTUNITY and PRE_IPO.
          example: 1000
    RedeemRequest:
      type: object
      required:
        - holdingId
        - unitsToRedeem
      properties:
        holdingId:
          type: string
          description: Sub-account fund holding doc ID (same as the fund Firestore doc ID).
          example: fund_mmf_africa
        unitsToRedeem:
          type: number
          description: Units to redeem. Must not exceed available unlocked units.
          example: 200
    WebhookCreateRequest:
      type: object
      required:
        - url
        - events
      properties:
        url:
          type: string
          format: uri
          description: HTTPS endpoint that receives POST requests. Must respond 2xx within 8 seconds.
          example: https://yourapp.com/webhooks/mystocks
        events:
          type: array
          description: Event types to subscribe to.
          items:
            type: string
            enum:
              - order.pending
              - order.filled
              - order.rejected
              - order.cancelled
              - trade.settled
              - trade.rejected
              - deposit.confirmed
              - withdraw.confirmed
              - wallet.credited
              - kyc.updated
              - account.frozen
              - dividend.paid
              - incident.declared
              - incident.resolved
          example:
            - trade.settled
            - deposit.confirmed
            - kyc.updated
        secret:
          type: string
          minLength: 16
          description: HMAC-SHA256 signing secret (min 16 chars). MyStocks sends x-mystocks-signature on every delivery.
          example: my-signing-secret-min-16-chars
    TopupRequest:
      type: object
      required:
        - amount
        - paymentReference
      properties:
        amount:
          type: number
          description: USD amount to top up. Must be positive.
          example: 10000
        paymentReference:
          type: string
          description: Your wire transfer reference or payment ID. Used by admin to match the incoming wire.
          example: WIRE-2026-05-001
        paymentMethod:
          type: string
          description: Payment method, e.g. SWIFT wire, SEPA, ACH.
          example: SWIFT wire
        notes:
          type: string
          description: Optional note to the MyStocks operations team.
    PayoutRequest:
      type: object
      required:
        - amount
        - bankDetails
      properties:
        amount:
          type: number
          description: USD to withdraw. Must not exceed master wallet balance.
          example: 5000
        bankDetails:
          type: object
          required:
            - bankName
            - accountName
            - accountNumber
          properties:
            bankName:
              type: string
              description: Name of the receiving bank.
              example: Equity Bank Kenya
            accountName:
              type: string
              description: Account holder name.
              example: ACME Fintech Ltd
            accountNumber:
              type: string
              description: Bank account number or IBAN.
              example: '0123456789'
            swiftCode:
              type: string
              description: SWIFT/BIC code for international wires.
              example: EQBLKENA
        notes:
          type: string
          description: Optional note to the MyStocks team.
    SettingsUpdateRequest:
      type: object
      properties:
        logoUrl:
          type:
            - string
            - 'null'
          description: HTTPS URL of your company logo. Shown in partner-branded emails.
          example: https://cdn.yourapp.com/logo.png
        emailConfig:
          type:
            - object
            - 'null'
          description: Custom SMTP config. Set to null to revert to MyStocks default emails.
          properties:
            host:
              type: string
              description: SMTP hostname. e.g. smtp.sendgrid.net, smtp.gmail.com.
              example: smtp.sendgrid.net
            port:
              type: integer
              description: SMTP port. Default 587 (STARTTLS). Use 465 for implicit TLS.
              example: 587
            secure:
              type: boolean
              description: true = implicit TLS (port 465). false = STARTTLS (port 587).
              example: false
            username:
              type: string
              description: SMTP auth username.
              example: apikey
            password:
              type: string
              description: SMTP password or API key. Omit to keep existing.
            fromName:
              type: string
              description: Display name in the From header.
              example: Acme Investments
            fromEmail:
              type: string
              description: From email address.
              example: noreply@acme.com
    SandboxDepositRequest:
      type: object
      required:
        - subAccountId
        - amount
      properties:
        subAccountId:
          type: string
          description: MyStocks sub-account ID to credit. Must belong to your partner key.
          example: usr_test_abc123
        amount:
          type: number
          description: Virtual USD to credit. No real funds move. Max 1,000,000.
          example: 10000
        note:
          type: string
          description: Optional label shown in the admin log and the deposit.confirmed webhook payload.
    SandboxTradeRequest:
      type: object
      required:
        - symbol
        - type
        - quantity
      properties:
        symbol:
          type: string
          description: Stock symbol (exchange-qualified or bare ticker).
          example: SCOM.KE
        type:
          type: string
          enum:
            - BUY
            - SELL
          description: Order direction.
        quantity:
          type: integer
          minimum: 1
          description: 'Number of shares. Cheat codes: 100 = instant auto-fill, 999 = instant auto-reject.'
          example: 10
        subAccountId:
          type: string
          description: Sub-account to trade on. BUY checks wallet and escrows; SELL checks held units. Omit to bypass all checks.
        note:
          type: string
          description: Free-text note shown to the admin for labelling test scenarios.
    RevokeKeyRequest:
      type: object
      required:
        - apiKey
      properties:
        apiKey:
          type: string
          description: The pk_live_ key to permanently revoke. Must belong to your account.
          example: pk_live_a1b2c3d4e5f6...
    StockSummary:
      type: object
      properties:
        id:
          type: string
          example: SCOM.KE
          description: Unique stock identifier.
        symbol:
          type: string
          example: SCOM.KE
          description: Exchange-qualified ticker symbol.
        name:
          type: string
          example: Safaricom PLC
          description: Full company name.
        slug:
          type:
            - string
            - 'null'
          example: safaricom
          description: URL-safe identifier. Interchangeable with symbol.
        exchange:
          type: string
          example: NSE
          description: 'Exchange code: NSE, NGX, JSE, GSE, BRVM, LUSE, EGX, BSE, ZSE.'
        currency:
          type: string
          example: KES
          description: Local trading currency.
        sector:
          type:
            - string
            - 'null'
          example: Telecommunications
        assetType:
          type: string
          enum:
            - STOCK
            - ETF
        listingStatus:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
            - DELISTED
        price:
          type:
            - number
            - 'null'
          example: 16.5
          description: Latest price in local currency.
        usdPrice:
          type:
            - number
            - 'null'
          example: 0.1274
          description: Latest price converted to USD at live spot rate.
        change:
          type:
            - number
            - 'null'
          example: 0.25
          description: Absolute price change from previous close.
        changePct:
          type:
            - number
            - 'null'
          example: 1.54
          description: Percentage price change from previous close.
        dayHigh:
          type:
            - number
            - 'null'
          description: Intraday high in local currency.
        dayLow:
          type:
            - number
            - 'null'
          description: Intraday low in local currency.
        volume:
          type:
            - integer
            - 'null'
          description: Current session trading volume.
        previousClose:
          type:
            - number
            - 'null'
          description: Prior session closing price.
        logoUrl:
          type:
            - string
            - 'null'
          example: https://mystocks.africa/logos/scom-ke.svg
          description: Self-hosted SVG logo. Safe to use as img src.
        lastPriceUpdate:
          type:
            - string
            - 'null'
          format: date-time
    EtfSummary:
      type: object
      properties:
        id:
          type: string
          example: GLD.ZA
          description: Unique ETF identifier.
        symbol:
          type: string
          example: GLD.ZA
          description: Exchange-qualified ticker symbol.
        name:
          type: string
          example: 1nvest Gold ETF
          description: Full ETF name.
        slug:
          type:
            - string
            - 'null'
          example: gold-etf
          description: URL-safe identifier. Interchangeable with symbol.
        exchange:
          type: string
          example: JSE
          description: 'Exchange code: NSE, NGX, JSE, GSE, BRVM, LUSE, EGX, BSE, ZSE.'
        currency:
          type: string
          example: ZAR
          description: Local trading currency.
        sector:
          type:
            - string
            - 'null'
          example: Precious Metals
        assetType:
          type: string
          example: ETF
        listingStatus:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
            - DELISTED
        price:
          type:
            - number
            - 'null'
          example: 22000
          description: Latest price in local currency.
        usdPrice:
          type:
            - number
            - 'null'
          example: 1180.5
          description: Latest price converted to USD at live spot rate.
        change:
          type:
            - number
            - 'null'
          example: 15
          description: Absolute price change from previous close.
        changePct:
          type:
            - number
            - 'null'
          example: 0.68
          description: Percentage price change from previous close.
        dayHigh:
          type:
            - number
            - 'null'
          description: Intraday high in local currency.
        dayLow:
          type:
            - number
            - 'null'
          description: Intraday low in local currency.
        volume:
          type:
            - integer
            - 'null'
          description: Current session trading volume.
        previousClose:
          type:
            - number
            - 'null'
          description: Prior session closing price.
        logoUrl:
          type:
            - string
            - 'null'
          example: https://mystocks.africa/logos/gld-za.svg
          description: Self-hosted SVG logo. Safe to use as img src.
        lastPriceUpdate:
          type:
            - string
            - 'null'
          format: date-time
        expenseRatio:
          type:
            - number
            - 'null'
          example: 0.0025
          description: The ETF annual management fee/expense ratio.
        indexTracked:
          type:
            - string
            - 'null'
          example: Gold Spot Price
          description: The underlying index or asset tracked by the ETF.
        inceptionDate:
          type:
            - string
            - 'null'
          example: '2010-04-12'
          description: The inception date of the fund.
        topHoldings:
          type:
            - array
            - 'null'
          items:
            type: string
          example:
            - Physical Gold Bullion
          description: Top constituents or holdings of the ETF.
        managementStyle:
          type:
            - string
            - 'null'
          example: PASSIVE
          description: PASSIVE or ACTIVE management style.
        geographicalFocus:
          type:
            - string
            - 'null'
          example: Global
          description: Geographical focus of the fund's assets.
        riskLevel:
          type:
            - string
            - 'null'
          example: Medium-High
          description: Risk profile rating of the ETF.
        distributionFrequency:
          type:
            - string
            - 'null'
          example: Semi-Annually
          description: Distribution frequency (e.g. Annually, Semi-Annually, Quarterly, None).
        brand:
          type:
            - string
            - 'null'
          example: 1nvest
          description: Fund sponsor/issuer brand.
        structure:
          type:
            - string
            - 'null'
          example: Mutual Fund / Trust
          description: Legal structure of the ETF fund.
        replicationMethod:
          type:
            - string
            - 'null'
          example: Physical
          description: Physical or Synthetic replication.
        dividendTreatment:
          type:
            - string
            - 'null'
          example: Accumulating
          description: Accumulating or Distributing.
        primaryAdvisor:
          type:
            - string
            - 'null'
          example: Stanlib Crowd
          description: Advisor company of the ETF.
        issuer:
          type:
            - string
            - 'null'
          example: Stanlib
          description: The actual fund manager or issuing house.
    Order:
      type: object
      properties:
        orderId:
          type: string
          example: ord_abc123xyz
          description: Unique order ID. Use to poll status or cancel.
        symbol:
          type: string
          example: SCOM.KE
        name:
          type: string
          example: Safaricom PLC
        exchange:
          type: string
          example: NSE
        type:
          type: string
          enum:
            - BUY
            - SELL
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - COMPLETED
            - FILLED
            - REJECTED
            - CANCELLED
          description: "PENDING after submission. PROCESSING during admin review. COMPLETED when settled (production). FILLED = instant sandbox settlement. REJECTED if declined. CANCELLED via API."
        quantity:
          type: integer
        priceAtOrder:
          type: number
          description: Stock price in local currency at submission.
        usdPriceAtOrder:
          type: number
          description: USD equivalent of priceAtOrder.
        fee:
          type: number
          description: Total fee in USD (base + partner markup).
        baseFee:
          type: number
          description: MyStocks base broker fee (0.75% of gross).
        partnerMarkupFee:
          type: number
          description: Your markup fee component. Zero if markupBps = 0.
        totalAmount:
          type: number
          description: 'BUY: gross + fee. SELL: gross - fee.'
        currency:
          type: string
          example: USD
        localCurrency:
          type: string
          example: KES
        rejectionCode:
          type:
            - string
            - 'null'
          description: Structured rejection code. Populated when status = REJECTED.
          enum:
            - INSUFFICIENT_FUNDS
            - KYC_REQUIRED
            - MARKET_CLOSED
            - COMPLIANCE_HOLD
            - TECHNICAL_ISSUE
            - OTHER
            - 'null'
        rejectionReason:
          type:
            - string
            - 'null'
          description: Free-text rejection detail provided by the dealing desk. Populated when status = REJECTED.
        cancelledAt:
          type:
            - string
            - 'null'
          format: date-time
        settledAt:
          type:
            - string
            - 'null'
          format: date-time
          description: Set when COMPLETED.
        createdAt:
          type: string
          format: date-time
    SubAccount:
      type: object
      properties:
        subAccountId:
          type: string
          example: usr_abc123
          description: MyStocks internal ID. Use in /users/{userId}/... paths.
        externalId:
          type: string
          example: usr_8821
          description: Your own user identifier as set at creation.
        displayName:
          type:
            - string
            - 'null'
          example: Alice K.
        email:
          type:
            - string
            - 'null'
          example: alice@yourapp.com
        kycStatus:
          type: string
          enum:
            - NONE
            - PENDING
            - VERIFIED
          description: VERIFIED required for trading and asset subscriptions.
        kycLevel:
          type: string
          enum:
            - NONE
            - BASIC
            - FULL
          description: BASIC = ID verified. FULL = enhanced due diligence.
        status:
          type: string
          enum:
            - active
            - frozen
        walletBalance:
          type: number
          example: 1250
          description: Current USD wallet balance.
    WebhookRegistration:
      type: object
      properties:
        id:
          type: string
          example: wh_abc123
          description: Unique webhook ID.
        url:
          type: string
          format: uri
          example: https://yourapp.com/webhooks/mystocks
        events:
          type: array
          items:
            type: string
          description: Subscribed event types.
        status:
          type: string
          enum:
            - active
            - disabled
    Meta:
      type: object
      description: Standard response metadata included on every market-data envelope.
      properties:
        request_id:
          type: string
          format: uuid
          description: Unique identifier for this request. Include in support tickets for tracing.
          example: 4f6e1a2b-3c4d-5e6f-7a8b-9c0d1e2f3a4b
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 UTC timestamp of when the response was generated.
          example: '2026-06-04T10:23:45.123Z'
        exchange:
          type: string
          description: Exchange code the request was scoped to, when applicable.
          example: NSE
        symbol:
          type: string
          description: Ticker symbol the request was scoped to, when applicable.
          example: SCOM.KE
paths:
  /register:
    servers:
      - url: https://mystocks.africa/api/sandbox/v1
        description: Sandbox (registration endpoint lives outside the /partner/ namespace)
    post:
      tags:
        - Registration
      summary: Register sandbox account
      description: |
        Creates a sandbox account and returns a **sandbox API key** (`sk_sandbox_…`).

        **Authentication:** Pass your Firebase ID token as `Authorization: Bearer <token>`.
        Obtain this token by signing in at the [partner dashboard](https://mystocks.africa/partners/login),
        then copying it from the API settings tab. The token identifies your Firebase account and is
        used to link the sandbox key to your partner profile.

        - Your master wallet is seeded with a virtual **$100,000** on first registration.
        - Calling this endpoint again with the same Firebase token returns your existing key.
        - The sandbox is fully isolated — no real money moves.

        To go live, complete KYB verification via the partner dashboard and request a production key.
      security:
        - BearerAuth: []
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/sandbox/v1/register \
              -H "Authorization: Bearer FIREBASE_ID_TOKEN"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/sandbox/v1/register', {
              method: 'POST',
              headers: { Authorization: 'Bearer FIREBASE_ID_TOKEN' },
            });
            const { apiKey, walletBalance } = await res.json();
            // apiKey starts with sk_sandbox_ — use it for all sandbox calls
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post(
              'https://mystocks.africa/api/sandbox/v1/register',
              headers={'Authorization': 'Bearer FIREBASE_ID_TOKEN'},
            )
            api_key = r.json()['apiKey']
      responses:
        '201':
          description: Registration successful.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Sandbox account created.
                  apiKey:
                    type: string
                    example: sk_sandbox_a1b2c3d4e5f6
                  walletBalance:
                    type: number
                    example: 100000
                  currency:
                    type: string
                    example: USD
                  existing:
                    type: boolean
                    description: true if your account already existed and the key was retrieved rather than created.
                    example: false
        '401':
          description: Missing or invalid Firebase ID token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: No partner application on file. Apply at /partners/register first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createRegister
  /account:
    get:
      tags:
        - Registration
      summary: Get partner account
      description: Returns your partner profile — tier, wallet balance, API key metadata, and feature flags.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/account \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/account', {
              headers: { Authorization: 'Bearer pk_live_KEY' },
            });
            const account = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/account',
              headers={'Authorization': 'Bearer pk_live_KEY'})
            account = r.json()
      responses:
        '200':
          description: Partner account details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  partnerId:
                    type: string
                  businessName:
                    type: string
                  email:
                    type: string
                  tier:
                    type: string
                    enum:
                      - sandbox
                      - starter
                      - growth
                      - enterprise
                  walletBalance:
                    type: number
                    description: Master wallet balance in USD.
                  apiKeyPrefix:
                    type: string
                    example: pk_live_a1b2
                  createdAt:
                    type: string
                    format: date-time
        '401':
          description: Invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: listAccount
  /me:
    get:
      tags:
        - Registration
      summary: My status
      description: |
        Returns the current partner account status.

        **Authentication:** This endpoint requires a **Firebase ID token** (`Authorization: Bearer <firebase-token>`), not a partner API key. It is used by the partner portal dashboard on login.

        Response varies by account state:
        - Active live key → returns `status: active`, `apiKey`, `businessName`, `email`
        - Suspended key → returns `status: suspended`, `businessName`
        - Pending/rejected application (no live key yet) → returns `status`, `businessName`, `applicationSubmittedAt`, `applicationRejectionReason`
        - No application → returns `status: not_found`
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            TOKEN=$(firebase-auth-token)
            curl https://mystocks.africa/api/v1/partner/me \
              -H "Authorization: Bearer $TOKEN"
        - lang: JavaScript
          label: JavaScript
          source: |
            const token = await firebaseUser.getIdToken();
            const res = await fetch('https://mystocks.africa/api/v1/partner/me', {
              headers: { Authorization: `Bearer ${token}` },
            });
            const { status, apiKey, businessName } = await res.json();
      responses:
        '200':
          description: Partner account status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [active, suspended, pending, approved, rejected, not_found]
                  apiKey:
                    type: string
                    description: Present when status is active.
                  businessName:
                    type:
                      - string
                      - 'null'
                  email:
                    type:
                      - string
                      - 'null'
                  applicationSubmittedAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
                    description: When the partner application was submitted. Present for pending/approved/rejected states.
                  applicationRejectionReason:
                    type:
                      - string
                      - 'null'
                    description: Admin-provided rejection reason. Present when status is rejected.
        '401':
          description: Invalid key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: listMe
  /reset:
    servers:
      - url: https://mystocks.africa/api/sandbox/v1
        description: Sandbox (reset endpoint lives outside the /partner/ namespace)
    post:
      tags:
        - Registration
      summary: Reset sandbox account
      description: |
        **Sandbox only.** Wipes all sub-accounts, orders, wallet balances, and webhooks for your partner key.
        Useful when you want a clean slate for a new test run.
        This action is **irreversible** within a session — there is no undo.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/sandbox/v1/reset \
              -H "Authorization: Bearer sk_sandbox_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/sandbox/v1/reset', {
              method: 'POST',
              headers: { Authorization: 'Bearer sk_sandbox_KEY' },
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/sandbox/v1/reset',
              headers={'Authorization': 'Bearer sk_sandbox_KEY'})
      responses:
        '200':
          description: Account wiped.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Sandbox account reset successfully.
        '403':
          description: Not allowed on production keys.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createReset
  /upgrade-request:
    get:
      tags:
        - Registration
      summary: Check upgrade request status
      description: |
        Returns the status of the caller's most recent upgrade request.
        **Authentication:** Firebase ID token required (`Authorization: Bearer <firebase-token>`).
        Pass `?type=tier_upgrade` to check a tier upgrade request specifically.
      parameters:
        - name: type
          in: query
          description: Filter to a specific request type. Omit for the most recent request of any type.
          schema:
            type: string
            enum: [live_key, tier_upgrade]
      responses:
        '200':
          description: Upgrade request status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  requestId:
                    type: string
                  type:
                    type:
                      - string
                      - 'null'
                    enum: [live_key, tier_upgrade, 'null']
                  status:
                    type: string
                    enum: [none, pending, approved, rejected]
                  targetTier:
                    type:
                      - string
                      - 'null'
                    description: Present for tier_upgrade requests.
                  currentTier:
                    type:
                      - string
                      - 'null'
                  createdAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
                  reviewedAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
                  reviewNote:
                    type:
                      - string
                      - 'null'
      operationId: getUpgradeRequest
    post:
      tags:
        - Registration
      summary: Submit upgrade request
      description: |
        Submits an upgrade request to the MyStocks partnerships team.
        **Authentication:** Firebase ID token required (`Authorization: Bearer <firebase-token>`).

        **Two modes:**
        - **Sandbox → live key** (default, no `targetTier`): for partners without a live key yet. Triggers KYB review; live `pk_live_` key issued within 1–2 business days.
        - **Tier upgrade** (`targetTier` required): for partners with an active live key who want to move from starter → growth or enterprise.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            TOKEN=$(firebase-auth-token)
            # Request live key
            curl -X POST https://mystocks.africa/api/v1/partner/upgrade-request \
              -H "Authorization: Bearer $TOKEN" \
              -H "Content-Type: application/json" \
              -d '{"message":"Integration complete, ready for live clients."}'
            # Request tier upgrade
            curl -X POST https://mystocks.africa/api/v1/partner/upgrade-request \
              -H "Authorization: Bearer $TOKEN" \
              -H "Content-Type: application/json" \
              -d '{"targetTier":"growth","message":"Expecting 300 req/min at launch."}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const token = await firebaseUser.getIdToken();
            await fetch('https://mystocks.africa/api/v1/partner/upgrade-request', {
              method: 'POST',
              headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
              body: JSON.stringify({ targetTier: 'growth', message: 'Need higher rate limit.' }),
            });
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                message:
                  type: string
                  description: Optional message to the reviewing team.
                targetTier:
                  type: string
                  enum: [growth, enterprise]
                  description: Required for tier upgrade requests. Omit for sandbox-to-live requests.
      responses:
        '201':
          description: Request submitted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  requestId:
                    type: string
        '400':
          description: Invalid targetTier or already on that tier.
      operationId: createUpgradeRequest
  /etfs:
    get:
      tags:
        - Market Data
      summary: List ETFs
      description: |
        Returns the full list of tradeable ETFs across all supported African exchanges.
        Prices are updated periodically during market hours.
        Filter by exchange or perform a text search on the ticker or name.
      parameters:
        - name: exchange
          in: query
          description: 'Filter by exchange code. One of: NSE, NGX, JSE, GSE, BRVM, LUSE, EGX, BSE, ZSE.'
          schema:
            type: string
            example: JSE
        - name: search
          in: query
          description: Search term matching the ETF ticker symbol or name.
          schema:
            type: string
            example: Gold
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/etfs?exchange=JSE" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/etfs?exchange=JSE',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { etfs, count } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/etfs',
              params={'exchange': 'JSE'},
              headers={'x-api-key': 'pk_live_KEY'})
            etfs = r.json()['etfs']
      responses:
        '200':
          description: List of ETFs.
          content:
            application/json:
              schema:
                type: object
                properties:
                  etfs:
                    type: array
                    items:
                      $ref: '#/components/schemas/EtfSummary'
                  count:
                    type: integer
      operationId: listEtfs
  /etfs/{symbol}:
    get:
      tags:
        - Market Data
      summary: Get ETF detail
      description: Returns full price data, historical range chart data, and rich fund-specific metadata for a single ETF. Accepts exchange-qualified symbols (e.g., `GLD.ZA`) or bare tickers (e.g., `GLD`). Gated strictly to ETFs; querying standard stocks returns 404.
      parameters:
        - name: symbol
          in: path
          required: true
          description: Exchange-qualified ETF symbol (e.g. `GLD.ZA`) or bare ticker.
          schema:
            type: string
            example: GLD.ZA
        - name: range
          in: query
          description: Time range of legacy price history points to include. Default is `3M`.
          schema:
            type: string
            enum:
              - 1W
              - 1M
              - 3M
              - 6M
              - 1Y
              - ALL
            default: 3M
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/etfs/GLD.ZA?range=3M" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/etfs/GLD.ZA?range=3M', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const etf = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/etfs/GLD.ZA',
              params={'range': '3M'},
              headers={'x-api-key': 'pk_live_KEY'})
            etf = r.json()
      responses:
        '200':
          description: ETF details and historical price series.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/EtfSummary'
                  - type: object
                    properties:
                      currentPrice:
                        type: number
                        example: 22000
                      range:
                        type: string
                        example: 3M
                      priceHistory:
                        type: array
                        items:
                          type: object
                          properties:
                            date:
                              type: string
                              format: date-time
                            price:
                              type: number
                            label:
                              type: string
                              example: Live
                      count:
                        type: integer
        '404':
          description: Symbol not found or security is not an ETF.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getEtf
  /etfs/{symbol}/history:
    get:
      tags:
        - Market Data
      summary: ETF price history (candles)
      description: |
        Returns OHLCV candles for the given ETF symbol. Gated strictly to ETFs; querying standard stocks returns 404.
        Supported periods: `1d`, `1w`, `1m`, `3m`, `6m`, `1y`, `3y`, `5y`, `max`.
        Candle interval is auto-selected based on period.
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: GLD.ZA
        - name: period
          in: query
          description: 'Time range. Default: 1m.'
          schema:
            type: string
            enum:
              - 1d
              - 1w
              - 1m
              - 3m
              - 6m
              - 1y
              - 3y
              - 5y
              - max
            default: 1m
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/etfs/GLD.ZA/history?period=3m" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/etfs/GLD.ZA/history?period=3m',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { candles } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/etfs/GLD.ZA/history',
              params={'period': '3m'}, headers={'x-api-key': 'pk_live_KEY'})
            candles = r.json()['candles']
      responses:
        '200':
          description: Price history candles.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbol:
                    type: string
                  period:
                    type: string
                  candles:
                    type: array
                    items:
                      type: object
                      properties:
                        date:
                          type: string
                          format: date
                        open:
                          type: number
                        high:
                          type: number
                        low:
                          type: number
                        close:
                          type: number
                        volume:
                          type: integer
        '404':
          description: Symbol not found or security is not an ETF.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getEtfHistory
  /etfs/{symbol}/chart:
    get:
      tags:
        - Market Data
      summary: ETF price chart
      description: Returns pre-computed chart data for rendering price charts. Equivalent to `/etfs/{symbol}/history` but shaped for direct chart rendering with primary prices in local currency. Gated strictly to ETFs; querying standard stocks returns 404.
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: GLD.ZA
        - name: period
          in: query
          schema:
            type: string
            enum:
              - 1d
              - 1w
              - 1m
              - 3m
              - 6m
              - 1y
              - 3y
              - 5y
              - max
            default: 1m
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/etfs/GLD.ZA/chart?period=1y" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/etfs/GLD.ZA/chart?period=1y',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { labels, prices } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/etfs/GLD.ZA/chart',
              params={'period': '1y'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Chart data.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbol:
                    type: string
                  name:
                    type: string
                  exchange:
                    type: string
                  currency:
                    type: string
                  range:
                    type: string
                  period:
                    type: string
                  currentPrice:
                    type: number
                  rangeChange:
                    type: number
                  rangeChangePct:
                    type: number
                  priceHistory:
                    type: array
                    items:
                      type: object
                      properties:
                        date:
                          type: string
                        price:
                          type: number
                        usdPrice:
                          type: number
                  labels:
                    type: array
                    items:
                      type: string
                  prices:
                    type: array
                    items:
                      type: number
                  volumes:
                    type: array
                    items:
                      type: integer
                  count:
                    type: integer
        '404':
          description: Symbol not found or security is not an ETF.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getEtfChart
  /market/quotes:
    get:
      tags:
        - Market Data
      summary: Get quotes — single symbol or batch (NSE, NGX, JSE, GSE, etc.)
      description: |
        Returns real-time price quotes for one or many stock/ETF symbols. Operates in two modes:

        **Single-symbol mode** (`?symbol=X&exchange=Y`)
        Both `symbol` and `exchange` are required. Returns a single `data` object.

        **Batch mode** (`?symbols=X,Y,Z`)
        Comma-separated list of exchange-qualified tickers (up to 50). `exchange` is optional as a filter.
        Returns `data` array and a `not_found` array for any symbols that could not be resolved.

        Also available at `GET /market-data/quotes` (identical handler).
      parameters:
        - name: symbol
          in: query
          description: Single ticker symbol — exchange-qualified (e.g. `SCOM.KE`) or bare. Requires `exchange`. Use this or `symbols`, not both.
          schema:
            type: string
            example: SCOM.KE
        - name: symbols
          in: query
          description: Comma-separated batch of up to 50 exchange-qualified ticker symbols (e.g. `SCOM.KE,GLD.ZA,BAT.KE`). Use this or `symbol`, not both.
          schema:
            type: string
            example: SCOM.KE,GLD.ZA
        - name: exchange
          in: query
          description: Exchange code (NSE, NGX, JSE, GSE, BRVM, LUSE, EGX, BSE, ZSE). Required in single-symbol mode; optional filter in batch mode.
          schema:
            type: string
            example: NSE
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            # Single-symbol
            curl "https://mystocks.africa/api/v1/partner/market/quotes?symbol=SCOM.KE&exchange=NSE" \
              -H "x-api-key: pk_live_KEY"
            # Batch
            curl "https://mystocks.africa/api/v1/partner/market/quotes?symbols=SCOM.KE,GLD.ZA" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            // Single-symbol
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/market/quotes?symbol=SCOM.KE&exchange=NSE',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { data, meta } = await res.json();
            // Batch
            const batchRes = await fetch(
              'https://mystocks.africa/api/v1/partner/market/quotes?symbols=SCOM.KE,GLD.ZA',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { data: quotes, not_found, meta: batchMeta } = await batchRes.json();
        - lang: Python
          label: Python
          source: |
            import requests
            # Single-symbol
            r = requests.get('https://mystocks.africa/api/v1/partner/market/quotes',
              params={'symbol': 'SCOM.KE', 'exchange': 'NSE'},
              headers={'x-api-key': 'pk_live_KEY'})
            stock = r.json()['data']
            # Batch
            r = requests.get('https://mystocks.africa/api/v1/partner/market/quotes',
              params={'symbols': 'SCOM.KE,GLD.ZA'},
              headers={'x-api-key': 'pk_live_KEY'})
            quotes = r.json()['data']
      responses:
        '200':
          description: |
            Quote response. Shape varies by mode:
            - **Single-symbol**: `data` is a single StockSummary object.
            - **Batch**: `data` is an array of StockSummary objects; `not_found` lists unresolvable symbols.
            Both modes include a `meta` block with `request_id` and `timestamp`.
          content:
            application/json:
              schema:
                oneOf:
                  - title: Single-symbol response
                    type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/StockSummary'
                      meta:
                        $ref: '#/components/schemas/Meta'
                  - title: Batch response
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/StockSummary'
                      not_found:
                        type: array
                        description: Symbols that could not be resolved (unknown ticker or exchange mismatch).
                        items:
                          type: string
                        example:
                          - UNKNOWN.NG
                      meta:
                        $ref: '#/components/schemas/Meta'
        '400':
          description: Validation error (missing required params, invalid symbol format, batch limit exceeded).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Symbol not found (single-symbol mode only).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getBatchQuotes
  /stocks:
    get:
      tags:
        - Market Data
      summary: List stocks (NSE, NGX, JSE, GSE, BRVM, etc.)
      description: |
        Returns the full tradeable stock and ETF universe across all supported African exchanges. 
        Provides real-time stock price data (refreshed every 15 minutes during trading hours) for key markets:
        - Nairobi Stock Exchange (NSE) in Kenya (KES)
        - Nigerian Exchange Group (NGX) in Nigeria (NGN)
        - Johannesburg Stock Exchange (JSE) in South Africa (ZAR)
        - Ghana Stock Exchange (GSE) in Ghana (GHS)
        - BRVM serving 8 West African French-speaking countries (XOF)
        - LuSE (Zambia), USE (Uganda), BSE (Botswana), and DSE (Tanzania).
        
        Filter by exchange, sector, or asset type. Results are paginated — use `page` and `limit`. Excellent for populating stock search screens and building live tickers.
      parameters:
        - name: exchange
          in: query
          description: 'Filter by exchange code. One of: NSE, NGX, JSE, GSE, BRVM, LUSE, EGX, BSE, ZSE.'
          schema:
            type: string
            example: NSE
        - name: sector
          in: query
          description: Filter by GICS sector name.
          schema:
            type: string
            example: Telecommunications
        - name: assetType
          in: query
          description: Filter to stocks or ETFs.
          schema:
            type: string
            enum:
              - STOCK
              - ETF
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/stocks?exchange=NSE&limit=20" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/stocks?exchange=NSE&limit=20',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { stocks, total } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/stocks',
              params={'exchange': 'NSE', 'limit': 20},
              headers={'x-api-key': 'pk_live_KEY'})
            stocks = r.json()['stocks']
      responses:
        '200':
          description: List of stocks.
          content:
            application/json:
              schema:
                type: object
                properties:
                  stocks:
                    type: array
                    items:
                      $ref: '#/components/schemas/StockSummary'
                  total:
                    type: integer
                  page:
                    type: integer
      operationId: listStocks
  /stocks/{symbol}:
    get:
      tags:
        - Market Data
      summary: Get stock detail (NSE, NGX, JSE, etc.)
      description: Returns full real-time price and detail data for a single stock. Accepts exchange-qualified symbols (e.g. `SCOM.KE` for Safaricom PLC on the Nairobi Stock Exchange, `DANGCEM.NG` for Dangote Cement on the Nigerian Exchange Group, `MTN.ZA` on the Johannesburg Stock Exchange) or bare tickers (`SCOM`) when unambiguous.
      parameters:
        - name: symbol
          in: path
          required: true
          description: Exchange-qualified ticker (e.g. `SCOM.KE`, `DANGCEM.NG`) or bare ticker.
          schema:
            type: string
            example: SCOM.KE
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/stocks/SCOM.KE \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/stocks/SCOM.KE', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const stock = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/stocks/SCOM.KE',
              headers={'x-api-key': 'pk_live_KEY'})
            stock = r.json()
      responses:
        '200':
          description: Stock detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StockSummary'
        '404':
          description: Symbol not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getStocks
  /stocks/{symbol}/history:
    get:
      tags:
        - Market Data
      summary: Stock price history
      description: |
        Returns OHLCV candles for the given symbol.
        Supported periods: `1d`, `1w`, `1m`, `3m`, `6m`, `1y`, `3y`, `5y`, `max`.
        Candle interval is auto-selected based on period (daily for ≤3 m, weekly for ≤1 y, monthly beyond).
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: SCOM.KE
        - name: period
          in: query
          description: 'Time range. Default: 1m.'
          schema:
            type: string
            enum:
              - 1d
              - 1w
              - 1m
              - 3m
              - 6m
              - 1y
              - 3y
              - 5y
              - max
            default: 1m
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/history?period=3m" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/history?period=3m',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { candles } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/history',
              params={'period': '3m'}, headers={'x-api-key': 'pk_live_KEY'})
            candles = r.json()['candles']
      responses:
        '200':
          description: Price history candles.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbol:
                    type: string
                  period:
                    type: string
                  candles:
                    type: array
                    items:
                      type: object
                      properties:
                        date:
                          type: string
                          format: date
                        open:
                          type: number
                        high:
                          type: number
                        low:
                          type: number
                        close:
                          type: number
                        volume:
                          type: integer
      operationId: listHistory
  /stocks/{symbol}/chart:
    get:
      tags:
        - Market Data
      summary: Stock price chart
      description: Returns pre-computed chart data for rendering price charts. Equivalent to `/stocks/{symbol}/history` but shaped for direct chart rendering.
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: SCOM.KE
        - name: period
          in: query
          schema:
            type: string
            enum:
              - 1d
              - 1w
              - 1m
              - 3m
              - 6m
              - 1y
              - 3y
              - 5y
              - max
            default: 1m
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/chart?period=1y" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/chart?period=1y',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { labels, prices } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/chart',
              params={'period': '1y'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Chart data.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbol:
                    type: string
                  period:
                    type: string
                  labels:
                    type: array
                    items:
                      type: string
                  prices:
                    type: array
                    items:
                      type: number
                  volumes:
                    type: array
                    items:
                      type: integer
                  priceHistory:
                    type: array
                    items:
                      type: object
                      properties:
                        date:
                          type: string
                        price:
                          type: number
                        usdPrice:
                          type: number
                  rangeChange:
                    type: number
                  rangeChangePct:
                    type: number
                  currentPrice:
                    type: number
                  count:
                    type: integer
        '404':
          description: Symbol not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getStockChart
  /stocks/{symbol}/pulse:
    get:
      tags:
        - Market Data
      summary: Stock Pulse (news)
      description: Returns recent news articles and exchange announcements for a specific stock. Sourced from African financial news networks and official exchange feeds.
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: SCOM.KE
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
            maximum: 50
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/pulse?limit=5" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/pulse?limit=5',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { articles } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/stocks/SCOM.KE/pulse',
              params={'limit': 5}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: News articles for this stock.
          content:
            application/json:
              schema:
                type: object
                properties:
                  articles:
                    type: array
                    items:
                      type: object
                      properties:
                        title:
                          type: string
                        summary:
                          type: string
                        url:
                          type: string
                          format: uri
                        source:
                          type: string
                        publishedAt:
                          type: string
                          format: date-time
                        imageUrl:
                          type:
                            - string
                            - 'null'
                          format: uri
      operationId: listPulse
  /companies:
    get:
      tags:
        - Market Data
      summary: List companies
      description: Returns company profiles with fundamental data (description, sector, country, financials). Supports exchange and sector filtering.
      parameters:
        - name: exchange
          in: query
          schema:
            type: string
            example: JSE
        - name: sector
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/companies?exchange=JSE&limit=10" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/companies?exchange=JSE',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { companies } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/companies',
              params={'exchange': 'JSE'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Company list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  companies:
                    type: array
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                        name:
                          type: string
                        exchange:
                          type: string
                        sector:
                          type:
                            - string
                            - 'null'
                        country:
                          type: string
                        description:
                          type:
                            - string
                            - 'null'
                        logoUrl:
                          type:
                            - string
                            - 'null'
                  total:
                    type: integer
      operationId: listCompanies
  /companies/tickers:
    get:
      tags:
        - Market Data
      summary: Ticker symbols
      description: Returns a flat list of all ticker symbols across all exchanges. Useful for symbol autocomplete or validation.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/companies/tickers \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/companies/tickers', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { tickers } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/companies/tickers',
              headers={'x-api-key': 'pk_live_KEY'})
            tickers = r.json()['tickers']
      responses:
        '200':
          description: All ticker symbols.
          content:
            application/json:
              schema:
                type: object
                properties:
                  tickers:
                    type: array
                    items:
                      type: string
                    example:
                      - SCOM.KE
                      - EQTY.KE
                      - DANGCEM.NG
                      - NPN.ZA
      operationId: listTickers
  /companies/{symbol}:
    get:
      tags:
        - Market Data
      summary: Company profile
      description: Returns detailed company profile including description, headquarters, sector, key financials, and links.
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: SCOM.KE
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/companies/SCOM.KE \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/companies/SCOM.KE', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const company = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/companies/SCOM.KE',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Company profile.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbol:
                    type: string
                  name:
                    type: string
                  exchange:
                    type: string
                  sector:
                    type:
                      - string
                      - 'null'
                  country:
                    type: string
                  description:
                    type:
                      - string
                      - 'null'
                  website:
                    type:
                      - string
                      - 'null'
                    format: uri
                  logoUrl:
                    type:
                      - string
                      - 'null'
                  marketCap:
                    type:
                      - number
                      - 'null'
                  peRatio:
                    type:
                      - number
                      - 'null'
        '404':
          description: Company not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getCompanies
  /companies/{symbol}/chart:
    get:
      tags:
        - Market Data
      summary: Price chart
      description: Returns pre-computed chart data for rendering price charts. Equivalent to `/stocks/{symbol}/history` but shaped for direct chart rendering.
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: SCOM.KE
        - name: period
          in: query
          schema:
            type: string
            enum:
              - 1d
              - 1w
              - 1m
              - 3m
              - 6m
              - 1y
              - 3y
              - 5y
              - max
            default: 1m
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/companies/SCOM.KE/chart?period=1y" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/companies/SCOM.KE/chart?period=1y',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { labels, prices } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/companies/SCOM.KE/chart',
              params={'period': '1y'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Chart data.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbol:
                    type: string
                  period:
                    type: string
                  labels:
                    type: array
                    items:
                      type: string
                  prices:
                    type: array
                    items:
                      type: number
                  volumes:
                    type: array
                    items:
                      type: integer
      operationId: listChart
  /companies/{symbol}/news:
    get:
      tags:
        - Market Data
      summary: Company news
      description: Returns recent news and press releases for the specified company.
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: SCOM.KE
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
            maximum: 50
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/companies/SCOM.KE/news?limit=5" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/companies/SCOM.KE/news?limit=5',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { articles } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/companies/SCOM.KE/news',
              params={'limit': 5}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Company news articles.
          content:
            application/json:
              schema:
                type: object
                properties:
                  articles:
                    type: array
                    items:
                      type: object
                      properties:
                        title:
                          type: string
                        summary:
                          type: string
                        url:
                          type: string
                          format: uri
                        source:
                          type: string
                        publishedAt:
                          type: string
                          format: date-time
      operationId: listNews
  /market-intel:
    get:
      tags:
        - Market Intelligence
      summary: Market Intel feed
      description: |
        Returns curated editorial articles across all African exchanges — macro analysis, sector reports,
        earnings commentary, and exchange announcements. Updated throughout the trading day.
      parameters:
        - name: symbol
          in: query
          description: Filter to articles tagged with a specific stock symbol (e.g. SCOM.KE). Uses Firestore array-contains index.
          schema:
            type: string
            example: SCOM.KE
        - name: exchange
          in: query
          description: Filter articles by exchange code (client-side filter applied after symbol query).
          schema:
            type: string
            example: NGX
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/market-intel?exchange=NGX&limit=10" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/market-intel?exchange=NGX',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { articles, count } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/market-intel',
              params={'exchange': 'NGX', 'limit': 10},
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Market intelligence articles.
          content:
            application/json:
              schema:
                type: object
                properties:
                  articles:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        title:
                          type:
                            - string
                            - 'null'
                        summary:
                          type:
                            - string
                            - 'null'
                        slug:
                          type:
                            - string
                            - 'null'
                        category:
                          type:
                            - string
                            - 'null'
                        symbols:
                          type: array
                          items:
                            type: string
                          description: Stock symbols this article is tagged with.
                        exchange:
                          type:
                            - string
                            - 'null'
                        imageUrl:
                          type:
                            - string
                            - 'null'
                          format: uri
                        source:
                          type:
                            - string
                            - 'null'
                        sourceUrl:
                          type:
                            - string
                            - 'null'
                          format: uri
                        publishedAt:
                          type:
                            - string
                            - 'null'
                          format: date-time
                        createdAt:
                          type:
                            - string
                            - 'null'
                          format: date-time
                  count:
                    type: integer
        '401':
          description: Invalid or missing API key.
      operationId: listMarketIntel
  /market-intel/{id}:
    get:
      tags:
        - Market Intelligence
      summary: Market intel article detail
      description: Returns full detail for a single market intelligence article, including the full body. Resolves by Firestore document ID or slug.
      parameters:
        - name: id
          in: path
          required: true
          description: Article document ID or URL slug.
          schema:
            type: string
            example: nse-q1-2026-outlook
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/market-intel/nse-q1-2026-outlook \
              -H "x-api-key: pk_live_KEY"
      responses:
        '200':
          description: Article detail with full body.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  title:
                    type:
                      - string
                      - 'null'
                  summary:
                    type:
                      - string
                      - 'null'
                  body:
                    type:
                      - string
                      - 'null'
                    description: Full article HTML or markdown content.
                  slug:
                    type:
                      - string
                      - 'null'
                  category:
                    type:
                      - string
                      - 'null'
                  symbols:
                    type: array
                    items:
                      type: string
                  exchange:
                    type:
                      - string
                      - 'null'
                  imageUrl:
                    type:
                      - string
                      - 'null'
                    format: uri
                  source:
                    type:
                      - string
                      - 'null'
                  sourceUrl:
                    type:
                      - string
                      - 'null'
                    format: uri
                  author:
                    type:
                      - string
                      - 'null'
                  tags:
                    type: array
                    items:
                      type: string
                  publishedAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
                  createdAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
                  updatedAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
        '401':
          description: Invalid or missing API key.
        '404':
          description: Article not found.
      operationId: getMarketIntelArticle
  /market/status:
    get:
      tags:
        - Trading
      summary: Market status (NSE, NGX, JSE, GSE, etc.)
      description: |
        Returns the real-time open/closed status for each supported African stock exchange (NSE, NGX, JSE, GSE, BRVM, ZSE, BSE, LUSE, EGX), next open timestamp, and local trading hours.

        Pass `?exchange=NSE` to get status for a single exchange. Without the filter the response includes all exchanges plus an `anyOpen` boolean.
      parameters:
        - name: exchange
          in: query
          description: Filter to a single exchange code (NSE, NGX, JSE, GSE, BRVM, ZSE, BSE, LUSE, EGX).
          schema:
            type: string
            example: NSE
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/market/status \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/market/status', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { anyOpen, exchanges } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/market/status',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Exchange status map.
          content:
            application/json:
              schema:
                type: object
                properties:
                  anyOpen:
                    type: boolean
                    description: True if at least one exchange is currently open.
                  checkedAt:
                    type: string
                    format: date-time
                  exchanges:
                    type: object
                    description: Keyed by exchange code (e.g. "NSE", "NGX"). Each value is an exchange status object.
                    additionalProperties:
                      type: object
                      properties:
                        name:
                          type: string
                          example: Nairobi Securities Exchange
                        country:
                          type: string
                          example: Kenya
                        currency:
                          type: string
                          example: KES
                        isOpen:
                          type: boolean
                        status:
                          type: string
                          enum: [OPEN, CLOSED]
                        localOpen:
                          type: string
                          description: Local market open time (HH:MM).
                          example: '09:00'
                        localClose:
                          type: string
                          description: Local market close time (HH:MM).
                          example: '15:00'
                        timezone:
                          type: string
                          example: Africa/Nairobi
                        nextOpen:
                          type:
                            - string
                            - 'null'
                          format: date-time
                          description: ISO timestamp of the next session open. Null when market is currently open.
        '404':
          description: Unknown exchange code.
      operationId: getMarketStatus
  /market/settlement:
    get:
      tags:
        - Trading
      summary: Settlement cycles and order processing window
      description: |
        Returns the official T+N settlement cycle for each supported exchange (i.e. when title and beneficial ownership transfer after trade execution), plus the MyStocks order-processing window — the target time between order submission and admin approval in production.

        **Important distinction**
        - **MyStocks processing window** — the time before a PENDING order moves to COMPLETED or REJECTED. Target: 4 business hours during market hours.
        - **Exchange settlement cycle** — the standard T+N cycle mandated by the exchange and its CSD. This governs when the underlying shares clear through the depository (T+2 for EGX, T+3 for all other supported exchanges).

        Use `?exchange=NSE` to filter to a single exchange.
      operationId: getMarketSettlement
      parameters:
        - in: query
          name: exchange
          schema:
            type: string
            example: NSE
          description: Filter to a single exchange code (NSE, NGX, JSE, GSE, BRVM, ZSE, BSE, LUSE, EGX).
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/market/settlement \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/market/settlement', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { processingWindow, exchanges } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/market/settlement',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Settlement cycles and processing window.
          content:
            application/json:
              schema:
                type: object
                properties:
                  processingWindow:
                    type: object
                    properties:
                      targetTurnaroundHours:
                        type: integer
                        example: 4
                        description: Target hours between order submission and admin review during market hours.
                      description:
                        type: string
                      outsideHoursNote:
                        type: string
                      processingDays:
                        type: string
                        example: Monday – Friday (excluding public holidays)
                      webhookNote:
                        type: string
                      pollEndpoint:
                        type: string
                        example: GET /api/v1/partner/orders/{orderId}
                  exchanges:
                    type: object
                    additionalProperties:
                      type: object
                      properties:
                        exchange:
                          type: string
                          example: NSE
                        name:
                          type: string
                          example: Nairobi Securities Exchange
                        country:
                          type: string
                          example: Kenya
                        currency:
                          type: string
                          example: KES
                        cycle:
                          type: string
                          example: T+3
                          description: Official exchange settlement cycle.
                        cycleDays:
                          type: integer
                          example: 3
                        notes:
                          type: string
        '401':
          description: Unauthorized.
        '404':
          description: Unknown exchange code.
  /market/holidays:
    get:
      tags:
        - Market Data
      summary: Market holiday calendar
      description: |
        Returns exchange closure dates for a given date range. Use this to:
        - Show users when a specific exchange is closed before they try to trade.
        - Build "next trading day" schedulers (skip weekends and holidays).
        - Display an in-app holiday calendar alongside market status.

        Defaults: `from` = today, `to` = 12 months from today. Maximum range is 2 years.
        All African exchanges (UTC+0 to UTC+3) use ISO 8601 dates in UTC.
      parameters:
        - name: exchange
          in: query
          required: false
          schema:
            type: string
            example: NSE
          description: 'Filter to a single exchange. One of: NSE, NGX, JSE, GSE, BRVM, ZSE, BSE, LUSE, EGX. Omit to return holidays for all exchanges.'
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date
            example: '2026-06-06'
          description: 'Start date (inclusive), YYYY-MM-DD. Defaults to today.'
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date
            example: '2026-12-31'
          description: 'End date (inclusive), YYYY-MM-DD. Defaults to 12 months from from. Maximum 2 years from from.'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            # All NSE holidays for the next 6 months
            curl "https://mystocks.africa/api/v1/partner/market/holidays?exchange=NSE&from=2026-06-06&to=2026-12-31" \
              -H "x-api-key: pk_live_KEY"

            # All exchanges, full upcoming year (default range)
            curl "https://mystocks.africa/api/v1/partner/market/holidays" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            // NSE holidays — next 6 months
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/market/holidays?exchange=NSE&from=2026-06-06&to=2026-12-31',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { data } = await res.json();
            // data: [{ exchange: 'NSE', date: '2026-06-01', reason: 'Madaraka Day' }, ...]
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get(
              'https://mystocks.africa/api/v1/partner/market/holidays',
              params={'exchange': 'NSE', 'from': '2026-06-06', 'to': '2026-12-31'},
              headers={'x-api-key': 'pk_live_KEY'}
            )
      responses:
        '200':
          description: Holiday calendar entries for the requested range.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      required:
                        - exchange
                        - date
                        - reason
                      properties:
                        exchange:
                          type: string
                          example: NSE
                          description: Exchange code.
                        date:
                          type: string
                          format: date
                          example: '2026-12-25'
                          description: Closure date in YYYY-MM-DD format.
                        reason:
                          type: string
                          example: Christmas Day
                          description: Human-readable reason for the closure.
                  count:
                    type: integer
                    example: 8
                  from:
                    type: string
                    format: date
                    example: '2026-06-06'
                  to:
                    type: string
                    format: date
                    example: '2026-12-31'
                  meta:
                    $ref: '#/components/schemas/Meta'
        '400':
          description: Invalid exchange code or date format.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
      operationId: getMarketHolidays
  /quote/{symbol}:
    get:
      tags:
        - Trading
      summary: Pre-trade fee quote
      description: |
        Returns a real-time fee quote for a symbol before placing a trade. Includes the current local and USD price, gross order cost, base fee, partner markup fee, and total cost for BUY or estimated proceeds for SELL. Also returns whether the partner wallet has sufficient balance/holdings.

        Always fetch a quote immediately before placing a trade to confirm the current cost.
      parameters:
        - name: symbol
          in: path
          required: true
          description: Exchange-qualified ticker (e.g. SCOM.KE) or bare ticker.
          schema:
            type: string
            example: SCOM.KE
        - name: type
          in: query
          description: Order direction. Defaults to BUY.
          schema:
            type: string
            enum: [BUY, SELL]
            default: BUY
        - name: quantity
          in: query
          required: true
          description: Number of shares to quote.
          schema:
            type: integer
            example: 500
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/quote/SCOM.KE?type=BUY&quantity=500" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/quote/SCOM.KE?type=BUY&quantity=500',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { totalCost, fee, sufficientFunds } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get(
              'https://mystocks.africa/api/v1/partner/quote/SCOM.KE',
              params={'type': 'BUY', 'quantity': 500},
              headers={'x-api-key': 'pk_live_KEY'})
            quote = r.json()
      responses:
        '200':
          description: Fee quote for the requested symbol, type, and quantity.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbol:
                    type: string
                    example: SCOM.KE
                  name:
                    type: string
                  exchange:
                    type: string
                  currency:
                    type: string
                    example: KES
                  type:
                    type: string
                    enum: [BUY, SELL]
                  quantity:
                    type: integer
                  localPrice:
                    type: number
                    description: Current price in the stock's local currency.
                  usdPrice:
                    type: number
                    description: Current price converted to USD.
                  gross:
                    type: number
                    description: quantity × usdPrice.
                  baseFee:
                    type: number
                    description: MyStocks base broker fee (0.75% of gross).
                  partnerMarkupFee:
                    type: number
                    description: Partner markup component of the fee. Zero if markupBps = 0.
                  fee:
                    type: number
                    description: Total fee (baseFee + partnerMarkupFee).
                  feeRate:
                    type: number
                    description: Effective fee rate as a percentage (e.g. 0.75).
                  totalCost:
                    type: number
                    description: BUY only — gross + fee. The amount escrowed on order submission.
                  estimatedProceeds:
                    type: number
                    description: SELL only — gross - fee. Estimated wallet credit on settlement.
                  walletBalance:
                    type: number
                    description: Partner master wallet balance in USD.
                  sufficientFunds:
                    type: boolean
                    description: BUY only — true if walletBalance >= totalCost.
                  units:
                    type: number
                    description: SELL only — units of this stock currently held.
                  sufficientHoldings:
                    type: boolean
                    description: SELL only — true if units >= quantity.
                  note:
                    type: string
                    example: This is a quote only. No order has been placed.
        '404':
          description: Symbol not found.
      operationId: getQuote
  /trade:
    post:
      tags:
        - Trading
      summary: Place trade
      description: |
        Places a BUY or SELL order on your own partner account (not a sub-account — use `/users/{userId}/trade` for those).

        **BUY** — escrows USD from your master wallet. Settlement is T+3 for most African exchanges.
        **SELL** — requires you to hold the shares. Proceeds are credited on settlement.

        Pass `Idempotency-Key` to make retries safe. The same key returns the cached order within 24 h.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TradeRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/trade \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: trade_$(date +%s)" \
              -d '{"symbol":"SCOM.KE","type":"BUY","quantity":500}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/trade', {
              method: 'POST',
              headers: {
                Authorization: 'Bearer pk_live_KEY',
                'Content-Type': 'application/json',
                'Idempotency-Key': `trade_${Date.now()}`,
              },
              body: JSON.stringify({ symbol: 'SCOM.KE', type: 'BUY', quantity: 500 }),
            });
            const { orderId, status } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests, time
            r = requests.post('https://mystocks.africa/api/v1/partner/trade',
              headers={
                'Authorization': 'Bearer pk_live_KEY',
                'Idempotency-Key': f'trade_{int(time.time())}',
              },
              json={'symbol': 'SCOM.KE', 'type': 'BUY', 'quantity': 500})
            order = r.json()
      responses:
        '201':
          description: Order placed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '402':
          description: Insufficient wallet balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Idempotency conflict — duplicate in-flight.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Business rule violation (e.g. market closed, ambiguous symbol).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createTrade
  /portfolio:
    get:
      tags:
        - Trading
      summary: Portfolio
      description: Returns all open equity positions on your partner account — current holdings, average cost, unrealised P&L, and market value in USD.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/portfolio \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/portfolio', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { positions, totalValue } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/portfolio',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Portfolio positions.
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalValueUsd:
                    type: number
                  positions:
                    type: array
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                        name:
                          type: string
                        exchange:
                          type: string
                        quantity:
                          type: integer
                        avgCostUsd:
                          type: number
                        currentPriceUsd:
                          type: number
                        marketValueUsd:
                          type: number
                        unrealisedPnl:
                          type: number
                        unrealisedPct:
                          type: number
      operationId: listPortfolio
  /orders:
    get:
      tags:
        - Trading
      summary: List orders
      description: Returns your partner order history. Filter by status, symbol, or date range.
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum:
              - PENDING
              - PROCESSING
              - COMPLETED
              - FILLED
              - REJECTED
              - CANCELLED
        - name: symbol
          in: query
          schema:
            type: string
            example: SCOM.KE
        - name: from
          in: query
          description: ISO 8601 date — return orders created on or after this date.
          schema:
            type: string
            format: date
            example: '2026-01-01'
        - name: to
          in: query
          schema:
            type: string
            format: date
            example: '2026-03-31'
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/orders?status=COMPLETED&limit=20" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/orders?status=COMPLETED',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { orders, total } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/orders',
              params={'status': 'COMPLETED', 'limit': 20},
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Order list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  orders:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  total:
                    type: integer
                  page:
                    type: integer
      operationId: listOrders
  /orders/{orderId}:
    get:
      tags:
        - Trading
      summary: Get order
      description: Returns a single order by ID.
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
            example: ord_abc123xyz
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/orders/ord_abc123xyz \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/orders/ord_abc123xyz', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const order = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/orders/ord_abc123xyz',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Order detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          description: Order not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getOrder
    delete:
      tags:
        - Trading
      summary: Cancel order
      description: Cancels a PENDING order. Returns 422 if the order has already been settled or rejected.
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
            example: ord_abc123xyz
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X DELETE https://mystocks.africa/api/v1/partner/orders/ord_abc123xyz \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/orders/ord_abc123xyz', {
              method: 'DELETE',
              headers: { Authorization: 'Bearer pk_live_KEY' },
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.delete('https://mystocks.africa/api/v1/partner/orders/ord_abc123xyz',
              headers={'Authorization': 'Bearer pk_live_KEY'})
      responses:
        '200':
          description: Order cancelled. BUY escrow refunded atomically.
          content:
            application/json:
              schema:
                type: object
                properties:
                  orderId:
                    type: string
                  status:
                    type: string
                    enum: [CANCELLED]
                  refunded:
                    type: number
                    description: USD amount returned to master wallet. Present for BUY orders only.
                  currency:
                    type: string
                    example: USD
                  message:
                    type: string
        '409':
          description: Order cannot be cancelled — it is no longer PENDING (already PROCESSING, COMPLETED, or REJECTED).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: deleteOrders
  /bonds:
    get:
      tags:
        - Asset Classes
      summary: List bonds & fixed income
      description: Returns all available bonds, treasury bills, and fixed-income instruments. Includes coupon rate, maturity date, minimum investment, and current yield.
      parameters:
        - name: country
          in: query
          description: Filter by country code.
          schema:
            type: string
            example: KE
        - name: type
          in: query
          description: Instrument type filter.
          schema:
            type: string
            enum:
              - BOND
              - TBILL
              - CORPORATE
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/bonds?country=KE" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/bonds?country=KE', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { bonds } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/bonds',
              params={'country': 'KE'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Bond list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  bonds:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        type:
                          type: string
                        country:
                          type: string
                        couponRate:
                          type: number
                          description: Annual coupon rate as a percentage.
                        currentYield:
                          type:
                            - number
                            - 'null'
                          description: Computed current yield (couponRate × faceValue / pricePerUnit). Null for zero-coupon/discount instruments.
                        yieldToMaturity:
                          type:
                            - number
                            - 'null'
                          description: Expected annualised return if held to maturity. Admin-entered; null if not provided.
                        modifiedDuration:
                          type:
                            - number
                            - 'null'
                          description: Modified duration in years — measures price sensitivity to a 1% yield change.
                        creditRating:
                          type:
                            - string
                            - 'null'
                          description: Credit rating assigned by a rating agency (e.g. AAA, BBB+).
                          example: BBB+
                        creditRatingAgency:
                          type:
                            - string
                            - 'null'
                          description: Rating agency that issued the credit rating (e.g. S&P, GCR Africa).
                          example: GCR Africa
                        maturityDate:
                          type: string
                          format: date
                        faceValue:
                          type: number
                        minInvestment:
                          type: number
                        currency:
                          type: string
      operationId: listBonds
  /bonds/{id}:
    get:
      tags:
        - Asset Classes
      summary: Bond detail
      description: Returns full detail for a specific bond or fixed-income instrument.
      parameters:
        - name: id
          in: path
          required: true
          description: Bond document ID from the list endpoint.
          schema:
            type: string
            example: ke_tbill_91d_2026
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/bonds/ke_tbill_91d_2026 \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/bonds/ke_tbill_91d_2026', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/bonds/ke_tbill_91d_2026',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Bond detail.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  description:
                    type:
                      - string
                      - 'null'
                  type:
                    type: string
                  country:
                    type: string
                  couponRate:
                    type: number
                    description: Annual coupon rate as a percentage.
                  currentYield:
                    type:
                      - number
                      - 'null'
                    description: Computed current yield (couponRate × faceValue / pricePerUnit).
                  yieldToMaturity:
                    type:
                      - number
                      - 'null'
                    description: Expected annualised return if held to maturity.
                  modifiedDuration:
                    type:
                      - number
                      - 'null'
                    description: Modified duration in years.
                  creditRating:
                    type:
                      - string
                      - 'null'
                    description: Credit rating (e.g. AAA, BBB+).
                    example: BBB+
                  creditRatingAgency:
                    type:
                      - string
                      - 'null'
                    description: Agency that issued the credit rating (e.g. S&P, GCR Africa).
                  maturityDate:
                    type: string
                    format: date
                  faceValue:
                    type: number
                  minInvestment:
                    type: number
                  currency:
                    type: string
                  issuer:
                    type:
                      - string
                      - 'null'
                  prospectusUrl:
                    type:
                      - string
                      - 'null'
                    format: uri
        '404':
          description: Bond not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getBonds
  /funds:
    get:
      tags:
        - Asset Classes
      summary: List funds & ETFs
      description: Returns all available money market funds, unit trusts, and ETFs. Includes NAV, annualised return, and minimum investment.
      parameters:
        - name: type
          in: query
          description: Filter by fund type.
          schema:
            type: string
            enum:
              - MMF
              - UNIT_TRUST
              - ETF
        - name: country
          in: query
          schema:
            type: string
            example: KE
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/funds?type=MMF" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/funds?type=MMF', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { funds } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/funds',
              params={'type': 'MMF'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Fund list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  funds:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        type:
                          type: string
                        country:
                          type: string
                        nav:
                          type: number
                          description: Current net asset value per unit.
                        annualisedReturn:
                          type:
                            - number
                            - 'null'
                        minInvestment:
                          type: number
                        currency:
                          type: string
      operationId: listFunds
  /funds/{id}:
    get:
      tags:
        - Asset Classes
      summary: Fund detail
      description: Returns full details for a specific fund or ETF.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: fund_mmf_africa
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/funds/fund_mmf_africa \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/funds/fund_mmf_africa', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/funds/fund_mmf_africa',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Fund detail.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  description:
                    type:
                      - string
                      - 'null'
                  type:
                    type: string
                  country:
                    type: string
                  nav:
                    type: number
                  annualisedReturn:
                    type:
                      - number
                      - 'null'
                  minInvestment:
                    type: number
                  currency:
                    type: string
                  managementFee:
                    type:
                      - number
                      - 'null'
                  factsheetUrl:
                    type:
                      - string
                      - 'null'
                    format: uri
      operationId: getFunds
  /opportunities:
    get:
      tags:
        - Asset Classes
      summary: Private credit & pre-IPO
      description: |
        Returns private credit deals and pre-IPO offerings available to your sub-accounts.
        Results are merged from the `opportunities` collection (private credit) and `preIPOOfferings` collection,
        sorted newest first. Use `?type=` to filter to one category.
      parameters:
        - name: type
          in: query
          description: Filter by deal type. Omit for all types.
          schema:
            type: string
            enum: [OPPORTUNITY, PRE_IPO, ALL]
            default: ALL
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/opportunities \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/opportunities', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { opportunities, count } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/opportunities',
              params={'type': 'PRE_IPO'},
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Open opportunities and pre-IPO rounds.
          content:
            application/json:
              schema:
                type: object
                properties:
                  opportunities:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        assetType:
                          type: string
                          enum: [OPPORTUNITY, PRE_IPO]
                        name:
                          type:
                            - string
                            - 'null'
                        slug:
                          type:
                            - string
                            - 'null'
                        symbol:
                          type:
                            - string
                            - 'null'
                        issuer:
                          type:
                            - string
                            - 'null'
                        description:
                          type:
                            - string
                            - 'null'
                        minInvestment:
                          type:
                            - number
                            - 'null'
                          description: Minimum commitment in USD.
                        targetAmount:
                          type:
                            - number
                            - 'null'
                          description: Total raise target in USD.
                        currentRaised:
                          type:
                            - number
                            - 'null'
                          description: Amount raised so far in USD.
                        expectedReturn:
                          type:
                            - number
                            - 'null'
                          description: Target annualised return as a decimal (e.g. 0.28 = 28%).
                        riskLevel:
                          type:
                            - string
                            - 'null'
                          description: LOW | MEDIUM | MEDIUM_HIGH | HIGH
                        currency:
                          type: string
                          example: USD
                        status:
                          type:
                            - string
                            - 'null'
                        maturityDate:
                          type:
                            - string
                            - 'null'
                        expectedExitDate:
                          type:
                            - string
                            - 'null'
                        createdAt:
                          type:
                            - string
                            - 'null'
                          format: date-time
                  count:
                    type: integer
        '401':
          description: Invalid or missing API key.
      operationId: listOpportunities
  /opportunities/{id}:
    get:
      tags:
        - Asset Classes
      summary: Opportunity or pre-IPO detail
      description: |
        Returns full detail for a single private market deal or pre-IPO offering.
        Resolves from the `opportunities` collection first, then `preIPOOfferings`.
        Accepts Firestore document ID or URL slug.
      parameters:
        - name: id
          in: path
          required: true
          description: Opportunity document ID or URL slug.
          schema:
            type: string
            example: acme-series-b-2026
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/opportunities/acme-series-b-2026 \
              -H "x-api-key: pk_live_KEY"
      responses:
        '200':
          description: Opportunity detail.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  assetType:
                    type: string
                    enum: [OPPORTUNITY, PRE_IPO]
                  name:
                    type:
                      - string
                      - 'null'
                  slug:
                    type:
                      - string
                      - 'null'
                  symbol:
                    type:
                      - string
                      - 'null'
                  issuer:
                    type:
                      - string
                      - 'null'
                  description:
                    type:
                      - string
                      - 'null'
                  minInvestment:
                    type:
                      - number
                      - 'null'
                    description: Minimum commitment in USD.
                  targetAmount:
                    type:
                      - number
                      - 'null'
                    description: Total raise target in USD.
                  currentRaised:
                    type:
                      - number
                      - 'null'
                    description: Amount raised so far in USD.
                  expectedReturn:
                    type:
                      - number
                      - 'null'
                    description: Target annualised return as a decimal (e.g. 0.28 = 28%).
                  riskLevel:
                    type:
                      - string
                      - 'null'
                    description: LOW | MEDIUM | MEDIUM_HIGH | HIGH
                  currency:
                    type: string
                    example: USD
                  status:
                    type:
                      - string
                      - 'null'
                  maturityDate:
                    type:
                      - string
                      - 'null'
                  expectedExitDate:
                    type:
                      - string
                      - 'null'
                  createdAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
        '401':
          description: Invalid or missing API key.
        '404':
          description: Opportunity not found.
      operationId: getOpportunity
  /auto-register:
    post:
      tags:
        - Sub-Accounts
      summary: Auto-register sub-account
      description: |
        Single-call convenience endpoint that creates a sub-account **and** returns an API token for it.
        Idempotent on `uid` — calling twice with the same uid returns the existing sub-account and a fresh token.
        Equivalent to POST /users but returns auth context in one round trip.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AutoRegisterRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/auto-register \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"uid":"user_42","email":"alice@yourapp.com","name":"Alice K.","country":"KE"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/auto-register', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ uid: 'user_42', email: 'alice@yourapp.com', name: 'Alice K.', country: 'KE' }),
            });
            const { subAccountId, token } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/auto-register',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'uid': 'user_42', 'email': 'alice@yourapp.com', 'name': 'Alice K.', 'country': 'KE'})
            data = r.json()
      responses:
        '200':
          description: Sub-account created or retrieved.
          content:
            application/json:
              schema:
                type: object
                properties:
                  subAccountId:
                    type: string
                  token:
                    type: string
                    description: Short-lived auth token for the sub-account.
                  isNew:
                    type: boolean
                    description: true if this call created the account, false if it already existed.
      operationId: createAutoRegister
  /users:
    post:
      tags:
        - Sub-Accounts
      summary: Create sub-account
      description: |
        Creates a new sub-account. Each sub-account has an isolated USD wallet, portfolio, and order book.
        The `externalId` is your own reference — it must be unique per partner and is used in all subsequent calls.
        Sub-accounts start with a `walletBalance` of $0 — fund them via POST `/users/{userId}/deposit`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSubAccountRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"externalId":"usr_8821","displayName":"Alice K.","email":"alice@yourapp.com"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ externalId: 'usr_8821', displayName: 'Alice K.', email: 'alice@yourapp.com' }),
            });
            const { subAccountId } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/users',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'externalId': 'usr_8821', 'displayName': 'Alice K.', 'email': 'alice@yourapp.com'})
      responses:
        '201':
          description: Sub-account created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubAccount'
        '409':
          description: externalId already exists.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createUsers
    get:
      tags:
        - Sub-Accounts
      summary: List sub-accounts
      description: Returns all sub-accounts for your partner key. Supports pagination and status filtering.
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum:
              - active
              - frozen
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/users?limit=20" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users?limit=20', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { users, total } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/users',
              params={'limit': 20}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Sub-account list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/SubAccount'
                  total:
                    type: integer
                  page:
                    type: integer
      operationId: listUsers
  /users/{userId}:
    get:
      tags:
        - Sub-Accounts
      summary: Get sub-account
      description: Returns a single sub-account by its MyStocks `subAccountId`. Use this to check wallet balance, KYC status, and account state.
      parameters:
        - name: userId
          in: path
          required: true
          description: MyStocks sub-account ID (e.g. `usr_abc123`).
          schema:
            type: string
            example: usr_abc123
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/users/usr_abc123 \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const user = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/users/usr_abc123',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Sub-account detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubAccount'
        '404':
          description: Sub-account not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getUsers
    patch:
      tags:
        - Sub-Accounts
      summary: Update sub-account
      description: |
        Updates mutable fields on a sub-account. All fields are optional — only provided fields are changed.
        Set `status` to `frozen` to immediately suspend all trading, deposits, and withdrawals for a user.
        Set back to `active` to restore access.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubAccountRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X PATCH https://mystocks.africa/api/v1/partner/users/usr_abc123 \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"status":"frozen"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123', {
              method: 'PATCH',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ status: 'frozen' }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.patch('https://mystocks.africa/api/v1/partner/users/usr_abc123',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'status': 'frozen'})
      responses:
        '200':
          description: Updated sub-account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubAccount'
      operationId: updateUsers
  /users/{userId}/deposit:
    post:
      tags:
        - Sub-Accounts
      summary: Deposit to sub-account
      description: |
        Credits USD from your **master wallet** into a sub-account's wallet.
        Your master wallet must have sufficient balance — check via GET /account.

        **FX model** — you collect local currency from your user and convert to USD yourself.
        Record the exchange rate in `fxRate` for audit. Only `amount` (USD) is credited.

        Pass `Idempotency-Key` to make deposit retries safe on network failure.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepositRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users/usr_abc123/deposit \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: dep_usr_abc123_1743152580" \
              -d '{"amount":500,"note":"Mpesa STK push ref KE2482","localAmount":65000,"localCurrency":"KES","fxRate":130}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/deposit', {
              method: 'POST',
              headers: {
                Authorization: 'Bearer pk_live_KEY',
                'Content-Type': 'application/json',
                'Idempotency-Key': 'dep_usr_abc123_1743152580',
              },
              body: JSON.stringify({ amount: 500, note: 'Mpesa STK push', localAmount: 65000, localCurrency: 'KES', fxRate: 130 }),
            });
            const { walletBalance } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/users/usr_abc123/deposit',
              headers={
                'Authorization': 'Bearer pk_live_KEY',
                'Idempotency-Key': 'dep_usr_abc123_1743152580',
              },
              json={'amount': 500, 'note': 'Mpesa STK push', 'localAmount': 65000, 'localCurrency': 'KES', 'fxRate': 130})
      responses:
        '200':
          description: Deposit processed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  walletBalance:
                    type: number
                    description: New wallet balance after deposit.
                  transactionId:
                    type: string
        '402':
          description: Master wallet has insufficient balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Idempotency conflict.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createDeposit
  /users/{userId}/withdraw:
    post:
      tags:
        - Sub-Accounts
      summary: Withdraw from sub-account
      description: |
        Moves USD from a sub-account's wallet back to your **master wallet**.
        Use this when your user requests a cash-out. The funds are immediately available in your master wallet.
        Pass `Idempotency-Key` to make withdrawals safe to retry.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users/usr_abc123/withdraw \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: wd_usr_abc123_1743152999" \
              -d '{"amount":200,"note":"User requested withdrawal","localAmount":26000,"localCurrency":"KES","fxRate":130}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/withdraw', {
              method: 'POST',
              headers: {
                Authorization: 'Bearer pk_live_KEY',
                'Content-Type': 'application/json',
                'Idempotency-Key': `wd_${userId}_${Date.now()}`,
              },
              body: JSON.stringify({ amount: 200, note: 'User withdrawal', localAmount: 26000, localCurrency: 'KES', fxRate: 130 }),
            });
        - lang: Python
          label: Python
          source: |
            import requests, time
            requests.post('https://mystocks.africa/api/v1/partner/users/usr_abc123/withdraw',
              headers={'Authorization': 'Bearer pk_live_KEY', 'Idempotency-Key': f'wd_abc123_{int(time.time())}'},
              json={'amount': 200, 'localAmount': 26000, 'localCurrency': 'KES', 'fxRate': 130})
      responses:
        '200':
          description: Withdrawal processed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  walletBalance:
                    type: number
                    description: New sub-account wallet balance.
                  transactionId:
                    type: string
        '402':
          description: Sub-account has insufficient wallet balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createWithdraw
  /users/{userId}/wallet:
    get:
      tags:
        - Sub-Accounts
      summary: Sub-account wallet
      description: Returns the current wallet balance and recent transaction history for a sub-account.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/users/usr_abc123/wallet \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/wallet', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { balance, transactions } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/users/usr_abc123/wallet',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Wallet and recent transactions.
          content:
            application/json:
              schema:
                type: object
                properties:
                  balance:
                    type: number
                    description: Current USD wallet balance.
                  transactions:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        type:
                          type: string
                          enum:
                            - DEPOSIT
                            - WITHDRAWAL
                            - TRADE_ESCROW
                            - TRADE_CREDIT
                            - DIVIDEND
                        amount:
                          type: number
                        note:
                          type:
                            - string
                            - 'null'
                        createdAt:
                          type: string
                          format: date-time
      operationId: listWallet
  /users/{userId}/trade:
    post:
      tags:
        - Sub-Accounts
      summary: Trade on behalf
      description: |
        Places a BUY or SELL order on behalf of a sub-account.
        The sub-account's wallet is escrowed for BUY orders; holdings are checked for SELL.
        The sub-account must have `kycStatus: VERIFIED` to trade.
        Pass `Idempotency-Key` to make trade retries safe.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TradeRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users/usr_abc123/trade \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: tr_usr_abc123_1743153100" \
              -d '{"symbol":"SCOM.KE","type":"BUY","quantity":500}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/trade', {
              method: 'POST',
              headers: {
                Authorization: 'Bearer pk_live_KEY',
                'Content-Type': 'application/json',
                'Idempotency-Key': `tr_usr_abc123_${Date.now()}`,
              },
              body: JSON.stringify({ symbol: 'SCOM.KE', type: 'BUY', quantity: 500 }),
            });
            const { orderId } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests, time
            r = requests.post('https://mystocks.africa/api/v1/partner/users/usr_abc123/trade',
              headers={'Authorization': 'Bearer pk_live_KEY', 'Idempotency-Key': f'tr_abc123_{int(time.time())}'},
              json={'symbol': 'SCOM.KE', 'type': 'BUY', 'quantity': 500})
      responses:
        '201':
          description: Order placed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '402':
          description: Sub-account has insufficient wallet balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Sub-account is frozen or KYC not verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Market closed, ambiguous symbol, or invalid quantity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createUserTrade
  /users/{userId}/portfolio:
    get:
      tags:
        - Sub-Accounts
      summary: Sub-account portfolio
      description: Returns all open equity positions held by a sub-account, with current market value and unrealised P&L in USD.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/users/usr_abc123/portfolio \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/portfolio', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { positions } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/users/usr_abc123/portfolio',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Portfolio positions.
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalValueUsd:
                    type: number
                  positions:
                    type: array
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                        name:
                          type: string
                        exchange:
                          type: string
                        quantity:
                          type: integer
                        avgCostUsd:
                          type: number
                        currentPriceUsd:
                          type: number
                        marketValueUsd:
                          type: number
                        unrealisedPnl:
                          type: number
                        unrealisedPct:
                          type: number
      operationId: getUserPortfolio
  /users/{userId}/orders:
    get:
      tags:
        - Sub-Accounts
      summary: Sub-account orders
      description: Returns order history for a sub-account. Filter by status, symbol, or date range.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - name: status
          in: query
          schema:
            type: string
            enum:
              - PENDING
              - PROCESSING
              - COMPLETED
              - FILLED
              - REJECTED
              - CANCELLED
        - name: symbol
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/users/usr_abc123/orders?status=COMPLETED" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/orders?status=COMPLETED',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { orders } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/users/usr_abc123/orders',
              params={'status': 'COMPLETED'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Order list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  orders:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  total:
                    type: integer
                  page:
                    type: integer
      operationId: listUserOrders
  /users/{userId}/orders/{orderId}:
    get:
      tags:
        - Sub-Accounts
      summary: Get sub-account order
      description: Returns a single order for a sub-account by order ID.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - name: orderId
          in: path
          required: true
          schema:
            type: string
            example: ord_abc123xyz
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/users/usr_abc123/orders/ord_abc123xyz \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/orders/ord_abc123xyz',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/orders/ord_abc123xyz',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Order detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          description: Order not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getUserOrder
    delete:
      tags:
        - Sub-Accounts
      summary: Cancel sub-account order
      description: Cancels a PENDING order on a sub-account. Returns 422 if the order is already settled or rejected.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - name: orderId
          in: path
          required: true
          schema:
            type: string
            example: ord_abc123xyz
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X DELETE \
              https://mystocks.africa/api/v1/partner/users/usr_abc123/orders/ord_abc123xyz \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/orders/ord_abc123xyz',
              { method: 'DELETE', headers: { Authorization: 'Bearer pk_live_KEY' } }
            );
        - lang: Python
          label: Python
          source: |
            import requests
            requests.delete(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/orders/ord_abc123xyz',
              headers={'Authorization': 'Bearer pk_live_KEY'})
      responses:
        '200':
          description: Order cancelled. BUY escrow refunded to sub-account wallet atomically.
          content:
            application/json:
              schema:
                type: object
                properties:
                  orderId:
                    type: string
                  subAccountId:
                    type: string
                  externalId:
                    type:
                      - string
                      - 'null'
                  type:
                    type: string
                    enum: [BUY, SELL]
                  symbol:
                    type: string
                  quantity:
                    type: number
                  status:
                    type: string
                    enum: [CANCELLED]
                  refunded:
                    type: number
                    description: USD amount returned to sub-account wallet. Present for BUY orders only.
                  currency:
                    type: string
                    example: USD
                  message:
                    type: string
        '409':
          description: Order cannot be cancelled — it is no longer PENDING.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: deleteUsersUserIdOrdersOrderId
  /users/{userId}/transactions:
    get:
      tags:
        - Sub-Accounts
      summary: Sub-account transactions
      description: |
        Returns the full ledger transaction history for a sub-account — deposits, withdrawals,
        trade escrows (INVEST), trade settlements (SELL), dividend distributions, fund redemptions,
        and fee entries. Cursor-paginated; use `nextCursor` from the previous response as the
        `cursor` query param to fetch the next page.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - name: type
          in: query
          description: Filter to a single transaction type.
          schema:
            type: string
            enum:
              - DEPOSIT
              - WITHDRAWAL
              - INVEST
              - SELL
              - DISTRIBUTION
              - REDEEM
              - FEE
              - TRANSFER_IN
              - TRANSFER_OUT
        - name: from
          in: query
          description: Return transactions on or after this date (YYYY-MM-DD, UTC).
          schema:
            type: string
            format: date
            example: '2026-01-01'
        - name: to
          in: query
          description: Return transactions on or before this date (YYYY-MM-DD, UTC).
          schema:
            type: string
            format: date
            example: '2026-06-30'
        - name: cursor
          in: query
          description: Opaque cursor from `nextCursor` of the previous response. Omit for the first page.
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/users/usr_abc123/transactions?limit=20&type=DEPOSIT" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/transactions?limit=20&type=DEPOSIT',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { transactions, hasMore, nextCursor } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/transactions',
              params={'limit': 20, 'type': 'DEPOSIT'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Transaction ledger.
          content:
            application/json:
              schema:
                type: object
                properties:
                  transactions:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        type:
                          type: string
                          enum:
                            - DEPOSIT
                            - WITHDRAWAL
                            - INVEST
                            - SELL
                            - DISTRIBUTION
                            - REDEEM
                            - FEE
                            - TRANSFER_IN
                            - TRANSFER_OUT
                        status:
                          type: string
                          enum: [PENDING, COMPLETED, FAILED]
                        direction:
                          type: string
                          enum: [CREDIT, DEBIT]
                        amount:
                          type: number
                        currency:
                          type: string
                          example: USD
                        description:
                          type:
                            - string
                            - 'null'
                        reference:
                          type:
                            - string
                            - 'null'
                        createdAt:
                          type: string
                          format: date-time
                        settledAt:
                          type:
                            - string
                            - 'null'
                          format: date-time
                  count:
                    type: integer
                  hasMore:
                    type: boolean
                  nextCursor:
                    type:
                      - string
                      - 'null'
      operationId: listTransactions
  /users/{userId}/kyc:
    post:
      tags:
        - Sub-Accounts
      summary: KYC assertion
      description: |
        Asserts the KYC status of a sub-account. MyStocks defers identity verification to your platform —
        you send us the result of your own KYC process.

        `VERIFIED` + `BASIC` is sufficient to unlock trading and asset subscriptions.
        `FULL` is required for higher transaction limits and private credit deals.

        The `reference` field stores your internal KYC session ID for audit correlation.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KycRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users/usr_abc123/kyc \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"status":"VERIFIED","level":"BASIC","reference":"kyc_session_88721"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/kyc', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ status: 'VERIFIED', level: 'BASIC', reference: 'kyc_session_88721' }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/v1/partner/users/usr_abc123/kyc',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'status': 'VERIFIED', 'level': 'BASIC', 'reference': 'kyc_session_88721'})
      responses:
        '200':
          description: KYC status updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  kycStatus:
                    type: string
                  kycLevel:
                    type: string
      operationId: createKyc
  /users/{userId}/subscribe:
    post:
      tags:
        - Sub-Accounts
      summary: Subscribe (bond/fund/deal)
      description: |
        Subscribes a sub-account to a bond, money market fund, or private credit/pre-IPO deal.
        Funds are escrowed from the sub-account wallet on submission.

        For **FUND** subscriptions: specify `units`. For **OPPORTUNITY** / **PRE_IPO**: specify `amount` in USD.
        The sub-account must be KYC-verified (`kycStatus: VERIFIED`).
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscribeRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users/usr_abc123/subscribe \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"assetType":"FUND","assetId":"fund_mmf_africa","units":500}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/subscribe', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ assetType: 'FUND', assetId: 'fund_mmf_africa', units: 500 }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/v1/partner/users/usr_abc123/subscribe',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'assetType': 'FUND', 'assetId': 'fund_mmf_africa', 'units': 500})
      responses:
        '201':
          description: Subscription created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  subscriptionId:
                    type: string
                  amountEscrowed:
                    type: number
        '402':
          description: Insufficient wallet balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: KYC not verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createSubscribe
  /users/{userId}/redeem:
    post:
      tags:
        - Sub-Accounts
      summary: Redeem fund units
      description: Redeems fund units back to the sub-account wallet. Proceeds are credited at the current NAV once the redemption is processed by the fund manager (typically T+1 for MMFs).
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RedeemRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users/usr_abc123/redeem \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"holdingId":"fund_mmf_africa","unitsToRedeem":200}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/users/usr_abc123/redeem', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ holdingId: 'fund_mmf_africa', unitsToRedeem: 200 }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/v1/partner/users/usr_abc123/redeem',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'holdingId': 'fund_mmf_africa', 'unitsToRedeem': 200})
      responses:
        '200':
          description: Redemption queued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  redemptionId:
                    type: string
                  unitsRedeemed:
                    type: number
      operationId: createRedeem
  /users/{userId}/dividends:
    get:
      tags:
        - Sub-Accounts
      summary: Sub-account dividends
      description: Returns dividend payments received by a sub-account, including declared date, pay date, and USD amount credited.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - name: from
          in: query
          schema:
            type: string
            format: date
        - name: to
          in: query
          schema:
            type: string
            format: date
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/users/usr_abc123/dividends \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/dividends',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { dividends } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/users/usr_abc123/dividends',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Dividend history.
          content:
            application/json:
              schema:
                type: object
                properties:
                  dividends:
                    type: array
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                        name:
                          type: string
                        amountUsd:
                          type: number
                        sharesHeld:
                          type: integer
                        dividendPerShare:
                          type: number
                        exDate:
                          type: string
                          format: date
                        payDate:
                          type: string
                          format: date
                        creditedAt:
                          type: string
                          format: date-time
      operationId: listDividends
  /users/{userId}/watchlist:
    get:
      tags:
        - Sub-Accounts
      summary: List sub-account watchlist
      description: Returns the watchlist for a sub-account, including live price and day-change for each stock where available.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { data } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Watchlist items with live prices.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        assetId:
                          type: string
                        symbol:
                          type: string
                        name:
                          type: string
                        exchange:
                          type: string
                        sourceType:
                          type: string
                          example: LISTED_STOCK
                        addedAt:
                          type: string
                          format: date-time
                        price:
                          type: number
                          description: Live local-currency price (omitted if unavailable).
                        usdPrice:
                          type: number
                        change:
                          type: number
                          description: Day change percent.
                        currency:
                          type: string
                  count:
                    type: integer
                  meta:
                    $ref: '#/components/schemas/Meta'
      operationId: listWatchlist
    post:
      tags:
        - Sub-Accounts
      summary: Add symbol to watchlist
      description: |
        Adds a stock to a sub-account's watchlist. The symbol is resolved to its canonical
        exchange-qualified form (e.g. "SCOM" → "SCOM.KE"). Returns 409 if already present.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - symbol
              properties:
                symbol:
                  type: string
                  description: Ticker symbol (exchange-qualified preferred, e.g. SCOM.KE).
                  example: SCOM.KE
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist \
              -H "x-api-key: pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"symbol":"SCOM.KE"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist',
              {
                method: 'POST',
                headers: { 'x-api-key': 'pk_live_KEY', 'Content-Type': 'application/json' },
                body: JSON.stringify({ symbol: 'SCOM.KE' }),
              }
            );
            const item = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist',
              headers={'x-api-key': 'pk_live_KEY'},
              json={'symbol': 'SCOM.KE'})
      responses:
        '201':
          description: Symbol added to watchlist.
          content:
            application/json:
              schema:
                type: object
                properties:
                  assetId:
                    type: string
                  symbol:
                    type: string
                  name:
                    type: string
                  exchange:
                    type: string
                  addedAt:
                    type: string
                    format: date-time
                  meta:
                    $ref: '#/components/schemas/Meta'
        '409':
          description: Symbol already on watchlist.
      operationId: addToWatchlist
  /users/{userId}/watchlist/{symbol}:
    delete:
      tags:
        - Sub-Accounts
      summary: Remove symbol from watchlist
      description: Removes a stock from a sub-account's watchlist. Returns 404 if the symbol is not on the watchlist.
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            example: usr_abc123
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: SCOM.KE
          description: Exchange-qualified ticker symbol (e.g. SCOM.KE).
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X DELETE https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist/SCOM.KE \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch(
              'https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist/SCOM.KE',
              { method: 'DELETE', headers: { 'x-api-key': 'pk_live_KEY' } }
            );
        - lang: Python
          label: Python
          source: |
            import requests
            requests.delete('https://mystocks.africa/api/v1/partner/users/usr_abc123/watchlist/SCOM.KE',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '204':
          description: Symbol removed. No response body.
        '404':
          description: Symbol not on watchlist or sub-account not found.
      operationId: removeFromWatchlist
  /client-activity:
    get:
      tags:
        - Reports
      summary: Client activity feed
      description: Returns a chronological feed of all sub-account activity — trades, deposits, withdrawals, KYC changes, and dividends. Useful for building an admin dashboard.
      parameters:
        - name: from
          in: query
          schema:
            type: string
            format: date
            example: '2026-01-01'
        - name: to
          in: query
          schema:
            type: string
            format: date
        - name: type
          in: query
          description: Filter by event type.
          schema:
            type: string
            enum:
              - TRADE
              - DEPOSIT
              - WITHDRAWAL
              - KYC
              - DIVIDEND
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 500
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/client-activity?from=2026-01-01&limit=50" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/client-activity?from=2026-01-01',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { events } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/client-activity',
              params={'from': '2026-01-01', 'limit': 50},
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Activity events.
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      type: object
                      properties:
                        eventId:
                          type: string
                        type:
                          type: string
                        subAccountId:
                          type: string
                        detail:
                          type: object
                          description: Event-specific payload.
                        createdAt:
                          type: string
                          format: date-time
                  total:
                    type: integer
      operationId: listClientActivity
  /report/aum:
    get:
      tags:
        - Reports
      summary: Report — AUM
      description: Returns assets under management across all sub-accounts, broken down by exchange, asset class, and currency.
      parameters:
        - name: asOf
          in: query
          description: Report date. Defaults to today.
          schema:
            type: string
            format: date
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/report/aum?asOf=2026-05-01" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/report/aum', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { totalAumUsd, breakdown } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/report/aum',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: AUM report.
          content:
            application/json:
              schema:
                type: object
                properties:
                  asOf:
                    type: string
                    format: date
                  totalAumUsd:
                    type: number
                  totalAccounts:
                    type: integer
                  breakdown:
                    type: array
                    items:
                      type: object
                      properties:
                        assetClass:
                          type: string
                        exchange:
                          type:
                            - string
                            - 'null'
                        valueUsd:
                          type: number
      operationId: listAum
  /report/positions:
    get:
      tags:
        - Reports
      summary: Report — Positions
      description: Returns all open positions across all sub-accounts, aggregated by symbol. Useful for calculating exposure and hedging needs.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/report/positions \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/report/positions', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { positions } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/report/positions',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Aggregated positions.
          content:
            application/json:
              schema:
                type: object
                properties:
                  positions:
                    type: array
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                        name:
                          type: string
                        exchange:
                          type: string
                        totalQuantity:
                          type: integer
                        totalValueUsd:
                          type: number
                        accountCount:
                          type: integer
                          description: Number of sub-accounts holding this position.
      operationId: listPositions
  /report/fees:
    get:
      tags:
        - Reports
      summary: Report — Fees
      description: Returns fees earned by MyStocks and your markup fees, broken down by period and sub-account.
      parameters:
        - name: from
          in: query
          schema:
            type: string
            format: date
        - name: to
          in: query
          schema:
            type: string
            format: date
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/report/fees?from=2026-01-01&to=2026-03-31" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/report/fees?from=2026-01-01&to=2026-03-31',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { totalFeesUsd, breakdown } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/report/fees',
              params={'from': '2026-01-01', 'to': '2026-03-31'},
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Fee report.
          content:
            application/json:
              schema:
                type: object
                properties:
                  from:
                    type: string
                    format: date
                  to:
                    type: string
                    format: date
                  totalFeesUsd:
                    type: number
                    description: Total fees (base + markup).
                  baseFeeUsd:
                    type: number
                  markupFeeUsd:
                    type: number
                  breakdown:
                    type: array
                    items:
                      type: object
                      properties:
                        subAccountId:
                          type: string
                        feesUsd:
                          type: number
                        trades:
                          type: integer
      operationId: listFees
  /report/revenue:
    get:
      tags:
        - Reports
      summary: Report — Revenue
      description: Returns your net revenue (markup fees) across all sub-accounts for the given period.
      parameters:
        - name: from
          in: query
          schema:
            type: string
            format: date
        - name: to
          in: query
          schema:
            type: string
            format: date
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/report/revenue?from=2026-01-01" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/report/revenue?from=2026-01-01',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { totalRevenueUsd } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/report/revenue',
              params={'from': '2026-01-01'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Revenue report.
          content:
            application/json:
              schema:
                type: object
                properties:
                  from:
                    type: string
                    format: date
                  to:
                    type: string
                    format: date
                  totalRevenueUsd:
                    type: number
                  monthly:
                    type: array
                    items:
                      type: object
                      properties:
                        month:
                          type: string
                          example: 2026-01
                        revenueUsd:
                          type: number
      operationId: listRevenue
  /report/invoice:
    get:
      tags:
        - Reports
      summary: Report — Invoice
      description: Returns invoiceable fee data for a billing period, formatted for your finance team. Includes base fees billed by MyStocks and markup collected by you.
      parameters:
        - name: month
          in: query
          required: true
          description: Billing month in YYYY-MM format.
          schema:
            type: string
            example: 2026-04
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/report/invoice?month=2026-04" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/report/invoice?month=2026-04',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const invoice = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/report/invoice',
              params={'month': '2026-04'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Invoice data.
          content:
            application/json:
              schema:
                type: object
                properties:
                  month:
                    type: string
                  invoiceNumber:
                    type: string
                  totalTrades:
                    type: integer
                  grossVolumeUsd:
                    type: number
                  baseFeeUsd:
                    type: number
                  markupFeeUsd:
                    type: number
                  netPayableUsd:
                    type: number
                    description: Amount owed to MyStocks for this period.
                  dueDate:
                    type: string
                    format: date
      operationId: listInvoice
  /topup:
    get:
      tags:
        - Fund Flow
      summary: List top-up requests
      description: Returns all master wallet top-up requests and their approval status.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/topup \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/topup', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { topups } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/topup',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Top-up request list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  topups:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        amount:
                          type: number
                        paymentReference:
                          type: string
                        status:
                          type: string
                          enum:
                            - pending
                            - approved
                            - rejected
                        createdAt:
                          type: string
                          format: date-time
      operationId: listTopup
    post:
      tags:
        - Fund Flow
      summary: Request top-up
      description: |
        Notifies MyStocks that you have sent a wire transfer to top up your master wallet.
        An admin confirms the wire receipt and credits your wallet (typically within 1 business day).
        Provide your wire transfer reference in `paymentReference` so we can match the incoming funds.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TopupRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/topup \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"amount":10000,"paymentReference":"WIRE-2026-05-001","paymentMethod":"SWIFT wire"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/topup', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ amount: 10000, paymentReference: 'WIRE-2026-05-001', paymentMethod: 'SWIFT wire' }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/v1/partner/topup',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'amount': 10000, 'paymentReference': 'WIRE-2026-05-001', 'paymentMethod': 'SWIFT wire'})
      responses:
        '201':
          description: Top-up request received.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  topupId:
                    type: string
      operationId: createTopup
  /payout:
    get:
      tags:
        - Fund Flow
      summary: List payout requests
      description: Returns all payout requests from your master wallet.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/payout \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/payout', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { payouts } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/payout',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Payout request list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  payouts:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        amount:
                          type: number
                        status:
                          type: string
                          enum:
                            - pending
                            - approved
                            - rejected
                        createdAt:
                          type: string
                          format: date-time
      operationId: listPayout
    post:
      tags:
        - Fund Flow
      summary: Request payout
      description: |
        Requests a cash withdrawal from your master wallet to a bank account.
        An admin reviews and processes payouts within 2 business days.
        The requested amount is reserved immediately from your wallet balance.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PayoutRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/payout \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"amount":5000,"bankDetails":{"bankName":"Equity Bank Kenya","accountName":"ACME Fintech Ltd","accountNumber":"0123456789","swiftCode":"EQBLKENA"}}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/payout', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({
                amount: 5000,
                bankDetails: { bankName: 'Equity Bank Kenya', accountName: 'ACME Fintech Ltd', accountNumber: '0123456789', swiftCode: 'EQBLKENA' },
              }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/v1/partner/payout',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={
                'amount': 5000,
                'bankDetails': {'bankName': 'Equity Bank Kenya', 'accountName': 'ACME Fintech Ltd', 'accountNumber': '0123456789', 'swiftCode': 'EQBLKENA'},
              })
      responses:
        '201':
          description: Payout request submitted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  payoutId:
                    type: string
        '402':
          description: Insufficient master wallet balance.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createPayout
  /webhooks:
    get:
      tags:
        - Webhooks
      summary: List webhooks
      description: Returns all registered webhook endpoints for your partner account.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/webhooks \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/webhooks', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { webhooks } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/webhooks',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Webhook list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhooks:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookRegistration'
      operationId: listWebhooks
    post:
      tags:
        - Webhooks
      summary: Register webhook
      description: |
        Registers a new HTTPS webhook endpoint. MyStocks sends a POST to your URL for each subscribed event.

        **Verification** — every delivery includes an `x-mystocks-signature` header:
        `HMAC-SHA256(secret, rawBody)` hex-encoded. Verify it before processing.

        **Retries** — deliveries are retried with exponential back-off (3 attempts, up to 10 minutes).
        Your endpoint must respond `2xx` within 8 seconds or the delivery is marked failed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookCreateRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/webhooks \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"url":"https://yourapp.com/webhooks/mystocks","events":["trade.settled","deposit.confirmed"],"secret":"my-signing-secret-min-16-chars"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/webhooks', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({
                url: 'https://yourapp.com/webhooks/mystocks',
                events: ['trade.settled', 'deposit.confirmed'],
                secret: 'my-signing-secret-min-16-chars',
              }),
            });
            const { id } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/webhooks',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={
                'url': 'https://yourapp.com/webhooks/mystocks',
                'events': ['trade.settled', 'deposit.confirmed'],
                'secret': 'my-signing-secret-min-16-chars',
              })
            webhook_id = r.json()['id']
      responses:
        '201':
          description: Webhook registered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookRegistration'
      operationId: createWebhooks
  /webhooks/{id}:
    delete:
      tags:
        - Webhooks
      summary: Delete webhook
      description: Permanently removes a webhook registration. In-flight deliveries will still complete.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: wh_abc123
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X DELETE https://mystocks.africa/api/v1/partner/webhooks/wh_abc123 \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/webhooks/wh_abc123', {
              method: 'DELETE',
              headers: { Authorization: 'Bearer pk_live_KEY' },
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.delete('https://mystocks.africa/api/v1/partner/webhooks/wh_abc123',
              headers={'Authorization': 'Bearer pk_live_KEY'})
      responses:
        '200':
          description: Webhook deleted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Webhook deleted.
        '404':
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: deleteWebhooks
  /webhooks/{id}/test:
    post:
      tags:
        - Webhooks
      summary: Test webhook
      description: Sends a synthetic `ping` event to the registered webhook URL. Use this to verify connectivity and HMAC signature verification before going live.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: wh_abc123
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/webhooks/wh_abc123/test \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/webhooks/wh_abc123/test', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY' },
            });
            const { delivered, statusCode } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/webhooks/wh_abc123/test',
              headers={'Authorization': 'Bearer pk_live_KEY'})
      responses:
        '200':
          description: Test delivery result.
          content:
            application/json:
              schema:
                type: object
                properties:
                  delivered:
                    type: boolean
                  statusCode:
                    type:
                      - integer
                      - 'null'
                    description: HTTP status your endpoint returned.
                  latencyMs:
                    type:
                      - integer
                      - 'null'
                  error:
                    type:
                      - string
                      - 'null'
                    description: Error message if delivery failed.
      operationId: createTest
  /webhooks/{id}/deliveries:
    get:
      tags:
        - Webhooks
      summary: Webhook deliveries
      description: Returns the delivery log for a webhook endpoint — each attempt, response code, and body.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: wh_abc123
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/webhooks/wh_abc123/deliveries?limit=20" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/webhooks/wh_abc123/deliveries',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { deliveries } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get(
              'https://mystocks.africa/api/v1/partner/webhooks/wh_abc123/deliveries',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Delivery log.
          content:
            application/json:
              schema:
                type: object
                properties:
                  deliveries:
                    type: array
                    items:
                      type: object
                      properties:
                        deliveryId:
                          type: string
                        event:
                          type: string
                        statusCode:
                          type:
                            - integer
                            - 'null'
                        success:
                          type: boolean
                        latencyMs:
                          type:
                            - integer
                            - 'null'
                        attempts:
                          type: integer
                        deliveredAt:
                          type: string
                          format: date-time
      operationId: listDeliveries
  /dividends/calendar:
    get:
      tags:
        - Dividends
      summary: Dividend calendar
      description: Returns upcoming and recent dividend events across all exchanges — ex-date, record date, pay date, and dividend per share.
      parameters:
        - name: exchange
          in: query
          description: Filter by exchange.
          schema:
            type: string
            example: NSE
        - name: from
          in: query
          schema:
            type: string
            format: date
        - name: to
          in: query
          schema:
            type: string
            format: date
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/dividends/calendar?exchange=NSE" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/dividends/calendar?exchange=NSE',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { events } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/dividends/calendar',
              params={'exchange': 'NSE'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Dividend calendar events.
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                        name:
                          type: string
                        exchange:
                          type: string
                        exDate:
                          type: string
                          format: date
                        recordDate:
                          type:
                            - string
                            - 'null'
                          format: date
                        payDate:
                          type:
                            - string
                            - 'null'
                          format: date
                        dividendPerShare:
                          type: number
                        currency:
                          type: string
      operationId: listCalendar
  /report/dividends:
    get:
      tags:
        - Dividends
      summary: Report — Dividends
      description: |
        Aggregated dividend distributions paid to sub-accounts for a given period.
        Returns totals, a per-symbol breakdown, and a per-sub-account breakdown.
        Add `?format=csv` to download a spreadsheet of the by-symbol totals.
      parameters:
        - name: from
          in: query
          schema:
            type: string
            format: date
          description: Start of period (ISO date, e.g. 2026-01-01). Defaults to 365 days ago.
        - name: to
          in: query
          schema:
            type: string
            format: date
          description: End of period (ISO date). Defaults to today.
        - name: format
          in: query
          schema:
            type: string
            enum: [csv]
          description: Pass `csv` to receive a downloadable spreadsheet instead of JSON.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/report/dividends?from=2026-01-01" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/report/dividends?from=2026-01-01',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { totalUsdReceived, bySymbol, bySubAccount } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/report/dividends',
              params={'from': '2026-01-01'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Dividend distribution report (JSON) or CSV file.
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalUsdReceived:
                    type: number
                    description: Total USD credited to sub-accounts as dividends in the period.
                  distributionCount:
                    type: integer
                    description: Number of individual distribution transactions.
                  currency:
                    type: string
                    example: USD
                  from:
                    type: string
                    format: date-time
                  to:
                    type: string
                    format: date-time
                  bySymbol:
                    type: array
                    description: Totals grouped by stock symbol.
                    items:
                      type: object
                      properties:
                        symbol:
                          type: string
                        totalUsd:
                          type: number
                        count:
                          type: integer
                  bySubAccount:
                    type: array
                    description: Totals grouped by sub-account.
                    items:
                      type: object
                      properties:
                        subAccountId:
                          type: string
                        externalId:
                          type:
                            - string
                            - 'null'
                        displayName:
                          type:
                            - string
                            - 'null'
                        totalUsd:
                          type: number
                        count:
                          type: integer
      operationId: getDividendReport
  /api-keys/data-key:
    get:
      tags:
        - Key Management
      summary: Get data API key
      description: Returns the current read-only data API key (if issued). Data keys can access market data endpoints but cannot execute trades or move funds.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/api-keys/data-key \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/api-keys/data-key', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { dataKey } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/api-keys/data-key',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Data API key info.
          content:
            application/json:
              schema:
                type: object
                properties:
                  dataKey:
                    type:
                      - string
                      - 'null'
                    description: Read-only key starting with dk_. null if not yet issued.
                  issuedAt:
                    type:
                      - string
                      - 'null'
                    format: date-time
        '404':
          description: No data key issued yet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: listDataKey
    post:
      tags:
        - Key Management
      summary: Issue data API key
      description: Issues a new read-only data API key. Only one data key can be active at a time — calling this again rotates the existing key.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/api-keys/data-key \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/api-keys/data-key', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY' },
            });
            const { dataKey } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/api-keys/data-key',
              headers={'Authorization': 'Bearer pk_live_KEY'})
            data_key = r.json()['dataKey']
      responses:
        '201':
          description: Data key issued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  dataKey:
                    type: string
                    example: dk_a1b2c3d4e5f6
                  issuedAt:
                    type: string
                    format: date-time
      operationId: createDataKey
    delete:
      tags:
        - Key Management
      summary: Revoke data API key
      description: Permanently revokes the data API key. Market data endpoints will return 401 until a new key is issued.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X DELETE https://mystocks.africa/api/v1/partner/api-keys/data-key \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/api-keys/data-key', {
              method: 'DELETE',
              headers: { Authorization: 'Bearer pk_live_KEY' },
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.delete('https://mystocks.africa/api/v1/partner/api-keys/data-key',
              headers={'Authorization': 'Bearer pk_live_KEY'})
      responses:
        '200':
          description: Data key revoked.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Data API key revoked.
      operationId: deleteDataKey
  /api-keys/rotate:
    post:
      tags:
        - Key Management
      summary: Rotate API key
      description: |
        Issues a new primary API key and **immediately invalidates** the current one.
        The new key is returned in the response — store it securely.
        There is a 60-second grace period during rotation where both keys are valid to allow a zero-downtime swap.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/api-keys/rotate \
              -H "Authorization: Bearer pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/api-keys/rotate', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY' },
            });
            const { newKey } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/api-keys/rotate',
              headers={'Authorization': 'Bearer pk_live_KEY'})
            new_key = r.json()['newKey']
      responses:
        '200':
          description: Key rotated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  newKey:
                    type: string
                    description: New primary API key. Store immediately — shown once.
                  expiresAt:
                    type: string
                    format: date-time
                    description: Old key expires at this time.
      operationId: createRotate
  /api-keys/revoke:
    post:
      tags:
        - Key Management
      summary: Revoke API key
      description: Permanently revokes a specific API key. Use this to invalidate a compromised key immediately. If you revoke your only active key, you must contact support to restore access.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RevokeKeyRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/api-keys/revoke \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"apiKey":"pk_live_a1b2c3..."}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/api-keys/revoke', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ apiKey: 'pk_live_a1b2c3...' }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/v1/partner/api-keys/revoke',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'apiKey': 'pk_live_a1b2c3...'})
      responses:
        '200':
          description: Key revoked.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: API key revoked.
        '404':
          description: Key not found or does not belong to this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: createRevoke
  /audit:
    get:
      tags:
        - Observability
      summary: Audit log
      description: |
        Returns a cursor-paginated list of API calls made with your live key, ordered newest first.
        Each entry includes the endpoint path, HTTP method, source IP, user-agent, and timestamp.
        Use `?limit=` and `?cursor=` for pagination (see the Pagination section).
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
        - name: cursor
          in: query
          description: Opaque cursor from the previous page's nextCursor field.
          schema:
            type: string
        - name: before
          in: query
          description: ISO timestamp — return only entries older than this value.
          schema:
            type: string
            format: date-time
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/audit?limit=50" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/audit?limit=50',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { entries, hasMore, nextCursor } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/audit',
              params={'limit': 50}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Paginated audit log entries.
          content:
            application/json:
              schema:
                type: object
                properties:
                  entries:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        endpoint:
                          type: string
                          example: /api/v1/partner/users/usr_abc/trade
                        method:
                          type: string
                          example: POST
                        ip:
                          type:
                            - string
                            - 'null'
                        userAgent:
                          type:
                            - string
                            - 'null'
                        timestamp:
                          type:
                            - string
                            - 'null'
                          format: date-time
                  count:
                    type: integer
                  hasMore:
                    type: boolean
                  nextCursor:
                    type:
                      - string
                      - 'null'
      operationId: listAudit
  /usage:
    get:
      tags:
        - Observability
      summary: Usage analytics
      description: |
        Returns daily API call totals for the last N days (default 30, max 90), aggregated from per-minute rate-limit windows.
        Also returns the current rate-limit window status so you can see remaining capacity in real time.
      parameters:
        - name: days
          in: query
          description: Number of days to look back. Default 30, max 90.
          schema:
            type: integer
            default: 30
            maximum: 90
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/usage?days=30" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/usage?days=30', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { totalCalls, daily, currentWindow, tier } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/usage',
              params={'days': 30}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Usage analytics and current rate-limit window.
          content:
            application/json:
              schema:
                type: object
                properties:
                  keyId:
                    type: string
                  tier:
                    type: string
                    example: starter
                  periodDays:
                    type: integer
                    example: 30
                  totalCalls:
                    type: integer
                    description: Total API calls in the requested period.
                  daily:
                    type: array
                    description: One entry per calendar day, ordered oldest first.
                    items:
                      type: object
                      properties:
                        date:
                          type: string
                          format: date
                          example: '2026-05-01'
                        calls:
                          type: integer
                  currentWindow:
                    type: object
                    description: Live rate-limit window status.
                    properties:
                      limit:
                        type: integer
                        description: Maximum requests allowed per minute for your tier.
                      remaining:
                        type: integer
                        description: Requests remaining in the current 1-minute window.
                      resetAt:
                        type: string
                        format: date-time
                        description: When the current window resets.
      operationId: listUsage
  /sla:
    get:
      tags:
        - SLA
      summary: SLA status
      description: |
        Returns real-time service health, active incidents, upcoming maintenance windows, and your SLA tier commitments.

        | Tier | Uptime SLO | Rate Limit |
        |------|-----------|------------|
        | Standard (starter) | 99.5% | 100 req/min |
        | Professional (growth) | 99.9% | 500 req/min |
        | Enterprise | 99.95% | 2,000 req/min |
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/sla \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/sla', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { overallStatus, services, activeIncidents, partner } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/sla',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Service health, incidents, and partner SLA commitments.
          content:
            application/json:
              schema:
                type: object
                properties:
                  overallStatus:
                    type: string
                    enum: [operational, partial_outage, major_outage]
                    description: Platform-wide health summary.
                  checkedAt:
                    type: string
                    format: date-time
                  services:
                    type: array
                    description: Per-service health breakdown.
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: trading-api
                        name:
                          type: string
                          example: Trading API
                        description:
                          type: string
                        status:
                          type: string
                          enum: [operational, degraded, outage]
                        uptime30d:
                          type:
                            - number
                            - 'null'
                        uptime90d:
                          type:
                            - number
                            - 'null'
                        currentMonthUptime:
                          type:
                            - number
                            - 'null'
                  activeIncidents:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        title:
                          type: string
                        severity:
                          type: string
                          enum: [P0, P1, P2, P3]
                        status:
                          type: string
                        affectedServices:
                          type: array
                          items:
                            type: string
                        startedAt:
                          type:
                            - string
                            - 'null'
                          format: date-time
                        resolvedAt:
                          type:
                            - string
                            - 'null'
                          format: date-time
                  pastIncidents:
                    type: array
                    description: Incidents resolved in the last 30 days.
                    items:
                      type: object
                  maintenance:
                    type: array
                    description: Upcoming scheduled maintenance windows.
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        title:
                          type: string
                        scheduledStart:
                          type:
                            - string
                            - 'null'
                          format: date-time
                        scheduledEnd:
                          type:
                            - string
                            - 'null'
                          format: date-time
                        status:
                          type: string
                  partner:
                    type: object
                    description: Your SLA tier and committed SLO targets.
                    properties:
                      slaTier:
                        type: string
                        example: Standard
                      slo:
                        type: object
                        properties:
                          uptimeSlo:
                            type: number
                            example: 99.5
                          responseP0:
                            type: string
                            example: 4h
                          responseP1:
                            type: string
                            example: 8h
                          responseP2:
                            type: string
                            example: 24h
                          responseP3:
                            type: string
                            example: 72h
                          credits:
                            type: number
                            description: SLA credit percentage on breach.
      operationId: listSla
  /settings:
    get:
      tags:
        - Settings
      summary: Get settings
      description: Returns the current partner configuration — logo URL, custom email config, and notification preferences.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/settings \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/settings', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const settings = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/settings',
              headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Partner settings.
          content:
            application/json:
              schema:
                type: object
                properties:
                  logoUrl:
                    type:
                      - string
                      - 'null'
                  emailConfig:
                    type:
                      - object
                      - 'null'
                    description: Custom SMTP config. null if using MyStocks default emails.
      operationId: listSettings
    patch:
      tags:
        - Settings
      summary: Update settings
      description: Updates partner configuration. All fields are optional — only provided fields are changed. Set `emailConfig` to `null` to revert to MyStocks default transactional emails.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SettingsUpdateRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X PATCH https://mystocks.africa/api/v1/partner/settings \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"logoUrl":"https://cdn.yourapp.com/logo.png"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/settings', {
              method: 'PATCH',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ logoUrl: 'https://cdn.yourapp.com/logo.png' }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.patch('https://mystocks.africa/api/v1/partner/settings',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'logoUrl': 'https://cdn.yourapp.com/logo.png'})
      responses:
        '200':
          description: Settings updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Settings updated.
      operationId: updateSettings
    post:
      tags:
        - Settings
      summary: Test SMTP
      description: Sends a test email via your custom SMTP configuration. Use this to verify that your email settings are correct before going live.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/settings \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"action":"test-smtp","recipient":"dev@acme.com"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/settings', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ action: 'test-smtp', recipient: 'dev@acme.com' }),
            });
            const { delivered } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/settings',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'action': 'test-smtp', 'recipient': 'dev@acme.com'})
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - action
                - recipient
              properties:
                action:
                  type: string
                  enum:
                    - test-smtp
                recipient:
                  type: string
                  format: email
                  description: Address to send the test email to.
      responses:
        '200':
          description: SMTP test result.
          content:
            application/json:
              schema:
                type: object
                properties:
                  delivered:
                    type: boolean
                  error:
                    type:
                      - string
                      - 'null'
      operationId: createSettings
  /sandbox/deposit:
    post:
      tags:
        - Sandbox Testing
      summary: Credit virtual funds
      description: |
        **Live key only** (`pk_live_`). Credits virtual USD to a sandbox sub-account for end-to-end webhook integration testing.
        No real money moves. Maximum $1,000,000 per call.
        Triggers a `deposit.confirmed` webhook if one is registered.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxDepositRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/sandbox/deposit \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"subAccountId":"usr_test_abc123","amount":10000,"note":"Integration test deposit"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            await fetch('https://mystocks.africa/api/v1/partner/sandbox/deposit', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ subAccountId: 'usr_test_abc123', amount: 10000, note: 'Integration test' }),
            });
        - lang: Python
          label: Python
          source: |
            import requests
            requests.post('https://mystocks.africa/api/v1/partner/sandbox/deposit',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'subAccountId': 'usr_test_abc123', 'amount': 10000, 'note': 'Integration test'})
      responses:
        '200':
          description: Virtual funds credited.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  walletBalance:
                    type: number
                  transactionId:
                    type: string
      operationId: createSandboxDeposit
  /sandbox/trade:
    post:
      tags:
        - Sandbox Testing
      summary: Submit test trade
      description: |
        **Live key only** (`pk_live_`). Places a test trade order without hitting a real exchange.

        **Cheat codes** for deterministic outcomes:
        | Quantity | Outcome |
        |----------|---------|
        | `100` | Auto-fills immediately (triggers `trade.settled`) |
        | `999` | Auto-rejects immediately (triggers `trade.rejected`) |
        | Any other | Enters PENDING state — use admin panel to settle |
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxTradeRequest'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            # Auto-fill trade (quantity=100)
            curl -X POST https://mystocks.africa/api/v1/partner/sandbox/trade \
              -H "Authorization: Bearer pk_live_KEY" \
              -H "Content-Type: application/json" \
              -d '{"symbol":"SCOM.KE","type":"BUY","quantity":100,"subAccountId":"usr_test_abc123"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            // quantity=100 auto-fills; quantity=999 auto-rejects
            const res = await fetch('https://mystocks.africa/api/v1/partner/sandbox/trade', {
              method: 'POST',
              headers: { Authorization: 'Bearer pk_live_KEY', 'Content-Type': 'application/json' },
              body: JSON.stringify({ symbol: 'SCOM.KE', type: 'BUY', quantity: 100, subAccountId: 'usr_test_abc123' }),
            });
            const { orderId, status } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            # quantity=100 = auto-fill, quantity=999 = auto-reject
            r = requests.post('https://mystocks.africa/api/v1/partner/sandbox/trade',
              headers={'Authorization': 'Bearer pk_live_KEY'},
              json={'symbol': 'SCOM.KE', 'type': 'BUY', 'quantity': 100, 'subAccountId': 'usr_test_abc123'})
      responses:
        '201':
          description: Test order placed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
      operationId: createSandboxTrade
  /sandbox/orders:
    get:
      tags:
        - Sandbox Testing
      summary: List test orders
      description: Returns all test orders created via the sandbox trade endpoint.
      parameters:
        - name: subAccountId
          in: query
          description: Filter by sub-account ID.
          schema:
            type: string
        - name: status
          in: query
          schema:
            type: string
            enum:
              - PENDING
              - PROCESSING
              - COMPLETED
              - FILLED
              - REJECTED
              - CANCELLED
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/sandbox/orders?status=COMPLETED" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/sandbox/orders?status=COMPLETED',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { orders } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/sandbox/orders',
              params={'status': 'COMPLETED'}, headers={'x-api-key': 'pk_live_KEY'})
      responses:
        '200':
          description: Test order list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  orders:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  total:
                    type: integer
      operationId: listSandboxOrders
  /market/movers:
    get:
      tags:
        - Market Data
      summary: Top gainers & losers
      description: |
        Returns the top price movers (gainers or losers) for a given exchange, sorted by percentage change.
        Results are paginated. Also available at `GET /market-data/movers` (identical handler).
      parameters:
        - name: exchange
          in: query
          required: true
          description: Exchange code (NSE, NGX, JSE, GSE, BRVM, LUSE, EGX, BSE, ZSE).
          schema:
            type: string
            example: NSE
        - name: direction
          in: query
          description: '"gainers" for top price increases, "losers" for top price decreases. Default: gainers.'
          schema:
            type: string
            enum:
              - gainers
              - losers
            default: gainers
        - name: limit
          in: query
          description: Number of results to return. Default 10, max 100.
          schema:
            type: integer
            default: 10
            maximum: 100
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/market/movers?exchange=NSE&direction=gainers&limit=10" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/market/movers?exchange=NSE&direction=gainers',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { data, meta } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/market/movers',
              params={'exchange': 'NSE', 'direction': 'gainers', 'limit': 10},
              headers={'x-api-key': 'pk_live_KEY'})
            movers = r.json()['data']
      responses:
        '200':
          description: Top movers for the requested exchange and direction.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/StockSummary'
                  meta:
                    allOf:
                      - $ref: '#/components/schemas/Meta'
                      - type: object
                        properties:
                          direction:
                            type: string
                            enum: [gainers, losers]
                          total_count:
                            type: integer
                            description: Total number of stocks with price change data on this exchange.
                          page:
                            type: integer
                          per_page:
                            type: integer
                          has_next:
                            type: boolean
        '400':
          description: Missing or invalid parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getMarketMovers
  /market-data/movers:
    get:
      tags:
        - Market Data
      summary: Top gainers & losers (alias)
      description: Alias for `GET /market/movers`. Identical handler and response shape. Prefer `/market/movers` as the canonical path.
      parameters:
        - name: exchange
          in: query
          required: true
          schema:
            type: string
            example: NSE
        - name: direction
          in: query
          schema:
            type: string
            enum: [gainers, losers]
            default: gainers
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
            maximum: 100
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        '200':
          description: See `GET /market/movers`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StockSummary'
      operationId: getMarketDataMovers
  /market-data/quotes:
    get:
      tags:
        - Market Data
      summary: Get quotes — single or batch (alias)
      description: Alias for `GET /market/quotes`. Identical handler and response shape. Prefer `/market/quotes` as the canonical path.
      parameters:
        - name: symbol
          in: query
          schema:
            type: string
            example: SCOM.KE
        - name: symbols
          in: query
          schema:
            type: string
            example: SCOM.KE,GLD.ZA
        - name: exchange
          in: query
          schema:
            type: string
            example: NSE
      responses:
        '200':
          description: See `GET /market/quotes`.
          content:
            application/json:
              schema:
                type: object
      operationId: getMarketDataQuotes
  /market-data/exchanges:
    get:
      tags:
        - Market Data
      summary: List supported exchanges
      description: |
        Returns metadata for all supported exchanges — exchange code, name, country, currency, timezone, local trading hours, and real-time market status (OPEN/CLOSED).
        No parameters required. Response reflects the current server time.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl https://mystocks.africa/api/v1/partner/market-data/exchanges \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/market-data/exchanges', {
              headers: { 'x-api-key': 'pk_live_KEY' },
            });
            const { data, meta } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/market-data/exchanges',
              headers={'x-api-key': 'pk_live_KEY'})
            exchanges = r.json()['data']
      responses:
        '200':
          description: List of supported exchanges with current market status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        code:
                          type: string
                          description: Exchange code.
                          example: NSE
                        name:
                          type: string
                          description: Full exchange name.
                          example: Nairobi Securities Exchange
                        country:
                          type: string
                          description: ISO 3166-1 alpha-2 country code.
                          example: KE
                        currency:
                          type: string
                          description: Local trading currency.
                          example: KES
                        timezone:
                          type: string
                          description: IANA timezone identifier.
                          example: Africa/Nairobi
                        trading_hours:
                          type: object
                          properties:
                            open:
                              type: string
                              description: Local session open time (HH:MM).
                              example: '09:00'
                            close:
                              type: string
                              description: Local session close time (HH:MM).
                              example: '15:00'
                        market_status:
                          type: string
                          enum: [OPEN, CLOSED]
                          description: Current market status based on server time and exchange schedule.
                  meta:
                    $ref: '#/components/schemas/Meta'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getMarketDataExchanges
  /market-data/ohlcv:
    get:
      tags:
        - Market Data
      summary: OHLCV price history (end-of-day candles)
      description: |
        Returns end-of-day OHLCV (Open, High, Low, Close, Volume) candles for a stock over a date range.

        **Current limitations:**
        - Only `interval=1d` (end-of-day) is supported. Intraday intervals are not yet available.
        - `open`, `high`, and `low` fields are `null` until intraday data is ingested — only `close` and `volume` are populated.
        - Maximum date range per request: 365 days. Split larger requests.

        Candles are sorted oldest-first and filtered to the `[from, to]` window inclusive.
      parameters:
        - name: symbol
          in: query
          required: true
          description: Exchange-qualified ticker (e.g. `SCOM.KE`) or bare ticker.
          schema:
            type: string
            example: SCOM.KE
        - name: exchange
          in: query
          required: true
          description: Exchange code (NSE, NGX, JSE, GSE, BRVM, LUSE, EGX, BSE, ZSE).
          schema:
            type: string
            example: NSE
        - name: interval
          in: query
          description: Candle interval. Only `1d` is currently supported.
          schema:
            type: string
            enum:
              - 1d
            default: 1d
        - name: from
          in: query
          description: Start date inclusive (YYYY-MM-DD). Defaults to 30 days ago.
          schema:
            type: string
            format: date
            example: '2026-01-01'
        - name: to
          in: query
          description: End date inclusive (YYYY-MM-DD). Defaults to today.
          schema:
            type: string
            format: date
            example: '2026-06-04'
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl "https://mystocks.africa/api/v1/partner/market-data/ohlcv?symbol=SCOM.KE&exchange=NSE&from=2026-01-01&to=2026-06-04" \
              -H "x-api-key: pk_live_KEY"
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch(
              'https://mystocks.africa/api/v1/partner/market-data/ohlcv?symbol=SCOM.KE&exchange=NSE&from=2026-01-01&to=2026-06-04',
              { headers: { 'x-api-key': 'pk_live_KEY' } }
            );
            const { data, meta } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.get('https://mystocks.africa/api/v1/partner/market-data/ohlcv',
              params={'symbol': 'SCOM.KE', 'exchange': 'NSE', 'from': '2026-01-01', 'to': '2026-06-04'},
              headers={'x-api-key': 'pk_live_KEY'})
            candles = r.json()['data']
      responses:
        '200':
          description: OHLCV candles for the requested symbol and date range.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    description: Candles sorted oldest-first. One entry per trading day with available price data.
                    items:
                      type: object
                      properties:
                        timestamp:
                          type: string
                          format: date-time
                          description: ISO 8601 start-of-day UTC timestamp for the candle period.
                          example: '2026-01-02T00:00:00.000Z'
                        open:
                          type:
                            - number
                            - 'null'
                          description: Session open price (local currency). null until intraday data is available.
                        high:
                          type:
                            - number
                            - 'null'
                          description: Session high price (local currency). null until intraday data is available.
                        low:
                          type:
                            - number
                            - 'null'
                          description: Session low price (local currency). null until intraday data is available.
                        close:
                          type:
                            - number
                            - 'null'
                          description: End-of-day closing price in local currency.
                          example: 16.5
                        volume:
                          type: integer
                          description: Trading volume for the session. 0 if not recorded.
                          example: 4200000
                  meta:
                    allOf:
                      - $ref: '#/components/schemas/Meta'
                      - type: object
                        properties:
                          interval:
                            type: string
                            example: 1d
                          from:
                            type: string
                            format: date
                            example: '2026-01-01'
                          to:
                            type: string
                            format: date
                            example: '2026-06-04'
                          count:
                            type: integer
                            description: Number of candles returned.
                            example: 107
                          ohlc_available:
                            type: boolean
                            description: false until intraday OHLC data is ingested. When false, open/high/low are null.
                            example: false
        '400':
          description: Validation error (missing params, invalid date format, range > 365 days).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Symbol not found on the specified exchange.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      operationId: getOhlcv
  /apply:
    post:
      tags:
        - Registration
      summary: Submit partner application
      description: |
        Submits a new B2B partner application to the MyStocks team. No authentication required.

        On success, a sandbox API key (`sk_sandbox_…`) is automatically provisioned so you can start building immediately — it is returned in the response. Your live production key (`pk_live_…`) is issued after the team reviews your application (typically 1–2 business days).

        Calling again with the same email returns an idempotent message if an application is already pending or approved.
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl -X POST https://mystocks.africa/api/v1/partner/apply \
              -H "Content-Type: application/json" \
              -d '{"businessName":"Acme Fintech","email":"dev@acme.com","useCase":"Embed stock trading into our neobank app","website":"https://acme.com"}'
        - lang: JavaScript
          label: JavaScript
          source: |
            const res = await fetch('https://mystocks.africa/api/v1/partner/apply', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                businessName: 'Acme Fintech',
                email: 'dev@acme.com',
                useCase: 'Embed stock trading into our neobank app',
                website: 'https://acme.com',
              }),
            });
            const { sandboxKey } = await res.json();
        - lang: Python
          label: Python
          source: |
            import requests
            r = requests.post('https://mystocks.africa/api/v1/partner/apply',
              json={
                'businessName': 'Acme Fintech',
                'email': 'dev@acme.com',
                'useCase': 'Embed stock trading into our neobank app',
              })
            sandbox_key = r.json().get('sandboxKey')
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - businessName
                - email
                - useCase
              properties:
                businessName:
                  type: string
                  minLength: 2
                  description: Your company or project name.
                  example: Acme Fintech
                email:
                  type: string
                  format: email
                  description: Business contact email. Used to prevent duplicate applications.
                  example: dev@acme.com
                website:
                  type: string
                  format: uri
                  description: Optional company website URL.
                  example: https://acme.com
                useCase:
                  type: string
                  description: Brief description of your integration use case.
                  example: Embed stock trading into our neobank app
                description:
                  type: string
                  description: Optional additional context about your product or team.
      responses:
        '201':
          description: Application received. Sandbox key provisioned.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Application received. Our team will review it and contact you within 1–2 business days.
                  sandboxKey:
                    type: string
                    description: Pre-provisioned sandbox API key. Use it immediately at the sandbox base URL.
                    example: sk_sandbox_a1b2c3d4e5f6
                  note:
                    type: string
                    description: Instructions for using the sandbox key.
        '400':
          description: Missing or invalid fields (businessName, email, or useCase).
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
      operationId: submitPartnerApplication
