diff --git a/README.md b/README.md
index 605c5bb6ea3391ac1afb2eaa9d1e49d8e9a3f1a4..a71dd671ee3c43122361116ce2dace66faf19fe3 100644
--- a/README.md
+++ b/README.md
@@ -663,6 +663,17 @@ This uses the PostcodeAnywhere UK Geocode service, this will geocode any string
 * **Limitations**: ?
 * **Notes**: To use PostcodeAnywhere you must include an API key: `Geocoder.configure(:lookup => :postcode_anywhere_uk, :api_key => 'your_api_key')`.
 
+#### LatLon.io (`:latlon`)
+
+* **API key**: required
+* **Quota**: Depends on the user's plan (free and paid plans available)
+* **Region**: US
+* **SSL support**: yes
+* **Languages**: en
+* **Documentation**: https://latlon.io/documentation
+* **Terms of Service**: ?
+* **Limitations**: No restrictions on use
+
 
 ### IP Address Services
 
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index c48960851bb4aa71656ea1102857c510cfc30ded..92d5a50b2069319c9dda6c6576aa0aa413359041 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -45,7 +45,8 @@ module Geocoder
         :okf,
         :postcode_anywhere_uk,
         :geoportail_lu,
-        :test
+        :test,
+        :latlon
       ]
     end
 
diff --git a/lib/geocoder/lookups/latlon.rb b/lib/geocoder/lookups/latlon.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0e1faee454bf9d28ddc10c42b090173c2e2a55f6
--- /dev/null
+++ b/lib/geocoder/lookups/latlon.rb
@@ -0,0 +1,59 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/latlon'
+
+module Geocoder::Lookup
+  class Latlon < Base
+
+    def name
+      "LatLon.io"
+    end
+
+    def required_api_key_parts
+      ["api_key"]
+    end
+
+    def query_url(query)
+      "#{protocol}://latlon.io/api/v1/#{if query.reverse_geocode? then 'reverse_' end}geocode?#{url_query_string(query)}"
+    end
+
+    private # ---------------------------------------------------------------
+
+    def results(query)
+      return [] unless doc = fetch_data(query)
+      if doc['error'].nil?
+        [doc]
+      # The API returned a 404 response, which indicates no results found
+      elsif doc['error']['type'] == 'api_error'
+        []
+      elsif
+        doc['error']['type'] == 'authentication_error'
+        raise_error(Geocoder::InvalidApiKey) ||
+          Geocoder.log(:warn, "LatLon.io service error: invalid API key.")
+      else
+        []
+      end
+    end
+
+    def supported_protocols
+      [:https]
+    end
+
+    private # ---------------------------------------------------------------
+
+    def query_url_params(query)
+      if query.reverse_geocode?
+        {
+          :token => configuration.api_key,
+          :lat => query.coordinates[0],
+          :lon => query.coordinates[1]
+        }.merge(super)
+      else
+        {
+          :token => configuration.api_key,
+          :address => query.sanitized_text
+        }.merge(super)
+      end
+    end
+
+  end
+end
diff --git a/lib/geocoder/results/latlon.rb b/lib/geocoder/results/latlon.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0f106bf23eea052ec527e80b5b030a7a29b754e6
--- /dev/null
+++ b/lib/geocoder/results/latlon.rb
@@ -0,0 +1,71 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Latlon < Base
+
+    def city
+      address_components["city"]
+    end
+
+    def coordinates
+      [@data['lat'].to_f, @data['lon'].to_f]
+    end
+
+    def country
+      "United States" # LatLon.io only supports the US
+    end
+
+    def country_code
+      "US" # LatLon.io only supports the US
+    end
+
+    def formatted_address(format = :full)
+      address_components["address"]
+    end
+    alias_method :address, :formatted_address
+
+    def number
+      address_components["number"]
+    end
+
+    def prefix
+      address_components["prefix"]
+    end
+
+    def state
+      address_components["state"]
+    end
+    alias_method :state_code, :state
+
+    def street
+      [street_name, street_type].compact.join(' ')
+    end
+
+    def street_name
+      address_components["street_name"]
+    end
+
+    def street_type
+      address_components["street_type"]
+    end
+
+    def suffix
+      address_components["suffix"]
+    end
+
+    def unit
+      address_components["unit"]
+    end
+
+    def zip
+      address_components["zip"]
+    end
+    alias_method :postal_code, :zip
+
+    private
+
+    def address_components
+      @data["address"] || {}
+    end
+  end
+end
diff --git a/test/fixtures/latlon_6000_universal_blvd b/test/fixtures/latlon_6000_universal_blvd
new file mode 100644
index 0000000000000000000000000000000000000000..97baa14f59c22f7f8b771c7cd13100ce4fcb0882
--- /dev/null
+++ b/test/fixtures/latlon_6000_universal_blvd
@@ -0,0 +1,16 @@
+{
+   "lat": 28.4750507575094,
+   "lon": -81.4630386931719,
+   "address": {
+      "address": "6000 Universal Blvd, Orlando, FL 32819",
+      "prefix": null,
+      "number": "6000",
+      "street_name": "Universal",
+      "street_type": "Blvd",
+      "suffix": null,
+      "unit": null,
+      "city": "Orlando",
+      "state": "FL",
+      "zip": "32819"
+   }
+}
\ No newline at end of file
diff --git a/test/fixtures/latlon_invalid_key b/test/fixtures/latlon_invalid_key
new file mode 100644
index 0000000000000000000000000000000000000000..d00ea46f1e40e000274381899ccdfe4e63f6456b
--- /dev/null
+++ b/test/fixtures/latlon_invalid_key
@@ -0,0 +1,6 @@
+{
+   "error": {
+      "type": "authentication_error",
+      "message": "Failed to authenticate your request."
+   }
+}
\ No newline at end of file
diff --git a/test/fixtures/latlon_no_results b/test/fixtures/latlon_no_results
new file mode 100644
index 0000000000000000000000000000000000000000..f0d05791d27232a8e17c6640217e61e45d8f3908
--- /dev/null
+++ b/test/fixtures/latlon_no_results
@@ -0,0 +1,6 @@
+{
+   "error": {
+      "type": "api_error",
+      "message": "The requested resource could not be found."
+   }
+}
diff --git a/test/test_helper.rb b/test/test_helper.rb
index d6519f24076016359273307d89922c173cb98f00..973bd083f9a5e3ab15a3326e398266fc9b7bbb10 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -312,6 +312,14 @@ module Geocoder
         "#{fixture_prefix}_boulevard_royal"
       end
     end
+
+    require 'geocoder/lookups/latlon'
+    class Latlon
+      private
+      def default_fixture_filename
+        "latlon_6000_universal_blvd"
+      end
+    end
   end
 end
 
diff --git a/test/unit/lookups/latlon_test.rb b/test/unit/lookups/latlon_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2dcb47a9bbe289d66f38b44d0992428726d88bd1
--- /dev/null
+++ b/test/unit/lookups/latlon_test.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+require 'test_helper'
+
+class LatlonTest < GeocoderTestCase
+
+  def setup
+    Geocoder.configure(lookup: :latlon)
+    set_api_key!(:latlon)
+  end
+
+  def test_result_components
+    result = Geocoder.search("6000 Universal Blvd, Orlando, FL 32819").first
+    assert_equal "6000", result.number
+    assert_equal "Universal", result.street_name
+    assert_equal "Blvd", result.street_type
+    assert_equal "Universal Blvd", result.street
+    assert_equal "Orlando", result.city
+    assert_equal "FL", result.state
+    assert_equal "32819", result.zip
+    assert_equal "6000 Universal Blvd, Orlando, FL 32819", result.formatted_address
+    assert_equal(28.4750507575094, result.latitude)
+    assert_equal(-81.4630386931719, result.longitude)
+  end
+
+  def test_no_results
+    results = Geocoder.search("no results")
+    assert_equal 0, results.length
+  end
+
+  def test_latlon_reverse_url
+    query = Geocoder::Query.new([45.423733, -75.676333])
+    assert_match /reverse_geocode/, query.url
+  end
+
+  def test_raises_api_key_exception
+    Geocoder.configure Geocoder.configure(:always_raise => [Geocoder::InvalidApiKey])
+    assert_raises Geocoder::InvalidApiKey do
+      Geocoder.search("invalid key")
+    end
+  end
+
+end