{"record":{"id":"9909a3f158d37d49","repo":"we-promise/sure","slug":"api-error-parsed-message-parsed-status","errorCode":null,"errorMessage":"API error: #{parsed['message'] || parsed['status']}","messagePattern":"API error: #(.+?)","errorType":"exception","errorClass":"Provider::Mfapi::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/mfapi.rb","lineNumber":165,"sourceCode":"          interval: 1.0,\n          interval_randomness: 0.5,\n          backoff_factor: 2,\n          exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [ Faraday::ConnectionFailed ]\n        })\n\n        faraday.request :json\n        faraday.response :raise_error\n        faraday.headers[\"Accept\"] = \"application/json\"\n      end\n    end\n\n    # throttle_request and min_request_interval provided by RateLimitable\n\n    def check_api_error!(parsed)\n      return unless parsed.is_a?(Hash)\n\n      if parsed[\"status\"] == \"ERROR\" || parsed[\"status\"] == \"FAIL\"\n        raise Error, \"API error: #{parsed['message'] || parsed['status']}\"\n      end\n    end\nend\n","sourceCodeStart":147,"sourceCodeEnd":169,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/mfapi.rb#L147-L169","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the embedded message — it usually says exactly what MFAPI disliked (e.g. invalid scheme code).","Re-resolve the symbol through search_securities and use the returned schemeCode rather than a hand-entered value.","If the scheme was genuinely delisted/merged, update or remove the stored Security instead of retrying.","Only retry when the message indicates a transient condition; status ERROR for unknown codes will never succeed on retry."],"exampleFix":"# before\nsecurity = provider.fetch_security_prices(symbol: \"AXIS_BLUECHIP\", start_date: ..., end_date: ...)\n\n# after\nmatch = provider.search_securities(\"Axis Bluechip Direct Growth\").data.first\nsecurity = provider.fetch_security_prices(symbol: match.symbol, start_date: ..., end_date: ...)","handlingStrategy":"try-catch","validationCode":"code = code.to_s.strip\nraise ArgumentError, \"schemeCode must be numeric\" unless code.match?(/\\A\\d+\\z/)","typeGuard":null,"tryCatchPattern":"begin\n  provider.fetch_security_prices(symbol: code, start_date: from, end_date: to)\nrescue Provider::Mfapi::Error => e\n  raise if e.message.exclude?(\"API error\") # only swallow upstream API errors\n  mark_security_stale(code, e.message)\nend","preventionTips":["Never feed user-typed symbols directly to /mf/{code} — always resolve through search first.","Persist the resolved schemeCode on the Security record so later syncs reuse a known-good id.","Distinguish permanent ERROR statuses (no retry) from transient FAIL ones (retry with backoff) in your handler."],"tags":["mfapi","mutual-funds","api-error","invalid-symbol"],"backgroundTag":"provider-api-error-status","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}