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

> ## Agent Instructions
> Bitnob API requests authenticate with an `X-Auth-Client` API key header against https://api.bitnob.com.
> Always use the sandbox environment for testing. Never put live API keys in sample code.
> Money amounts are integers in the smallest unit of the currency (satoshis for BTC, cents for fiat). Never use floats.
> Every state-changing request takes a client-supplied `reference` and must be treated as idempotent on retry.
> Quotes expire. Re-quote rather than reusing a stale quote id.

# Create sponsored transfer (quote)

> Quotes a gas-sponsored transfer for an external end-user (someone who holds their own key and has no Bitnob wallet) and returns the exact payload they must sign. Bitnob pays the network gas and recoups it as a fee in the transferred token, so neither party needs to hold the native gas asset (SOL, ETH, or TRX).

Amounts. `amount` is a decimal string in the asset's normal units (for example `"10.50"` USDC). By default (net mode) `amount` is what the recipient receives and the sender (`from`) is debited `total_debit`, which is `recipient_amount` plus `fee` (the sponsorship fee) plus `partner_fee` (your optional markup). Set `treat_as_gross_amount: true` for gross mode: `amount` becomes the sender's total debit (`total_debit` equals `amount`) and the sponsorship and partner fees are deducted from it, so `recipient_amount` is `amount` minus those fees. Gross mode lets you sweep a full source balance without overdrawing it; the request is rejected (`invalid_request`) when `amount` does not exceed the fees. The response echoes `treat_as_gross_amount` and both `recipient_amount` and `total_debit` so you can confirm the split.

Flow. First call create (this endpoint) to get a `sign` block and an `expires_at`. Then have the end-user sign it verbatim with their own key. Then submit the signature or signatures to `POST /sponsored-transfers/{id}/submit`. Finally poll `GET /sponsored-transfers/{id}` (or subscribe to `sponsored_transfer.*` webhooks) until the status is terminal. Sign and submit before `expires_at`.

What to sign depends on `sign.scheme`:
- `solana_ed25519`: sign the raw message bytes in `sign.message_base64` with the `from` ed25519 key, then submit the base58 signature as `signature`.
- `evm_7702`: sign both the EIP-7702 authorization (`sign.authorization`) and the EIP-712 witness (`sign.witness`) with the `from` key, then submit both as 0x-prefixed 65-byte signatures. `sign.digests` gives the raw 32-byte hashes if you use a non-standard signer.
- `tron_router`: sign the 0x-prefixed 32-byte digest in `sign.digest` with the `from` secp256k1 key and submit it as `tron_signature`. Important: on a sender's first sponsored transfer of a given token, the quote also returns a `sign.approve` block with `sign.approve.needed` set to true. When that block is present you must ALSO sign `sign.approve.digest` with the same `from` key and submit it as `tron_approve_signature` in the same submit call, alongside `tron_signature`. This one-time approval is what lets the router pull the tokens on the sender's behalf, and the transfer cannot be broadcast without it. It is needed only on the first transfer (later transfers do not return the block and need only `tron_signature`), the single quoted fee already covers its cost, and Bitnob activates the sender's account first if it is brand new.

Partner markup (optional). Include `partner_fee` to charge your own flat fee (in the same asset) to an address you control, on top of the sponsorship fee. `address` and `amount` are required together, the address must not equal the sender or recipient, and the amount is capped per chain. Supported on Solana, EVM, and Tron. Requires the Idempotency-Key header.



## OpenAPI

````yaml /api-collections/swagger/bitnob-platform.openapi.json post /sponsored-transfers
openapi: 3.0.3
info:
  description: >-
    # Bitnob Enterprise API


    A high-performance Wallet-as-a-Service API for partners and developers:

    non-custodial wallets, transactions, gas stations, on-chain subscriptions
    and

    platform webhooks across multiple chains.


    ## Authentication


    Authenticate every request with your secret API key in the **X-API-Key**

    request header. Keys are scoped to one organization and carry a fixed set of

    permissions; an endpoint returns **403** when the key lacks the required

    permission. Treat the key as a credential — never expose it in a browser or

    mobile client.


    ## Request tracing


    Every response carries an **X-Request-Id** header. Log it, and quote it when

    contacting support — it identifies the exact request in our systems.


    ## Rate limits


    Wallet/business API and node RPC traffic have **independent rate-limit
    budgets**,

    even when they use the same API key and organization:


    | Traffic | Limit |

    |---|---|

    | Wallet and other non-RPC API requests | A per-key requests-per-minute
    allowance plus a configured burst, and a shared organization per-minute
    allowance |

    | **POST /rpc/{chain_network}**, including the **/v1/rpc/{chain_network}**
    alias | A shared organization requests-per-second limit, counted per
    JSON-RPC call |


    Authenticated RPC requests do not consume your wallet API quota, and wallet

    requests do not consume your RPC limit. The RPC limit is shared across all
    keys

    and chains in your organization; creating another key does not increase it.


    A JSON-RPC batch counts each call in the batch, not as a single request —
    batching

    many calls into one HTTP request consumes the limit per call. On live there
    is

    currently no additional per-day volume cap; sandbox applies a low per-second
    and

    per-day default so it cannot be used as production infrastructure. An RPC
    429

    carries **Retry-After**.


    Rate-limited responses expose the current state:


    - **X-RateLimit-Limit** — requests permitted in the current window.

    - **X-RateLimit-Remaining** — requests still available in this window.

    - **X-RateLimit-Reset** — Unix time (seconds) when the window resets.

    - **X-RateLimit-Scope** — **key** for authenticated non-RPC traffic, or
    **ip**
      for unauthenticated/failed-auth traffic. (RPC uses a separate per-second limit
      and returns only Retry-After on 429.)

    For non-RPC traffic, **X-Org-RateLimit-Limit**,
    **X-Org-RateLimit-Remaining**,

    and **X-Org-RateLimit-Reset** describe your organization-wide allowance
    shared

    across keys; the unprefixed headers report your per-key allowance or the

    organization allowance, whichever has less remaining. RPC proxy traffic is
    limited

    separately and returns only **Retry-After** on 429 (no X-RateLimit headers).


    Read these proactively and slow down before you are throttled. When a limit
    is

    exceeded the API responds **429** with a **Retry-After** header (seconds to

    wait); retry only after that delay.


    These are request-quota boundaries, not dedicated infrastructure: CPU,
    database

    and upstream provider capacity may still be shared. Authentication,
    permissions,

    IP restrictions, signatures and nonce replay protection are unchanged.


    ## Idempotency


    Send an **Idempotency-Key** header (a unique value you generate, e.g. a
    UUID)

    on **POST**, **PATCH** and **DELETE** requests so a network retry never

    applies the same change twice.


    - It is **required** on value-moving endpoints (creating transactions,
      creating addresses, refilling and sponsor-broadcasting gas stations,
      creating wallets); those return **400 idempotency_key_required** if absent.
    - Keys are retained for **24 hours**.

    - Retrying with the same key replays the original response byte-for-byte and
      adds an **Idempotent-Replayed: true** header.
    - Reusing a key with a **different request body** is rejected with
      **409 idempotency_conflict** — generate a fresh key per distinct operation.
    - Only successful (2xx) responses are stored, so a request that failed can
    be
      safely retried with the same key.
    - An Idempotency-Key sent without authentication is rejected with **401**.


    ## Pagination


    List endpoints are cursor-paginated. Parameters:


    - **limit** — page size; bounded by the endpoint maximum.

    - **starting_after** — return the page after this opaque cursor.

    - **ending_before** — return the page before this opaque cursor.


    Supplying both **starting_after** and **ending_before** is rejected with

    **400 ambiguous_cursor**. Every list response is the same envelope:

        {
          "object": "list",
          "data": [ ... ],
          "has_more": true,
          "next_cursor": "b3JnOjE2ODk...",
          "url": "/wallets"
        }

    When **has_more** is true, pass **next_cursor** as **starting_after** to
    fetch

    the following page. Cursors are opaque — do not parse or construct them.


    ## Transaction lifecycle and async outcomes


    Creating a transaction (POST /v1/transactions) returns one of two outcomes:


    - **201 Created** — signed and submitted for broadcast.

    - **202 Accepted** — accepted but not yet executing. The body's **status**
    says why:
      - **pending_authorization** — organization policy requires approval; the
        transaction executes once approvers approve it.
      - **delayed** — a policy time-lock holds the transaction until **execute_at**
        (an ISO-8601 timestamp on the response), when it executes automatically.

      In both cases the transaction is durably persisted and its funds are locked.

    A transaction's **status** moves through these states:


    - **Non-terminal**: pending_signature, pending_authorization, delayed,
    signed,
      awaiting_sponsorship, broadcasting, confirming, pending_screening.
    - **Terminal**: completed, failed, cancelled, rejected.


    Poll **GET /v1/transactions/{id}** (or subscribe to a webhook) until the
    status

    is terminal. An empty **tx_hash** means the transaction has not been
    broadcast

    on-chain yet; once broadcast, **confirmations** climbs until the transaction
    is

    **completed**.


    ## Amounts and units


    All monetary values are JSON **strings**, never numbers — parse them with a

    decimal/big-number type, not a float, to avoid precision loss.


    Every monetary value appears in two forms so you never have to guess
    decimals:


    - **amount** / **fee_amount** — human-readable decimal in the asset's
    display
      unit (e.g. "25.5" USDC).
    - **amount_raw** / **fee_amount_raw** / wallet **balance** — the integer
    value
      in the asset's smallest base unit (e.g. "25500000" for 25.5 USDC at 6
      decimals). **balance** fields are ALWAYS base units.

    **asset_decimals** (and **fee_decimals**) give the conversion:

    display = raw / 10^decimals. The fee is denominated in **fee_asset**, which
    for

    most chains is the native coin (ETH, TRX, …) and differs from the
    transferred

    asset — always read **fee_asset**/**fee_decimals** rather than assuming.
    After a

    transaction confirms, **realized_fee_amount**(_raw) carries the actual fee
    paid.


    Two balance shapes:


    - **GET /v1/wallets/{id}/balances** — one row PER FUNDED ADDRESS (an HD
    wallet
      has many addresses).
    - **GET /v1/wallets/{id}/balances/{asset}** — a single AGGREGATED total for
    that
      asset across the wallet.

    ## Fees


    Control the fee in one of two ways on transaction create and estimate:


    - **fee_level** — "low", "medium", or "high"; the platform picks a rate.

    - **fee_rate** / **max_fee_rate** — Bitcoin only, in sat/vB, for explicit
      control with an optional cap (the transaction will not pay above
      **max_fee_rate**).

    Use **POST /v1/transactions/estimate-fee** to preview low/medium/high quotes
    and

    any gas-funding options before sending.


    ## Errors


    Every error response is the same envelope:

        {
          "error": {
            "type": "invalid_request_error",
            "code": "invalid_cursor",
            "message": "starting_after is not a valid cursor",
            "param": "starting_after",
            "request_id": "req_01HX..."
          }
        }

    **type** is the broad category; **code** is a stable, machine-readable

    identifier — branch on **code**, show **message** to humans, and **param**

    names the offending field when applicable. Some errors also carry a
    **details**

    object with structured, error-specific context (for example the list of

    conflicting fields); treat it as optional and error-specific.


    ### Error code catalogue


    **invalid_request_error** (HTTP 400) — the request was malformed or failed

    validation: invalid_request, invalid_json, invalid_body, empty_body,

    trailing_data, type_mismatch, unknown_field, missing_required_field,

    invalid_id, invalid_cursor, ambiguous_cursor, invalid_expand, invalid_chain,

    invalid_vault_id, wallet_chain_unknown, invalid_transaction_data,

    amount_precision_loss, gas_station_low_balance, body_too_large,

    idempotency_key_required, idempotency_key_invalid, invalid_webhook_id,

    method_not_allowed.


    **authentication_error** (HTTP 401) — the request was not authenticated:

    not_authenticated, auth_required, org_required.


    **permission_error** (HTTP 403) — the request is authenticated but not

    permitted: insufficient_permissions (the API key lacks the required

    permission), ip_not_allowed (the request's source IP is not in the key's IP

    allowlist).


    **not_found_error** (HTTP 404) — the addressed resource does not exist:

    not_found, route_not_found, asset_not_found, event_not_found,

    gas_station_not_found, subscription_not_found, transaction_not_found,

    vault_not_found, wallet_not_found, address_not_found, webhook_not_found.


    **conflict_error** (HTTP 409) — the request conflicts with current state:

    conflict, idempotency_conflict, wallet_archived.


    **precondition_error** (HTTP 412) — a precondition on the request was not
    met,

    for example an ETag mismatch on a conditional request.


    **unprocessable_entity_error** (HTTP 422) — the request was well-formed but
    a

    business rule prevented it from being processed.


    **rate_limit_error** (HTTP 429) — too many requests: rate_limit_exceeded.


    **api_error** (HTTP 500) — an unexpected server error: request_error,

    internal_error.


    **service_unavailable_error** (HTTP 503) — a dependency is unavailable;

    retry after the Retry-After delay: service_unavailable.


    ## Webhook signature verification


    The platform signs every webhook delivery with HMAC-SHA256 using the signing

    secret issued when you create the webhook or subscription (and rotated via
    the

    rotate-secret endpoints — the plaintext secret is shown only once).


    The signature covers the canonical JSON object below, where **data**,

    **event** and **timestamp** are the corresponding fields of the delivered

    payload and **timestamp** is Unix seconds:

        {"data": <data>, "event": "<event>", "timestamp": <unix_seconds>}

    The signed object contains ONLY those three keys — when verifying, build a

    fresh object from the delivery's data, event and timestamp and exclude the

    signature field (and any other body fields) from the HMAC input. Serialize
    the

    keys in lexical order (data, event, timestamp), matching Go's encoding/json.

    The signature value is:

        "sha256=" + hex( HMAC_SHA256(signing_secret, canonical_json) )

    **Platform webhooks** (created via /webhooks) carry this value in the

    **X-Webhook-Signature** response header. **On-chain subscription webhooks**

    (created via /subscriptions) carry it as a **signature** field inside the

    delivered JSON body — there is no signature header.


    To verify a delivery: recompute the signature from your stored secret and

    compare it to the received value with a constant-time comparison. Reject the

    delivery on mismatch, and reject deliveries whose **timestamp** is older
    than

    your tolerance window (a few minutes is typical) to defend against replay.


    ## Webhook event types


    **Platform notification webhooks** (/webhooks) deliver these event types:

    wallet.created, wallet.deleted, wallet.suspended, wallet_address.created,

    transaction.created, transaction.signed, transaction.broadcasting,

    transaction.confirmed, transaction.completed, transaction.received,

    transaction.failed, transaction.cancelled, gas_station.low_balance,

    gas_station.critical_balance, gas_station.auto_refilled, session.created,

    api_key.created, api_key.deleted, policy.violation,

    policy.created, policy.updated, policy.activated, policy.archived,

    policy.deleted, policy.rule.added, policy.rule.updated, policy.rule.deleted,

    kyt.deposit.held, kyt.deposit.flagged, kyt.deposit.approved,
    kyt.deposit.rejected, kyt.deposit.blocked,

    kyt.withdrawal.blocked, kyt.withdrawal.flagged, kyt.sanctions.match,
    kyt.alert.escalated,

    kyt.screening.timeout, kyt.screening.requeued, kyt.screening.completed.


    Use **GET /v1/webhooks/event-types** for the authoritative machine-readable
    list.


    **On-chain subscription webhooks** (/subscriptions) deliver these event

    types: incoming_native_tx, incoming_token_tx, incoming_nft_transfer,

    incoming_internal_tx, outgoing_native_tx, outgoing_token_tx,

    outgoing_nft_transfer, outgoing_internal_tx, tx_failed, tx_pending,

    confirmation_threshold_met, confirmation_finalized, contract_deployed,

    contract_executed, balance_changed.


    ## Versioning and deprecation


    Pin the API version with the **API-Version** request header; omit it to take

    the current default. Additive changes (new endpoints, new optional fields)
    are

    not versioned and may appear at any time — write tolerant clients.


    When an operation or version is scheduled for removal it is marked

    **deprecated** in this specification, and its responses carry a

    **Deprecation** header plus a **Sunset** header (RFC 8594) giving the date

    after which it stops functioning. Monitor those headers and migrate before
    the

    Sunset date.
  title: Bitnob Enterprise API
  version: v1
servers:
  - description: Production
    url: https://api.platform.bitnob.com
security:
  - ApiKeyAuth: []
paths:
  /sponsored-transfers:
    post:
      tags:
        - Sponsored-transfers
      summary: Create sponsored transfer (quote)
      description: >-
        Quotes a gas-sponsored transfer for an external end-user (someone who
        holds their own key and has no Bitnob wallet) and returns the exact
        payload they must sign. Bitnob pays the network gas and recoups it as a
        fee in the transferred token, so neither party needs to hold the native
        gas asset (SOL, ETH, or TRX).


        Amounts. `amount` is a decimal string in the asset's normal units (for
        example `"10.50"` USDC). By default (net mode) `amount` is what the
        recipient receives and the sender (`from`) is debited `total_debit`,
        which is `recipient_amount` plus `fee` (the sponsorship fee) plus
        `partner_fee` (your optional markup). Set `treat_as_gross_amount: true`
        for gross mode: `amount` becomes the sender's total debit (`total_debit`
        equals `amount`) and the sponsorship and partner fees are deducted from
        it, so `recipient_amount` is `amount` minus those fees. Gross mode lets
        you sweep a full source balance without overdrawing it; the request is
        rejected (`invalid_request`) when `amount` does not exceed the fees. The
        response echoes `treat_as_gross_amount` and both `recipient_amount` and
        `total_debit` so you can confirm the split.


        Flow. First call create (this endpoint) to get a `sign` block and an
        `expires_at`. Then have the end-user sign it verbatim with their own
        key. Then submit the signature or signatures to `POST
        /sponsored-transfers/{id}/submit`. Finally poll `GET
        /sponsored-transfers/{id}` (or subscribe to `sponsored_transfer.*`
        webhooks) until the status is terminal. Sign and submit before
        `expires_at`.


        What to sign depends on `sign.scheme`:

        - `solana_ed25519`: sign the raw message bytes in `sign.message_base64`
        with the `from` ed25519 key, then submit the base58 signature as
        `signature`.

        - `evm_7702`: sign both the EIP-7702 authorization
        (`sign.authorization`) and the EIP-712 witness (`sign.witness`) with the
        `from` key, then submit both as 0x-prefixed 65-byte signatures.
        `sign.digests` gives the raw 32-byte hashes if you use a non-standard
        signer.

        - `tron_router`: sign the 0x-prefixed 32-byte digest in `sign.digest`
        with the `from` secp256k1 key and submit it as `tron_signature`.
        Important: on a sender's first sponsored transfer of a given token, the
        quote also returns a `sign.approve` block with `sign.approve.needed` set
        to true. When that block is present you must ALSO sign
        `sign.approve.digest` with the same `from` key and submit it as
        `tron_approve_signature` in the same submit call, alongside
        `tron_signature`. This one-time approval is what lets the router pull
        the tokens on the sender's behalf, and the transfer cannot be broadcast
        without it. It is needed only on the first transfer (later transfers do
        not return the block and need only `tron_signature`), the single quoted
        fee already covers its cost, and Bitnob activates the sender's account
        first if it is brand new.


        Partner markup (optional). Include `partner_fee` to charge your own flat
        fee (in the same asset) to an address you control, on top of the
        sponsorship fee. `address` and `amount` are required together, the
        address must not equal the sender or recipient, and the amount is capped
        per chain. Supported on Solana, EVM, and Tron. Requires the
        Idempotency-Key header.
      operationId: postSponsored-transfers
      parameters:
        - description: API version (defaults to the latest supported version if omitted).
          in: header
          name: API-Version
          required: false
          schema:
            default: v1
            enum:
              - v1
            type: string
        - description: >-
            Unique key (1-255 printable ASCII chars) used to safely retry write
            operations. Same key + body returns the cached response; same key +
            different body returns 409.
          in: header
          name: Idempotency-Key
          required: true
          schema:
            maxLength: 255
            minLength: 1
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSponsoredTransferRequest'
        required: true
      responses:
        '201':
          content:
            application/json:
              example:
                fee:
                  amount: '0.02'
                  asset: USDC
                  usd: '0.02'
                object: sponsored_transfer
                partner_fee:
                  address: 9xQe...partner
                  amount: '0.20'
                  asset: USDC
                  usd: '0.20'
                recipient_amount: '10.50'
                sign:
                  scheme: solana_ed25519
                status: quoted
                total_debit: '10.72'
              schema:
                $ref: '#/components/schemas/SponsoredTransferResponse'
          description: Created
          headers:
            Idempotent-Replayed:
              description: >-
                True when this response was replayed from an earlier request
                with the same Idempotency-Key.
              schema:
                type: boolean
            X-RateLimit-Limit:
              description: Requests permitted in the current window.
              schema:
                type: integer
            X-RateLimit-Remaining:
              description: Requests remaining in the current window.
              schema:
                type: integer
            X-RateLimit-Reset:
              description: Unix time (seconds) at which the rate-limit window resets.
              schema:
                type: integer
            X-RateLimit-Scope:
              description: >-
                Scope the rate limit was applied at: key for authenticated API
                traffic, or ip for unauthenticated/failed-auth traffic. RPC
                proxy traffic is limited separately (per second, per
                organization) and is not bucketed here.
              schema:
                type: string
            X-Request-Id:
              description: Unique id for this request; quote it in support requests.
              schema:
                type: string
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            Invalid request: an unsupported chain, asset, or network, a
            malformed address or amount, a `treat_as_gross_amount` that does not
            exceed the sponsorship and partner fees, or an invalid `partner_fee`
            (code `invalid_request`), or a markup above the per-chain cap (code
            `partner_fee_too_high`).
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Authentication required
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Insufficient permissions
        '409':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Conflict
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            Quote rate limit hit (code `rate_limited`) or the organization's
            daily sponsored-gas budget exceeded (code `budget_exceeded`). Honour
            the `Retry-After` header.
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Internal server error
        '503':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            Sponsorship capacity temporarily unavailable (code
            `capacity_unavailable`). Retry shortly.
      security:
        - ApiKeyAuth: []
components:
  schemas:
    CreateSponsoredTransferRequest:
      properties:
        amount:
          description: >-
            Decimal amount in the asset's normal units (not base units). By
            default the recipient receives this amount; with
            treat_as_gross_amount, this is the sender's total debit.
          example: '10.50'
          type: string
        asset:
          description: >-
            Token symbol to transfer (a supported stablecoin), e.g. USDC on
            Solana/EVM or USDT on Tron.
          example: USDC
          type: string
        chain:
          description: >-
            Chain to transfer on: solana, ethereum, base, arbitrum, optimism,
            polygon, bsc, or tron.
          example: solana
          type: string
        from:
          description: >-
            Sender address. This is your end-user's own address, which holds the
            token and signs the transfer (and, on Tron for a first-time sender,
            the one-time approval).
          example: 7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2
          type: string
        network:
          description: >-
            Deprecated and ignored. The network follows the environment (live is
            mainnet, sandbox is testnet), so omit it. Any value sent is ignored,
            kept only for backward compatibility.
          example: mainnet
          type: string
        partner_fee:
          $ref: '#/components/schemas/SponsoredTransferPartnerFeeInput'
        router_version:
          description: >-
            Optional EVM paymaster router contract version. Omit for the chain
            default. Rejected if not allowed for the chain.
          example: 2
          minimum: 1
          type: integer
        to:
          description: Recipient address.
          example: 3xTd...recipient
          type: string
        treat_as_gross_amount:
          description: >-
            Treat amount as the sender's total debit and deduct sponsorship and
            partner fees from the recipient amount.
          type: boolean
      required:
        - chain
        - asset
        - from
        - to
        - amount
      type: object
    SponsoredTransferResponse:
      properties:
        asset:
          type: string
        chain:
          type: string
        confirmations:
          type: integer
        created_at:
          type: string
        expires_at:
          description: >-
            RFC 3339 quote expiry; present while status is quoted. Sign and
            submit before this time.
          type: string
        failure_code:
          description: >-
            Machine-readable failure reason on a failed transfer, e.g.
            insufficient_token_balance, transfer_reverts, reverted.
          type: string
        failure_reason:
          description: Human-readable failure detail.
          type: string
        fee:
          $ref: '#/components/schemas/SponsoredTransferFee'
          description: Bitnob sponsorship fee, charged in the transferred token.
        from:
          type: string
        id:
          description: Transfer id; use it in the submit and get endpoints.
          type: string
        network:
          type: string
        object:
          description: Object type discriminator; always "sponsored_transfer".
          example: sponsored_transfer
          type: string
        partner_fee:
          $ref: '#/components/schemas/SponsoredTransferPartnerFee'
          description: >-
            Your markup breakdown; present only if you attached partner_fee on
            the quote.
        recipient_amount:
          description: What the recipient receives (decimal).
          example: '10.50'
          type: string
        router_version:
          description: >-
            The resolved EVM router contract version the transfer was quoted
            against (absent for non-EVM).
          type: integer
        sign:
          $ref: '#/components/schemas/SponsoredTransferSign'
          description: What the end-user must sign; present only while status is quoted.
        status:
          description: >-
            Lifecycle status: quoted, validating, submitted, assembled (EVM
            only), tron_activating and tron_approving (Tron first-time
            onboarding), pending, completed, failed, expired, cancelled.
          example: quoted
          type: string
        to:
          type: string
        total_debit:
          description: >-
            What leaves the sender (decimal). In gross mode this equals amount;
            otherwise it equals recipient_amount + fee + partner_fee.
          example: '10.72'
          type: string
        treat_as_gross_amount:
          description: Whether the request amount was treated as the sender's total debit.
          type: boolean
        tx_hash:
          description: On-chain transaction hash, set once broadcast.
          type: string
      type: object
    Error:
      description: Standard error envelope.
      properties:
        error:
          properties:
            code:
              type: string
            details:
              additionalProperties: true
              type: object
            message:
              type: string
            param:
              type: string
            request_id:
              type: string
            type:
              type: string
          type: object
      type: object
    SponsoredTransferPartnerFeeInput:
      properties:
        address:
          description: >-
            Address that receives your markup. Must not be the sender or
            recipient. On Solana it must already have a token account for the
            asset.
          example: 9xQe...partner
          type: string
        amount:
          description: >-
            Flat markup amount in the transferred asset's normal units. Capped
            per chain.
          example: '0.20'
          type: string
      required:
        - address
        - amount
      type: object
    SponsoredTransferFee:
      properties:
        amount:
          example: '0.02'
          type: string
        asset:
          example: USDC
          type: string
        usd:
          example: '0.02'
          type: string
      type: object
    SponsoredTransferPartnerFee:
      properties:
        address:
          example: 9xQe...partner
          type: string
        amount:
          example: '0.20'
          type: string
        asset:
          example: USDC
          type: string
        usd:
          example: '0.20'
          type: string
      type: object
    SponsoredTransferSign:
      properties:
        approve:
          $ref: '#/components/schemas/SponsoredTransferTronApprove'
          description: >-
            Tron: the one-time router approval a first-time sender must ALSO
            sign. Present only on the sender's first transfer of this token.
            When present, sign approve.digest and submit tron_approve_signature
            alongside tron_signature.
        authorization:
          additionalProperties: true
          description: >-
            EVM: the EIP-7702 authorization tuple (chainId, address, nonce) to
            sign.
          type: object
        digest:
          description: >-
            Tron: 0x-prefixed 32-byte EIP-191 transfer-authorization digest.
            Sign with the from key and submit as tron_signature. Always present
            on a Tron quote.
          type: string
        digests:
          additionalProperties: true
          description: >-
            EVM: raw 32-byte hashes of the authorization and witness, for
            hardware/non-standard signers.
          type: object
        message_base64:
          description: >-
            Solana: base64-encoded message bytes to sign as-is with the from
            ed25519 key.
          type: string
        scheme:
          description: >-
            Which fields are populated and how to sign: solana_ed25519,
            evm_7702, or tron_router.
          example: solana_ed25519
          type: string
        signer:
          description: 'Solana: the key that must produce the signature (equals from).'
          type: string
        witness:
          additionalProperties: true
          description: 'EVM: the EIP-712 typed-data witness to sign (signTypedData).'
          type: object
      type: object
    SponsoredTransferTronApprove:
      properties:
        digest:
          description: >-
            0x-prefixed 32-byte hash (sha256 of the approve transaction's raw
            data) to sign with the from key. Submit the signature as
            tron_approve_signature.
          type: string
        needed:
          description: >-
            Always true when this block is present. Its presence is the signal
            that tron_approve_signature is required on submit.
          type: boolean
        selector:
          description: >-
            The approve function selector, for callers that want to decode and
            verify the approval before signing.
          type: string
        spender:
          description: The router contract being granted the allowance.
          type: string
        token:
          description: >-
            The token contract the sender is approving (matches the transferred
            asset).
          type: string
      type: object
  securitySchemes:
    ApiKeyAuth:
      in: header
      name: X-API-Key
      type: apiKey

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.