diff --git a/README.md b/README.md
index 89a16bcfe1bb80c08dd84ed7ca1d1c2f99597e05..9be60179d53284e4092d628fb151a9a607ba3781 100644
--- a/README.md
+++ b/README.md
@@ -771,6 +771,16 @@ This uses the PostcodeAnywhere UK Geocode service, this will geocode any string
 * **Documentation**: http://ipinfo.io/developers
 * **Terms of Service**: http://ipinfo.io/developers
 
+#### IP-API.com (`:ipapi_com`)
+
+* **API key**: optional - see http://ip-api.com/docs/#usage_limits
+* **Quota**: 150/minute - unlimited with api key
+* **Region**: world
+* **SSL support**: no (not without access key - see https://signup.ip-api.com/)
+* **Languages**: English
+* **Documentation**: http://ip-api.com/docs/
+* **Terms of Service**: https://signup.ip-api.com/terms
+
 ### IP Address Local Database Services
 
 #### MaxMind Local (`:maxmind_local`) - EXPERIMENTAL
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index d09bd3ac91ed9144a4787acff5f50ed9b6c1bd23..fdc4a0705b09020079320b1e25fb3ffea3ffb3db 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -64,7 +64,8 @@ module Geocoder
         :telize,
         :pointpin,
         :maxmind_geoip2,
-        :ipinfo_io
+        :ipinfo_io,
+        :ipapi_com
       ]
     end
 
diff --git a/lib/geocoder/lookups/ipapi_com.rb b/lib/geocoder/lookups/ipapi_com.rb
new file mode 100644
index 0000000000000000000000000000000000000000..df6f098b4182da3b7b7ac5b5235ba9fe19781b74
--- /dev/null
+++ b/lib/geocoder/lookups/ipapi_com.rb
@@ -0,0 +1,64 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/ipapi_com'
+
+module Geocoder::Lookup
+  class IpapiCom < Base
+
+    def name
+      "ip-api.com"
+    end
+
+    def query_url(query)
+      url_ = "#{protocol}://ip-api.com/json/#{query.sanitized_text}"
+
+      if (params = url_query_string(query)) && !params.empty?
+        url_ + "?" + params
+      else
+        url_
+      end
+    end
+
+    def supported_protocols
+      if configuration.api_key
+        [:http, :https]
+      else
+        [:http]
+      end
+    end
+
+
+    private
+
+    def results(query)
+      return [reserved_result(query.text)] if query.loopback_ip_address?
+
+      (doc = fetch_data(query)) ? [doc] : []
+    end
+
+    def reserved_result(query)
+      {
+        "message"      => "reserved range",
+        "query"        => query,
+        "status"       => fail,
+        "ip"           => query,
+        "city"         => "",
+        "region_code"  => "",
+        "region_name"  => "",
+        "metrocode"    => "",
+        "zipcode"      => "",
+        "latitude"     => "0",
+        "longitude"    => "0",
+        "country_name" => "Reserved",
+        "country_code" => "RD"
+      }
+    end
+
+    def query_url_params(query)
+      params = {}
+      params.merge!(fields: configuration[:fields]) if configuration.has_key?(:fields)
+      params.merge!(key: configuration.api_key) if configuration.api_key
+      params.merge(super)
+    end
+
+  end
+end
diff --git a/lib/geocoder/results/ipapi_com.rb b/lib/geocoder/results/ipapi_com.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0ac3264868646879ca937306a2db67651472e764
--- /dev/null
+++ b/lib/geocoder/results/ipapi_com.rb
@@ -0,0 +1,53 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class IpapiCom < Base
+
+    def latitude
+      lat
+    end
+
+    def longitude
+      lon
+    end
+
+    def coordinates
+      [lat, lon]
+    end
+
+    def address
+      "#{city}, #{state_code} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
+    end
+
+    def state
+      region_name
+    end
+
+    def state_code
+      region
+    end
+
+    def postal_code
+      zip
+    end
+
+    def country_code
+      @data['countryCode']
+    end
+
+    def region_name
+      @data['regionName']
+    end
+
+    def self.response_attributes
+      %w[country region city zip timezone isp org as reverse query status message mobile proxy lat lon]
+    end
+
+    response_attributes.each do |attribute|
+      define_method attribute do
+        @data[attribute]
+      end
+    end
+
+  end
+end
diff --git a/test/fixtures/ipapi_com_74_200_247_59 b/test/fixtures/ipapi_com_74_200_247_59
new file mode 100644
index 0000000000000000000000000000000000000000..5304304721bf7fd917a3b54072a77182b2f5634f
--- /dev/null
+++ b/test/fixtures/ipapi_com_74_200_247_59
@@ -0,0 +1,19 @@
+{
+  "as": "AS22576 DataPipe, Inc.",
+  "city": "Jersey City",
+  "country": "United States",
+  "countryCode": "US",
+  "isp": "DataPipe",
+  "lat": 40.7209,
+  "lon": -74.0468,
+  "mobile": false,
+  "org": "DataPipe",
+  "proxy": false,
+  "query": "74.200.247.59",
+  "region": "NJ",
+  "regionName": "New Jersey",
+  "reverse": "",
+  "status": "success",
+  "timezone": "America/New_York",
+  "zip": "07302"
+}
diff --git a/test/fixtures/ipapi_com_no_results b/test/fixtures/ipapi_com_no_results
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 4a9c4d62c1fe549b51ef1865312d46cd02ffe939..8ad22fc798c4f541acfeab13b8540fe72fba2bff 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -336,6 +336,14 @@ module Geocoder
       end
     end
 
+    require 'geocoder/lookups/ipapi_com'
+    class IpapiCom
+      private
+      def default_fixture_filename
+        "ipapi_com_74_200_247_59"
+      end
+    end
+
   end
 end
 
diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb
index 496bb5bfdcf5edc352552b5f4755353495f98325..5a91ad573596da1fbf2a8acda5b3772ff882a721 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, :ipinfo_io].include? l # does not use query string
+      next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipinfo_io, :ipapi_com].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/ipapi_com_test.rb b/test/unit/lookups/ipapi_com_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6cb07cb9545cd98ff4433872837030351ac9a311
--- /dev/null
+++ b/test/unit/lookups/ipapi_com_test.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+require 'test_helper'
+
+class IpapiComTest < GeocoderTestCase
+
+  def setup
+    Geocoder::Configuration.instance.data.clear
+    Geocoder::Configuration.set_defaults
+    Geocoder.configure(ip_lookup: :ipapi_com)
+  end
+
+  def test_result_on_ip_address_search
+    result = Geocoder.search("74.200.247.59").first
+    assert result.is_a?(Geocoder::Result::IpapiCom)
+  end
+
+  def test_result_components
+    result = Geocoder.search("74.200.247.59").first
+    assert_equal "Jersey City, NJ 07302, United States", result.address
+  end
+
+  def test_all_api_fields
+    result = Geocoder.search("74.200.247.59").first
+    assert_equal "United States", result.country
+    assert_equal "US", result.country_code
+    assert_equal "NJ", result.region
+    assert_equal "New Jersey", result.region_name
+    assert_equal "Jersey City", result.city
+    assert_equal "07302", result.zip
+    assert_equal 40.7209, result.lat
+    assert_equal -74.0468, result.lon
+    assert_equal "America/New_York", result.timezone
+    assert_equal "DataPipe", result.isp
+    assert_equal "DataPipe", result.org
+    assert_equal "AS22576 DataPipe, Inc.", result.as
+    assert_equal "", result.reverse
+    assert_equal false, result.mobile
+    assert_equal false, result.proxy
+    assert_equal "74.200.247.59", result.query
+    assert_equal "success", result.status
+    assert_equal nil, result.message
+  end
+
+  def test_api_key
+    Geocoder.configure(:api_key => "MY_KEY")
+    g = Geocoder::Lookup::IpapiCom.new
+    assert_match "key=MY_KEY", g.query_url(Geocoder::Query.new("74.200.247.59"))
+  end
+
+  def test_url_with_api_key_and_fields
+    Geocoder.configure(:api_key => "MY_KEY", :ipapi_com => {:fields => "lat,lon,xyz"})
+    g = Geocoder::Lookup::IpapiCom.new
+    assert_equal "http://ip-api.com/json/74.200.247.59?fields=lat%2Clon%2Cxyz&key=MY_KEY", g.query_url(Geocoder::Query.new("74.200.247.59"))
+  end
+
+  def test_url_with_fields
+    Geocoder.configure(:ipapi_com => {:fields => "lat,lon"})
+    g = Geocoder::Lookup::IpapiCom.new
+    assert_equal "http://ip-api.com/json/74.200.247.59?fields=lat%2Clon", g.query_url(Geocoder::Query.new("74.200.247.59"))
+  end
+
+  def test_url_without_fields
+    g = Geocoder::Lookup::IpapiCom.new
+    assert_equal "http://ip-api.com/json/74.200.247.59", g.query_url(Geocoder::Query.new("74.200.247.59"))
+  end
+
+  def test_search_with_params
+    g = Geocoder::Lookup::IpapiCom.new
+    q = Geocoder::Query.new("74.200.247.59", :params => {:fields => 'lat,zip'})
+    assert_equal "http://ip-api.com/json/74.200.247.59?fields=lat%2Czip", g.query_url(q)
+  end
+
+end