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

No NAV data returned for scheme #{symbol}

Error message

No NAV data returned for scheme #{symbol}

What it means

Raised by Provider::Mfapi#fetch_security_prices (as InvalidSecurityPriceError) when the /mf/{schemeCode}?startDate=...&endDate=... response is valid JSON but its data field is either absent (nil) or not an Array. The MFAPI NAV contract is {meta: {...}, data: [{date, nav}, ...]}, so a missing/non-array data means the payload is an error or maintenance envelope that check_api_error! did not classify (it only fires on status ERROR/FAIL). Distinct from error 341: here the payload shape itself is wrong, before any row filtering happens.

Source

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

      historical_data.data.select { |p| p.date <= date }.max_by(&:date) || historical_data.data.first
    end
  end

  def fetch_security_prices(symbol:, exchange_operating_mic: nil, start_date:, end_date:)
    with_provider_response do
      throttle_request
      response = client.get("#{base_url}/mf/#{CGI.escape(symbol)}") do |req|
        req.params["startDate"] = start_date.to_s
        req.params["endDate"] = end_date.to_s
      end

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

      nav_data = parsed["data"]

      if nav_data.nil? || !nav_data.is_a?(Array)
        raise InvalidSecurityPriceError, "No NAV data returned for scheme #{symbol}"
      end

      nav_data.filter_map do |entry|
        nav = entry["nav"]
        date_str = entry["date"]

        next if nav.nil? || nav.to_f <= 0 || date_str.blank?

        # MFAPI returns dates as DD-MM-YYYY
        date = Date.strptime(date_str, "%d-%m-%Y")

        Price.new(
          symbol: symbol,
          date: date,
          price: nav.to_f,
          currency: "INR",
          exchange_operating_mic: exchange_operating_mic
        )

View on GitHub (pinned to e69894adb9)

Solutions

  1. Capture and inspect response.body for the failing schemeCode to see which envelope MFAPI actually returned.
  2. Treat a missing data key as an upstream error: log it via DebugLogEntry with provider_key and retry once after backoff before raising.
  3. Confirm the schemeCode exists via the /mf/search endpoint first when the symbol is user-supplied.
  4. If MFAPI renamed the field, map the new key with parsed["data"] || parsed["nav"] fallback only after verifying upstream docs.

Example fix

# before
nav_data = parsed["data"]
if nav_data.nil? || !nav_data.is_a?(Array)
  raise InvalidSecurityPriceError, "No NAV data returned for scheme #{symbol}"
end

# after
nav_data = parsed.is_a?(Hash) ? parsed["data"] : nil
if nav_data.nil? || !nav_data.is_a?(Array)
  DebugLogEntry.capture("mfapi", :error, "Unexpected NAV payload for #{symbol}", metadata: { body: parsed.to_s.truncate(500) })
  raise InvalidSecurityPriceError, "No NAV data returned for scheme #{symbol}"
end
Defensive patterns

Strategy: try-catch

Try / catch

begin
  series = provider.fetch_security_prices(symbol: code, start_date: from, end_date: to)
rescue Provider::Mfapi::InvalidSecurityPriceError => e
  DebugLogEntry.capture("mfapi", :error, e.message, provider_key: "mfapi")
  series = ProviderResponse.new(data: [])
end

Prevention

When it happens

Trigger: MFAPI returns {"message": "..."} or {} for an overloaded/rate-limited request; a scheme that exists but returns an object without data while its status field is missing; upstream schema change renaming data; intermediate proxy returning valid-JSON error object without status.

Common situations: Hitting MFAPI's unwritten rate limits during a large portfolio backfill; MFAPI maintenance windows; using a schemeCode that triggers a non-standard response (e.g. non-numeric garbage in the URL segment, though that usually yields status ERROR); CDN edge responses.

Related errors


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