{"record":{"id":"e0538bc209f5e2c1","repo":"we-promise/sure","slug":"no-data-returned-from-search-endpoint","errorCode":null,"errorMessage":"No data returned from search endpoint","messagePattern":"No data returned from search endpoint","errorType":"exception","errorClass":"Provider::AlphaVantage::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/alpha_vantage.rb","lineNumber":106,"sourceCode":"\n  # ================================\n  #           Securities\n  # ================================\n\n  def search_securities(symbol, country_code: nil, exchange_operating_mic: nil)\n    with_provider_response do\n      throttle_request\n      response = client.get(\"#{base_url}/query\") do |req|\n        req.params[\"function\"] = \"SYMBOL_SEARCH\"\n        req.params[\"keywords\"] = symbol\n      end\n\n      parsed = JSON.parse(response.body)\n      check_api_error!(parsed)\n      data = parsed.dig(\"bestMatches\")\n\n      if data.nil?\n        raise Error, \"No data returned from search endpoint\"\n      end\n\n      data.first(25).map do |match|\n        av_ticker = match[\"1. symbol\"]\n        region = match[\"4. region\"]\n        currency = match[\"8. currency\"]\n\n        # Cache the API-returned currency so fetch_security_prices can use it\n        # instead of relying solely on the hardcoded suffix→currency fallback\n        if currency.present?\n          cache_key = \"alpha_vantage:currency:#{av_ticker.upcase}\"\n          Rails.cache.write(cache_key, currency, expires_in: 24.hours)\n        end\n\n        Security.new(\n          symbol: strip_av_suffix(av_ticker),\n          name: match[\"2. name\"],\n          logo_url: nil,","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/alpha_vantage.rb#L88-L124","documentation":"Provider::AlphaVantage#search_securities raises Provider::AlphaVantage::Error when a SYMBOL_SEARCH response's JSON has no 'bestMatches' key. Alpha Vantage signals errors as JSON keys rather than HTTP statuses; check_api_error! already handled 'Note', 'Information', and 'Error Message', so a missing bestMatches means an unexpected payload shape (empty keywords, API plan change, or undocumented response).","triggerScenarios":"search_securities with an empty/blank symbol so AV returns an empty object; upstream API returning {} or an unmapped key; API version/plan behavior change removing bestMatches.","commonSituations":"Autocomplete firing with an empty query; symbol with only special characters; free-tier endpoint behavior drift.","solutions":["Skip the call for blank/whitespace keywords: return [] unless symbol.to_s.strip.present?.","Log/inspect response.body for the actual keys to detect shape changes.","Wrap the call and treat it as 'no results' for UI purposes while alerting on the unexpected shape.","If the shape genuinely changed, update the parser to the current Alpha Vantage response format."],"exampleFix":"# before\nprovider.search_securities(params[:q])\n\n# after\nresults = params[:q].to_s.strip.present? ? provider.search_securities(params[:q].strip) : []","handlingStrategy":"validation","validationCode":"symbol.to_s.strip.present? # skip search for blank keywords","typeGuard":null,"tryCatchPattern":"begin\n  provider.search_securities(q)\nrescue Provider::AlphaVantage::Error => e\n  return [] if e.message.include?(\"No data returned\")\n  raise\nend","preventionTips":["Do not call search with empty/whitespace keywords.","Cache search results to avoid burning the 25/day quota on repeats.","Monitor for response-shape drift; extend check_api_error! if AV adds new message keys."],"tags":["alpha-vantage","provider","api-response","search","ruby"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}