openapi: 3.1.0
info:
  title: Romu API
  version: "1.0"
  summary: API عمومی رومو برای وصل کردن سیستم صرافی به نگهداری امن دارایی‌ها.
  description: |
    این فایل مسیرهای عمومی رومو با کلید API را شرح می‌دهد. نام فیلدها و مسیرها انگلیسی می‌مانند.
    مبلغ در پاسخ همیشه یک شکل دارد، بی صفر انتهایی: `"100.5"`. در درخواست `"100.50"` هم درست است.
  contact:
    name: Romu
    url: https://docs.romu.io
servers:
  - url: https://api.romu.ir
    description: sandbox (TRON Nile و BSC testnet)
  - url: https://api.romu.io
    description: تولید
security:
  - romuSignature: []
tags:
  - name: identity
    x-title: هویت
    x-slug: identity
  - name: wallets
    x-title: کیف پول
    x-slug: wallets
  - name: transactions
    x-title: برداشت و تراکنش
    x-slug: transactions
  - name: addresses
    x-title: آدرس واریز
    x-slug: addresses
  - name: deposits
    x-title: واریز
    x-slug: deposits
  - name: sweeps
    x-title: Sweep
    x-slug: sweeps
  - name: assets
    x-title: دارایی و ایستگاه کارمزد
    x-slug: assets
  - name: approvals
    x-title: قانون و تأیید
    x-slug: approvals
  - name: webhooks
    x-title: Webhook
    x-slug: webhooks
  - name: usage
    x-title: مصرف
    x-slug: usage

x-samples:
  transaction: &sampleTransaction
    id: 0192f1a2-5e4f-7a10-b2c3-d4e5f6a7b8c9
    wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
    idempotency_key: withdraw-8841
    type: withdrawal
    origin: api
    network: TRON
    asset: TRON_USDT
    amount: "100.5"
    amount_raw: "100500000"
    to_address: TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE
    external_ref: user-1207
    status: pending
    stage: waiting_approval
    reason: null
    tx_hash: ""
    expires_at: "2026-10-06T13:00:00Z"
    created_at: "2026-10-06T12:00:00Z"
    policy:
      decision: needs_approval
      matched_rule: { rule_id: 0192f1c1-0000-7000-8000-000000000001, name: تراکنش بزرگ }
  address: &sampleAddress
    id: 0192f1b2-3333-7f3b-8a4e-6b7c8d9e0f01
    wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
    group_id: 01926e00-0000-7000-8000-000000000201
    group: users
    kind: deposit
    label: vip-customers
    status: active
    addresses:
      - { network: TRON, address: TE48BLyVNUSHicSrpwaoRh4SQqXSkoG5S5 }
    created_at: "2026-10-01T09:00:00Z"
    archived_at: null

paths:
  /v1/whoami:
    get:
      operationId: apiWhoAmI
      x-slug: whoami
      x-scopes: [read]
      tags: [identity]
      summary: این کلید کیست
      description: سازمان، شناسه و دسترسی‌های همین کلید و کیف پول‌هایش را برمی‌گرداند. بهترین مسیر برای آزمودن امضا است.
      responses:
        "200":
          description: اطلاعات کلید.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WhoAmI" }
              example:
                org_id: 0192f1a0-7c3e-7b42-9d1e-5a8b3c6d2e10
                key_id: 01926e60-0000-7000-8000-000000000001
                scopes: [read, withdraw]
                allowed_wallet_ids: [0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12]
                all_wallets: false
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/wallets:
    get:
      operationId: apiWallet
      x-slug: get-wallets
      x-scopes: [read, withdraw]
      x-query-example: limit=50
      tags: [wallets]
      summary: یک کیف پول یا فهرست کیف پول‌ها
      description: با `id` یک کیف پول را برمی‌گرداند، با همه‌ی شبکه‌ها و آدرس هر کدام. بی `id` یک صفحه از کیف پول‌های همین کلید را می‌دهد. آماده بودن هر شبکه در `readiness` است.
      parameters:
        - { name: id, in: query, description: شناسه‌ی کیف پول. با این پارامتر پاسخ فقط همان کیف پول است., schema: { type: string, format: uuid } }
        - { name: network, in: query, description: فقط کیف پول‌هایی که روی این شبکه هستند., schema: { $ref: "#/components/schemas/Network" } }
        - { $ref: "#/components/parameters/cursor" }
        - { $ref: "#/components/parameters/limit" }
      responses:
        "200":
          description: یک کیف پول (با `id`) یا یک صفحه.
          content:
            application/json:
              schema:
                oneOf:
                  - { $ref: "#/components/schemas/Wallet" }
                  - { $ref: "#/components/schemas/WalletPage" }
              example:
                wallets:
                  - wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
                    name: کیف پول گرم
                    status: active
                    networks:
                      - { network: TRON, address: TKyu1LVaqp8EAbcxBoRTvsm5cp7wpbBWdg, readiness: ready }
                      - { network: BSC, address: "0x6dD2A6f3b05fD15d901fBEeC61b87a34BdCFb843", readiness: ready }
                    value: { usd_total: "126.25", unpriced_count: 0, oldest_read_at: "2026-10-14T07:10:00Z" }
                next_cursor: ""
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/wallets/assets:
    get:
      operationId: apiWalletAssets
      x-slug: wallet-assets
      x-scopes: [read, withdraw]
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
      tags: [wallets]
      summary: دارایی‌های یک کیف پول
      description: یک صفحه از دارایی‌های کیف پول، بزرگ‌ترین ارزش اول، با مبلغ، ارزش دلاری، نرخ و زمان خواندن.
      parameters:
        - { $ref: "#/components/parameters/walletIdRequired" }
        - { $ref: "#/components/parameters/cursor" }
        - { $ref: "#/components/parameters/limit" }
      responses:
        "200":
          description: یک صفحه از دارایی‌ها.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WalletAssetPage" }
              example:
                assets:
                  - asset: TRON_USDT
                    network: TRON
                    amount: "25"
                    usd_value: "25"
                    rate_usd: "1"
                    read_at: "2026-10-14T07:10:00Z"
                next_cursor: ""
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/wallets/balance-summary:
    get:
      operationId: apiWalletBalanceSummary
      x-slug: balance-summary
      x-scopes: [read, withdraw]
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
      tags: [wallets]
      summary: خلاصه‌ی موجودی کیف پول
      description: موجودی کیف پول را در سه دسته می‌دهد. `main` آدرس اصلی، `sweep` آدرس‌های sweep، و `unswept` آدرس‌های واریز یعنی آنچه هنوز sweep نشده است.
      parameters:
        - { $ref: "#/components/parameters/walletIdRequired" }
      responses:
        "200":
          description: سه دسته با یک شکل.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BalanceSummary" }
              example:
                main: { total: { usd_total: "100", unpriced_count: 0, oldest_read_at: "2026-10-14T07:10:00Z" }, assets: [], address_count: 1, unread_count: 0 }
                sweep: { total: { usd_total: "0", unpriced_count: 0, oldest_read_at: "2026-10-14T07:10:00Z" }, assets: [], address_count: 1, unread_count: 0 }
                unswept:
                  total: { usd_total: "125", unpriced_count: 0, oldest_read_at: "2026-10-13T08:00:00Z" }
                  assets:
                    - { asset: TRON_USDT, network: TRON, amount: "125", usd_value: "125", holder_count: 4 }
                  address_count: 40
                  unread_count: 2
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/transactions:
    post:
      operationId: apiWithdraw
      x-slug: create-withdrawal
      x-scopes: [withdraw]
      x-rate-limit: { per: key, per_minute: 60 }
      x-idempotency-example: withdraw-8841
      tags: [transactions]
      summary: ثبت برداشت
      description: |
        یک برداشت ثبت می‌کند.
        کد `201` یعنی تراکنش تازه ساخته شد و `200` یعنی همان درخواست تکراری بوده و همان تراکنش اول برگشته است.
      parameters:
        - { $ref: "#/components/parameters/idempotencyKey" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WithdrawalRequest" }
            example:
              wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
              asset: TRON_USDT
              amount: "100.50"
              to_address: TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE
              expires_at: "2026-10-06T13:00:00Z"
              external_ref: user-1207
      responses:
        "201":
          description: تراکنش تازه ساخته شد.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TransactionCreated" }
              example:
                transaction: *sampleTransaction
                created: true
        "200":
          description: همان درخواست با همان `Idempotency-Key`. همان تراکنش اول برمی‌گردد و `created` برابر `false` است.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TransactionCreated" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/ForbiddenOrSuspended" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/WithdrawalConflict" }
        "413": { $ref: "#/components/responses/TooLarge" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLaterOrPaused" }
    get:
      operationId: apiTransaction
      x-slug: get-transactions
      x-scopes: [read, withdraw]
      x-query-example: network=TRON&status=pending&limit=50
      tags: [transactions]
      summary: یک تراکنش یا فهرست تراکنش‌ها
      description: "با `id` یک تراکنش را می‌دهد. بی `id` یک صفحه از تراکنش‌های کیف پول‌های همین کلید را می‌دهد، تازه‌ترین اول. sweep و شارژ ایستگاه کارمزد در این فهرست نیستند. همه‌ی فیلترها اختیاری‌اند، فقط مقدار دقیق را می‌سنجند و با هم ترکیب می‌شوند. فهرست صفحه‌به‌صفحه است: `next_cursor` هر پاسخ را در `cursor` درخواست بعد بفرستید تا خالی شود. اندازه‌ی صفحه پیش‌فرض 50 و حداکثر 200 است."
      parameters:
        - { name: id, in: query, description: شناسه‌ی تراکنش., schema: { type: string, format: uuid } }
        - { $ref: "#/components/parameters/walletIdOptional" }
        - { name: asset, in: query, description: "کد دارایی، مثل `TRON_USDT`.", schema: { type: string } }
        - { name: network, in: query, description: "شبکه، مثل `TRON`.", schema: { $ref: "#/components/schemas/Network" } }
        - { name: status, in: query, description: "یکی از `pending`، `checking`، `confirmed`، `failed` و `cancelled`، مثل `pending`. تکرارپذیر است.", schema: { $ref: "#/components/schemas/TransactionStatus" } }
        - { name: type, in: query, description: "نوع تراکنش، مثل `withdrawal`.", schema: { type: string, enum: [withdrawal, internal] } }
        - { name: origin, in: query, description: "محل ساخت تراکنش، مثل `api`.", schema: { type: string, enum: [api, console] } }
        - { name: stage, in: query, description: "قدم، مثل `waiting_approval`. تکرارپذیر است.", schema: { $ref: "#/components/schemas/TransactionStage" } }
        - { name: to_address, in: query, description: "آدرس مقصد، مثل `TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE`.", schema: { type: string } }
        - { name: tx_hash, in: query, description: "هش زنجیره، مثل `a3f1c2d4e5b6978877665544332211ffeeddccbbaa99887766554433221100aa`. حروف بزرگ و کوچک هش hex فرقی ندارند. اگر برداشتی روی شبکه‌ی BSC با کارمزد بالاتر دوباره فرستاده شده باشد، با همان هشی بگردید که API یا Webhook به شما داده است.", schema: { type: string } }
        - { name: external_ref, in: query, description: "مرجع خود شما، مثل `user-1207`.", schema: { type: string } }
        - { $ref: "#/components/parameters/from" }
        - { $ref: "#/components/parameters/to" }
        - { $ref: "#/components/parameters/cursor" }
        - { $ref: "#/components/parameters/limit" }
      responses:
        "200":
          description: یک تراکنش (با `id`) یا یک صفحه.
          content:
            application/json:
              schema:
                oneOf:
                  - { $ref: "#/components/schemas/TransactionOne" }
                  - { $ref: "#/components/schemas/TransactionPage" }
              examples:
                one:
                  summary: با id
                  value:
                    transaction: *sampleTransaction
                list:
                  summary: فهرست
                  value:
                    transactions:
                      - *sampleTransaction
                    next_cursor: MjAyNi0xMC0wNlQxMjowMDowMFo
                    total: 12
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/transactions/cancel:
    post:
      operationId: apiTransactionCancel
      x-slug: cancel-withdrawal
      x-scopes: [withdraw]
      tags: [transactions]
      summary: لغو برداشت
      description: |
        برداشتی را لغو می‌کند که همین کلید ساخته و هنوز امضا نشده است.
        لغو دوباره همان نتیجه را بی تغییر برمی‌گرداند. این مسیر وقتی سازمان متوقف یا معلق است هم کار می‌کند.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CancelRequest" }
            example:
              transaction_id: 0192f1a2-5e4f-7a10-b2c3-d4e5f6a7b8c9
              reason: کاربر درخواست را پس گرفت
      responses:
        "200":
          description: "`status` برابر `cancelled`. رویداد `transaction.updated` هم فرستاده می‌شود."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TransactionOne" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/FailedPrecondition" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/transactions/approvals:
    get:
      operationId: apiTransactionApprovals
      x-slug: transaction-approvals
      x-scopes: [approve-read]
      x-query-example: id=0192f1a2-5e4f-7a10-b2c3-d4e5f6a7b8c9
      tags: [approvals]
      summary: وضعیت تأیید یک تراکنش
      description: نشان می‌دهد هر حدنصاب چند تأیید و رد دارد و چه کسانی رأی داده‌اند. این مسیر فقط می‌خواند؛ تأیید هرگز با API انجام نمی‌شود.
      parameters:
        - { name: id, in: query, required: true, description: شناسه‌ی تراکنش., schema: { type: string, format: uuid } }
      responses:
        "200":
          description: وضعیت تأیید.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ApprovalDetail" }
              example:
                result: pending
                quorums:
                  - { mode: part, threshold: 2, eligible_count: 3, approved: 1, rejected: 0 }
                approvers:
                  - { approver_name: نگین, decision: approve, at: "2026-10-06T12:10:00Z" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/FailedPrecondition" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/addresses:
    post:
      operationId: apiAddressCreate
      x-slug: create-address
      x-scopes: [addresses]
      x-rate-limit: { per: key, per_minute: 60 }
      x-idempotency-example: address-user-1207
      tags: [addresses]
      summary: ساخت آدرس واریز
      description: |
        یک آدرس واریز می‌سازد. این آدرس روی همه‌ی شبکه‌های کیف پول کار می‌کند.
        همان `Idempotency-Key` با همان بدنه همان آدرس را برمی‌گرداند (باز هم `201`). همان کلید با بدنه‌ی دیگر `409` است.
        سهمیه‌ی پلن آدرس‌ها را می‌شمارد، با آدرس‌های بایگانی‌شده.
      parameters:
        - { $ref: "#/components/parameters/idempotencyKey" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AddressCreate" }
            example: { wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12, group: users, label: vip-customers }
      responses:
        "201":
          description: آدرس ساخته شد.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AddressOne" }
              example:
                address: *sampleAddress
        "400": { $ref: "#/components/responses/AddressBadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/AddressConflict" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/AddressMismatch" }
        "503": { $ref: "#/components/responses/AddressTryLater" }
    get:
      operationId: apiAddresses
      x-slug: get-addresses
      x-scopes: [read]
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12&status=active&limit=50
      tags: [addresses]
      summary: یک آدرس یا فهرست آدرس‌ها
      description: "با `id` یک آدرس را می‌دهد. بی `id` یک صفحه می‌دهد. `total` فقط در صفحه‌ی اول عدد است و در صفحه‌های بعد `null`. فهرست به ازای هر رکورد آدرس صفحه‌بندی می‌شود و با `next_cursor` جلو می‌رود. هر رکورد برای هر شبکه حداکثر یک ورودی در `addresses` دارد، پس امروز حداکثر دو تا. اندازه‌ی صفحه پیش‌فرض 50 و حداکثر 200 است."
      parameters:
        - { name: id, in: query, description: شناسه‌ی آدرس., schema: { type: string, format: uuid } }
        - { $ref: "#/components/parameters/walletIdOptional" }
        - { name: group, in: query, description: نام گروه., schema: { type: string } }
        - { name: label, in: query, description: پیشوند برچسب., schema: { type: string } }
        - { name: status, in: query, schema: { type: string, enum: [active, archived] } }
        - { name: kind, in: query, schema: { type: string, enum: [main, deposit, sweep, fee] } }
        - { $ref: "#/components/parameters/cursor" }
        - { name: limit, in: query, description: "1 تا 200. پیش‌فرض 50.", schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
      responses:
        "200":
          description: یک آدرس (با `id`) یا یک صفحه.
          content:
            application/json:
              schema:
                oneOf:
                  - { $ref: "#/components/schemas/AddressOne" }
                  - { $ref: "#/components/schemas/AddressPage" }
              example:
                addresses: [*sampleAddress]
                next_cursor: ""
                total: 1
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/AddressMismatch" }
        "503": { $ref: "#/components/responses/TryLater" }
    patch:
      operationId: apiAddressUpdate
      x-slug: update-address
      x-scopes: [addresses]
      x-rate-limit: { per: key, per_minute: 60 }
      x-query-example: id=0192f1b2-3333-7f3b-8a4e-6b7c8d9e0f01
      tags: [addresses]
      summary: تغییر برچسب یا بایگانی آدرس
      description: برچسب تازه می‌گذارد یا آدرس را برای همیشه بایگانی می‌کند. واریز به آدرس بایگانی‌شده تا 30 روز دیگر هم دیده می‌شود. نوع آدرس هرگز عوض نمی‌شود.
      parameters:
        - { name: id, in: query, required: true, description: شناسه‌ی آدرس., schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AddressUpdate" }
            example: { label: gold-customers }
      responses:
        "200":
          description: آدرس به‌روز شد.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AddressOne" }
        "400": { $ref: "#/components/responses/AddressBadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/AddressMismatch" }
        "503": { $ref: "#/components/responses/TryLaterOrPaused" }

  /v1/addresses/batch:
    post:
      operationId: apiAddressBatch
      x-slug: create-addresses-batch
      x-scopes: [addresses]
      x-rate-limit: { per: key, per_minute: 10 }
      x-idempotency-example: addresses-batch-2026-10
      tags: [addresses]
      summary: ساخت چند آدرس واریز با هم
      description: |
        1 تا 100 آدرس را به ترتیب `labels` زیر یک `Idempotency-Key` می‌سازد. یا همه ساخته می‌شوند یا هیچ‌کدام.
        به جای `labels` می‌توانید `label_prefix` و `count` بفرستید: برچسب‌ها شماره می‌خورند و از بعد از بزرگ‌ترین شماره‌ی موجود ادامه پیدا می‌کنند. برچسب‌های ساخته‌شده در پاسخ هستند.
      parameters:
        - { $ref: "#/components/parameters/idempotencyKey" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AddressBatch" }
            example: { wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12, group: users, labels: [vip-customers, gold-customers] }
      responses:
        "201":
          description: آدرس‌ها ساخته شدند.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AddressList" }
        "400": { $ref: "#/components/responses/AddressBadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/AddressConflict" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/AddressMismatch" }
        "503": { $ref: "#/components/responses/AddressTryLater" }

  /v1/addresses/balances:
    get:
      operationId: apiAddressBalances
      x-slug: address-balances
      x-scopes: [read]
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12&sort=value&limit=50
      tags: [addresses]
      summary: موجودی هر آدرس
      description: یک صفحه از آدرس‌های کیف پول با موجودی هر کدام. مبلغ `"0"` یعنی آدرس چیزی ندارد و `null` یعنی موجودی هنوز معلوم نیست.
      parameters:
        - { $ref: "#/components/parameters/walletIdRequired" }
        - { name: address_id, in: query, description: فقط همین آدرس., schema: { type: string, format: uuid } }
        - { name: kind, in: query, schema: { type: string, enum: [main, deposit, sweep] } }
        - { name: asset, in: query, description: "فقط آدرس‌هایی که از این دارایی بیش از صفر دارند، مثل `TRON_USDT`.", schema: { type: string } }
        - { name: sort, in: query, description: "`cursor` یک ترتیب با ترتیب دیگر `400` است.", schema: { type: string, enum: [value, last_activity], default: value } }
        - { name: network, in: query, schema: { $ref: "#/components/schemas/Network" } }
        - { name: q, in: query, description: "بخشی از آدرس یا برچسب، 1 تا 64 نویسه.", schema: { type: string, minLength: 1, maxLength: 64 } }
        - { name: nonzero, in: query, description: فقط آدرس‌هایی که چیزی دارند., schema: { type: boolean } }
        - { $ref: "#/components/parameters/cursor" }
        - { name: limit, in: query, description: "1 تا 200. پیش‌فرض 50.", schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
      responses:
        "200":
          description: یک صفحه از آدرس‌ها.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AddressBalancePage" }
              example:
                addresses:
                  - address_id: 01926e00-0000-7000-8000-000000000411
                    wallet_id: 01926e00-0000-7000-8000-000000000101
                    kind: deposit
                    network: TRON
                    address: TE48BLyVNUSHicSrpwaoRh4SQqXSkoG5S5
                    label: vip-customers
                    status: active
                    group_id: 01926e00-0000-7000-8000-000000000201
                    total: { usd_total: "26.25", unpriced_count: 0, oldest_read_at: "2026-10-14T07:10:00Z" }
                    assets:
                      - { asset: TRON_USDT, network: TRON, amount: "25", usd_value: "25", rate_usd: "1", read_at: "2026-10-14T07:10:00Z" }
                    last_activity_at: "2026-10-14T06:58:00Z"
                next_cursor: ""
                total: 1
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/addresses/export:
    get:
      operationId: apiAddressExport
      x-slug: export-addresses
      x-scopes: [read]
      x-rate-limit: { per: key, per_minute: 5 }
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
      x-not-json: true
      tags: [addresses]
      summary: خروجی CSV آدرس‌ها
      description: |
        همه‌ی آدرس‌های کیف پول را به صورت CSV می‌دهد، با UTF-8 و BOM، هر آدرس و شبکه یک ردیف.
        ستون‌ها: `group`، `label`، `kind`، `status`، `network`، `address`، `created_at`، `archived_at`، `usd_value`، `balances`.
        `status` یکی از `active`، `archived` یا `pending` است. وقتی `pending` است، ستون `address` خالی است. به آن آدرس چیزی نفرستید.
        `usd_value` جمع ارزش دلاری آدرس روی همان شبکه است، طبق آخرین خواندن. اگر هنوز خوانده نشده یا قیمتی ندارد، خالی است.
        `balances` موجودی هر دارایی فعال آن شبکه است، به شکل «مقدار کد دارایی» و با `|` جدا شده، مثل `9.199 TRON_TRX|0 TRON_USDT`. مقدار به واحد خود دارایی است.
        اگر دانلود ناقص ماند، فایل را به کار نبرید.
      parameters:
        - { $ref: "#/components/parameters/walletIdRequired" }
        - { name: group, in: query, description: نام گروه., schema: { type: string } }
        - { name: label, in: query, description: پیشوند برچسب., schema: { type: string } }
        - { name: status, in: query, schema: { type: string, enum: [active, archived] } }
        - { name: kind, in: query, schema: { type: string, enum: [main, deposit, sweep, fee] } }
      responses:
        "200":
          description: فایل CSV.
          content:
            text/csv:
              schema: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/address-groups:
    get:
      operationId: apiAddressGroups
      x-slug: get-address-groups
      x-scopes: [read]
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
      tags: [addresses]
      summary: گروه‌های آدرس
      description: گروه‌های آدرس یک کیف پول. قانون sweep روی گروه تعریف می‌شود.
      parameters:
        - { $ref: "#/components/parameters/walletIdRequired" }
      responses:
        "200":
          description: گروه‌ها.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AddressGroupList" }
              example:
                groups:
                  - { id: 01926e00-0000-7000-8000-000000000201, wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12, name: users, status: active, created_at: "2026-10-01T09:00:00Z" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }
    post:
      operationId: apiAddressGroupCreate
      x-slug: create-address-group
      x-scopes: [addresses]
      x-rate-limit: { per: key, per_minute: 60 }
      tags: [addresses]
      summary: ساخت گروه آدرس
      description: گروه تازه می‌سازد. نامی که در کیف پول هست `409` می‌گیرد. گروه هیچ‌وقت حذف نمی‌شود.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AddressGroupCreate" }
            example: { wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12, name: users }
      responses:
        "201":
          description: گروه ساخته شد.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AddressGroupOne" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/AlreadyExists" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLaterOrPaused" }

  /v1/deposits:
    get:
      operationId: apiDeposits
      x-slug: get-deposits
      x-scopes: [read]
      x-query-example: network=TRON&status=confirmed&limit=50
      tags: [deposits]
      summary: یک واریز یا فهرست واریزها
      description: "با `id` یک واریز را می‌دهد. بی `id` یک صفحه می‌دهد، تازه‌ترین اول. `total` فقط در صفحه‌ی اول عدد است. همه‌ی فیلترها اختیاری‌اند، فقط مقدار دقیق را می‌سنجند و با هم ترکیب می‌شوند."
      parameters:
        - { name: id, in: query, description: شناسه‌ی واریز., schema: { type: string, format: uuid } }
        - { $ref: "#/components/parameters/walletIdOptional" }
        - { name: address_id, in: query, description: "شناسه‌ی آدرس، مثل `0192f1b2-3333-7f3b-8a4e-6b7c8d9e0f01`.", schema: { type: string, format: uuid } }
        - { name: asset, in: query, description: "کد دارایی، مثل `TRON_USDT`.", schema: { type: string } }
        - { name: network, in: query, description: "شبکه، مثل `TRON`.", schema: { $ref: "#/components/schemas/Network" } }
        - { name: status, in: query, description: "وضعیت، مثل `confirmed`. تکرارپذیر است.", schema: { type: string, enum: [detected, confirmed, reorged] } }
        - { name: to_address, in: query, description: "آدرس واریز، مثل `TE48BLyVNUSHicSrpwaoRh4SQqXSkoG5S5`.", schema: { type: string } }
        - { name: tx_hash, in: query, description: "هش زنجیره، مثل `afac59572365099a560875a7ace8c9dbbecd562d3750b9702c59f6ef935d7c7c`. حروف بزرگ و کوچک هش hex فرقی ندارند.", schema: { type: string } }
        - { name: from, in: query, description: "شروع بازه روی `detected_at`، خود این لحظه هم حساب است. RFC 3339، مثل `2026-10-01T00:00:00Z`.", schema: { type: string, format: date-time } }
        - { name: to, in: query, description: "پایان بازه روی `detected_at`، تا پیش از این لحظه. RFC 3339، مثل `2026-11-01T00:00:00Z`.", schema: { type: string, format: date-time } }
        - { $ref: "#/components/parameters/cursor" }
        - { $ref: "#/components/parameters/limit" }
      responses:
        "200":
          description: یک واریز (با `id`) یا یک صفحه.
          content:
            application/json:
              schema:
                oneOf:
                  - { $ref: "#/components/schemas/DepositOne" }
                  - { $ref: "#/components/schemas/DepositPage" }
              example:
                deposits:
                  - id: 0192f1b1-2222-7a10-b2c3-d4e5f6a7b801
                    wallet_id: 0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
                    address_id: 0192f1b2-3333-7f3b-8a4e-6b7c8d9e0f01
                    network: TRON
                    asset: TRON_TRX
                    amount: "25"
                    amount_raw: "25000000"
                    tx_hash: afac59572365099a560875a7ace8c9dbbecd562d3750b9702c59f6ef935d7c7c
                    confirmations: 19
                    status: confirmed
                    to_address: TE48BLyVNUSHicSrpwaoRh4SQqXSkoG5S5
                    from_address: TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE
                    detected_at: "2026-10-09T08:15:30Z"
                    confirmed_at: "2026-10-09T08:16:30Z"
                next_cursor: ""
                total: 1
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/sweep-rules:
    get:
      operationId: apiSweepRules
      x-slug: get-sweep-rules
      x-scopes: [read]
      tags: [sweeps]
      summary: قانون‌های sweep
      description: قانون‌های sweep کیف پول‌های این کلید. گروه بی قانون sweep نمی‌شود.
      parameters:
        - { $ref: "#/components/parameters/walletIdOptional" }
        - { name: group_id, in: query, description: شناسه‌ی گروه., schema: { type: string, format: uuid } }
      responses:
        "200":
          description: قانون‌ها.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SweepRuleList" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }
    put:
      operationId: apiSweepRuleSet
      x-slug: set-sweep-rule
      x-scopes: [sweep]
      tags: [sweeps]
      summary: تنظیم قانون sweep
      description: قانون یک گروه را می‌نویسد. برای قانون اول `base_version` را 0 بفرستید و بعد نسخه‌ای را که جایگزین می‌شود. نسخه‌ی کهنه `409` است. API هرگز مقصد sweep را تعیین نمی‌کند؛ مقصد فقط از کنسول و با تأیید عوض می‌شود.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SweepRuleSet" }
            example:
              group_id: 01926e00-0000-7000-8000-000000000201
              base_version: 0
              enabled: true
              schedule: { kind: interval, every_hours: 6, hours: [], timezone: "" }
              min_amounts: [{ asset: TRON_TRX, amount: "10" }]
              excluded_assets: []
      responses:
        "200":
          description: قانون ثبت شد.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SweepRuleOne" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/FailedPrecondition" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLaterOrPaused" }

  /v1/sweeps:
    get:
      operationId: apiSweeps
      x-slug: get-sweeps
      x-scopes: [read]
      x-query-example: status=completed&limit=50
      tags: [sweeps]
      summary: تاریخچه‌ی sweep
      description: یک صفحه از sweep ها، تازه‌ترین اول.
      parameters:
        - { $ref: "#/components/parameters/walletIdOptional" }
        - { name: group_id, in: query, schema: { type: string, format: uuid } }
        - { name: address_id, in: query, schema: { type: string, format: uuid } }
        - { name: asset, in: query, description: کد دارایی., schema: { type: string } }
        - { name: status, in: query, description: تکرارپذیر است., schema: { type: string, enum: [pending, completed, failed] } }
        - { $ref: "#/components/parameters/from" }
        - { $ref: "#/components/parameters/to" }
        - { $ref: "#/components/parameters/cursor" }
        - { $ref: "#/components/parameters/limit" }
      responses:
        "200":
          description: یک صفحه از sweep ها.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SweepPage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/AddressMismatch" }
        "503": { $ref: "#/components/responses/TryLater" }
    post:
      operationId: apiSweepNow
      x-slug: sweep-now
      x-scopes: [sweep]
      x-rate-limit: { per: key, per_minute: 6 }
      tags: [sweeps]
      summary: sweep همین الان
      description: یک گروه یا یک آدرس را طبق قانون گروه همین الان sweep می‌کند. دقیقاً یکی از `group_id` و `address_id` را بفرستید. اگر درخواست قبلی همان هدف هنوز باز باشد، همان برمی‌گردد.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SweepNow" }
            example: { group_id: 01926e00-0000-7000-8000-000000000201 }
      responses:
        "201":
          description: درخواست تازه ثبت شد.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SweepNowResult" }
        "200":
          description: درخواست قبلی هنوز باز است و همان برگشت (`created` برابر `false`).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SweepNowResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/FailedPrecondition" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLaterOrPaused" }

  /v1/fee-station:
    get:
      operationId: apiFeeStation
      x-slug: fee-station
      x-scopes: [read]
      x-query-example: network=TRON
      tags: [assets]
      summary: ایستگاه کارمزد
      description: ایستگاه کارمزد سازمان روی یک شبکه. سازمان بی ایستگاه فقط `found` برابر `false` می‌گیرد.
      parameters:
        - { name: network, in: query, description: پیش‌فرض `TRON`., schema: { $ref: "#/components/schemas/Network" } }
      responses:
        "200":
          description: ایستگاه کارمزد.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FeeStation" }
              example:
                found: true
                wallet_id: 01926e00-0000-7000-8000-000000000301
                network: TRON
                address: TDHakFLTixSuJF5xxDsmvZ9zSn2TvQM9YL
                balance: "150.5"
                asset: TRON_TRX
                enabled: true
                per_tx_cap: "30"
                daily_cap: "1000"
                topped_up_24h: "60"
                threshold: "200"
                below: true
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/assets:
    get:
      operationId: apiAssets
      x-slug: registry-assets
      x-scopes: [read]
      x-query-example: network=TRON&limit=100
      tags: [assets]
      summary: دارایی‌های پشتیبانی‌شده
      description: فهرست دارایی‌های رومو، برای همه‌ی سازمان‌ها یکسان، به ترتیب کد. واریز تازه‌ی دارایی `disabled` ثبت نمی‌شود، ولی موجودی‌اش هنوز برداشت و sweep می‌شود.
      parameters:
        - { name: network, in: query, description: نبودنش یعنی همه‌ی شبکه‌ها., schema: { $ref: "#/components/schemas/Network" } }
        - { name: after_code, in: query, description: آخرین `code` صفحه‌ی قبل., schema: { type: string } }
        - { name: limit, in: query, description: "1 تا 500. پیش‌فرض 100.", schema: { type: integer, minimum: 1, maximum: 500, default: 100 } }
      responses:
        "200":
          description: یک صفحه از فهرست.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistryPage" }
              example:
                assets:
                  - { code: TRON_TRX, network: TRON, type: native, contract: "", decimals: 6, min_deposit: "1", name: TRON, status: active }
                  - { code: TRON_USDT, network: TRON, type: trc20, contract: TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf, decimals: 6, min_deposit: "0.1", name: Tether USD, status: active }
                next_after_code: ""
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "502": { $ref: "#/components/responses/BadGateway" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/policy:
    get:
      operationId: apiPolicy
      x-slug: get-policy
      x-scopes: [read]
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
      tags: [approvals]
      summary: قانون‌های ثبت‌شده‌ی کیف پول
      description: قانون‌هایی را که برای یک کیف پول ثبت شده‌اند می‌دهد. قانون‌های سازمان را در کنسول ببینید.
      parameters:
        - { $ref: "#/components/parameters/walletIdRequired" }
      responses:
        "200":
          description: قانون‌های کیف پول.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WalletPolicy" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/change-requests:
    get:
      operationId: apiChangeRequests
      x-slug: get-change-requests
      x-scopes: [approve-read]
      x-query-example: wallet_id=0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12
      tags: [approvals]
      summary: درخواست‌های تغییر
      description: درخواست‌های تغییر یک کیف پول، تازه‌ترین اول، یا یک درخواست. دقیقاً یکی از `id` و `wallet_id` را بفرستید.
      parameters:
        - { name: id, in: query, description: شناسه‌ی درخواست تغییر., schema: { type: string, format: uuid } }
        - { $ref: "#/components/parameters/walletIdOptional" }
      responses:
        "200":
          description: یک درخواست (با `id`) یا فهرست.
          content:
            application/json:
              schema:
                oneOf:
                  - { $ref: "#/components/schemas/ChangeRequestOne" }
                  - { $ref: "#/components/schemas/ChangeRequestList" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/webhooks/key:
    get:
      operationId: apiWebhookKey
      x-slug: webhook-key
      x-scopes: [read]
      tags: [webhooks]
      summary: کلید عمومی Webhook
      description: کلیدهای عمومی امضای Webhook، هر کدام با نسخه‌اش. آن‌ها را نگه دارید و اگر `X-Custody-Key-Version` ناشناخته‌ای رسید، این مسیر را دوباره بخوانید.
      responses:
        "200":
          description: کلیدها.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WebhookKeys" }
              example:
                keys: [{ key_version: 1, public_key: VSX1y38XE3pPle9xXdo8QY98yVtH+PNMoZFPlX5Jt5o= }]
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/webhooks/replay:
    post:
      operationId: apiWebhookReplay
      x-slug: webhook-replay
      x-scopes: [read]
      x-rate-limit: { per: org, per_minute: 60 }
      tags: [webhooks]
      summary: ارسال دوباره‌ی یک رویداد
      description: یک رویداد را با همان بدنه و همان امضا دوباره می‌فرستد و یک دور تازه‌ی 8 تلاشی شروع می‌کند.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookReplay" }
            example: { event_id: 0192f1a4-2b6d-7e01-8f3a-9c4d5e6f7a81 }
      responses:
        "200":
          description: ارسال در صف قرار گرفت.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WebhookDeliveryOne" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/WebhookOff" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/webhooks/replay-failed:
    post:
      operationId: apiWebhookReplayFailed
      x-slug: webhook-replay-failed
      x-scopes: [read]
      x-rate-limit: { per: org, per_minute: 5 }
      tags: [webhooks]
      summary: ارسال دوباره‌ی رویدادهای ناموفق
      description: همه‌ی رویدادهای ناموفق یک بازه را، از رویدادهای انتخاب‌شده، دوباره می‌فرستد. حداکثر 7 روز و 500 رویداد. `from` جزو بازه است و `to` نیست.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookReplayFailed" }
            example: { from: "2026-10-05T00:00:00Z", to: "2026-10-06T00:00:00Z" }
      responses:
        "200":
          description: تعداد رویدادهایی که دوباره فرستاده شدند.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WebhookReplayed" }
              example: { replayed: 12 }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409": { $ref: "#/components/responses/WebhookOff" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

  /v1/usage:
    get:
      operationId: apiUsage
      x-slug: usage
      x-scopes: [read]
      x-query-example: from=2026-09&to=2026-10
      tags: [usage]
      summary: مصرف ماهانه
      description: برداشت‌های قطعی‌شده را به تفکیک ماه UTC، شبکه و دارایی می‌دهد. بی بازه، 12 ماه آخر را می‌دهد و حداکثر بازه 24 ماه است.
      parameters:
        - { name: from, in: query, description: "ماه شروع، `YYYY-MM`.", schema: { type: string, pattern: "^[0-9]{4}-[0-9]{2}$" } }
        - { name: to, in: query, description: "ماه پایان، `YYYY-MM`.", schema: { type: string, pattern: "^[0-9]{4}-[0-9]{2}$" } }
      responses:
        "200":
          description: مصرف.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Usage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthenticated" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503": { $ref: "#/components/responses/TryLater" }

webhooks:
  transaction:
    post:
      operationId: eventTransaction
      summary: رویداد تراکنش
      description: یک رویداد برای هر تغییر تراکنش. `type` و `origin` نوع و محل ساخت را می‌گویند.
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/TransactionEvent" }
      responses:
        "200": { description: هر پاسخ `2xx` در 10 ثانیه یعنی «رسید». }
  deposit:
    post:
      operationId: eventDeposit
      summary: رویداد واریز
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DepositEvent" }
      responses:
        "200": { description: هر پاسخ `2xx` در 10 ثانیه یعنی «رسید». }
  sweep:
    post:
      operationId: eventSweep
      summary: رویداد sweep
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SweepEvent" }
      responses:
        "200": { description: هر پاسخ `2xx` در 10 ثانیه یعنی «رسید». }
  feeStation:
    post:
      operationId: eventFeeStation
      summary: رویداد ایستگاه کارمزد
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FeeStationEvent" }
      responses:
        "200": { description: هر پاسخ `2xx` در 10 ثانیه یعنی «رسید». }

components:
  securitySchemes:
    romuSignature:
      type: apiKey
      in: header
      name: X-Custody-Signature
      description: امضای Ed25519 هر درخواست، همراه `X-Custody-Key-Id`، `X-Custody-Timestamp` و `X-Custody-Nonce`. جزئیات در راهنمای «امضای درخواست».
  parameters:
    idempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: "شناسه‌ی یکتای شما، 1 تا 255 نویسه‌ی ASCII چاپی بی فاصله. جزو رشته‌ی امضاست. پیشوند `rebuild:` رزرو است."
      schema: { type: string, minLength: 1, maxLength: 255 }
    cursor:
      name: cursor
      in: query
      description: مقدار `next_cursor` پاسخ قبلی.
      schema: { type: string }
    limit:
      name: limit
      in: query
      description: 1 تا 200.
      schema: { type: integer, minimum: 1, maximum: 200 }
    walletIdRequired:
      name: wallet_id
      in: query
      required: true
      description: شناسه‌ی کیف پول.
      schema: { type: string, format: uuid }
    walletIdOptional:
      name: wallet_id
      in: query
      description: "شناسه‌ی کیف پول، مثل `0192f19f-1d2c-7f3b-8a4e-6b7c8d9e0f12`."
      schema: { type: string, format: uuid }
    from:
      name: from
      in: query
      description: "شروع بازه روی `created_at`، خود این لحظه هم حساب است. RFC 3339، مثل `2026-10-01T00:00:00Z`."
      schema: { type: string, format: date-time }
    to:
      name: to
      in: query
      description: "پایان بازه روی `created_at`، تا پیش از این لحظه. RFC 3339، مثل `2026-11-01T00:00:00Z`."
      schema: { type: string, format: date-time }

  responses:
    BadRequest:
      description: "`invalid request` یا `malformed request body`: بدنه، پارامتر یا فیلد نادرست است، یا فیلد ناشناخته دارد."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Unauthenticated:
      description: "`unauthenticated`: هر شکست احراز هویت همین پاسخ را دارد."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Forbidden:
      description: "`permission denied`: کلید دسترسی این مسیر را ندارد."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    ForbiddenOrSuspended:
      description: "`permission denied` (کلید دسترسی ندارد یا کیف پول بیرون از فهرست کلید است)، یا `org_suspended` (سازمان معلق است، با `reason_code` و `public_note`)."
      content: { application/json: { schema: { $ref: "#/components/schemas/Suspended" } } }
    NotFound:
      description: "`not found`: مسیر یا شیء پیدا نشد. خواندن کیف پولی بیرون از فهرست کلید هم همین است."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    FailedPrecondition:
      description: "`failed precondition`: این کار در وضعیت فعلی ممکن نیست."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    AlreadyExists:
      description: "`already exists`: این نام پیش‌تر ثبت شده است."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    WithdrawalConflict:
      description: "`already exists` (همان `Idempotency-Key` با درخواست دیگر)، `failed precondition`، یا `insufficient_balance` (با مقدارها)."
      content: { application/json: { schema: { $ref: "#/components/schemas/InsufficientBalance" } } }
    AddressConflict:
      description: "`already exists` (همان `Idempotency-Key` با بدنه‌ی دیگر)، یا `quota_exceeded` با `code` برابر `address_limit` (سهمیه‌ی آدرس پلن تمام شده و چیزی ساخته نشده)."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    WebhookOff:
      description: "Webhook خاموش است (`the organisation's webhook is off`)، رویداد هنوز در صف ارسال است، یا نوع رویداد انتخاب نشده است."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    TooLarge:
      description: "`request too large`: بدنه بیش از 16 KiB است."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    TooManyRequests:
      description: "`too many requests`: بیش از سقف نرخ. پس از `Retry-After` ثانیه دوباره بفرستید."
      headers:
        Retry-After: { description: چند ثانیه صبر کنید., schema: { type: integer } }
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Internal:
      description: "`internal error`: خطای پیش‌بینی‌نشده."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    AddressMismatch:
      description: "`internal error`: آدرس نشان داده نشد. به رومو خبر دهید."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    BadGateway:
      description: پاسخی رسید که رومو نمی‌تواند به آن معنا بدهد. کمی بعد دوباره بخوانید.
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    TryLater:
      description: "`try again later`: سرویس موقتاً در دسترس نیست."
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    TryLaterOrPaused:
      description: "`try again later`، `org_paused` (سازمان موقتاً متوقف شده، با `Retry-After: 60`)، یا در ثبت برداشت `suspension_unknown` (با `Retry-After: 5`). همان درخواست را با همان `Idempotency-Key` دوباره بفرستید."
      headers:
        Retry-After: { description: چند ثانیه صبر کنید., schema: { type: integer } }
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }

    AddressBadRequest:
      description: "`invalid request` یا `malformed request body`؛ یا `label_taken`: برچسب `labels[index]` (در ساخت تکی و تغییر برچسب، `index` برابر `0`) در این کیف پول هست یا در همین درخواست دو بار آمده. هیچ آدرسی ساخته یا عوض نشد. برچسب دیگری بفرستید، با `Idempotency-Key` تازه."
      content: { application/json: { schema: { $ref: "#/components/schemas/AddressRefused" } } }

    AddressTryLater:
      description: "`try again later` یا `org_paused`؛ یا `network_unavailable`: شبکه‌ی `network` پاسخ نداد و فقط در این حالت معلوم است که هیچ آدرسی ساخته نشد. در هر سه، همان درخواست را با همان `Idempotency-Key` دوباره بفرستید: آدرس‌های ساخته‌شده همان‌ها برمی‌گردند."
      headers:
        Retry-After: { description: چند ثانیه صبر کنید., schema: { type: integer } }
      content: { application/json: { schema: { $ref: "#/components/schemas/AddressUnavailable" } } }

  schemas:
    AddressRefused:
      type: object
      required: [error]
      properties:
        error: { type: string, description: "`invalid request`، `malformed request body` یا `label_taken`." }
        index: { type: integer, minimum: 0, maximum: 99, description: با `label_taken`، جای برچسب در `labels`. }
        code: { $ref: "#/components/schemas/RefusalCode" }
    AddressUnavailable:
      type: object
      required: [error]
      properties:
        error: { type: string, description: "`try again later`، `org_paused` یا `network_unavailable`." }
        network: { type: string, enum: [TRON, BSC, ETHEREUM, BITCOIN], description: با `network_unavailable`، شبکه‌ای که پاسخ نداد. }
    Error:
      type: object
      required: [error]
      properties:
        error: { type: string, description: پیام ثابت. جزئیات داخلی هرگز در پاسخ نمی‌آید. }
        code: { $ref: "#/components/schemas/RefusalCode" }
    RefusalCode:
      type: string
      description: "فقط وقتی هست که دلیل رد شدن معلوم و ثابت است؛ وضعیت HTTP و `error` همان‌اند که بودند. `fee_topup_cap_required`: افزودن شبکه به ایستگاه کارمزد سقف شارژ همان شبکه را می‌خواهد. `fee_station_exists`: سازمان ایستگاه کارمزد دارد یا یکی در انتظار تأیید است. `fee_station_main_address_only`: ایستگاه کارمزد جز آدرس اصلی آدرسی ندارد. `wallet_frozen`: کیف پول قفل است. `chain_not_enabled`: این شبکه در رومو هنوز فعال نیست. `org_policy_pending`: قانون سازمان هنوز تأیید نشده است. `address_limit`: سهمیه‌ی آدرس پلن تمام شده است. `name_taken`: این نام یا برچسب پیش‌تر گرفته شده است. برای `code` ناشناخته، `error` و وضعیت HTTP را بخوانید."
      enum: [fee_topup_cap_required, fee_station_exists, fee_station_main_address_only, wallet_frozen, chain_not_enabled, org_policy_pending, address_limit, name_taken]
    Suspended:
      type: object
      required: [error]
      description: سازمان معلق است. برداشت تازه رد می‌شود و بقیه‌ی کارها ادامه دارند.
      properties:
        error: { type: string, description: "`org_suspended` یا `permission denied`." }
        reason_code: { type: string, enum: [non_payment, security_review, contract_end, other] }
        public_note: { type: string, maxLength: 200, description: توضیح اپراتور رومو برای مشتری. }
    InsufficientBalance:
      type: object
      required: [error]
      description: آدرس مبدأ مبلغ و کارمزد را ندارد. هیچ برداشتی ثبت و هیچ قفلی گرفته نمی‌شود.
      properties:
        error: { type: string, description: "`insufficient_balance`، یا پیام‌های دیگر خطای `409`." }
        asset: { type: string }
        requested: { type: string, description: رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. }
        available: { type: string, description: رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. }
        fee_asset: { type: [string, "null"], description: سکه‌ی زنجیره که کارمزد را می‌دهد. وقتی خود دارایی کم است `null`. }
        fee_ceiling: { type: [string, "null"], description: کارمزدی که لازم است. }
        fee_available: { type: [string, "null"], description: موجودی سکه‌ی کارمزد. }
    Network:
      type: string
      enum: [TRON, BSC]
    Scope:
      type: string
      enum: [read, withdraw, approve-read, addresses, sweep]
    TransactionStatus:
      type: string
      enum: [pending, checking, confirmed, failed, cancelled]
    TransactionStage:
      type: string
      enum: [requested, waiting_approval, waiting_phone, signing, signing_retry, signed, broadcast, confirming]
    TransactionReason:
      type: string
      enum: [rejected, expired, signing_failed, broadcast_failed, reorged]
    WhoAmI:
      type: object
      required: [org_id, key_id, scopes, allowed_wallet_ids, all_wallets]
      properties:
        org_id: { type: string, format: uuid, description: شناسه‌ی سازمان. }
        key_id: { type: string, format: uuid }
        scopes: { type: array, items: { $ref: "#/components/schemas/Scope" } }
        allowed_wallet_ids: { type: array, items: { type: string, format: uuid }, description: کیف پول‌های این کلید. }
        all_wallets: { type: boolean, description: کلید به همه‌ی کیف پول‌ها دسترسی دارد. فقط برای کلید بی دسترسی نوشتن ممکن است. }
    Value:
      type: object
      description: ارزش دلاری، فقط برای نمایش.
      properties:
        usd_total: { type: [string, "null"], description: جمع ارزش دارایی‌های ارزش‌دار، رشته‌ی اعشاری. `null` یعنی هنوز خوانده نشده. }
        unpriced_count: { type: integer, description: دارایی‌هایی که نرخ قابل استفاده ندارند. }
        oldest_read_at: { type: [string, "null"], format: date-time }
    Wallet:
      type: object
      title: با id
      required: [wallet_id, name, status, networks]
      properties:
        wallet_id: { type: string, format: uuid }
        name: { type: string }
        status: { type: string, enum: [keygen, awaiting_backup_confirmation, active, frozen, retired] }
        networks: { type: array, items: { $ref: "#/components/schemas/WalletNetwork" }, description: شبکه‌های کیف پول. }
    WalletNetwork:
      type: object
      required: [network, address, readiness]
      properties:
        network: { $ref: "#/components/schemas/Network" }
        address: { type: string, description: آدرس اصلی کیف پول روی این شبکه. روی شبکه‌های EVM به شکل EIP-55. در حالت `preparing` خالی است. }
        readiness: { type: string, enum: [ready, preparing], description: "`preparing` یعنی شبکه تازه اضافه شده یا کیف پول هنوز در حال ساخت کلید است؛ آدرس خالی است و برداشت روی آن رد می‌شود." }
    WalletRow:
      allOf:
        - { $ref: "#/components/schemas/Wallet" }
        - type: object
          properties:
            value: { $ref: "#/components/schemas/Value" }
    WalletPage:
      type: object
      title: بی id
      required: [wallets, next_cursor]
      properties:
        wallets: { type: array, items: { $ref: "#/components/schemas/WalletRow" } }
        next_cursor: { type: string, description: در صفحه‌ی آخر خالی است. }
    WalletAsset:
      type: object
      required: [asset, network, amount]
      properties:
        asset: { type: string, description: "کد دارایی، مثل `TRON_USDT`." }
        network: { $ref: "#/components/schemas/Network" }
        amount: { type: string, description: رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. }
        usd_value: { type: [string, "null"], description: ارزش دلاری، هرگز صفر به جای «بی نرخ». }
        rate_usd: { type: [string, "null"] }
        read_at: { type: [string, "null"], format: date-time }
    WalletAssetPage:
      type: object
      required: [assets, next_cursor]
      properties:
        assets: { type: array, items: { $ref: "#/components/schemas/WalletAsset" } }
        next_cursor: { type: string }
    BalanceBucketAsset:
      type: object
      properties:
        asset: { type: string }
        network: { $ref: "#/components/schemas/Network" }
        amount: { type: string }
        usd_value: { type: [string, "null"] }
        holder_count: { type: integer, description: تعداد آدرس‌هایی که بیش از صفر دارند. }
    BalanceBucket:
      type: object
      properties:
        total: { $ref: "#/components/schemas/Value" }
        assets: { type: array, items: { $ref: "#/components/schemas/BalanceBucketAsset" } }
        address_count: { type: integer }
        unread_count: { type: integer, description: آدرس‌هایی که هنوز خوانده نشده‌اند. }
    BalanceSummary:
      type: object
      required: [main, sweep, unswept]
      properties:
        main: { $ref: "#/components/schemas/BalanceBucket" }
        sweep: { $ref: "#/components/schemas/BalanceBucket" }
        unswept: { $ref: "#/components/schemas/BalanceBucket" }
    WithdrawalRequest:
      type: object
      additionalProperties: false
      required: [wallet_id, asset, amount, to_address, expires_at]
      properties:
        wallet_id: { type: string, format: uuid, description: کیف پول مبدأ. }
        asset: { type: string, description: "کد دارایی، مثل `TRON_USDT` یا `BSC_USDT`. شبکه از همین نام معلوم می‌شود." }
        amount: { type: string, description: "رشته‌ی اعشاری در واحد خود دارایی، مثل `100.50`. حداکثر به اندازه‌ی `decimals` دارایی در `GET /v1/assets`، بیشتر از آن `400`." }
        to_address: { type: string, description: "آدرس مقصد. روی شبکه‌های EVM تمام‌کوچک یا دقیقاً EIP-55." }
        expires_at: { type: string, format: date-time, description: تا این زمان باید تأیید و امضا شود، وگرنه منقضی می‌شود. اگر زمان گذشته باشد، برداشت ثبت می‌شود و پس از سنجش قانون‌ها `failed` می‌شود. }
        external_ref: { type: string, description: مرجع خود شما. در Webhook برمی‌گردد. }
    TxPolicy:
      type: [object, "null"]
      description: تصمیم قانون‌ها. وقتی تصمیمی ثبت نشده باشد `null` است.
      properties:
        decision: { type: string, enum: [auto_approve, needs_approval, reject] }
        matched_rule:
          type: [object, "null"]
          properties:
            rule_id: { type: string }
            name: { type: string }
    Transaction:
      type: object
      required: [id, wallet_id, status, network, asset, amount]
      properties:
        id: { type: string, format: uuid }
        wallet_id: { type: string, format: uuid }
        idempotency_key: { type: string }
        type: { type: string, enum: [withdrawal, internal, sweep], description: "فقط‌خواندنی. `internal` جابه‌جایی از کنسول است. `sweep` فقط با `?id=` دیده می‌شود." }
        origin: { type: string, enum: [api, console, system], description: "فقط‌خواندنی. `system` فقط برای `sweep` است." }
        network: { $ref: "#/components/schemas/Network" }
        asset: { type: string, description: "کد دارایی، مثل `TRON_USDT`." }
        amount: { type: string, description: رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. }
        amount_raw: { type: string, description: همان مبلغ در کوچک‌ترین واحد، برای تطبیق دقیق. }
        amount_usd: { type: [string, "null"], description: "ارزش مبلغ به دلار آمریکا وقتی قانون‌ها آن را سنجیدند، رشته‌ی اعشاری مثل `301.5`. پیش از آن، یا وقتی دارایی نرخ نداشت، `null`." }
        usd_rate: { type: [string, "null"], description: "نرخ دلاری هر واحد دارایی که `amount_usd` با آن حساب شد، مثل `1.005`. هر وقت `amount_usd` نباشد، `null`." }
        usd_rate_at: { type: [string, "null"], format: date-time, description: "زمانی که آن نرخ خوانده شد. هر وقت `amount_usd` نباشد، `null`." }
        from_address: { type: [string, "null"], description: "آدرس سازمان که تراکنش از آن فرستاده می‌شود: آدرس اصلی کیف پول یا یکی از آدرس‌های جمع‌آوری. اگر شناخته نباشد، `null`." }
        from_label: { type: [string, "null"], description: "برچسب همان آدرس، همان که الان دارد. بی برچسب، `null`." }
        to_address: { type: string }
        external_ref: { type: string }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        stage: { type: [string, "null"], enum: [requested, waiting_approval, waiting_phone, signing, signing_retry, signed, broadcast, confirming, null], description: "کجای کار است. فقط در `pending` و `checking`، وگرنه `null`." }
        reason: { type: [string, "null"], enum: [rejected, expired, signing_failed, broadcast_failed, reorged, null], description: "چرا شکست خورد. فقط در `failed`، وگرنه `null`." }
        tx_hash: { type: string, description: هش زنجیره، پس از ارسال. }
        expires_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        superseded_by: { type: string, description: جانشین تراکنش، فقط وقتی هست که جانشین ساخته شده باشد. }
        policy: { $ref: "#/components/schemas/TxPolicy" }
    TransactionRow:
      allOf:
        - { $ref: "#/components/schemas/Transaction" }
        - type: object
          properties:
            approvals:
              type: object
              description: فقط وقتی `stage` برابر `waiting_approval` است.
              properties:
                approved: { type: integer }
                required: { type: integer }
    TransactionOne:
      type: object
      title: با id
      required: [transaction]
      properties:
        transaction: { $ref: "#/components/schemas/Transaction" }
    TransactionCreated:
      type: object
      required: [transaction, created]
      properties:
        transaction: { $ref: "#/components/schemas/Transaction" }
        created: { type: boolean, description: "`true` برای تراکنش تازه، `false` برای تکرار همان درخواست." }
    TransactionPage:
      type: object
      title: بی id
      required: [transactions, next_cursor, total]
      properties:
        transactions: { type: array, items: { $ref: "#/components/schemas/TransactionRow" } }
        next_cursor: { type: string, description: در صفحه‌ی آخر خالی است. }
        total: { type: [integer, "null"], description: همه‌ی ردیف‌های منطبق در صفحه‌ی اول، و `null` در صفحه‌های بعد. }
    CancelRequest:
      type: object
      additionalProperties: false
      required: [transaction_id, reason]
      properties:
        transaction_id: { type: string, format: uuid }
        reason: { type: string, minLength: 1, maxLength: 500, description: دلیل لغو، 1 تا 500 نویسه. }
    ApprovalDetail:
      type: object
      required: [result, quorums, approvers]
      properties:
        result: { type: string, enum: [pending, approved, rejected, expired] }
        quorums:
          type: array
          items:
            type: object
            properties:
              mode: { type: string, enum: [part, full, percent] }
              threshold: { type: integer }
              eligible_count: { type: integer }
              approved: { type: integer }
              rejected: { type: integer }
        approvers:
          type: array
          items:
            type: object
            properties:
              approver_name: { type: string, description: نام نمایشی عضو، همان که در کنسول دیده می‌شود. اگر عضو دیگر در سازمان نباشد ممکن است خالی باشد. }
              decision: { type: string, enum: [approve, reject] }
              at: { type: [string, "null"], format: date-time }
    NetworkAddress:
      type: object
      required: [network, address]
      properties:
        network: { $ref: "#/components/schemas/Network" }
        address: { type: [string, "null"], description: "روی شبکه‌های EVM به شکل EIP-55. تا وقتی `watch` آدرس `pending` است `null` است: آدرس را تا ثبت شدنش در فهرست پایش نشان ندهید و پول به آن نفرستید." }
    Address:
      type: object
      required: [id, wallet_id, kind, status, addresses]
      properties:
        id: { type: string, format: uuid }
        wallet_id: { type: string, format: uuid }
        group_id: { type: string }
        group: { type: string }
        kind: { type: string, enum: [main, deposit, sweep, fee] }
        label: { type: string }
        status: { type: string, enum: [active, archived] }
        addresses: { type: array, items: { $ref: "#/components/schemas/NetworkAddress" }, description: "یک آدرس روی یک شبکه همه‌ی دارایی‌های آن شبکه را می‌گیرد. فهرست دارایی‌ها در `GET /v1/assets` است." }
        created_at: { type: string, format: date-time }
        archived_at: { type: [string, "null"], format: date-time }
        watch: { type: string, enum: [pending, watched], description: "`pending`: هنوز در فهرست پایش شبکه ثبت نشده (معمولاً چند ثانیه؛ رومو خودش دوباره تلاش می‌کند) و متن آدرس تا آن وقت `null` است. واریزی که در این فاصله برسد، پس از ثبت پیدا و ثبت می‌شود. `watched`: پایش می‌شود." }
    AddressOne:
      type: object
      title: با id
      required: [address]
      properties:
        address: { $ref: "#/components/schemas/Address" }
    AddressList:
      type: object
      required: [addresses]
      properties:
        addresses: { type: array, items: { $ref: "#/components/schemas/Address" } }
    AddressPage:
      type: object
      title: بی id
      required: [addresses, next_cursor, total]
      properties:
        addresses: { type: array, items: { $ref: "#/components/schemas/Address" } }
        next_cursor: { type: string }
        total: { type: [integer, "null"] }
    AddressCreate:
      type: object
      additionalProperties: false
      required: [wallet_id, label]
      properties:
        wallet_id: { type: string, format: uuid }
        group: { type: string, description: نام گروه. خالی یعنی گروه پیش‌فرض. }
        label: { type: string, description: 'نام نمایشی، مثلاً "مشتری طلایی".' }
    AddressBatch:
      type: object
      additionalProperties: false
      required: [wallet_id]
      description: یا `labels`، یا `label_prefix` با `count`.
      properties:
        wallet_id: { type: string, format: uuid }
        group: { type: string }
        labels: { type: array, minItems: 1, maxItems: 100, items: { type: string }, description: هر برچسب در کیف پول یکتاست. }
        label_prefix: { type: string, description: "برچسب‌ها «`label_prefix` n» می‌شوند. n از بعد از بزرگ‌ترین شماره‌ی همین پیشوند در این کیف پول ادامه پیدا می‌کند." }
        count: { type: integer, minimum: 1, maximum: 100, description: با `label_prefix`، تعداد آدرس‌ها. }
    AddressUpdate:
      type: object
      additionalProperties: false
      description: دست‌کم یکی از دو فیلد لازم است.
      properties:
        label: { type: string }
        archive: { type: boolean, description: "`true` آدرس را برای همیشه بایگانی می‌کند." }
    AddressBalance:
      type: object
      properties:
        address_id: { type: string }
        wallet_id: { type: string }
        kind: { type: string, enum: [main, deposit, sweep] }
        network: { $ref: "#/components/schemas/Network" }
        address: { type: [string, "null"], description: "فقط وقتی `watch` برابر `watched` است پر است. در غیر این صورت `null` است." }
        label: { type: string }
        status: { type: string, enum: [active, archived] }
        group_id: { type: string }
        total: { $ref: "#/components/schemas/Value" }
        assets: { type: array, items: { $ref: "#/components/schemas/WalletAsset" }, description: فقط دارایی‌های بیش از صفر. }
        last_activity_at: { type: [string, "null"], format: date-time }
        watch: { type: [string, "null"], enum: [pending, watched, null], description: "همان `watch` آدرس (`GET /v1/addresses`). اگر خوانده نشد `null` است و `address` هم `null` است." }
    AddressBalancePage:
      type: object
      required: [addresses, next_cursor, total]
      properties:
        addresses: { type: array, items: { $ref: "#/components/schemas/AddressBalance" } }
        next_cursor: { type: string }
        total: { type: [integer, "null"] }
    AddressGroup:
      type: object
      properties:
        id: { type: string, format: uuid }
        wallet_id: { type: string, format: uuid }
        name: { type: string }
        status: { type: string, enum: [active, archived] }
        created_at: { type: string, format: date-time }
    AddressGroupList:
      type: object
      required: [groups]
      properties:
        groups: { type: array, items: { $ref: "#/components/schemas/AddressGroup" } }
    AddressGroupOne:
      type: object
      required: [group]
      properties:
        group: { $ref: "#/components/schemas/AddressGroup" }
    AddressGroupCreate:
      type: object
      additionalProperties: false
      required: [wallet_id, name]
      properties:
        wallet_id: { type: string, format: uuid }
        name: { type: string }
    Deposit:
      type: object
      properties:
        id: { type: string, format: uuid }
        wallet_id: { type: string, format: uuid }
        address_id: { type: string, format: uuid }
        network: { $ref: "#/components/schemas/Network" }
        asset: { type: string, description: "کد دارایی، مثل `TRON_TRX`." }
        amount: { type: string, description: "مبلغی که اعتبار می‌شود، رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. واریزی که چند انتقال کوچک را جمع کرده کل جمع را نشان می‌دهد." }
        amount_raw: { type: string, description: همان مبلغ در کوچک‌ترین واحد، برای تطبیق دقیق. }
        tx_hash: { type: string, description: "هش انتقال. در واریز جمع‌شده هش آخرین انتقال است." }
        tx_hashes: { type: array, items: { type: string }, description: "فقط در واریز جمع‌شده می‌آید: همه‌ی تراکنش‌هایی که این واریز پوشش می‌دهد، قدیمی‌ترین اول و `tx_hash` آخر." }
        confirmations: { type: integer }
        status: { type: string, enum: [detected, confirmed, reorged] }
        to_address: { type: string }
        from_address: { type: string }
        detected_at: { type: string, format: date-time }
        confirmed_at: { type: [string, "null"], format: date-time }
    DepositOne:
      type: object
      title: با id
      required: [deposit]
      properties:
        deposit: { $ref: "#/components/schemas/Deposit" }
    DepositPage:
      type: object
      title: بی id
      required: [deposits, next_cursor, total]
      properties:
        deposits: { type: array, items: { $ref: "#/components/schemas/Deposit" } }
        next_cursor: { type: string }
        total: { type: [integer, "null"] }
    SweepSchedule:
      type: object
      required: [kind]
      properties:
        kind: { type: string, enum: [interval, daily, manual] }
        every_hours: { type: integer, description: "1 تا 24، فقط برای `interval`." }
        hours: { type: array, items: { type: integer }, description: "1 تا 24 ساعت از 0 تا 23، مرتب و بی تکرار، فقط برای `daily`." }
        timezone: { type: string, description: "نام IANA، فقط برای `daily`." }
    SweepMinAmount:
      type: object
      properties:
        asset: { type: string }
        amount: { type: string }
    SweepRuleSet:
      type: object
      additionalProperties: false
      required: [group_id, base_version, enabled, schedule]
      properties:
        group_id: { type: string, format: uuid }
        base_version: { type: integer, description: "0 برای قانون اول، بعد نسخه‌ای که جایگزین می‌شود." }
        enabled: { type: boolean }
        schedule: { $ref: "#/components/schemas/SweepSchedule" }
        min_amounts: { type: array, items: { $ref: "#/components/schemas/SweepMinAmount" } }
        excluded_assets: { type: array, items: { type: string } }
    SweepRule:
      type: object
      properties:
        wallet_id: { type: string }
        group_id: { type: string }
        group: { type: string }
        enabled: { type: boolean }
        schedule: { $ref: "#/components/schemas/SweepSchedule" }
        min_amounts: { type: array, items: { $ref: "#/components/schemas/SweepMinAmount" } }
        excluded_assets: { type: array, items: { type: string } }
        version: { type: integer }
        updated_at: { type: [string, "null"], format: date-time }
        last_run_at: { type: [string, "null"], format: date-time }
        next_run_at: { type: [string, "null"], format: date-time }
    SweepRuleList:
      type: object
      required: [rules]
      properties:
        rules: { type: array, items: { $ref: "#/components/schemas/SweepRule" } }
    SweepRuleOne:
      type: object
      required: [rule]
      properties:
        rule: { $ref: "#/components/schemas/SweepRule" }
    Sweep:
      type: object
      properties:
        id: { type: string }
        wallet_id: { type: string }
        group_id: { type: string }
        group: { type: string }
        address_id: { type: string }
        label: { type: string }
        from_address: { type: string }
        to_address: { type: string }
        network: { $ref: "#/components/schemas/Network" }
        asset: { type: string }
        amount: { type: string, description: رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. }
        amount_raw: { type: string, description: همان مبلغ در کوچک‌ترین واحد، برای تطبیق دقیق. }
        tx_id: { type: string, description: "تراکنش را با `GET /v1/transactions?id=` بخوانید." }
        status: { type: string, enum: [pending, completed, failed] }
        failure: { type: [string, "null"] }
        sweep_request_id: { type: [string, "null"], description: "شناسه‌ی درخواست `POST /v1/sweeps`. برای sweep زمان‌بندی‌شده `null` است." }
        tx_hash: { type: [string, "null"] }
        fee_actual: { type: [string, "null"] }
        created_at: { type: string, format: date-time }
        completed_at: { type: [string, "null"], format: date-time }
    SweepPage:
      type: object
      required: [sweeps, next_cursor]
      properties:
        sweeps: { type: array, items: { $ref: "#/components/schemas/Sweep" } }
        next_cursor: { type: string }
    SweepNow:
      type: object
      additionalProperties: false
      description: دقیقاً یکی از دو فیلد.
      properties:
        group_id: { type: string, format: uuid }
        address_id: { type: string, format: uuid }
    SweepNowResult:
      type: object
      required: [created, request]
      properties:
        created: { type: boolean }
        request:
          type: object
          properties:
            id: { type: string }
            wallet_id: { type: string }
            group_id: { type: string }
            address_id: { type: [string, "null"] }
            done: { type: boolean }
            sweeps_made: { type: integer }
            created_at: { type: string, format: date-time }
            done_at: { type: [string, "null"], format: date-time }
    FeeStation:
      type: object
      required: [found]
      properties:
        found: { type: boolean, description: بی ایستگاه `false` است و فیلد دیگری نمی‌آید. }
        wallet_id: { type: string }
        network: { $ref: "#/components/schemas/Network" }
        address: { type: string, description: آدرس شارژ ایستگاه. }
        balance: { type: string, description: موجودی همین آدرس روی زنجیره. }
        asset: { type: string }
        enabled: { type: boolean }
        per_tx_cap: { type: string, description: سقف هر شارژ. }
        daily_cap: { type: string, description: سقف 24 ساعت غلتان. }
        topped_up_24h: { type: string, description: جمع شارژهای 24 ساعت گذشته. }
        threshold: { type: string, description: آستانه‌ی هشدار. }
        below: { type: boolean, description: موجودی زیر آستانه است. }
    RegistryAsset:
      type: object
      properties:
        code: { type: string }
        network: { $ref: "#/components/schemas/Network" }
        type: { type: string, enum: [native, trc20, bep20] }
        contract: { type: string, description: برای سکه‌ی بومی خالی است. }
        decimals: { type: integer, description: حداکثر رقم بعد از ممیز در `amount`. }
        min_deposit: { type: string, description: حداقل واریز در واحد خود دارایی. }
        name: { type: string }
        status: { type: string, enum: [active, disabled] }
    RegistryPage:
      type: object
      required: [assets, next_after_code]
      properties:
        assets: { type: array, items: { $ref: "#/components/schemas/RegistryAsset" } }
        next_after_code: { type: string, description: در صفحه‌ی آخر خالی است. }
    WalletPolicy:
      type: object
      properties:
        wallet_id: { type: string }
        version: { type: integer }
        rules: { type: object, description: قانون‌ها به JSON. }
    ChangeRequest:
      type: object
      properties:
        id: { type: string }
        wallet_id: { type: string }
        kind: { type: string, description: "نوع تغییر با حروف کوچک، مثل `webhook_set` یا `api_key_add`." }
        fields: { type: array, items: { type: string } }
        status: { type: string, enum: [pending_approval, applied, rejected, expired] }
        close_reason: { type: string }
        required_approvals: { type: integer }
        approvals: { type: integer }
        rejections: { type: integer }
        created_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
    ChangeRequestOne:
      type: object
      title: با id
      required: [change_request]
      properties:
        change_request: { $ref: "#/components/schemas/ChangeRequest" }
    ChangeRequestList:
      type: object
      title: با wallet_id
      required: [change_requests]
      properties:
        change_requests: { type: array, items: { $ref: "#/components/schemas/ChangeRequest" } }
    WebhookKeys:
      type: object
      required: [keys]
      properties:
        keys:
          type: array
          items:
            type: object
            properties:
              key_version: { type: integer }
              public_key: { type: string, description: base64 استاندارد 32 بایت. }
    WebhookReplay:
      type: object
      additionalProperties: false
      required: [event_id]
      properties:
        event_id: { type: string, format: uuid }
    WebhookReplayFailed:
      type: object
      additionalProperties: false
      required: [from, to]
      properties:
        from: { type: string, format: date-time }
        to: { type: string, format: date-time }
    WebhookReplayed:
      type: object
      required: [replayed]
      properties:
        replayed: { type: integer }
    WebhookDelivery:
      type: object
      properties:
        event_id: { type: string }
        tx_id: { type: string }
        kind: { type: string }
        status: { type: string, enum: [pending, delivered, skipped, failed] }
        attempt: { type: integer }
        next_retry_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    WebhookDeliveryOne:
      type: object
      required: [delivery]
      properties:
        delivery: { $ref: "#/components/schemas/WebhookDelivery" }
    Usage:
      type: object
      required: [plan, months, address_usage]
      properties:
        plan: { type: string }
        months:
          type: array
          items:
            type: object
            properties:
              period: { type: string, description: "`YYYY-MM`." }
              closed_at: { type: [string, "null"], format: date-time, description: ماه باز `null` است. }
              networks:
                type: array
                items:
                  type: object
                  properties:
                    network: { type: string }
                    usd_amount: { type: string }
                    unpriced_count: { type: integer }
                    assets:
                      type: array
                      items:
                        type: object
                        properties:
                          asset: { type: string, description: کد دارایی. }
                          confirmed_count: { type: integer }
                          amount_sum: { type: string }
                          usd_amount: { type: string }
                          unpriced_count: { type: integer }
                          failed_count: { type: integer }
        address_usage:
          type: array
          items:
            type: object
            properties:
              period: { type: string }
              active_count: { type: integer }
    EventEnvelope:
      type: object
      required: [version, event_id, org_id, kind, timestamp, data]
      properties:
        version: { type: integer, const: 2 }
        event_id: { type: string, format: uuid, description: "همان `X-Custody-Event-Id`. تکراری را با این بشناسید." }
        org_id: { type: string, format: uuid }
        kind: { type: string }
        timestamp: { type: string, description: "همیشه 27 نویسه: `YYYY-MM-DDTHH:MM:SS.ffffffZ`. جزو پیام امضاشده است." }
    TransactionEvent:
      allOf:
        - { $ref: "#/components/schemas/EventEnvelope" }
        - type: object
          properties:
            data:
              type: object
              properties:
                tx_id: { type: string }
                type: { type: string, enum: [withdrawal, internal] }
                origin: { type: string, enum: [api, console] }
                idempotency_key: { type: string }
                external_ref: { type: string, description: اگر داده نشده باشد رشته‌ی خالی است. }
                wallet_id: { type: string }
                network: { type: string }
                asset: { type: string, description: کد دارایی. }
                to_address: { type: string, description: روی شبکه‌های EVM به شکل EIP-55. }
                amount: { type: string, description: رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. }
                amount_raw: { type: string, description: همان مبلغ در کوچک‌ترین واحد. }
                status: { $ref: "#/components/schemas/TransactionStatus" }
                stage: { $ref: "#/components/schemas/TransactionStage", description: "فقط در `pending` و `checking` می‌آید." }
                reason: { $ref: "#/components/schemas/TransactionReason", description: "فقط در `failed` می‌آید." }
                reason_detail: { type: string, description: "توضیح فارسی برای نمایش، فقط برای اطلاع. ممکن است نیاید. کد را روی `reason` بنویسید." }
                tx_hash: { type: string, description: فقط وقتی هست که وجود داشته باشد. }
                fee_actual: { type: string, description: "کارمزد واقعی در واحد سکه‌ی شبکه. فقط وقتی هست که وجود داشته باشد." }
    DepositEvent:
      allOf:
        - { $ref: "#/components/schemas/EventEnvelope" }
        - type: object
          properties:
            data:
              type: object
              properties:
                deposit_id: { type: string, description: "برای یک واریز همیشه یکی است، با هر چند انتقال. کلید یکتای اعتبار کاربر." }
                wallet_id: { type: string }
                group: { type: string }
                address_id: { type: string }
                label: { type: string }
                network: { type: string }
                asset: { type: string, description: کد دارایی. }
                amount: { type: string, description: "رشته‌ی اعشاری در واحد خود دارایی، مثل `100.5`. در `deposit.confirmed` مبلغی است که اعتبار می‌شود و اگر انتقال‌های کوچک منتظر بوده‌اند، می‌تواند بیشتر از `deposit.detected` همان واریز باشد." }
                amount_raw: { type: string, description: همان مبلغ در کوچک‌ترین واحد. }
                tx_hash: { type: string, description: "هش انتقال. در واریز جمع‌شده هش آخرین انتقال است." }
                tx_hashes: { type: array, items: { type: string }, description: "فقط در واریز جمع‌شده می‌آید: همه‌ی تراکنش‌هایی که این واریز پوشش می‌دهد، قدیمی‌ترین اول و `tx_hash` آخر." }
                confirmations: { type: integer }
    SweepEvent:
      allOf:
        - { $ref: "#/components/schemas/EventEnvelope" }
        - type: object
          properties:
            data:
              type: object
              properties:
                sweep_id: { type: string }
                tx_id: { type: string }
                wallet_id: { type: string }
                group: { type: string }
                address_id: { type: string }
                label: { type: string }
                network: { type: string }
                asset: { type: string }
                amount: { type: string, description: "آنچه به مقصد رسید، در واحد خود دارایی." }
                amount_raw: { type: string, description: همان مبلغ در کوچک‌ترین واحد. }
                from_address: { type: string }
                to_address: { type: string }
                tx_hash: { type: string }
                fee_actual: { type: string }
    FeeStationEvent:
      allOf:
        - { $ref: "#/components/schemas/EventEnvelope" }
        - type: object
          properties:
            data:
              type: object
              properties:
                wallet_id: { type: string }
                network: { type: string }
                asset: { type: string }
                address: { type: string }
                balance: { type: string }
                threshold: { type: string }
