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

Unauthorized

Error message

Unauthorized

What it means

Raised by Provider::Binance#handle_response when the Binance REST API returns HTTP 401. The client authenticates by sending the api_key in the X-MBX-APIKEY header (auth_headers). Binance returns 401 when the key is malformed, deleted, or lacks permission for the requested endpoint. The message comes from extract_error_message, which reads parsed["msg"], falling back to "Unauthorized" when the body is unparseable.

Source

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

    # HMAC-SHA256 of the query string.
    # Accepts either a Hash of params or a pre-built query string.
    def sign(params)
      query_string = params.is_a?(Hash) ? URI.encode_www_form(params.sort) : params
      OpenSSL::HMAC.hexdigest("sha256", api_secret, query_string)
    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. Inspect the stored credential for the Binance provider account and confirm api_key is present and has no whitespace.
  2. In Binance API Management, confirm the key still exists, is active, and has the required permission (at minimum "Enable Reading").
  3. If the key has an IP restriction, add the server's current egress IP (or remove the restriction) and retry.
  4. Regenerate the key on Binance and update the stored credential, then retry the request.
  5. If the body is empty (message fell back to "Unauthorized"), log response.parsed_response and the raw body to see Binance's exact msg (e.g. "API-key format invalid" vs "API-key format invalid.").

Example fix

# before
provider = Provider::Binance.new(api_key: key, secret_key: secret)
data = provider.get_account # raises AuthenticationError, "Unauthorized"

# after
raise ArgumentError, "Binance api_key is blank" if key.to_s.strip.empty?
provider = Provider::Binance.new(api_key: key.strip, secret_key: secret)
begin
  data = provider.get_account
rescue Provider::Binance::AuthenticationError => e
  # mark the provider connection as needing re-auth instead of failing the job
  provider_account.update!(status: :reauth_required)
  Rails.logger.warn("Binance auth failed: #{e.message}")
end
Defensive patterns

Strategy: try-catch

Validate before calling

raise ArgumentError, "Binance api_key is blank" if api_key.to_s.strip.empty?
raise ArgumentError, "Binance api_key has whitespace" if api_key != api_key.strip

Type guard

def binance_auth_error?(err)
  err.is_a?(Provider::Binance::AuthenticationError)
end

Try / catch

begin
  result = provider.get_account
rescue Provider::Binance::AuthenticationError => e
  provider_account.update!(status: :reauth_required)
  DebugLogEntry.capture(category: "binance", level: "error", message: e.message, provider_key: "binance")
end

Prevention

When it happens

Trigger: Any signed-off request method on Provider::Binance (account, trade, or user-data endpoints) with a blank, mistyped, revoked, or expired API key; a key created without the required "Read" (or trade) permission; a key with an IP access restriction that does not include the server's egress IP; or a key deleted from the Binance account while still stored in the app's provider credentials.

Common situations: Rotating or regenerating keys on Binance without updating the stored credential; copying the key with trailing whitespace/newline; environment drift between staging (working key) and production (stale key); IP-restricted keys behind a container/cloud NAT whose egress IP changed; test suites hitting live Binance with placeholder keys.

Understand the failure class

Related errors


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