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

API error: #{parsed['message'] || parsed['status']}

Error message

API error: #{parsed['message'] || parsed['status']}

What it means

Raised by Provider::Mfapi#check_api_error! when a parsed response Hash carries status == "ERROR" or "FAIL". MFAPI signals client-side failures this way with HTTP 200 — most commonly {"status": "ERROR", "message": "..."} for a scheme code it does not recognize. The thrown message embeds the upstream message when present, otherwise just the status word. This is MFAPI's canonical 'bad request / unknown scheme' channel, so the fix is almost always on the request side, not retry logic.

Source

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

          interval: 1.0,
          interval_randomness: 0.5,
          backoff_factor: 2,
          exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [ Faraday::ConnectionFailed ]
        })

        faraday.request :json
        faraday.response :raise_error
        faraday.headers["Accept"] = "application/json"
      end
    end

    # throttle_request and min_request_interval provided by RateLimitable

    def check_api_error!(parsed)
      return unless parsed.is_a?(Hash)

      if parsed["status"] == "ERROR" || parsed["status"] == "FAIL"
        raise Error, "API error: #{parsed['message'] || parsed['status']}"
      end
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Read the embedded message — it usually says exactly what MFAPI disliked (e.g. invalid scheme code).
  2. Re-resolve the symbol through search_securities and use the returned schemeCode rather than a hand-entered value.
  3. If the scheme was genuinely delisted/merged, update or remove the stored Security instead of retrying.
  4. Only retry when the message indicates a transient condition; status ERROR for unknown codes will never succeed on retry.

Example fix

# before
security = provider.fetch_security_prices(symbol: "AXIS_BLUECHIP", start_date: ..., end_date: ...)

# after
match = provider.search_securities("Axis Bluechip Direct Growth").data.first
security = provider.fetch_security_prices(symbol: match.symbol, start_date: ..., end_date: ...)
Defensive patterns

Strategy: try-catch

Validate before calling

code = code.to_s.strip
raise ArgumentError, "schemeCode must be numeric" unless code.match?(/\A\d+\z/)

Try / catch

begin
  provider.fetch_security_prices(symbol: code, start_date: from, end_date: to)
rescue Provider::Mfapi::Error => e
  raise if e.message.exclude?("API error") # only swallow upstream API errors
  mark_security_stale(code, e.message)
end

Prevention

When it happens

Trigger: GET /mf/{schemeCode} with a nonexistent or malformed scheme code (the classic trigger); a search query whose shape the API rejects; occasional upstream 5xx-style FAIL statuses during MFAPI incidents; passing an ISIN or scheme name where the numeric schemeCode is expected.

Common situations: User pastes an ISIN (INF109K01Z48) or fund name instead of the numeric schemeCode (120503); stale schemeCode persisted before a fund merger/termination; fat-fingered symbol; transient MFAPI FAIL during their maintenance.

Related errors


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