flippercloud/flipper · error · Flipper::Adapters::Http::Error

Failed with status: #{response.code}

Error message

Failed with status: #{response.code}

What it means

The Http adapter talks to a Flipper API endpoint (Flipper Cloud at https://www.flippercloud.io/adapter, or a self-hosted flipper-api). get() issues GET /features/<key> and only accepts 200 (parse gates) or 404 (feature unknown, return defaults); every other status raises Flipper::Adapters::Http::Error. The message shows the status code plus any JSON `message`/`more_info` from the API body, and the full Net::HTTP response is attached as error.response for inspection.

Source

Thrown at lib/flipper/adapters/http.rb:40

                             open_timeout: options[:open_timeout],
                             write_timeout: options[:write_timeout],
                             max_retries: options[:max_retries],
                             debug_output: options[:debug_output])
        @last_get_all_etag = nil
        @last_get_all_result = nil
        @last_get_all_response = nil
        @get_all_mutex = Mutex.new
      end

      def get(feature)
        response = @client.get("/features/#{path_escape(feature.key)}")
        if response.is_a?(Net::HTTPOK)
          parsed_response = Typecast.from_json(response.body)
          result_for_feature(feature, parsed_response.fetch('gates'))
        elsif response.is_a?(Net::HTTPNotFound)
          default_config
        else
          raise Error, response
        end
      end

      def get_multi(features)
        response = @client.get("/features?#{query_for_features(features)}")
        raise Error, response unless response.is_a?(Net::HTTPOK)

        parsed_response = Typecast.from_json(response.body)
        parsed_features = parsed_response.fetch('features')
        gates_by_key = parsed_features.each_with_object({}) do |parsed_feature, hash|
          hash[parsed_feature['key']] = parsed_feature['gates']
          hash
        end

        result = {}
        features.each do |feature|
          result[feature.key] = result_for_feature(feature, gates_by_key[feature.key])
        end

View on GitHub (pinned to 1f86de3ec9)

Solutions

  1. Check error.response.code and error.response.body (or set debug_output: $stderr on the adapter / FLIPPER_CLOUD_DEBUG_OUTPUT_STDOUT=1) to see the exact status and API message.
  2. For 401/403: confirm the token header — Flipper Cloud needs FLIPPER_CLOUD_TOKEN set and the URL left at the default /adapter endpoint; rotate the token in Cloud settings if it is stale.
  3. For wrong-endpoint responses: verify the url option actually reaches a Flipper API (self-hosted: https://app.example.com/flipper/api or wherever flipper-api is mounted), not the UI.
  4. For 429/5xx transient failures: serve reads from a local cache (Cloud does this via local_adapter + DualWrite/Poll), or wrap the Http adapter with Flipper::Adapters::Failsafe/Failover plus Memoizable so checks degrade instead of raising.
  5. Set explicit read/open/write timeouts on the adapter so hung endpoints fail fast rather than piling up.

Example fix

# before
adapter = Flipper::Adapters::Http.new(url: "http://localhost:9999/") # UI, not an API
Flipper.new(adapter)[:search].enabled? # => Failed with status: 404/500

# after
http = Flipper::Adapters::Http.new(
  url: ENV.fetch("FLIPPER_CLOUD_URL", "https://www.flippercloud.io/adapter"),
  headers: { "flipper-cloud-token" => ENV.fetch("FLIPPER_CLOUD_TOKEN") },
  read_timeout: 5, open_timeout: 2
)
flipper = Flipper.new(Flipper::Adapters::Failsafe.new(http, errors: [Flipper::Adapters::Http::Error]))
Defensive patterns

Strategy: fallback

Validate before calling

require "net/http"
uri = URI.join(flipper_url, "/features/nonexistent_probe")
response = Net::HTTP.get_response(uri)
# 200/401/403 tells you auth is the problem before wiring Flipper; a 404/HTML body tells you the URL is wrong
puts response.code

Try / catch

begin
  Flipper[:search].enabled?
rescue Flipper::Adapters::Http::Error => e
  case e.response
  when Net::HTTPUnauthorized, Net::HTTPForbidden then raise # config bug, fail loudly
  else false # transient upstream failure: fail closed
  end
end

Prevention

When it happens

Trigger: GET /features/<key> returning 401/403 (bad or missing flipper-cloud-token header / basic auth), 429 (Cloud rate limiting), 5xx (Cloud outage or self-hosted API error), or any non-200/404 from a URL that is not actually a Flipper API. Raised from feature checks (get, get_multi, get_all, features) when reads go over HTTP.

Common situations: Missing/rotated FLIPPER_CLOUD_TOKEN so every request 401s; URL pointed at the wrong place (the UI on port 9999 instead of the mounted /api path, or a typo'd host returning HTML error pages); expired/invalid Cloud environment token; a reverse proxy or WAF intercepting with 403/502; a Cloud incident returning 5xx.

Related errors


AI-assisted analysis of flippercloud/flipper@1f86de3ec9 (2026-08-23). Data as JSON: /api/errors/a32a6e28b4f3284a. Report an issue: GitHub.