we-promise/sure · error · Provider::TwelveData::InvalidExchangeRateError

API error (code: #{error_code}): #{error_message}

Error message

API error (code: #{error_code}): #{error_message}

What it means

Provider::TwelveData::InvalidExchangeRateError raised in fetch_exchange_rates when the response body has no "values" array after both attempts: the /time_series endpoint (direct pair, 1 credit) and the /time_series/cross fallback (5 credits). check_api_error! already passed, so the API responded successfully but with no data payload for the pair/date range.

Source

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

        Rails.logger.info("#{self.class.name}: Currency pair #{from}/#{to} not available, fetching via time_series/cross API")
        throttle_request(credits: 5)
        response = client.get("#{base_url}/time_series/cross") do |req|
          req.params["base"] = from
          req.params["quote"] = to
          req.params["start_date"] = start_date.to_s
          req.params["end_date"] = end_date.to_s
          req.params["interval"] = "1day"
        end

        parsed = JSON.parse(response.body)
        check_api_error!(parsed)
        data = parsed.dig("values")
      end

      if data.nil?
        error_message = parsed.dig("message") || "No data returned"
        error_code = parsed.dig("code") || "unknown"
        raise InvalidExchangeRateError, "API error (code: #{error_code}): #{error_message}"
      end

      data.map do |resp|
        rate = resp.dig("close")
        date = resp.dig("datetime")
        if rate.nil? || rate.to_f <= 0
          Rails.logger.warn("#{self.class.name} returned invalid rate data for pair from: #{from} to: #{to} on: #{date}.  Rate data: #{rate.inspect}")
          next
        end

        Rate.new(date: date.to_date, from:, to:, rate:)
      end.compact
    end
  end

  # ================================
  #           Securities
  # ================================

View on GitHub (pinned to e69894adb9)

Solutions

  1. Verify the currency pair with a direct API call (curl 'https://api.twelvedata.com/time_series?symbol=EUR/USD&apikey=...') to see whether values are returned at all
  2. Widen the date range — single-day or weekend-only windows commonly come back empty
  3. Check the error_code/message embedded in the message: code 429 means rate limit (raise came after throttle), other codes indicate plan/pair restrictions
  4. Fall back to another exchange-rate provider for pairs Twelve Data does not cover

Example fix

// before
rates = provider.fetch_exchange_rates(from: "TRY", to: "KRW", start_date: d, end_date: d)

// after
response = provider.fetch_exchange_rates(from: "TRY", to: "KRW", start_date: d - 7.days, end_date: d)
if response.error.present?
  Rails.logger.warn("TwelveData FX failed: #{response.error.message}")
  response = fallback_provider.fetch_exchange_rates(from: "TRY", to: "KRW", start_date: d - 7.days, end_date: d)
end
rates = response.data
Defensive patterns

Strategy: fallback

Validate before calling

response = provider.fetch_exchange_rates(from:, to:, start_date: date - 7.days, end_date: date)
if response.error.present?
  # use fallback provider rather than raising into the caller
end

Try / catch

begin
  provider.fetch_exchange_rates(from:, to:, start_date:, end_date:)
rescue Provider::TwelveData::InvalidExchangeRateError => e
  Rails.logger.warn("TwelveData FX unavailable for #{from}/#{to}: #{e.message}")
  fallback_provider.fetch_exchange_rates(from:, to:, start_date:, end_date:)
end

Prevention

When it happens

Trigger: Fetching exchange rates for a pair Twelve Data has no time series for (both direct and cross listings empty), a date range where no data exists (weekend/holiday-only range), or an unsupported/exotic currency code that returns JSON without a values key.

Common situations: Syncing an account with an exotic currency (e.g. TRY/KRW crosses); requesting a single weekend date; a typo'd currency code that silently yields an empty response; free-plan limits returning empty bodies instead of explicit errors.

Related errors


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