diff --git a/README.md b/README.md
index e749b272b9a945e28ada3a37cf1127522e80e992..2dc1605edebef4984e0ef3e1228eb56e35368cfc 100644
--- a/README.md
+++ b/README.md
@@ -427,6 +427,17 @@ The following is a comparison of the supported geocoding APIs. The "Limitations"
 * **Terms of Service**: http://wiki.openstreetmap.org/wiki/Nominatim_usage_policy
 * **Limitations**: Please limit request rate to 1 per second and include your contact information in User-Agent headers (eg: `Geocoder.configure(:http_headers => { "User-Agent" => "your contact info" })`). Data licensed under CC-BY-SA (you must provide attribution).
 
+#### OpenCageData (`:opencagedata`)
+
+* **API key**: required
+* **Key signup**: http://geocoder.opencagedata.com
+* **Quota**: 2500 requests / day, then ability to purchase more (free during beta)
+* **Region**: world
+* **SSL support**: yes
+* **Languages**: worldwide
+* **Documentation**: http://geocoder.opencagedata.com/api.html
+* **Limitations**: Data licensed under CC-BY-SA or (you must provide attribution).
+
 #### Yandex (`:yandex`)
 
 * **API key**: none
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index 9d380e3f3b76a2c9c7d887ecfda76172288fd413..eb1d4e5e004e7302ed6dc0e427ba862a58981aab 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -32,6 +32,7 @@ module Geocoder
         :yandex,
         :nominatim,
         :mapquest,
+        :opencagedata,
         :ovi,
         :here,
         :baidu,
diff --git a/lib/geocoder/lookups/opencagedata.rb b/lib/geocoder/lookups/opencagedata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8cb24b4189dd5691914b14c39dd1191c11890713
--- /dev/null
+++ b/lib/geocoder/lookups/opencagedata.rb
@@ -0,0 +1,45 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/opencagedata'
+
+module Geocoder::Lookup
+  class Opencagedata < Base
+
+    def name
+      "OpenCageData"
+    end
+
+    def query_url(query)
+      "#{protocol}://api.opencagedata.com/geocode/v1/json?key=#{configuration.api_key}&q=#{url_query_string(query)}"
+    end
+
+    def required_api_key_parts
+      ["key"]
+    end
+
+    private
+
+    def valid_response?(response)
+      status = parse_json(response.body)["status"]
+      super(response) and status['message'] == 'OK'
+    end
+
+    def results(query)
+      data = fetch_data(query)
+      (data && data['results']) || []
+    end
+
+    def query_url_params(query)
+      params = {
+        :query => query.sanitized_text,
+        :language => (query.language || configuration.language)
+      }.merge(super)
+
+      unless (bounds = query.options[:bounds]).nil?
+        params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join(',')
+      end
+
+      params
+    end
+
+  end
+end
diff --git a/lib/geocoder/results/opencagedata.rb b/lib/geocoder/results/opencagedata.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1dcf9582b03b82e98f9d39b6cef775e84772df07
--- /dev/null
+++ b/lib/geocoder/results/opencagedata.rb
@@ -0,0 +1,82 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Opencagedata < Base
+
+    def poi
+      %w[stadium bus_stop tram_stop].each do |key|
+        return @data['components'][key] if @data['components'].key?(key)
+      end
+      return nil
+    end
+
+    def house_number
+      @data['components']['house_number']
+    end
+
+    def address
+      @data['formatted']
+    end
+
+    def street
+      %w[road pedestrian highway].each do |key|
+        return @data['components'][key] if @data['components'].key?(key)
+      end
+      return nil
+    end
+
+    def city
+      %w[city town village hamlet].each do |key|
+        return @data['components'][key] if @data['components'].key?(key)
+      end
+      return nil
+    end
+
+    def village
+      @data['components']['village']
+    end
+
+
+    def state
+      @data['components']['state']
+    end
+
+    alias_method :state_code, :state
+
+    def postal_code
+      @data['components']['postcode'].to_s
+    end
+
+    def county
+      @data['components']['county']
+    end
+
+    def country
+      @data['components']['country']
+    end
+
+    def country_code
+      @data['components']['country_code']
+    end
+
+    def suburb
+      @data['components']['suburb']
+    end
+
+    def coordinates
+      [@data['lat'].to_f, @data['lon'].to_f]
+    end
+    def self.response_attributes
+      %w[boundingbox license 
+        formatted stadium]
+    end
+
+    response_attributes.each do |a|
+      unless method_defined?(a)
+        define_method a do
+          @data[a]
+        end
+      end
+    end
+  end
+end
diff --git a/test/fixtures/opencagedata_madison_square_garden b/test/fixtures/opencagedata_madison_square_garden
new file mode 100644
index 0000000000000000000000000000000000000000..f5ff5df9d252d1dae1abdd12a222d1355d82f618
--- /dev/null
+++ b/test/fixtures/opencagedata_madison_square_garden
@@ -0,0 +1,73 @@
+{
+   "licenses" : [
+      {
+         "name" : "CC-BY-SA",
+         "url" : "http://creativecommons.org/licenses/by-sa/3.0/"
+      },
+      {
+         "name" : "ODbL",
+         "url" : "http://opendatacommons.org/licenses/odbl/summary/"
+      }
+   ],
+   "rate" : {
+      "limit" : 2500,
+      "remaining" : 2488,
+      "reset" : 1407369600
+   },
+   "results" : [
+      {
+         "annotations" : {
+            "OSM" : {
+               "url" : "http://www.openstreetmap.org/?mlat=40.75052&mlon=-73.99355#map=17/40.75052/-73.99355"
+            },
+            "timezone" : {
+               "name" : "America/New_York",
+               "now_in_dst" : 1,
+               "offset_sec" : -14400,
+               "offset_string" : -400,
+               "short_name" : "EDT"
+            }
+         },
+         "bounds" : {
+            "northeast" : {
+               "lat" : 40.751161,
+               "lng" : -73.9925922
+            },
+            "southwest" : {
+               "lat" : 40.7498531,
+               "lng" : -73.9944444
+            }
+         },
+         "components" : {
+            "country" : "United States of America",
+            "country_code" : "US",
+            "county" : "New York County",
+            "house_number" : 46,
+            "neighbourhood" : "Koreatown",
+            "postcode" : 10011,
+            "road" : "West 31st Street",
+            "city": "New York City",
+            "stadium" : "Madison Square Garden",
+            "state" : "New York",
+            "state_district" : "New York City"
+         },
+         "confidence" : 10,
+         "formatted" : "46, West 31st Street, Koreatown, New York County, 10011, New York City, New York, United States of America, Madison Square Garden",
+         "geometry" : {
+            "lat" : 40.7505247,
+            "lng" : -73.9935500942432
+         }
+      }
+   ],
+   "status" : {
+      "code" : 200,
+      "message" : "OK"
+   },
+   "thanks" : "For using an OpenCage Data API",
+   "timestamp" : {
+      "created_http" : "Wed, 06 Aug 2014 12:53:59 GMT",
+      "created_unix" : 1407329639
+   },
+   "total_results" : 1,
+   "we_are_hiring" : "http://lokku.com/#jobs"
+}
\ No newline at end of file
diff --git a/test/fixtures/opencagedata_no_results b/test/fixtures/opencagedata_no_results
new file mode 100644
index 0000000000000000000000000000000000000000..16fdf7cb194eb37e7007bcd0d1badc71a1a7f18e
--- /dev/null
+++ b/test/fixtures/opencagedata_no_results
@@ -0,0 +1,29 @@
+{
+   "licenses" : [
+      {
+         "name" : "CC-BY-SA",
+         "url" : "http://creativecommons.org/licenses/by-sa/3.0/"
+      },
+      {
+         "name" : "ODbL",
+         "url" : "http://opendatacommons.org/licenses/odbl/summary/"
+      }
+   ],
+   "rate" : {
+      "limit" : 2500,
+      "remaining" : 2487,
+      "reset" : 1407369600
+   },
+   "results" : [],
+   "status" : {
+      "code" : 200,
+      "message" : "OK"
+   },
+   "thanks" : "For using an OpenCage Data API",
+   "timestamp" : {
+      "created_http" : "Wed, 06 Aug 2014 12:56:03 GMT",
+      "created_unix" : 1407329763
+   },
+   "total_results" : 0,
+   "we_are_hiring" : "http://lokku.com/#jobs"
+}
diff --git a/test/unit/lookups/opencagedata_test.rb b/test/unit/lookups/opencagedata_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b856a2efcb8f2d2be1c0f04758b931bd1e3a12c5
--- /dev/null
+++ b/test/unit/lookups/opencagedata_test.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+$: << File.join(File.dirname(__FILE__), "..", "..")
+require 'test_helper'
+
+class OpencagedataTest < GeocoderTestCase
+
+  def setup
+    Geocoder.configure(lookup: :opencagedata)
+    set_api_key!(:opencagedata)
+  end
+
+  def test_result_components
+    result = Geocoder.search("Madison Square Garden, New York, NY").first
+    assert_equal "West 31st Street", result.street
+    assert_match /46, West 31st Street, Koreatown, New York County, 10011, New York City, New York, United States of America/, result.address
+
+  end
+
+  def test_opencagedata_query_url_contains_bounds
+    lookup = Geocoder::Lookup::Opencagedata.new
+    url = lookup.query_url(Geocoder::Query.new(
+      "Some street",
+      :bounds => [[40.0, -120.0], [39.0, -121.0]]
+    ))
+    assert_match(/bounds=40.0+%2C-120.0+%2C39.0+%2C-121.0+/, url)
+  end
+
+
+  def test_no_results
+    results = Geocoder.search("no results")
+    assert_equal 0, results.length
+  end
+
+
+  def test_opencagedata_reverse_url
+    query = Geocoder::Query.new([45.423733, -75.676333])
+    assert_match /\bquery=45.423733%2C-75.676333\b/, query.url
+  end
+
+
+
+
+  # def test_raises_exception_when_over_query_limit
+  #   Geocoder.configure(:always_raise => [Geocoder::OverQueryLimitError])
+  #   l = Geocoder::Lookup.get(:opencagedata)
+  #   assert_raises Geocoder::OverQueryLimitError do
+  #     l.send(:results, Geocoder::Query.new("over limit"))
+  #   end
+  # end
+end