> ## Documentation Index
> Fetch the complete documentation index at: https://docs.venlyfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit a route ownership proof

> Submit the signed message and resume a paused pay-out route registration.

**Requires scope:** `manage:pay-outs` — see [Required scopes](/getting-started/authentication#required-scopes-finance-api).

Submits the signed message and resumes the route's registration. On success the route leaves `AWAITING_OWNERSHIP_PROOF` and continues to `ACTIVE` asynchronously.

<Note>A `200` means the proof was accepted, not that the route is ready. Poll the route or [register a webhook](/guides/finance/webhooks) to hear when it reaches `ACTIVE` — only then does it have a `depositAddress`.</Note>

Send `message` **exactly as [prepare](/api-reference/Finance-API/payout-routes/prepare-a-route-ownership-proof) returned it**. It must match the canonical text for this route, which embeds the route's own wallet, and the signature must recover that wallet.

That's what makes the stateless design safe, and it has two consequences worth knowing:

* A proof signed for a different wallet, route or tenant is rejected — it can't be replayed here.
* A message with extra text wrapped around the expected content is rejected too, even if the signature is valid.

You don't have to call `prepare` first. This endpoint only cares that the message is canonical and the signature matches.

<Note>The proof is never stored or logged. It's verified in memory, forwarded once, and discarded — only the outcome is persisted.</Note>

| Code                             | Meaning                                                                        |
| -------------------------------- | ------------------------------------------------------------------------------ |
| `ownership-proof-not-applicable` | This route needs no owner-signed proof, or its source wallet is Venly-managed. |
| `invalid-proof-message`          | `message` isn't the canonical text for this route.                             |
| `signature-mismatch`             | `signature` doesn't recover the route's wallet address.                        |


## OpenAPI

````yaml api-reference/Finance-API-Specs.yaml POST /accounts/{accountId}/payout-routes/{routeId}/ownership-proof/complete
openapi: 3.1.0
info:
  title: Venly Finance API
  description: >
    REST API for the Venly Finance platform:

    - Party management (Individuals & Organisations)

    - Identity verification — hosted KYC/KYB links and Sumsub share-token
    forwarding

    - Account management with party association

    - Wallet balances & token allowances on supported chains

    - Virtual bank accounts for global pay-in (EUR SEPA, USD ACH/WIRE/RTP/SWIFT)

    - Fiat-to-crypto payment sessions (pay-in)

    - Bank pay-outs — beneficiary allow-listing, routes, and PULL/PUSH pay-outs

    - Account-to-account fiat & crypto transfers

    - EIP-2612 permits and allowances

    - Webhook registration for asynchronous event delivery


    ## Authentication


    All endpoints use OAuth2 client credentials. Obtain a token first, then
    include it in every request:


    **Step 1 — Get a token:**

    ```bash

    curl -X POST
    https://login.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token
    \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=client_credentials&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}"
    ```


    **Step 2 — Use the token:**

    ```

    Authorization: Bearer {access_token}

    ```


    Tokens expire after **5 minutes**. Implement refresh logic in your client.
  version: 1.5.0
  contact:
    name: Venly Support
    email: support@venly.io
    url: https://docs.venlyfinance.com
  license:
    name: Proprietary
  x-logo:
    url: https://venlyfinance.com/logo.png
  x-security-contact: security@venly.io
servers:
  - url: https://api.venlyfinance.com/v1
    description: Production
  - url: https://api-staging.venlyfinance.com/v1
    description: Staging
security:
  - OAuth2: []
tags:
  - name: Parties
    description: Party management (Individuals & Organisations)
  - name: Accounts
    description: Account management, party-role associations
  - name: Wallets
    description: Blockchain wallet balances
  - name: Virtual Bank Accounts
    description: Virtual bank account payment references for global payments
  - name: Fiat-to-crypto Payment Sessions
    description: Fiat-to-crypto payment session creation
  - name: Payout Bank Accounts
    description: Allow-listing beneficiary bank accounts as pay-out destinations
  - name: Payout Routes
    description: >-
      Account-scoped routes pairing a beneficiary bank account with a crypto
      deposit asset
  - name: Payouts
    description: Crypto-to-fiat pay-outs over a registered route
  - name: Transfers
    description: Fiat and crypto transfer operations between accounts
  - name: Permits
    description: EIP-712 permit signature management for token approvals
  - name: Supported Assets
    description: >-
      Discover which chains and assets your tenant can settle in, and whether an
      account can use them yet.
  - name: Allowances
    description: Token allowance management for wallets
  - name: Webhooks
    description: Webhook registration and delivery testing
paths:
  /accounts/{accountId}/payout-routes/{routeId}/ownership-proof/complete:
    post:
      tags:
        - Payout Routes
      summary: Submit a pay-out route ownership proof
      description: >
        **Requires scope:** `manage:pay-outs`


        Submits the signed message and resumes the route's registration. On
        success the route leaves

        `AWAITING_OWNERSHIP_PROOF` and continues to `ACTIVE` asynchronously —
        poll the route or

        [register a webhook](/guides/finance/webhooks) for the outcome.


        Send the `message` **exactly as `prepare` returned it**. It must match
        the canonical text for

        this route, which embeds the route's own wallet, and the signature must
        recover that wallet.

        A proof signed for a different wallet, route or tenant is rejected, and
        so is a message with

        extra text wrapped around the expected content.


        You do not have to call `prepare` first — this endpoint only cares that
        the message is the

        canonical one and the signature matches. The proof is never stored or
        logged: it is verified

        in memory and discarded.
      operationId: completePayoutRouteOwnershipProof
      parameters:
        - $ref: '#/components/parameters/AccountId'
        - $ref: '#/components/parameters/RouteId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompletePayoutOwnershipProofRequest'
            example:
              message: I confirm ownership of wallet 0x7c6b9095... on 20/08/2026
              signature: >-
                0x9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a01b
      responses:
        '200':
          description: The route, with registration resumed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SinglePayoutRouteResponse'
        '400':
          description: >
            Bad request. Notable codes:

            - `ownership-proof-not-applicable` — this route needs no
            owner-signed proof, or its source wallet is Venly-managed

            - `invalid-proof-message` — `message` is not the canonical text for
            this route

            - `signature-mismatch` — `signature` does not recover the route's
            wallet address
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    AccountId:
      name: accountId
      in: path
      required: true
      description: Unique account identifier
      schema:
        type: string
        format: uuid
      example: b2a1f0e9-8c7d-4e3a-9f21-0a1b2c3d4e5f
    RouteId:
      name: routeId
      in: path
      required: true
      description: Unique pay-out route identifier
      schema:
        type: string
        format: uuid
      example: 7b683e87-4c73-4821-aa1d-52481805f40b
  schemas:
    CompletePayoutOwnershipProofRequest:
      type: object
      required:
        - message
        - signature
      properties:
        message:
          type: string
          description: The message exactly as `prepare` returned it
        signature:
          type: string
          description: EIP-191 signature produced by the route's source wallet
    SinglePayoutRouteResponse:
      allOf:
        - $ref: '#/components/schemas/BaseResponse'
        - type: object
          properties:
            result:
              $ref: '#/components/schemas/PayoutRoute'
    ErrorResponse:
      type: object
      description: Error response wrapper
      properties:
        success:
          type: boolean
          description: Always false for error responses
          example: false
        errors:
          type: array
          description: List of errors that occurred
          items:
            $ref: '#/components/schemas/ErrorBody'
        result:
          type: object
          nullable: true
          description: Null or omitted when success is false
    BaseResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Indicates whether the request was successful
    PayoutRoute:
      type: object
      description: Customer-facing route view. Provider identity is never exposed.
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: '#/components/schemas/PayoutRouteStatus'
        depositAsset:
          $ref: '#/components/schemas/DepositAsset'
        fiatCurrency:
          type: string
        depositAddress:
          type: string
          nullable: true
          description: >
            The address a customer sends to for a PUSH pay-out. Included
            **only** for an `ACTIVE` route

            on a `SELF_CUSTODY` account; null or omitted otherwise.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ErrorBody:
      type: object
      description: Individual error details
      properties:
        code:
          type: string
          description: Machine-readable error code
          example: invalid-request
        message:
          type: string
          description: Human-readable error message
          example: The request contains invalid parameters.
    PayoutRouteStatus:
      type: string
      description: >
        Lifecycle of a payout route. Only `ACTIVE` routes can be used for
        pay-outs or as PUSH deposit

        addresses.

        - `PENDING` — created; provider registration not yet started

        - `REGISTERING` — provider registration in progress

        - `AWAITING_OWNERSHIP_PROOF` — waiting for the owner of a self-custody
        source wallet to sign
          a wallet-ownership proof. Registration is paused until you submit it — see
          [Submit a pay-out route ownership proof](/api-reference/Finance-API/payout-routes/submit-a-route-ownership-proof).
          This is the only status that needs an action from you.
        - `ACTIVE` — registered; `depositAddress` populated

        - `REJECTED` — registration failed terminally or was rejected
      enum:
        - PENDING
        - REGISTERING
        - AWAITING_OWNERSHIP_PROOF
        - ACTIVE
        - REJECTED
    DepositAsset:
      type: object
      description: >-
        The crypto asset a customer sends to fund a route — a (chain, name)
        pair.
      required:
        - chain
        - name
      properties:
        chain:
          $ref: '#/components/schemas/BlockchainNetwork'
        name:
          type: string
          maxLength: 50
          description: Crypto asset name
          example: USDC
    BlockchainNetwork:
      type: string
      description: >
        Supported blockchain network. Availability per chain depends on your
        company's configured

        `supportedChains` — see [Supported chains and
        assets](/guides/finance/supported-chains-and-assets).
      enum:
        - AVALANCHE
        - BASE
        - ETHEREUM
        - POLYGON
        - SOLANA
  responses:
    Unauthorized:
      description: Authentication required or failed (401)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            errors:
              - code: unauthenticated
                message: Please authenticate to perform this action.
    Forbidden:
      description: Caller lacks the required authority/role (403)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            errors:
              - code: forbidden
                message: You do not have permission to access this resource.
    NotFound:
      description: Resource not found (404)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            errors:
              - code: account-not-found
                message: The requested resource was not found.
    InternalServerError:
      description: Unexpected server error (500)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            errors:
              - code: internal-error
                message: An unexpected error occurred. Please try again later.
  securitySchemes:
    OAuth2:
      type: oauth2
      description: >
        OAuth2 client credentials flow. Token endpoints:

        - Staging:
        https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token

        - Production:
        https://login.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token
      flows:
        clientCredentials:
          tokenUrl: >-
            https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token
          scopes: {}

````