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 doView on GitHub (pinned to e69894adb9)
Solutions
- 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
- Check FRANKFURTER_URL in the environment — a wrong base path (missing /v2 or trailing path) yields empty responses
- If self-hosting Frankfurter, verify the service is fully started and its upstream ECB data fetch has run
- Retry the health check after a short delay; transient empty responses from load balancers usually clear
- 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
- Treat a single failed health probe as 'degraded' — retry before declaring the provider down
- Pin FRANKFURTER_URL to the official versioned host in all environments
- Health-check third-party providers from the same network path the sync jobs use
- Cache the last-known health result briefly to avoid hammering /currencies from every process
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
- Unexpected Frankfurter response shape
- Invalid date in Frankfurter response: #{e.message}
- Invalid Frankfurter response: #{e.message}
- Unexpected Frankfurter response shape (expected an array)
- External assistant returned an empty response.
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/f2b0be1bd63286e9.
Report an issue: GitHub.