openapi: "3.0.3"

info:
  version: "2.0.0"
  title: NODE40 Balance API
  description: |
    API for interacting with a NODE40 Balance account.

    #### Authentication
    All requests require an `x-api-key` header, a `signature` query parameter (HMAC-SHA256),
    and a `timestamp` query parameter.
    See [Authentication](html/authentication.html) for details.

    #### Rate Limits
    Requests are rate limited per minute and per day.
    Current limits are returned in `X-RateLimit-*` response headers.
    If you need higher limits, contact your NODE40 account manager.

    #### Versioning
    V2 endpoints are current. V1 endpoints are deprecated and will be removed after 2026-06-30.

    #### Links
    [Download YAML](openapi.yaml) · [MCP Server on npm](https://www.npmjs.com/package/@node40/mcp-server)

    ---
  termsOfService: https://www.node40.com/tos/
  contact:
    name: Your NODE40 account manager

servers:
  - url: https://balance-api.node40.com
    description: Live production server

security:
  - ApiKeyAuth: []

tags:
  - name: Accounts
    description: Account and organization information
  - name: Ledgers
    description: Ledger listing for a profile
  - name: Transactions
    description: Paginated transaction timeline for a profile
  - name: V1 (Deprecated)
    description: >
      V1 endpoints are deprecated. Migrate to V2 equivalents before 2026-06-30.

paths:
  # ─── V2 ───────────────────────────────────────────────────

  /v2/accounts:
    get:
      tags: [Accounts]
      operationId: getAccounts
      summary: Get account details
      description: Returns organization and profile information for the API key.
      parameters:
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/AccountData"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/ledgers:
    get:
      tags: [Ledgers]
      operationId: getLedgers
      summary: List ledgers for a profile
      description: Returns all ledgers belonging to the specified profile.
      parameters:
        - $ref: "#/components/parameters/ProfileHeader"
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/V2LedgerItem"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/transactions:
    get:
      tags: [Transactions]
      operationId: getTransactions
      summary: List transactions for a profile
      description: >
        Returns a paginated list of transactions sorted by date (most recent first).
        Each page contains up to 50 items.
        Use `t`, `cf`, `p`, `h`, `sort`, and `order` for advanced filtering and sorting.
        When `h` is provided, all other filters are ignored.
      parameters:
        - $ref: "#/components/parameters/ProfileHeader"
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
        - $ref: "#/components/parameters/PageParam"
        - $ref: "#/components/parameters/StartDateParam"
        - $ref: "#/components/parameters/EndDateParam"
        - $ref: "#/components/parameters/LastModifiedDateParam"
        - name: t
          in: query
          required: false
          description: >
            Filter by token symbol(s). Repeat the parameter for multiple values (e.g. `?t=ETH&t=BTC`).
            Case-sensitive.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
        - name: cf
          in: query
          required: false
          description: >
            Filter by transaction category. Repeat for multiple values. Multiple values are OR'd together.
            Note: `SENT`+`RECEIVED` together cancel out (returns all).
            `EXTERNAL_DEPOSITS`/`EXTERNAL_WITHDRAWALS` override `SENT`/`RECEIVED`.
            Valid values: `EXTERNAL_DEPOSITS`, `EXTERNAL_WITHDRAWALS`, `MINED_MINTED`, `NEEDS_REVIEW`,
            `NFT`, `NFT_721`, `NFT_1155`, `NFT_RECEIVED_ZERO`, `NFT_SENT_ZERO`, `PRICE_NOT_FOUND`,
            `RECEIVED`, `SENT`, `MANUAL_TRANSACTIONS`, `MANUAL_OVERRIDES`, `MANUALLY_LINKED`,
            `VALUATION_ZERO`, `SMART_OVERRIDES`.
          schema:
            type: array
            items:
              type: string
              enum:
                - EXTERNAL_DEPOSITS
                - EXTERNAL_WITHDRAWALS
                - MINED_MINTED
                - NEEDS_REVIEW
                - NFT
                - NFT_721
                - NFT_1155
                - NFT_RECEIVED_ZERO
                - NFT_SENT_ZERO
                - PRICE_NOT_FOUND
                - RECEIVED
                - SENT
                - MANUAL_TRANSACTIONS
                - MANUAL_OVERRIDES
                - MANUALLY_LINKED
                - VALUATION_ZERO
                - SMART_OVERRIDES
          style: form
          explode: true
        - name: p
          in: query
          required: false
          description: >
            Filter by DeFi protocol name(s). Repeat for multiple values.
            Call `GET /v2/transactions/metadata` to discover valid protocol names for your portfolio.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
        - name: h
          in: query
          required: false
          description: >
            Search by transaction hash or ID. When provided, all other filters (`t`, `cf`, `p`,
            `startDate`, `endDate`, `sort`, `order`) are ignored and must not be present (returns 400
            if combined).
          schema:
            type: string
        - name: sort
          in: query
          required: false
          description: >
            Sort field. Default: `date`. Valid values: `date`, `value`, `fee`.
          schema:
            type: string
            enum: [date, value, fee]
            default: date
        - name: order
          in: query
          required: false
          description: >
            Sort direction. Default: `desc` (most recent first).
          schema:
            type: string
            enum: [asc, desc]
            default: desc
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/V2TransactionItem"
                      page:
                        $ref: "#/components/schemas/Page"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/transactions/date-bounds:
    get:
      tags: [Transactions]
      operationId: getTransactionsDateBounds
      summary: Get portfolio transaction date range
      description: >
        Returns the earliest and latest transaction dates in the portfolio.
        Useful for discovering the valid date range before constructing date-filtered queries.
      parameters:
        - $ref: "#/components/parameters/ProfileHeader"
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          startDate:
                            type: string
                            format: date-time
                            description: ISO 8601 date-time of the earliest transaction.
                            example: "2017-03-15T12:00:00.000Z"
                          endDate:
                            type: string
                            format: date-time
                            description: ISO 8601 date-time of the latest transaction.
                            example: "2024-12-31T23:59:59.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v2/transactions/metadata:
    get:
      tags: [Transactions]
      operationId: getTransactionsMetadata
      summary: Get available DeFi protocols
      description: >
        Returns available DeFi protocol names (and descriptions) for the portfolio within an optional
        date range. Use this to discover valid values for the `p` filter on `GET /v2/transactions`.
      parameters:
        - $ref: "#/components/parameters/ProfileHeader"
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
        - $ref: "#/components/parameters/StartDateParam"
        - $ref: "#/components/parameters/EndDateParam"
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          protocols:
                            type: object
                            additionalProperties:
                              type: string
                            description: Map of protocol name to description.
                            example:
                              Uniswap: "Decentralized exchange protocol"
                              Aave: "Lending protocol"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  # ─── V1 (Deprecated) ─────────────────────────────────────

  /v1/account:
    get:
      tags: [V1 (Deprecated)]
      deprecated: true
      operationId: getAccountV1
      summary: Get account details (deprecated)
      description: >
        **Deprecated** — use `/v2/accounts` instead.
        Returns raw account data from the exchange service.
      parameters:
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
            Deprecation:
              $ref: "#/components/headers/Deprecation"
            Sunset:
              $ref: "#/components/headers/Sunset"
            Link:
              $ref: "#/components/headers/Link"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        description: Raw account data from the exchange service.
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/ledgers/list:
    get:
      tags: [V1 (Deprecated)]
      deprecated: true
      operationId: listLedgersV1
      summary: List ledgers for a profile (deprecated)
      description: >
        **Deprecated** — use `/v2/ledgers` instead.
        Identical to V2 except `source.nickName` uses camelCase.
      parameters:
        - $ref: "#/components/parameters/ProfileHeader"
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
            Deprecation:
              $ref: "#/components/headers/Deprecation"
            Sunset:
              $ref: "#/components/headers/Sunset"
            Link:
              $ref: "#/components/headers/Link"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/V1LedgerItem"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/timeline:
    get:
      tags: [V1 (Deprecated)]
      deprecated: true
      operationId: getTimelineV1
      summary: List transactions for a profile (deprecated)
      description: >
        **Deprecated** — use `/v2/transactions` instead.
        Identical to V2 except `source.nickName` uses camelCase.
      parameters:
        - $ref: "#/components/parameters/ProfileHeader"
        - $ref: "#/components/parameters/SignatureParam"
        - $ref: "#/components/parameters/TimestampParam"
        - $ref: "#/components/parameters/PageParam"
        - $ref: "#/components/parameters/StartDateParam"
        - $ref: "#/components/parameters/EndDateParam"
        - $ref: "#/components/parameters/LastModifiedDateParam"
      responses:
        "200":
          description: Success
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Limit-Day:
              $ref: "#/components/headers/X-RateLimit-Limit-Day"
            X-RateLimit-Remaining-Day:
              $ref: "#/components/headers/X-RateLimit-Remaining-Day"
            Deprecation:
              $ref: "#/components/headers/Deprecation"
            Sunset:
              $ref: "#/components/headers/Sunset"
            Link:
              $ref: "#/components/headers/Link"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/V1TransactionItem"
                      page:
                        $ref: "#/components/schemas/Page"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/AccessDenied"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

  parameters:
    ProfileHeader:
      name: x-node40-profile
      in: header
      required: true
      description: GUID of the profile to query.
      schema:
        type: string
        format: uuid
      example: a8be9383-1f4b-4d30-a2da-44afac927eec

    SignatureParam:
      name: signature
      in: query
      required: true
      description: HMAC-SHA256 request signature. See Authentication documentation.
      schema:
        type: string

    TimestampParam:
      name: timestamp
      in: query
      required: true
      description: Unix timestamp in milliseconds when the request was created.
      schema:
        type: string

    PageParam:
      name: page
      in: query
      required: false
      description: Page number (1-based). Each page returns up to 50 items.
      schema:
        type: integer
        minimum: 1
        default: 1

    StartDateParam:
      name: startDate
      in: query
      required: false
      description: ISO 8601 date-time. Returns transactions on or after this date.
      schema:
        type: string
        format: date-time
      example: "2024-01-01T00:00:00.000Z"

    EndDateParam:
      name: endDate
      in: query
      required: false
      description: ISO 8601 date-time. Returns transactions on or before this date. Defaults to current date/time.
      schema:
        type: string
        format: date-time

    LastModifiedDateParam:
      name: lastModifiedDate
      in: query
      required: false
      description: ISO 8601 date-time. Returns transactions modified or deleted since this date.
      schema:
        type: string
        format: date-time

  headers:
    X-RateLimit-Limit:
      description: Per-minute request limit.
      schema:
        type: integer

    X-RateLimit-Remaining:
      description: Remaining requests in the current minute.
      schema:
        type: integer

    X-RateLimit-Limit-Day:
      description: Daily request limit.
      schema:
        type: integer

    X-RateLimit-Remaining-Day:
      description: Remaining requests for the current day.
      schema:
        type: integer

    Deprecation:
      description: Indicates this endpoint is deprecated.
      schema:
        type: string
        example: "true"

    Sunset:
      description: Date after which this endpoint may be removed.
      schema:
        type: string
        example: "2026-06-30"

    Link:
      description: Link to the successor endpoint.
      schema:
        type: string
        example: '</v2/transactions>; rel="successor-version"'

  responses:
    BadRequest:
      description: Invalid request parameter.
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Limit-Day:
          $ref: "#/components/headers/X-RateLimit-Limit-Day"
        X-RateLimit-Remaining-Day:
          $ref: "#/components/headers/X-RateLimit-Remaining-Day"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            status: error
            type: InvalidRequestParameter
            message: Invalid page number.

    AccessDenied:
      description: Access denied — invalid API key, signature, or insufficient permissions.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/GatewayErrorResponse"
          example:
            error: InvalidSignature
            message: The signature provided did not match the expected request signature.

    RateLimited:
      description: Rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/GatewayErrorResponse"
          example:
            error: RateLimitExceeded
            message: Rate limit exceeded. Try again later.

    InternalError:
      description: Internal server error.
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Limit-Day:
          $ref: "#/components/headers/X-RateLimit-Limit-Day"
        X-RateLimit-Remaining-Day:
          $ref: "#/components/headers/X-RateLimit-Remaining-Day"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            status: error
            type: InternalServerError
            message: An internal server error occurred. Our team has been notified.

  schemas:
    # ─── Envelope ─────────────────────────────────────────

    SuccessEnvelope:
      type: object
      properties:
        status:
          type: string
          enum: [success]
      required:
        - status

    ErrorResponse:
      type: object
      properties:
        status:
          type: string
          enum: [error]
        type:
          type: string
          description: Error type identifier.
        message:
          type: string
          description: Human-readable error message.

    GatewayErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Short error descriptor.
        message:
          type: string
          description: Detailed error message.

    # ─── Pagination ───────────────────────────────────────

    Page:
      type: object
      properties:
        items:
          type: integer
          description: Number of items returned in this page.
          example: 50
        availableItems:
          type: integer
          description: Total number of items matching the query.
          example: 127
        nextPage:
          type: integer
          description: Next page number. Absent when on the last page.
          example: 2
      required:
        - items
        - availableItems

    # ─── Source ────────────────────────────────────────────

    V2Source:
      type: object
      properties:
        name:
          type: string
          description: Name of the exchange or data source.
          example: KRAKEN
        nickname:
          type: string
          description: User-assigned name for this source connection.
        sourceGuid:
          type: string
          format: uuid
          description: Unique identifier for the source connection.

    V1Source:
      type: object
      description: Same as V2Source but uses camelCase `nickName` instead of lowercase `nickname`.
      properties:
        name:
          type: string
          description: Name of the exchange or data source.
          example: KRAKEN
        nickName:
          type: string
          description: User-assigned name for this source connection.
        sourceGuid:
          type: string
          format: uuid
          description: Unique identifier for the source connection.

    # ─── Account ──────────────────────────────────────────

    AccountData:
      type: object
      properties:
        organization:
          $ref: "#/components/schemas/Organization"
        profiles:
          type: array
          items:
            $ref: "#/components/schemas/Profile"
      example:
        organization:
          guid: "210caed1-59da-4cf8-8300-e8095036e215"
          name: "Sample Company"
          profileLimit: 20
          createDate: "2019-04-02T18:47:07.000Z"
        profiles:
          - guid: "abb42dd7-cc9a-42c4-968a-040aa30903d2"
            displayName: "Client profile 1"
            createDate: "2022-04-27T13:04:23.000Z"
          - guid: "fa1d0f6a-d135-46dc-8a94-57bd65d07633"
            displayName: "Client profile 2"
            createDate: "2021-06-07T19:52:32.000Z"
            profileOwner:
              username: "sample.user@node40.com"
              guid: "f29d6118-166e-4bb5-b385-9da8d0096802"
              firstName: "Alex"
              lastName: "Katz"

    Organization:
      type: object
      properties:
        guid:
          type: string
          format: uuid
        name:
          type: string
        profileLimit:
          type: integer
          description: Maximum number of active profiles allowed.
        createDate:
          type: string
          format: date-time

    Profile:
      type: object
      properties:
        guid:
          type: string
          format: uuid
        displayName:
          type: string
        createDate:
          type: string
          format: date-time
        profileOwner:
          $ref: "#/components/schemas/ProfileOwner"

    ProfileOwner:
      type: object
      properties:
        username:
          type: string
        guid:
          type: string
          format: uuid
        firstName:
          type: string
        lastName:
          type: string

    # ─── Ledger ───────────────────────────────────────────

    LedgerItemBase:
      type: object
      properties:
        ledgerId:
          type: string
          format: uuid
          description: Unique identifier of the ledger.
        asset:
          $ref: "#/components/schemas/LedgerAsset"
        sourceAccount:
          type: string
          description: Descriptor for the data source and account ID.
        created:
          type: string
          format: date-time
          description: When the ledger was created.
        methodology:
          type: string
          description: Accounting methodology applied to the ledger.
          example: FIFO
        entries:
          type: integer
          description: Number of entries in the ledger.
        synced:
          type: string
          format: date-time
          description: When the ledger was last updated with new entries.
        name:
          type: string
          description: User-assigned ledger name.
        balance:
          type: number
          format: double
        favorite:
          type: boolean
          description: Whether the ledger is in the user's favorites.
        price:
          $ref: "#/components/schemas/FiatPrice"

    V2LedgerItem:
      allOf:
        - $ref: "#/components/schemas/LedgerItemBase"
        - type: object
          properties:
            source:
              $ref: "#/components/schemas/V2Source"
      example:
        ledgerId: a8be9383-1f4b-4d30-a2da-44afac927eec
        asset:
          symbol: ETH
          name: Ethereum
        sourceAccount: "ethereum shadow exchange-cdp-31155-ETH"
        created: "2023-11-14T20:28:49.000Z"
        methodology: FIFO
        entries: 2
        synced: "2023-11-14T20:28:50.000Z"
        name: "ETH CDP-31155"
        balance: 10553
        favorite: false
        price:
          value: 2245.33
          fiat: USD
        source:
          name: KRAKEN
          nickname: TradingSubAccount
          sourceGuid: 8bf29368-fd1a-4d0e-badf-5e2807d26f93

    V1LedgerItem:
      allOf:
        - $ref: "#/components/schemas/LedgerItemBase"
        - type: object
          properties:
            source:
              $ref: "#/components/schemas/V1Source"

    LedgerAsset:
      type: object
      properties:
        symbol:
          type: string
          example: ETH
        name:
          type: string
          example: Ethereum
        contractAddress:
          type: string
          description: Contract address, present when applicable.

    FiatPrice:
      type: object
      properties:
        value:
          type: number
          format: double
        fiat:
          type: string
          description: Fiat currency the value is denominated in.
          example: USD

    # ─── Transaction ──────────────────────────────────────

    TransactionItemBase:
      type: object
      properties:
        asset:
          $ref: "#/components/schemas/TransactionAsset"
        customFields:
          type: object
          description: User-defined custom fields.
          properties:
            custom1:
              type: string
            custom2:
              type: string
            custom3:
              type: string
        date:
          type: string
          format: date-time
          description: When the transaction occurred.
          example: "2024-10-27T22:12:47.000Z"
        defi:
          $ref: "#/components/schemas/DefiData"
        deleted:
          type: object
          description: Present only if the transaction was deleted.
          properties:
            deletedBy:
              type: string
            deletedDate:
              type: string
              format: date-time
        description:
          type: string
          description: Description populated from the import source.
        direction:
          type: string
          enum: [IN, OUT]
          description: IN for incoming (debit), OUT for outgoing (credit).
        fromAddress:
          type: string
          description: Sender address. Present for IN transactions only.
        gains:
          type: object
          description: Gain/loss values for dispositions.
          properties:
            longTermFee:
              type: number
              format: double
              description: Long-term gain from the fee portion of a disposition.
            longTerm:
              type: number
              format: double
              description: Long-term gain from a disposition.
            shortTermFee:
              type: number
              format: double
              description: Short-term gain from the fee portion of a disposition.
            shortTerm:
              type: number
              format: double
              description: Short-term gain from a disposition.
        generalCategory:
          type: string
        groupId:
          type: string
          format: uuid
          description: Groups related transactions (e.g. transfer pairs).
        ledgerGuid:
          type: string
          format: uuid
          description: Ledger this transaction belongs to.
        manualJournalEntry:
          type: boolean
          description: True if this is a manual journal entry. Only present when true.
        mined:
          type: boolean
          description: True if this transaction resulted from mining or minting.
        network:
          type: object
          properties:
            blockHeight:
              type: number
              description: Block number on the blockchain.
              example: 18941927
            hash:
              type: string
              description: Transaction hash on the blockchain.
            status:
              type: string
              description: Network confirmation status.
              example: CONFIRMED
        notes:
          type: string
          description: User-provided notes.
        price:
          type: number
          format: double
          description: >
            Price per token at the time of the transaction.
            Uses cost basis for IN transactions and market value for OUT transactions.
        qty:
          type: object
          properties:
            transaction:
              type: number
              format: double
              description: Quantity of the asset in this transaction.
            fee:
              type: number
              format: double
              description: Fee amount in the asset's unit.
        sequence:
          type: number
          description: Internal sequence number.
        subType:
          type: string
          description: Further classification of the transaction within its category (e.g. BUY, SELL, SENT, RECEIVED).
          example: SENT
        tags:
          type: array
          items:
            type: string
          description: User-provided tags.
        toAddress:
          type: string
          description: Destination address.
        transactionId:
          type: string
          description: Transaction ID from the source (exchange ID or blockchain hash).
        value:
          type: object
          description: Fiat value of the transaction.
          properties:
            transaction:
              type: number
              format: double
              description: Fiat value of the transaction amount.
            fee:
              type: number
              format: double
              description: Fiat value of the fee.

    V2TransactionItem:
      allOf:
        - $ref: "#/components/schemas/TransactionItemBase"
        - type: object
          properties:
            source:
              $ref: "#/components/schemas/V2Source"

    V1TransactionItem:
      allOf:
        - $ref: "#/components/schemas/TransactionItemBase"
        - type: object
          properties:
            source:
              $ref: "#/components/schemas/V1Source"

    TransactionAsset:
      type: object
      properties:
        address:
          type: string
          description: Contract address of the ledger.
        contract:
          type: string
          description: Contract name.
        tokenSymbol:
          type: string
          example: ETH
        tokenName:
          type: string
          example: Ethereum
        tokenStandard:
          type: string
          example: ERC-20
        tokenId:
          type: string
          description: Token ID for NFTs.
        nft:
          type: boolean
          description: Whether the asset is an NFT.
        nftContractAddress:
          type: string
          description: NFT contract address.

    DefiData:
      type: object
      properties:
        category:
          type: string
        contract:
          type: string
        description:
          type: string
        operation:
          type: string
        operationCategory:
          type: string
        operationId:
          type: string
        protocol:
          type: string
