diff --git a/README.md b/README.md
index 3356f44d2c025682dd7386350b25840e0baecabb..67fb58f7b952c7fe6d14cdd114aff8921731c114 100644
--- a/README.md
+++ b/README.md
@@ -753,7 +753,31 @@ This uses the PostcodeAnywhere UK Geocode service, this will geocode any string
 
 ### IP Address Services
 
-#### FreeGeoIP (`:freegeoip`)
+#### Ipstack (`:ipstack`)
+
+* **API key**: required - see https://ipstack.com/product
+* **Quota**: 10,000 requests per month (with free API Key, 50,000/day and up for paid plans)
+* **Region**: world
+* **SSL support**: yes ( only with paid plan )
+* **Languages**: English, German, Spanish, French, Japanese, Portugues (Brazil), Russian, Chinese
+* **Documentation**: https://ipstack.com/documentation
+* **Terms of Service**: ?
+* **Limitations**: ?
+* **Notes**: To use Ipstack set `Geocoder.configure(:ip_lookup => :ipstack, :api_key => "your_ipstack_api_key")`. Support for the ipstack JSONP, batch lookup, and requester lookup features has not been implemented yet.  This lookup does support the params shown below:
+* ```ruby
+    # Search Options Examples
+
+    Geocoder.search('0.0.0.0', { 
+        params: {
+            hostname: '1', 
+            security: '1', 
+            fields: 'country_code,location.capital,...', # see ipstack documentation
+            language: 'en'
+        }
+    })
+    ```
+
+#### FreeGeoIP (`:freegeoip`) - DEPRECATED ( [ more information](https://github.com/alexreisner/geocoder/wiki/Freegeoip-Discontinuation) )
 
 * **API key**: none
 * **Quota**: 15,000 requests per hour. After reaching the hourly quota, all of your requests will result in HTTP 403 (Forbidden) until it clears up on the next roll over.
diff --git a/geocoder.gemspec b/geocoder.gemspec
index 8bb7462e6e41014a32df81429e767f08200da4ef..7488885ca218f0c8fa2593e9effce6e37d4b4bb4 100644
--- a/geocoder.gemspec
+++ b/geocoder.gemspec
@@ -19,6 +19,17 @@ Gem::Specification.new do |s|
   s.executables = ["geocode"]
   s.license     = 'MIT'
 
+  s.post_install_message = %q{
+
+IMPORTANT: Geocoder has recently switched its default ip lookup.  If you have specified :freegeoip
+in your configuration, you must choose a different ip lookup by July 1, 2018, which is when
+the Freegeoip API will be discontinued.
+
+For more information visit:
+https://github.com/alexreisner/geocoder/wiki/Freegeoip-Discontinuation
+
+}
+
   s.metadata = {
     'source_code_uri' => 'https://github.com/alexreisner/geocoder',
     'changelog_uri'   => 'https://github.com/alexreisner/geocoder/blob/master/CHANGELOG.md'
diff --git a/lib/generators/geocoder/config/templates/initializer.rb b/lib/generators/geocoder/config/templates/initializer.rb
index 41d682f2f24ac39815deaeffcd569576a5f51fed..88c64890cdfb956d497cae86e6b1f475f6c06827 100644
--- a/lib/generators/geocoder/config/templates/initializer.rb
+++ b/lib/generators/geocoder/config/templates/initializer.rb
@@ -2,7 +2,7 @@ Geocoder.configure(
   # Geocoding options
   # timeout: 3,                 # geocoding service timeout (secs)
   # lookup: :google,            # name of geocoding service (symbol)
-  # ip_lookup: :freegeoip,      # name of IP address geocoding service (symbol)
+  # ip_lookup: :ipinfo_io,      # name of IP address geocoding service (symbol)
   # language: :en,              # ISO-639 language code
   # use_https: false,           # use HTTPS for lookup requests? (if supported)
   # http_proxy: nil,            # HTTP proxy server (user:pass@host:port)
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index b756f768f40e9fe94d6b80fa56505bf688a72c41..d439c7d231ef20d5c2044b6171c8c6a4ed5cef7f 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -72,7 +72,8 @@ module Geocoder
         :ipinfo_io,
         :ipapi_com,
         :ipdata_co,
-        :db_ip_com
+        :db_ip_com,
+        :ipstack,
       ]
     end
 
diff --git a/lib/geocoder/lookups/ipstack.rb b/lib/geocoder/lookups/ipstack.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2d8d3c328c0cff237269495bc4dd0ef3d578d232
--- /dev/null
+++ b/lib/geocoder/lookups/ipstack.rb
@@ -0,0 +1,64 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/ipstack'
+
+module Geocoder::Lookup
+  class Ipstack < Base
+
+    ERROR_CODES = {
+      404 => Geocoder::InvalidRequest,
+      101 => Geocoder::InvalidApiKey,
+      102 => Geocoder::Error,
+      103 => Geocoder::InvalidRequest,
+      104 => Geocoder::OverQueryLimitError,
+      105 => Geocoder::RequestDenied,
+      301 => Geocoder::InvalidRequest,
+      302 => Geocoder::InvalidRequest,
+      303 => Geocoder::RequestDenied,
+    }
+    ERROR_CODES.default = Geocoder::Error
+
+    def name
+      "Ipstack"
+    end
+
+    def query_url(query)
+      extra_params = url_query_string(query)
+      url = "#{protocol}://#{host}/#{query.sanitized_text}?access_key=#{api_key}"
+      url << "&#{extra_params}" unless extra_params.empty?
+      url
+    end
+
+    private
+
+    def results(query)
+      # don't look up a loopback address, just return the stored result
+      return [reserved_result(query.text)] if query.loopback_ip_address?
+
+      return [] unless doc = fetch_data(query)
+
+      if error = doc['error']
+        code = error['code']
+        msg = error['info']
+        raise_error(ERROR_CODES[code], msg ) || Geocoder.log(:warn, "Ipstack Geocoding API error: #{msg}")
+        return []
+      end
+      [doc]
+    end
+
+    def reserved_result(ip)
+      {
+        "ip"           => ip,
+        "country_name" => "Reserved",
+        "country_code" => "RD"
+      }
+    end
+
+    def host
+      configuration[:host] || "api.ipstack.com"
+    end
+
+    def api_key
+      configuration.api_key
+    end
+  end
+end
diff --git a/lib/geocoder/results/ipstack.rb b/lib/geocoder/results/ipstack.rb
new file mode 100644
index 0000000000000000000000000000000000000000..af1099fb8cfcc47fe1e47adebff111af8f0f7e34
--- /dev/null
+++ b/lib/geocoder/results/ipstack.rb
@@ -0,0 +1,60 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Ipstack < Base
+
+    def address(format = :full)
+      s = region_code.empty? ? "" : ", #{region_code}"
+      "#{city}#{s} #{zip}, #{country_name}".sub(/^[ ,]*/, "")
+    end
+
+    def state
+      @data['region_name']
+    end
+
+    def state_code
+      @data['region_code']
+    end
+
+    def country
+      @data['country_name']
+    end
+
+    def postal_code
+      @data['zip'] || @data['zipcode'] || @data['zip_code']
+    end
+
+    def self.response_attributes
+      [
+        ['ip', ''],
+        ['hostname', ''],
+        ['continent_code', ''],
+        ['continent_name', ''],
+        ['country_code', ''],
+        ['country_name', ''],
+        ['region_code', ''],
+        ['region_name', ''],
+        ['city', ''],
+        ['zip', ''],
+        ['latitude', 0],
+        ['longitude', 0],
+        ['location', {}],
+        ['time_zone', {}],
+        ['currency', {}],
+        ['connection', {}],
+        ['security', {}],
+      ]
+    end
+
+    response_attributes.each do |attr, default|
+      define_method attr do
+        @data[attr] || default
+      end
+    end
+
+    def metro_code
+      Geocoder.log(:warn, "Ipstack does not implement `metro_code` in api results.  Please discontinue use.")
+      0 # no longer implemented by ipstack
+    end
+  end
+end
diff --git a/test/fixtures/freegeoip_74_200_247_59 b/test/fixtures/freegeoip_74_200_247_59
index e187b40bdea9734e7d9147f37867dd627f557960..defc4ace5a4d41cfad998228576c6ce00eeec117 100644
--- a/test/fixtures/freegeoip_74_200_247_59
+++ b/test/fixtures/freegeoip_74_200_247_59
@@ -4,9 +4,9 @@
   "region_name": "Texas",
   "metro_code": "623",
   "zipcode": "75093",
-  "longitude": "-96.8134",
+  "longitude": -96.8134,
   "country_name": "United States",
   "country_code": "US",
   "ip": "74.200.247.59",
-  "latitude": "33.0347"
+  "latitude": 33.0347
 }
diff --git a/test/fixtures/ipstack_134_201_250_155 b/test/fixtures/ipstack_134_201_250_155
new file mode 100644
index 0000000000000000000000000000000000000000..cff0568d2616887b9e19711c6f497f74c1c88349
--- /dev/null
+++ b/test/fixtures/ipstack_134_201_250_155
@@ -0,0 +1,49 @@
+{
+  "ip": "134.201.250.155",
+  "hostname": "134.201.250.155",
+  "type": "ipv4",
+  "continent_code": "NA",
+  "continent_name": "North America",
+  "country_code": "US",
+  "country_name": "United States",
+  "region_code": "CA",
+  "region_name": "California",
+  "city": "Los Angeles",
+  "zip": "90013",
+  "latitude": 34.0453,
+  "longitude": -118.2413,
+  "location": {
+    "geoname_id": 5368361,
+    "capital": "Washington D.C.",
+    "languages": [
+        {
+          "code": "en",
+          "name": "English",
+          "native": "English"
+        }
+    ],
+    "country_flag": "https://assets.ipstack.com/images/assets/flags_svg/us.svg",
+    "country_flag_emoji": "🇺🇸",
+    "country_flag_emoji_unicode": "U+1F1FA U+1F1F8",
+    "calling_code": "1",
+    "is_eu": false
+  },
+  "time_zone": {
+    "id": "America/Los_Angeles",
+    "current_time": "2018-03-29T07:35:08-07:00",
+    "gmt_offset": -25200,
+    "code": "PDT",
+    "is_daylight_saving": true
+  },
+  "currency": {
+    "code": "USD",
+    "name": "US Dollar",
+    "plural": "US dollars",
+    "symbol": "$",
+    "symbol_native": "$"
+  },
+  "connection": {
+    "asn": 25876,
+    "isp": "Los Angeles Department of Water & Power"
+  }
+}
diff --git a/test/fixtures/ipstack_access_restricted b/test/fixtures/ipstack_access_restricted
new file mode 100644
index 0000000000000000000000000000000000000000..652e1cb9cc71e7e4c5532d7977bd80028c945101
--- /dev/null
+++ b/test/fixtures/ipstack_access_restricted
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 105,
+    "type": "function_access_restricted",
+    "info": "The current subscription plan does not support this API endpoint."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_batch_not_supported b/test/fixtures/ipstack_batch_not_supported
new file mode 100644
index 0000000000000000000000000000000000000000..b60a245f39265888925cd705901541441a68113c
--- /dev/null
+++ b/test/fixtures/ipstack_batch_not_supported
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 303,
+    "type": "batch_not_supported_on_plan",
+    "info": "The Bulk Lookup Endpoint is not supported on the current subscription plan"
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_inactive_user b/test/fixtures/ipstack_inactive_user
new file mode 100644
index 0000000000000000000000000000000000000000..1ead8fa9e34108b0136fcb89e143e91768219b06
--- /dev/null
+++ b/test/fixtures/ipstack_inactive_user
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 102,
+    "type": "inactive_user",
+    "info": "The current user account is not active. User will be prompted to get in touch with Customer Support."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_invalid_access_key b/test/fixtures/ipstack_invalid_access_key
new file mode 100644
index 0000000000000000000000000000000000000000..f119c8521205fde48a1368554eaa4745f7902115
--- /dev/null
+++ b/test/fixtures/ipstack_invalid_access_key
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 101,
+    "type": "invalid_access_key",
+    "info": "No API Key was specified or an invalid API Key was specified."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_invalid_api_function b/test/fixtures/ipstack_invalid_api_function
new file mode 100644
index 0000000000000000000000000000000000000000..e58000501006ee321716f20a2cb96fc85ac251e6
--- /dev/null
+++ b/test/fixtures/ipstack_invalid_api_function
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 103,
+    "type": "invalid_api_function",
+    "info": "The requested API endpoint does not exist."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_invalid_fields b/test/fixtures/ipstack_invalid_fields
new file mode 100644
index 0000000000000000000000000000000000000000..105f54119b44fe8109f3e06b5ac05077de696056
--- /dev/null
+++ b/test/fixtures/ipstack_invalid_fields
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 301,
+    "type": "invalid_fields",
+    "info": "One or more invalid fields were specified using the fields parameter."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_missing_access_key b/test/fixtures/ipstack_missing_access_key
new file mode 100644
index 0000000000000000000000000000000000000000..358e5c8e0c4c6cf63ba5d35b393f3e8c95a3402b
--- /dev/null
+++ b/test/fixtures/ipstack_missing_access_key
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 101,
+    "type": "missing_access_key",
+    "info": "No API Key was specified."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_no_results b/test/fixtures/ipstack_no_results
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/fixtures/ipstack_not_found b/test/fixtures/ipstack_not_found
new file mode 100644
index 0000000000000000000000000000000000000000..f644fa30af0867af86df35cbb909e7543b233de3
--- /dev/null
+++ b/test/fixtures/ipstack_not_found
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 404,
+    "type": "404_not_found",
+    "info": "The requested resource does not exist."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_protocol_access_restricted b/test/fixtures/ipstack_protocol_access_restricted
new file mode 100644
index 0000000000000000000000000000000000000000..7c915afaf5ef5f3386bac04d488a1fd919a65b41
--- /dev/null
+++ b/test/fixtures/ipstack_protocol_access_restricted
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 105,
+    "type": "https_access_restricted",
+    "info": "The user's current subscription plan does not support HTTPS Encryption."
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_too_many_ips b/test/fixtures/ipstack_too_many_ips
new file mode 100644
index 0000000000000000000000000000000000000000..b85c5e5d1cc355f47e7ca41d90ed8e3d440ac022
--- /dev/null
+++ b/test/fixtures/ipstack_too_many_ips
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 302,
+    "type": "too_many_ips",
+    "info": "Too many IPs have been specified for the Bulk Lookup Endpoint. (max. 50)"
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/ipstack_usage_limit b/test/fixtures/ipstack_usage_limit
new file mode 100644
index 0000000000000000000000000000000000000000..d3753f02a0acf2a6cc4c3e5142faff66b3cff1ee
--- /dev/null
+++ b/test/fixtures/ipstack_usage_limit
@@ -0,0 +1,8 @@
+{
+  "success": false,
+  "error": {
+    "code": 104,
+    "type": "usage_limit_reached",
+    "info": "The maximum allowed amount of monthly API requests has been reached."
+  }
+}
\ No newline at end of file
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 4857dfed71f30051ee1e0abd24a840b8257576bb..5698e49ef6841651f924dde2dcc11e2b7bba14ef 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -217,6 +217,14 @@ module Geocoder
       end
     end
 
+    require 'geocoder/lookups/ipstack'
+    class Ipstack
+      private
+      def default_fixture_filename
+        "ipstack_134_201_250_155"
+      end
+    end
+
     require 'geocoder/lookups/geoip2'
     class Geoip2
       private
diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb
index 7f21877f77b4359c3fe6b8c37a70ac5e123214c9..2afa95474ed44afe0aec22653f5742afc444b4be 100644
--- a/test/unit/lookup_test.rb
+++ b/test/unit/lookup_test.rb
@@ -24,7 +24,7 @@ class LookupTest < GeocoderTestCase
 
   def test_query_url_contains_values_in_params_hash
     Geocoder::Lookup.all_services_except_test.each do |l|
-      next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipdata_co, :ipinfo_io, :ipapi_com].include? l # does not use query string
+      next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipdata_co, :ipinfo_io, :ipapi_com, :ipstack].include? l # does not use query string
       set_api_key!(l)
       url = Geocoder::Lookup.get(l).query_url(Geocoder::Query.new(
         "test", :params => {:one_in_the_hand => "two in the bush"}
diff --git a/test/unit/lookups/ipstack_test.rb b/test/unit/lookups/ipstack_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd2a17682273c7c6e04353caf55c7e7463aed2bf
--- /dev/null
+++ b/test/unit/lookups/ipstack_test.rb
@@ -0,0 +1,285 @@
+# encoding: utf-8
+require 'test_helper'
+
+class SpyLogger
+  def initialize
+    @log = []
+  end
+
+  def logged?(msg)
+    @log.include?(msg)
+  end
+
+  def add(level, msg)
+    @log << msg
+  end
+end
+
+class IpstackTest < GeocoderTestCase
+
+  def setup
+    @logger = SpyLogger.new
+    Geocoder::Configuration.instance.data.clear
+    Geocoder::Configuration.set_defaults
+    Geocoder.configure(
+      :api_key => '123',
+      :ip_lookup => :ipstack,
+      :always_raise => :all,
+      :logger => @logger
+    )
+  end
+
+  def test_result_on_ip_address_search
+    result = Geocoder.search("134.201.250.155").first
+    assert result.is_a?(Geocoder::Result::Ipstack)
+  end
+
+  def test_result_components
+    result = Geocoder.search("134.201.250.155").first
+    assert_equal "Los Angeles, CA 90013, United States", result.address
+  end
+
+  def test_all_top_level_api_fields
+    result = Geocoder.search("134.201.250.155").first
+    assert_equal "134.201.250.155", result.ip
+    assert_equal "134.201.250.155", result.hostname
+    assert_equal "NA",              result.continent_code
+    assert_equal "North America",   result.continent_name
+    assert_equal "US",              result.country_code
+    assert_equal "United States",   result.country_name
+    assert_equal "CA",              result.region_code
+    assert_equal "California",      result.region_name
+    assert_equal "Los Angeles",     result.city
+    assert_equal "90013",           result.zip
+    assert_equal 34.0453,           result.latitude
+    assert_equal (-118.2413),       result.longitude
+  end
+
+  def test_nested_api_fields
+    result = Geocoder.search("134.201.250.155").first
+
+    assert result.location.is_a?(Hash)
+    assert_equal 5368361, result.location['geoname_id']
+
+    assert result.time_zone.is_a?(Hash)
+    assert_equal "America/Los_Angeles", result.time_zone['id']
+
+    assert result.currency.is_a?(Hash)
+    assert_equal "USD", result.currency['code']
+
+    assert result.connection.is_a?(Hash)
+    assert_equal 25876, result.connection['asn']
+
+    assert result.security.is_a?(Hash)
+  end
+
+  def test_required_base_fields
+    result = Geocoder.search("134.201.250.155").first
+    assert_equal "California",      result.state
+    assert_equal "CA",              result.state_code
+    assert_equal "United States",   result.country
+    assert_equal "90013",           result.postal_code
+    assert_equal [34.0453, -118.2413], result.coordinates
+  end
+
+  def test_logs_deprecation_of_metro_code_field
+    result = Geocoder.search("134.201.250.155").first
+    result.metro_code
+
+    assert @logger.logged?("Ipstack does not implement `metro_code` in api results.  Please discontinue use.")
+  end
+
+  def test_localhost_loopback
+    result = Geocoder.search("127.0.0.1").first
+    assert_equal "127.0.0.1", result.ip
+    assert_equal "RD",        result.country_code
+    assert_equal "Reserved",  result.country_name
+  end
+
+  def test_localhost_loopback_defaults
+    result = Geocoder.search("127.0.0.1").first
+    assert_equal "127.0.0.1", result.ip
+    assert_equal "",          result.hostname
+    assert_equal "",          result.continent_code
+    assert_equal "",          result.continent_name
+    assert_equal "RD",        result.country_code
+    assert_equal "Reserved",  result.country_name
+    assert_equal "",          result.region_code
+    assert_equal "",          result.region_name
+    assert_equal "",          result.city
+    assert_equal "",          result.zip
+    assert_equal 0,           result.latitude
+    assert_equal 0,           result.longitude
+    assert_equal({},          result.location)
+    assert_equal({},          result.time_zone)
+    assert_equal({},          result.currency)
+    assert_equal({},          result.connection)
+  end
+
+  def test_api_request_adds_access_key
+    lookup = Geocoder::Lookup.get(:ipstack)
+    assert_match 'http://api.ipstack.com/74.200.247.59?access_key=123', lookup.query_url(Geocoder::Query.new("74.200.247.59"))
+  end
+
+  def test_api_request_adds_security_when_specified
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    query_url = lookup.query_url(Geocoder::Query.new("74.200.247.59", params: { security: '1' }))
+
+    assert_match(/&security=1/, query_url)
+  end
+
+  def test_api_request_adds_hostname_when_specified
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    query_url = lookup.query_url(Geocoder::Query.new("74.200.247.59", params: { hostname: '1' }))
+
+    assert_match(/&hostname=1/, query_url)
+  end
+
+  def test_api_request_adds_language_when_specified
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    query_url = lookup.query_url(Geocoder::Query.new("74.200.247.59", params: { language: 'es' }))
+
+    assert_match(/&language=es/, query_url)
+  end
+
+  def test_api_request_adds_fields_when_specified
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    query_url = lookup.query_url(Geocoder::Query.new("74.200.247.59", params: { fields: 'foo,bar' }))
+
+    assert_match(/&fields=foo%2Cbar/, query_url)
+  end
+
+  def test_logs_warning_when_errors_are_set_not_to_raise
+    Geocoder::Configuration.instance.data.clear
+    Geocoder::Configuration.set_defaults
+    Geocoder.configure(api_key: '123', ip_lookup: :ipstack, logger: @logger)
+
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    lookup.send(:results, Geocoder::Query.new("not_found"))
+
+    assert @logger.logged?("Ipstack Geocoding API error: The requested resource does not exist.")
+  end
+
+  def test_uses_lookup_specific_configuration
+    Geocoder::Configuration.instance.data.clear
+    Geocoder::Configuration.set_defaults
+    Geocoder.configure(api_key: '123', ip_lookup: :ipstack, logger: @logger, ipstack: { api_key: '345'})
+
+    lookup = Geocoder::Lookup.get(:ipstack)
+    assert_match 'http://api.ipstack.com/74.200.247.59?access_key=345', lookup.query_url(Geocoder::Query.new("74.200.247.59"))
+  end
+
+  def test_not_authorized   lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::InvalidRequest do
+      lookup.send(:results, Geocoder::Query.new("not_found"))
+    end
+
+    assert_equal error.message, "The requested resource does not exist."
+  end
+
+  def test_missing_access_key
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::InvalidApiKey do
+      lookup.send(:results, Geocoder::Query.new("missing_access_key"))
+    end
+
+    assert_equal error.message, "No API Key was specified."
+  end
+
+  def test_invalid_access_key
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::InvalidApiKey do
+      lookup.send(:results, Geocoder::Query.new("invalid_access_key"))
+    end
+
+    assert_equal error.message, "No API Key was specified or an invalid API Key was specified."
+  end
+
+  def test_inactive_user
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::Error do
+      lookup.send(:results, Geocoder::Query.new("inactive_user"))
+    end
+
+    assert_equal error.message, "The current user account is not active. User will be prompted to get in touch with Customer Support."
+  end
+
+  def test_invalid_api_function
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::InvalidRequest do
+      lookup.send(:results, Geocoder::Query.new("invalid_api_function"))
+    end
+
+    assert_equal error.message, "The requested API endpoint does not exist."
+  end
+
+  def test_usage_limit
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::OverQueryLimitError do
+      lookup.send(:results, Geocoder::Query.new("usage_limit"))
+    end
+
+    assert_equal error.message, "The maximum allowed amount of monthly API requests has been reached."
+  end
+
+  def test_access_restricted
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::RequestDenied do
+      lookup.send(:results, Geocoder::Query.new("access_restricted"))
+    end
+
+    assert_equal error.message, "The current subscription plan does not support this API endpoint."
+  end
+
+  def test_protocol_access_restricted
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::RequestDenied do
+      lookup.send(:results, Geocoder::Query.new("protocol_access_restricted"))
+    end
+
+    assert_equal error.message, "The user's current subscription plan does not support HTTPS Encryption."
+  end
+
+  def test_invalid_fields
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::InvalidRequest do
+      lookup.send(:results, Geocoder::Query.new("invalid_fields"))
+    end
+
+    assert_equal error.message, "One or more invalid fields were specified using the fields parameter."
+  end
+
+  def test_too_many_ips
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::InvalidRequest do
+      lookup.send(:results, Geocoder::Query.new("too_many_ips"))
+    end
+
+    assert_equal error.message, "Too many IPs have been specified for the Bulk Lookup Endpoint. (max. 50)"
+  end
+
+  def test_batch_not_supported
+    lookup = Geocoder::Lookup.get(:ipstack)
+
+    error = assert_raise Geocoder::RequestDenied do
+      lookup.send(:results, Geocoder::Query.new("batch_not_supported"))
+    end
+
+    assert_equal error.message, "The Bulk Lookup Endpoint is not supported on the current subscription plan"
+  end
+end