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
- Normalize the symbol to Binance's format (base+quote concatenated, e.g. BTCUSDT) before calling.
- Verify the pair exists: GET /api/v3/exchangeInfo?symbols=["BTCUSDT"] and check it is returned.
- If the coin was delisted, remove or re-map the Security so it stops syncing from Binance.
- 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
- Normalize symbols to Binance base+quote concatenation (BTCUSDT) before calling.
- Only route securities whose exchange_operating_mic is Binance to this provider.
- Rescue InvalidSymbolError separately from ApiError so bad symbols skip instead of aborting batches.
- Re-verify symbol lists after delistings/renames when -1121 suddenly appears.
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
- Unauthorized
- Unsupported Binance ticker: #{symbol}
- No price found for #{symbol} on #{date}
- Binance credentials not configured
- API error: #{parsed["Error Message"]}
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/d5f93b92d8e91107.
Report an issue: GitHub.