nautechsystems/nautilus_trader · error · anyhow::Error

Failed to repay spot borrow for {coin}: {e}

Error message

Failed to repay spot borrow for {coin}: {e}

What it means

The no-convert spot repay wrapper calls the inner no_convert_repay endpoint and maps any request error into an anyhow error naming the coin. After the request succeeds, ensure_repay_accepted additionally validates the result status. This error is the map_err wrapper, so {e} holds the underlying cause (HTTP failure, Bybit rejection like insufficient balance, auth error).

Source

Thrown at crates/adapters/bybit/src/http/client.rs:2366

    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - Credentials are missing.
    /// - The request fails.
    /// - Called during the hourly interest-calculation window (mm:04:00-mm:05:30 UTC each hour).
    /// - Insufficient spot balance for repayment.
    pub async fn repay_spot_borrow(
        &self,
        coin: &str,
        amount: Option<Quantity>,
    ) -> anyhow::Result<BybitNoConvertRepayResponse> {
        let amount_str = amount.as_ref().map(|q| q.to_string());
        let response = self
            .inner
            .no_convert_repay(coin, amount_str.as_deref())
            .await
            .map_err(|e| anyhow::anyhow!("Failed to repay spot borrow for {coin}: {e}"))?;
        Self::ensure_repay_accepted(coin, response.result.result_status)?;
        Ok(response)
    }

    /// Repays spot borrows for a specific coin, converting other assets if required.
    ///
    /// Unlike [`Self::repay_spot_borrow`], this uses the venue's manual repay endpoint,
    /// which may draw on other holdings when the debt coin's spot balance is insufficient.
    ///
    /// # Parameters
    ///
    /// - `coin`: The coin to repay (e.g., "BTC", "ETH")
    /// - `amount`: Optional amount to repay. If None, repays all outstanding borrows.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - Credentials are missing.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the embedded {e} for the concrete HTTP/Bybit error code
  2. Check the outstanding borrow amount for the coin and repay only up to it
  3. Verify API key permissions for UTA spot margin repay
  4. Retry on transient network/5xx errors; surface non-retryable rejections to the operator

Example fix

// before
client.no_convert_repay(coin, Some("100".into())).await?;
// after
if let Err(e) = client.no_convert_repay(coin, Some("100".into())).await {
    log::error!("repay {coin} failed: {e}");
    // fetch outstanding liability and retry with a valid amount
}
Defensive patterns

Strategy: try-catch

Validate before calling

let liability = client.get_outstanding_borrow(coin).await?;
if liability == 0 { return Ok(()); } // nothing to repay
let amt = amount.min(liability);

Try / catch

if let Err(e) = client.no_convert_repay(coin, amount).await {
    log::error!("repay {coin} failed: {e}");
    // re-query liability, adjust amount, retry once
}

Prevention

When it happens

Trigger: Calling repay_spot_borrow_no_convert when the HTTP request fails, the API key lacks permissions, the amount exceeds the outstanding borrow, or Bybit rejects the repay order for that coin.

Common situations: End-of-position cleanup code repaying spot borrows; repaying more than the current liability; Bybit maintenance windows or 5xx errors; API key rotation dropping required scopes.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/be92608cae8d60df. Report an issue: GitHub.