we-promise/sure · error · Provider::Binance::InvalidSymbolError

API error: #{response.code}

Error message

API error: #{response.code}

What it means

Raised at binance.rb:187 when a non-2xx Binance response body is a Hash whose "code" equals -1121 — Binance's "Invalid symbol" error code. The raised class is Provider::Binance::InvalidSymbolError (subclass of ApiError), carrying the Binance msg (the template "API error: #{response.code}" is only the fallback if the body has no msg). It fires before the generic ApiError on line 188.

Source

Thrown at app/models/provider/binance.rb:187

    end

    def auth_headers
      { "X-MBX-APIKEY" => api_key }
    end

    def handle_response(response)
      parsed = response.parsed_response

      case response.code
      when 200..299
        parsed
      when 401
        raise AuthenticationError, extract_error_message(parsed) || "Unauthorized"
      when 429
        raise RateLimitError, "Rate limit exceeded"
      else
        msg = extract_error_message(parsed) || "API error: #{response.code}"
        raise InvalidSymbolError, msg if parsed.is_a?(Hash) && parsed["code"] == -1121
        raise ApiError, msg
      end
    end

    def extract_error_message(parsed)
      return parsed if parsed.is_a?(String)
      return nil unless parsed.is_a?(Hash)
      parsed["msg"] || parsed["message"] || parsed["error"]
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Normalize the symbol to Binance's format (base+quote concatenated, e.g. BTCUSDT) before calling.
  2. Verify the pair exists: GET /api/v3/exchangeInfo?symbols=["BTCUSDT"] and check it is returned.
  3. If the coin was delisted, remove or re-map the Security so it stops syncing from Binance.
  4. Catch Provider::Binance::InvalidSymbolError specifically so one bad symbol does not abort a batch sync.

Example fix

# before
klines = provider.fetch_security_prices(symbol: "BTC-USD", exchange_operating_mic: "BINANCE", start_date: from, end_date: to) # -1121

# after
binance_symbol = "BTC-USD".delete("-") # => "BTCUSD" / use "BTCUSDT" for USD quotes
klines = provider.fetch_security_prices(symbol: binance_symbol, exchange_operating_mic: "BINANCE", start_date: from, end_date: to)
Defensive patterns

Strategy: validation

Validate before calling

INVALID = -1121
# Pre-check against Binance's own symbol list before syncing
pairs = JSON.parse(Net::HTTP.get(URI("https://api.binance.com/api/v3/exchangeInfo")))
valid = pairs["symbols"].map { |s| s["symbol"] }.to_set
raise ArgumentError, "#{sym} not listed on Binance" unless valid.include?(sym.delete("-"))

Type guard

def binance_invalid_symbol?(err)
  err.is_a?(Provider::Binance::InvalidSymbolError)
end

Try / catch

begin
  klines = provider.fetch_security_prices(symbol: sym, exchange_operating_mic: mic, start_date: from, end_date: to)
rescue Provider::Binance::InvalidSymbolError => e
  Rails.logger.warn("Skipping #{sym}: #{e.message}")
  next
end

Prevention

When it happens

Trigger: Requesting klines, ticker, or account-filtered data for a trading pair Binance does not list (e.g. "BTCUSD" instead of "BTCUSDT", "DOGE-EUR" where no such book exists); a delisted or renamed pair; a symbol built from the wrong quote asset; securities imported from another exchange/MIC being passed to the Binance provider unchanged.

Common situations: Mixing symbol conventions across providers (Tiingo/EODHD style "BTC-USD" vs Binance "BTCUSDT"); a Security record whose symbol predates a delisting; automated import pipelines that assume every crypto symbol trades on Binance; typos in manual symbol entry.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/d5f93b92d8e91107. Report an issue: GitHub.