we-promise/sure · warning · Provider::Frankfurter::Error

Frankfurter currencies endpoint returned no data

Error message

Frankfurter currencies endpoint returned no data

What it means

Raised as Provider::Frankfurter::Error by healthy? when GET /currencies on the Frankfurter service returns a blank body (empty string, nil, empty JSON). Frankfurter is a keyless public FX API; this check is the provider's health probe. A blank body means the service responded with nothing (or JSON.parse produced ''/nil), so the provider treats the integration as unhealthy rather than reporting a false-positive OK.

Source

Thrown at app/models/provider/frankfurter.rb:31

# unlike v1 this provider does not need its own lookback-window logic.
class Provider::Frankfurter < Provider
  include ExchangeRateConcept, RateLimitable
  extend SslConfigurable

  Error = Class.new(Provider::Error)
  RateLimitError = Class.new(Error)

  # No published rate limit, but a light throttle is cheap insurance.
  MIN_REQUEST_INTERVAL = 0.15

  def initialize
    # No API key required, public endpoint only.
  end

  def healthy?
    with_provider_response do
      body = get_json("/currencies")
      raise Error, "Frankfurter currencies endpoint returned no data" if body.blank?
      true
    end
  end

  def usage
    with_provider_response do
      UsageData.new(used: nil, limit: nil, utilization: nil, plan: "Free (no key required)")
    end
  end

  # GET /rate/{base}/{quote}?date=... -> { date:, base:, quote:, rate: }.
  # Frankfurter carries forward weekends/holidays itself, so the returned
  # date may differ from the requested one but is never simply missing.
  def fetch_exchange_rate(from:, to:, date:)
    from = sanitize_currency(from)
    to = sanitize_currency(to)

    with_provider_response do

View on GitHub (pinned to e69894adb9)

Solutions

  1. curl the endpoint your app is configured with: curl -i "${FRANKFURTER_URL:-https://api.frankfurter.dev/v2}/currencies" and confirm a non-empty JSON body
  2. Check FRANKFURTER_URL in the environment — a wrong base path (missing /v2 or trailing path) yields empty responses
  3. If self-hosting Frankfurter, verify the service is fully started and its upstream ECB data fetch has run
  4. Retry the health check after a short delay; transient empty responses from load balancers usually clear
  5. If api.frankfurter.dev itself is returning empty bodies, check the project's status page — nothing client-side to fix

Example fix

# before: healthy? raises, status page shows provider down on a transient empty body
provider.healthy?

# after: treat one blank probe as degraded, not failed
begin
  provider.healthy?
rescue Provider::Frankfurter::Error => e
  Rails.logger.warn("Frankfurter health probe failed once: #{e.message}; retrying")
  sleep 2
  provider.healthy?
end
Defensive patterns

Strategy: retry

Validate before calling

# Verify the configured URL returns a JSON object before relying on healthy?
uri = URI("#{ENV['FRANKFURTER_URL'].presence || 'https://api.frankfurter.dev/v2'}/currencies")
body = Net::HTTP.get(uri)
JSON.parse(body).is_a?(Hash) && body.length > 2

Try / catch

def frankfurter_healthy?(attempts: 2)
  attempts.times do
    return true if provider.healthy?
  rescue Provider::Frankfurter::Error => e
    Rails.logger.warn("Frankfurter probe failed: #{e.message}")
  end
  false
end

Prevention

When it happens

Trigger: Calling healthy? (e.g. from a provider status page or connection test) when the Frankfurter deployment returns an empty 200 body, a proxy strips the body, or an FRANKFURTER_URL override points at a mirror that 200s with no content. Note: non-2xx statuses raise Faraday errors instead; this error is specifically about a successful-but-empty response.

Common situations: Health checks against a self-hosted Frankfurter instance that is still booting, a misconfigured FRANKFURTER_URL pointing at the wrong path/host, CDN or ingress returning empty bodies under load, and frankfurter.dev API changes to the /currencies route.

Related errors


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