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_requestView on GitHub (pinned to e69894adb9)
Solutions
- Log response.body (or capture it in DebugLogEntry) at the failure site to see the actual payload MFAPI returned.
- 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.
- 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.
- 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
- Wrap provider search calls and degrade to an empty result set rather than surfacing the raw error in UI.
- Capture the raw response body via DebugLogEntry when the shape guard trips so envelope drift is diagnosable later.
- Keep a contract test asserting MFAPI /mf/search returns a JSON array, so upstream drift is caught in CI.
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
- No NAV data returned for scheme #{symbol}
- Unexpected response format from search API
- Unexpected response format from EOD API
- Unexpected Frankfurter response shape
- Malformed Kraken API response
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/127920b724910c57.
Report an issue: GitHub.