we-promise/sure · error · Provider::Mercury::MercuryError

fetch_failed

fetch_failed

Error message

Failed to fetch data: #{response.code} #{response.message} - #{response.body}

What it means

The catch-all branch of Provider::Mercury#handle_response: Mercury returned a status other than 200/400/401/403/404/429 (most commonly 5xx, but also unexpected 3xx or odd 4xx). The message interpolates the raw code, HTTParty message and body, and the code/body are also written to Rails.logger.error before raising, so the log line "Mercury API: Unexpected response" is the searchable breadcrumb.

Source

Thrown at app/models/provider/mercury.rb:129

      case response.code
      when 200
        JSON.parse(response.body, symbolize_names: true)
      when 400
        Rails.logger.error "Mercury API: Bad request - #{response.body}"
        raise MercuryError.new("Bad request to Mercury API: #{response.body}", :bad_request)
      when 401
        # Parse the error response for more specific messages
        error_message = parse_error_message(response.body)
        raise MercuryError.new(error_message, :unauthorized)
      when 403
        raise MercuryError.new("Access forbidden - check your API token permissions", :access_forbidden)
      when 404
        raise MercuryError.new("Resource not found", :not_found)
      when 429
        raise MercuryError.new("Rate limit exceeded. Please try again later.", :rate_limited)
      else
        Rails.logger.error "Mercury API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
        raise MercuryError.new("Failed to fetch data: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
      end
    end

    def parse_error_message(body)
      parsed = JSON.parse(body, symbolize_names: true)
      errors = parsed[:errors] || {}

      case errors[:errorCode]
      when "ipNotWhitelisted"
        ip = errors[:ip] || "unknown"
        "IP address not whitelisted (#{ip}). Add your IP to the API token's whitelist in Mercury dashboard."
      when "noTokenInDBButMaybeMalformed"
        "Invalid token format. Make sure to include the 'secret-token:' prefix."
      else
        errors[:message] || "Invalid API token"
      end
    rescue JSON::ParserError
      "Invalid API token"

View on GitHub (pinned to e69894adb9)

Solutions

  1. Check the logged body — grep the app log for 'Mercury API: Unexpected response' to get the exact code and upstream message.
  2. Check Mercury status/dashboard for an ongoing incident if the code is 5xx, then retry with backoff after a few minutes.
  3. If 410/405-style codes appear, compare the current Mercury API docs against the path in base_url — the endpoint may have moved.
  4. Narrow the request (smaller date range or pagination) if 504s repeat on heavy queries.
  5. Treat as transient first: this error_type is :fetch_failed, so retry once or twice before surfacing to the user.

Example fix

# before
result = provider.get_accounts

# after
begin
  result = provider.get_accounts
rescue Provider::Mercury::MercuryError => e
  raise if e.error_type != :fetch_failed || attempt >= 2
  attempt += 1
  sleep(30 * attempt)
  retry
end
Defensive patterns

Strategy: try-catch

Try / catch

begin
  provider.get_accounts
rescue Provider::Mercury::MercuryError => e
  if e.error_type == :fetch_failed && attempts < 2 # often 5xx/transient
    attempts += 1
    sleep(60)
    retry
  end
  raise
end

Prevention

When it happens

Trigger: Mercury outage returning 500/502/503; a gateway timeout 504 on a long transactions query with a huge date range; an unexpected 410/422 for a deprecated endpoint; HTTParty returning a non-listed code after following redirects.

Common situations: Incidents on api.mercury.com; very wide date ranges in get_account_transactions straining upstream; a new Mercury API version returning 410 Gone on the old path; proxies or corporate gateways injecting 502s.

Related errors


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