we-promise/sure · error · Provider::Mfapi::Error

Unexpected response format from search endpoint

Error message

Unexpected response format from search endpoint

What it means

Raised by Provider::Mfapi#search_securities when the GET /mf/search?q= response parses as valid JSON but is not a top-level JSON array. MFAPI's search contract is a bare array of fund objects ({schemeCode, schemeName}), so a Hash (e.g. an error envelope without a status field), String, or number means the payload is unusable. check_api_error! only inspects Hash payloads with status ERROR/FAIL, so object-shaped surprises fall through to this guard. It is a hard contract violation against the upstream API shape, not a network or auth failure.

Source

Thrown at app/models/provider/mfapi.rb:50

    end
  end

  # ================================
  #           Securities
  # ================================

  def search_securities(symbol, country_code: nil, exchange_operating_mic: nil)
    with_provider_response do
      throttle_request
      response = client.get("#{base_url}/mf/search") do |req|
        req.params["q"] = symbol
      end

      parsed = JSON.parse(response.body)
      check_api_error!(parsed)

      unless parsed.is_a?(Array)
        raise Error, "Unexpected response format from search endpoint"
      end

      parsed.first(25).map do |fund|
        Security.new(
          symbol: fund["schemeCode"].to_s,
          name: fund["schemeName"],
          logo_url: nil,
          exchange_operating_mic: "XBOM",
          country_code: "IN",
          currency: "INR"
        )
      end
    end
  end

  def fetch_security_info(symbol:, exchange_operating_mic:)
    with_provider_response do
      throttle_request

View on GitHub (pinned to e69894adb9)

Solutions

  1. Log response.body (or capture it in DebugLogEntry) at the failure site to see the actual payload MFAPI returned.
  2. Broaden check_api_error! to raise the body's message/inspect form for any Hash that is not an Array, so unexpected envelopes surface as API errors with their content.
  3. If upstream now wraps results (e.g. parsed["data"]), map the wrapper: treat a Hash with an Array under a known key as the result list before raising.
  4. Retry once after a short backoff in case the object payload was a transient rate-limit response.

Example fix

# before
unless parsed.is_a?(Array)
  raise Error, "Unexpected response format from search endpoint"
end

# after
results =
  if parsed.is_a?(Array)
    parsed
  elsif parsed.is_a?(Hash) && parsed["data"].is_a?(Array)
    Rails.logger.warn("MFAPI search returned wrapped payload: #{parsed.keys}")
    parsed["data"]
  else
    raise Error, "Unexpected response format from search endpoint: #{parsed.to_s.truncate(200)}"
  end
Defensive patterns

Strategy: try-catch

Try / catch

begin
  result = provider.search_securities(query)
rescue Provider::Mfapi::Error => e
  Rails.logger.warn("MFAPI search unusable: #{e.message}")
  result = nil # show user 'no results' instead of crashing
end

Prevention

When it happens

Trigger: Calling search_securities("axis blue") and MFAPI returning a JSON object such as {"message": "..."} or an empty object {} instead of an array; an upstream API revision that wraps results in {"data": [...]}; a rate-limit or maintenance payload that is valid JSON but object-shaped and lacks status == ERROR/FAIL.

Common situations: MFAPI occasionally returns object-shaped maintenance/rate-limit payloads that the status-based check_api_error! does not recognize; a proxy or CDN injecting a JSON error object; upstream schema drift after an MFAPI release; querying with an empty q param that yields a non-array body.

Related errors


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