openapi: 3.1.0
info:
  title: Agent External Wallet Transfer Audit API
  version: 0.1.0-draft
  license:
    name: Draft internal reference
    identifier: LicenseRef-Draft
  description: |
    Standalone one-endpoint draft for Agent-level wallet transfer auditing.

    The schema is synchronized to the QA-passed E1450 core
    `GetExternalTransferAudit` contract. The HTTP route remains draft until
    Gateway implementation is available. Gateway authentication, IP allowlist,
    and rate policy remain the existing external API policy.
  x-contract-status: core-qa-synced
servers:
  - url: https://api.example.invalid
    description: Placeholder only; replace only through an approved environment configuration.
x-contract-status: core-qa-synced
x-core-tested-sha: 3df23b2e7187fb1d5a61ce2e8c17bcad4ab3cc9a
tags:
  - name: Transfer Audit
    description: Read-only transfer audit records for an Agent.
paths:
  /v2/external/wallet/transfers:
    get:
      tags:
        - Transfer Audit
      operationId: listExternalWalletTransfers
      summary: List wallet transfer audit records
      description: |
        Returns read-only wallet transfer audit rows for the authenticated Agent.
        This is the first and only operation in this standalone draft.

        The core service forces `type_name=transfer`. Direction `in` maps to
        `type_sub_name=up`, `out` maps to `type_sub_name=down`, and `all` leaves
        the subtype unfiltered while remaining transfer-only.

        `start_time` and `end_time` are Unix seconds. The start is inclusive,
        the end is exclusive, and the requested range must be no more than
        86,400 seconds. A continuation cursor is the pair
        `cursor_transaction_time_ns` and `cursor_id`; both fields must be
        supplied together. Results are ordered ascending by
        `(transaction_time_ns, id)` and `has_more` indicates that another page
        exists.

        Access requires the `apikey` header and the caller must be within the
        configured IP allowlist. The existing policy is 1,000 requests per
        minute per Agent and route, with a burst allowance of 100 and a maximum
        of 100 in-flight requests.
      x-contract-status: core-qa-synced
      x-route-status: draft
      x-core-tested-sha: 3df23b2e7187fb1d5a61ce2e8c17bcad4ab3cc9a
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
        - $ref: '#/components/parameters/Direction'
        - $ref: '#/components/parameters/StartTime'
        - $ref: '#/components/parameters/EndTime'
        - $ref: '#/components/parameters/CursorTransactionTimeNs'
        - $ref: '#/components/parameters/CursorId'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Transfer audit rows returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransferAuditResponse'
              example:
                rows:
                  - id: transfer-row-1
                    transaction_time_ns: 1787097600000000000
                    ref_code: audit-ref-1
                    username: audit-user
                    type_name: transfer
                    type_sub_name: up
                    amount: 25.5
                    amount_before: 100
                    amount_after: 125.5
                    asset_name: USD
                    asset_unit: USD
                    note: audit example
                next_cursor:
                  transaction_time_ns: 1787097600000000000
                  id: transfer-row-1
                has_more: false
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: apikey
      description: Agent API key. Never place a real key in this draft, examples, or browser persistence.
  parameters:
    Username:
      name: username
      in: query
      required: true
      description: Username of the target Member whose transfer audit rows are requested.
      schema:
        type: string
        minLength: 1
      example: audit-user
    Direction:
      name: direction
      in: query
      required: false
      description: |-
        Closed external direction enum. `in` maps to core `EXTERNAL_TRANSFER_DIRECTION_IN`
        and `type_sub_name=up`; `out` maps to `EXTERNAL_TRANSFER_DIRECTION_OUT`
        and `type_sub_name=down`; `all` maps to `EXTERNAL_TRANSFER_DIRECTION_ALL`
        and applies no subtype predicate. Every value remains `type_name=transfer`.
      schema:
        type: string
        enum:
          - in
          - out
          - all
        default: all
      example: in
    StartTime:
      name: start_time
      in: query
      required: true
      description: Inclusive audit-window start as Unix seconds. Must be greater than zero.
      schema:
        type: integer
        format: int64
        minimum: 1
      example: 1787011200
    EndTime:
      name: end_time
      in: query
      required: true
      description: Exclusive audit-window end as Unix seconds. Must be greater than start_time and no more than 86,400 seconds later.
      schema:
        type: integer
        format: int64
        minimum: 1
      example: 1787097600
    CursorTransactionTimeNs:
      name: cursor_transaction_time_ns
      in: query
      required: false
      description: Exclusive cursor timestamp as Unix nanoseconds. Supply together with cursor_id, or omit both.
      schema:
        type: integer
        format: int64
        minimum: 1
      example: 1787097600000000000
    CursorId:
      name: cursor_id
      in: query
      required: false
      description: Exclusive cursor row ID. Supply together with cursor_transaction_time_ns, or omit both.
      schema:
        type: string
        minLength: 1
      example: transfer-row-1
    Limit:
      name: limit
      in: query
      required: false
      description: Maximum number of rows returned. Zero applies the service default of 100; valid explicit values are 1 through 1,000.
      schema:
        type: integer
        format: int64
        minimum: 0
        maximum: 1000
        default: 100
      example: 100
  schemas:
    TransferAuditResponse:
      type: object
      required:
        - rows
        - next_cursor
        - has_more
      properties:
        rows:
          type: array
          description: Transfer rows returned in stable ascending `(transaction_time_ns, id)` order.
          items:
            $ref: '#/components/schemas/TransferAuditRow'
        next_cursor:
          oneOf:
            - $ref: '#/components/schemas/TransferAuditCursor'
            - type: 'null'
          description: Cursor pair from the last returned row; null when no row is returned.
        has_more:
          type: boolean
          description: Whether another page is available.
    TransferAuditRow:
      type: object
      additionalProperties: false
      required:
        - id
        - transaction_time_ns
        - ref_code
        - username
        - type_name
        - type_sub_name
        - amount
        - amount_before
        - amount_after
        - asset_name
        - asset_unit
        - note
      properties:
        id:
          type: string
        transaction_time_ns:
          type: integer
          format: int64
          description: Transaction timestamp as Unix nanoseconds.
        ref_code:
          type: string
        username:
          type: string
        type_name:
          type: string
          const: transfer
        type_sub_name:
          type: string
          enum:
            - up
            - down
        amount:
          type: number
          format: double
        amount_before:
          type: number
          format: double
        amount_after:
          type: number
          format: double
        asset_name:
          type: string
        asset_unit:
          type: string
        note:
          type: string
    TransferAuditCursor:
      type: object
      additionalProperties: false
      required:
        - transaction_time_ns
        - id
      properties:
        transaction_time_ns:
          type: integer
          format: int64
          description: Last returned transaction timestamp as Unix nanoseconds.
        id:
          type: string
          description: Last returned row ID; tie-breaker for equal timestamps.
    Error:
      type: object
      additionalProperties: false
      required:
        - status
        - message
      properties:
        status:
          $ref: '#/components/schemas/ResponseStatus'
        message:
          $ref: '#/components/schemas/ResponseMessage'
    ResponseStatus:
      type: object
      additionalProperties: false
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
          description: HTTP-compatible core response status code.
        message:
          type: string
          description: HTTP status text.
    ResponseMessage:
      type: object
      additionalProperties: false
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum:
            - wler01
            - wler07
            - wler10
            - gmer01
            - gmer02
          description: Core response message code. `wler07` is invalid type/input, `wler10` is missing user data, `gmer02` is no permission, and `wler01` is internal error.
        message:
          type: string
          description: Core response message text.
  responses:
    BadRequest:
      description: Core validation or request error (`wler07` type-invalid, `wler10` user-data-not-found, or the Gateway's equivalent request mapping).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Gateway rejected a missing or invalid `apikey` header; HTTP mapping remains draft until Gateway implementation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Gateway rejected the caller's Agent or IP allowlist; core authorization maps no permission to `gmer02`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooManyRequests:
      description: Existing Gateway rate, burst, or in-flight concurrency policy was exceeded.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalServerError:
      description: Unexpected Gateway or upstream failure (`wler01` core internal error).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ServiceUnavailable:
      description: Gateway or upstream service is temporarily unavailable.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
