diff --git a/swh/web/api/views/origin.py b/swh/web/api/views/origin.py
--- a/swh/web/api/views/origin.py
+++ b/swh/web/api/views/origin.py
@@ -15,25 +15,62 @@
 from swh.web.api.views.utils import api_lookup
 
 
+DOC_RETURN_ORIGIN = '''
+        :>json string origin_visits_url: link to in order to get information
+            about the visits for that origin
+        :>json string url: the origin canonical url
+        :>json string type: the type of software origin (deprecated value;
+            types are now associated to visits instead of origins)
+        :>json number id: the origin unique identifier (deprecated value;
+            you should only refer to origins based on their URL)
+'''
+
+DOC_RETURN_ORIGIN_ARRAY = \
+    DOC_RETURN_ORIGIN.replace(':>json', ':>jsonarr')
+
+DOC_RETURN_ORIGIN_VISIT = '''
+        :>json string date: ISO representation of the visit date (in UTC)
+        :>json str origin: the origin canonical url
+        :>json string origin_url: link to get information about the origin
+        :>jsonarr string snapshot: the snapshot identifier of the visit
+        :>jsonarr string snapshot_url: link to
+            :http:get:`/api/1/snapshot/(snapshot_id)/` in order to get
+            information about the snapshot of the visit
+        :>json string status: status of the visit (either **full**,
+            **partial** or **ongoing**)
+        :>json number visit: the unique identifier of the visit
+'''
+
+DOC_RETURN_ORIGIN_VISIT_ARRAY = \
+    DOC_RETURN_ORIGIN_VISIT.replace(':>json', ':>jsonarr')
+
+DOC_RETURN_ORIGIN_VISIT_ARRAY += '''
+        :>jsonarr number id: the unique identifier of the origin
+        :>jsonarr string origin_visit_url: link to
+            :http:get:`/api/1/origin/(origin_url)/visit/(visit_id)/`
+            in order to get information about the visit
+'''
+
+
 def _enrich_origin(origin):
     if 'id' in origin:
         o = origin.copy()
         o['origin_visits_url'] = reverse(
-            'api-1-origin-visits', url_args={'origin_id': origin['id']})
+            'api-1-origin-visits', url_args={'origin_url': origin['url']})
         return o
 
     return origin
 
 
 def _enrich_origin_visit(origin_visit, *,
-                         with_origin_url, with_origin_visit_url):
+                         with_origin_link, with_origin_visit_link):
     ov = origin_visit.copy()
-    if with_origin_url:
+    if with_origin_link:
         ov['origin_url'] = reverse('api-1-origin',
-                                   url_args={'origin_id': ov['origin']})
-    if with_origin_visit_url:
+                                   url_args={'origin_url': ov['origin']})
+    if with_origin_visit_link:
         ov['origin_visit_url'] = reverse('api-1-origin-visit',
-                                         url_args={'origin_id': ov['origin'],
+                                         url_args={'origin_url': ov['origin'],
                                                    'visit_id': ov['visit']})
     snapshot = ov['snapshot']
     if snapshot:
@@ -46,7 +83,7 @@
 
 @api_route(r'/origins/', 'api-1-origins')
 @api_doc('/origins/', noargs=True)
-@format_docstring()
+@format_docstring(return_origin_array=DOC_RETURN_ORIGIN_ARRAY)
 def api_origins(request):
     """
     .. http:get:: /api/1/origins/
@@ -60,13 +97,7 @@
         :query int origin_count: The maximum number of origins to return
             (default to 100, can not exceed 10000)
 
-        :>jsonarr number id: the origin unique identifier
-        :>jsonarr string origin_visits_url: link to in order to get information
-            about the visits for that origin
-        :>jsonarr string type: the type of software origin (possible values
-            are ``git``, ``svn``, ``hg``, ``deb``, ``pypi``, ``npm``, ``ftp``
-            or ``deposit``)
-        :>jsonarr string url: the origin canonical url
+        {return_origin_array}
 
         {common_headers}
         {resheader_link}
@@ -98,26 +129,43 @@
     return response
 
 
-@api_route(r'/origin/(?P<origin_id>[0-9]+)/', 'api-1-origin')
 @api_route(r'/origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/',
            'api-1-origin')
+@api_route(r'/origin/(?P<origin_url>.+)/get/', 'api-1-origin')
+@api_route(r'/origin/(?P<origin_id>[0-9]+)/', 'api-1-origin')
 @api_doc('/origin/')
-@format_docstring()
+@format_docstring(return_origin=DOC_RETURN_ORIGIN)
 def api_origin(request, origin_id=None, origin_type=None, origin_url=None):
     """
+    .. http:get:: /api/1/origin/(origin_url)/get/
+
+        Get information about a software origin.
+
+        :param string origin_url: the origin url
+
+        {return_origin}
+
+        {common_headers}
+
+        **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
+            :http:method:`options`
+
+        :statuscode 200: no error
+        :statuscode 404: requested origin can not be found in the archive
+
+        **Example:**
+
+        .. parsed-literal::
+
+            :swh_web_api:`origin/git/url/https://github.com/python/cpython/`
+
     .. http:get:: /api/1/origin/(origin_id)/
 
         Get information about a software origin.
 
         :param int origin_id: a software origin identifier
 
-        :>json number id: the origin unique identifier
-        :>json string origin_visits_url: link to in order to get information
-            about the visits for that origin
-        :>json string type: the type of software origin (possible values are
-            ``git``, ``svn``, ``hg``, ``deb``, ``pypi``, ``npm``, ``ftp`` or
-            ``deposit``)
-        :>json string url: the origin canonical url
+        {return_origin}
 
         {common_headers}
 
@@ -137,16 +185,17 @@
 
         Get information about a software origin.
 
+        .. warning::
+
+            This endpoint is deprecated. You should use
+            :http:get:`/api/1/origin/(origin_url)/get/` instead.
+
         :param string origin_type: the origin type (possible values are
             ``git``, ``svn``, ``hg``, ``deb``, ``pypi``, ``npm``, ``ftp`` or
             ``deposit``)
         :param string origin_url: the origin url
 
-        :>json number id: the origin unique identifier
-        :>json string origin_visits_url: link to in order to get information
-            about the visits for that origin
-        :>json string type: the type of software origin
-        :>json string url: the origin canonical url
+        {return_origin}
 
         {common_headers}
 
@@ -168,11 +217,8 @@
         'url': origin_url
     }
     ori_dict = {k: v for k, v in ori_dict.items() if ori_dict[k]}
-    if 'id' in ori_dict:
-        error_msg = 'Origin with id %s not found.' % ori_dict['id']
-    else:
-        error_msg = 'Origin with type %s and URL %s not found' % (
-            ori_dict['type'], ori_dict['url'])
+    error_msg = 'Origin %s not found.' % \
+        (ori_dict.get('id') or ori_dict['url'])
 
     return api_lookup(
         service.lookup_origin, ori_dict,
@@ -183,7 +229,7 @@
 @api_route(r'/origin/search/(?P<url_pattern>.+)/',
            'api-1-origin-search')
 @api_doc('/origin/search/')
-@format_docstring()
+@format_docstring(return_origin_array=DOC_RETURN_ORIGIN_ARRAY)
 def api_origin_search(request, url_pattern):
     """
     .. http:get:: /api/1/origin/search/(url_pattern)/
@@ -201,11 +247,7 @@
         :query boolean with_visit: if true, only return origins with at least
             one visit by Software heritage
 
-        :>jsonarr number id: the origin unique identifier
-        :>jsonarr string origin_visits_url: link to in order to get information
-            about the visits for that origin
-        :>jsonarr string type: the type of software origin
-        :>jsonarr string url: the origin canonical url
+        {return_origin_array}
 
         {common_headers}
 
@@ -253,7 +295,7 @@
 @api_route(r'/origin/metadata-search/',
            'api-1-origin-metadata-search')
 @api_doc('/origin/metadata-search/', noargs=True, need_params=True)
-@format_docstring()
+@format_docstring(return_origin_array=DOC_RETURN_ORIGIN_ARRAY)
 def api_origin_metadata_search(request):
     """
     .. http:get:: /api/1/origin/metadata-search/
@@ -268,12 +310,7 @@
         :query int limit: the maximum number of found origins to return
             (bounded to 100)
 
-        :>jsonarr number origin_id: the origin unique identifier
-        :>jsonarr dict metadata: metadata of the origin (as a
-            JSON-LD/CodeMeta dictionary)
-        :>jsonarr string from_revision: the revision used to extract these
-            metadata (the current HEAD or one of the former HEADs)
-        :>jsonarr dict tool: the tool used to extract these metadata
+        {return_origin_array}
 
         {common_headers}
 
@@ -302,11 +339,42 @@
     }
 
 
+@api_route(r'/origin/(?P<origin_url>.*)/visits/', 'api-1-origin-visits')
 @api_route(r'/origin/(?P<origin_id>[0-9]+)/visits/', 'api-1-origin-visits')
 @api_doc('/origin/visits/')
-@format_docstring()
-def api_origin_visits(request, origin_id):
+@format_docstring(
+    return_origin_visit_array=DOC_RETURN_ORIGIN_VISIT_ARRAY)
+def api_origin_visits(request, origin_id=None, origin_url=None):
     """
+    .. http:get:: /api/1/origin/(origin_url)/visits/
+
+        Get information about all visits of a software origin.
+        Visits are returned sorted in descending order according
+        to their date.
+
+        :param str origin_url: a software origin URL
+        :query int per_page: specify the number of visits to list, for
+            pagination purposes
+        :query int last_visit: visit to start listing from, for pagination
+            purposes
+
+        {common_headers}
+        {resheader_link}
+
+        {return_origin_visit_array}
+
+        **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
+            :http:method:`options`
+
+        :statuscode 200: no error
+        :statuscode 404: requested origin can not be found in the archive
+
+        **Example:**
+
+        .. parsed-literal::
+
+            :swh_web_api:`origin/https://github.com/hylang/hy/visits/`
+
     .. http:get:: /api/1/origin/(origin_id)/visits/
 
         Get information about all visits of a software origin.
@@ -322,18 +390,7 @@
         {common_headers}
         {resheader_link}
 
-        :>jsonarr string date: ISO representation of the visit date (in UTC)
-        :>jsonarr number id: the unique identifier of the origin
-        :>jsonarr string origin_visit_url: link to
-            :http:get:`/api/1/origin/(origin_id)/visit/(visit_id)/` in order to
-            get information about the visit
-        :>jsonarr string snapshot: the snapshot identifier of the visit
-        :>jsonarr string snapshot_url: link to
-            :http:get:`/api/1/snapshot/(snapshot_id)/` in order to get
-            information about the snapshot of the visit
-        :>jsonarr string status: status of the visit (either **full**,
-            **partial** or **ongoing**)
-        :>jsonarr number visit: the unique identifier of the visit
+        {return_origin_visit_array}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
             :http:method:`options`
@@ -348,15 +405,22 @@
             :swh_web_api:`origin/1/visits/`
     """
     result = {}
-    origin_id = int(origin_id)
+    if origin_url:
+        origin_query = {'url': origin_url}
+        notfound_msg = 'No origin {} found'.format(origin_url)
+        url_args_next = {'origin_url': origin_url}
+    else:
+        origin_query = {'id': int(origin_id)}
+        notfound_msg = 'No origin {} found'.format(origin_id)
+        url_args_next = {'origin_id': origin_id}
     per_page = int(request.query_params.get('per_page', '10'))
     last_visit = request.query_params.get('last_visit')
     if last_visit:
         last_visit = int(last_visit)
 
     def _lookup_origin_visits(
-            origin_id, last_visit=last_visit, per_page=per_page):
-        all_visits = get_origin_visits({'id': origin_id})
+            origin_query, last_visit=last_visit, per_page=per_page):
+        all_visits = get_origin_visits(origin_query)
         all_visits.reverse()
         visits = []
         if not last_visit:
@@ -369,11 +433,11 @@
         for v in visits:
             yield v
 
-    results = api_lookup(_lookup_origin_visits, origin_id,
-                         notfound_msg='No origin {} found'.format(origin_id),
+    results = api_lookup(_lookup_origin_visits, origin_query,
+                         notfound_msg=notfound_msg,
                          enrich_fn=partial(_enrich_origin_visit,
-                                           with_origin_url=False,
-                                           with_origin_visit_url=True))
+                                           with_origin_link=False,
+                                           with_origin_visit_link=True))
 
     if results:
         nb_results = len(results)
@@ -387,7 +451,7 @@
 
             result['headers'] = {
                 'link-next': reverse('api-1-origin-visits',
-                                     url_args={'origin_id': origin_id},
+                                     url_args=url_args_next,
                                      query_params=query_params)
             }
 
@@ -398,12 +462,38 @@
     return result
 
 
+@api_route(r'/origin/(?P<origin_url>.*)/visit/(?P<visit_id>[0-9]+)/',
+           'api-1-origin-visit')
 @api_route(r'/origin/(?P<origin_id>[0-9]+)/visit/(?P<visit_id>[0-9]+)/',
            'api-1-origin-visit')
 @api_doc('/origin/visit/')
-@format_docstring()
-def api_origin_visit(request, origin_id, visit_id):
+@format_docstring(return_origin_visit=DOC_RETURN_ORIGIN_VISIT)
+def api_origin_visit(request, visit_id, origin_url=None, origin_id=None):
     """
+    .. http:get:: /api/1/origin/(origin_url)/visit/(visit_id)/
+
+        Get information about a specific visit of a software origin.
+
+        :param str origin_url: a software origin URL
+        :param int visit_id: a visit identifier
+
+        {common_headers}
+
+        {return_origin_visit}
+
+        **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
+            :http:method:`options`
+
+        :statuscode 200: no error
+        :statuscode 404: requested origin or visit can not be found in the
+            archive
+
+        **Example:**
+
+        .. parsed-literal::
+
+            :swh_web_api:`origin/https://github.com/hylang/hy/visit/1/`
+
     .. http:get:: /api/1/origin/(origin_id)/visit/(visit_id)/
 
         Get information about a specific visit of a software origin.
@@ -413,16 +503,7 @@
 
         {common_headers}
 
-        :>json string date: ISO representation of the visit date (in UTC)
-        :>json number origin: the origin unique identifier
-        :>json string origin_url: link to get information about the origin
-        :>jsonarr string snapshot: the snapshot identifier of the visit
-        :>jsonarr string snapshot_url: link to
-            :http:get:`/api/1/snapshot/(snapshot_id)/` in order to get
-            information about the snapshot of the visit
-        :>json string status: status of the visit (either **full**,
-            **partial** or **ongoing**)
-        :>json number visit: the unique identifier of the visit
+        {return_origin_visit}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
             :http:method:`options`
@@ -437,13 +518,15 @@
 
             :swh_web_api:`origin/1500/visit/1/`
     """
+    if not origin_url:
+        origin_url = service.lookup_origin({'id': int(origin_id)})['url']
     return api_lookup(
-        service.lookup_origin_visit, int(origin_id), int(visit_id),
+        service.lookup_origin_visit, origin_url, int(visit_id),
         notfound_msg=('No visit {} for origin {} found'
-                      .format(visit_id, origin_id)),
+                      .format(visit_id, origin_url)),
         enrich_fn=partial(_enrich_origin_visit,
-                          with_origin_url=True,
-                          with_origin_visit_url=False))
+                          with_origin_link=True,
+                          with_origin_visit_link=False))
 
 
 @api_route(r'/origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)'
@@ -480,8 +563,7 @@
         'url': origin_url
     }
 
-    error_msg = 'Origin with type %s and URL %s not found' % (
-        ori_dict['type'], ori_dict['url'])
+    error_msg = 'Origin with URL %s not found' % ori_dict['url']
 
     return api_lookup(
         service.lookup_origin_intrinsic_metadata, ori_dict,
diff --git a/swh/web/common/origin_visits.py b/swh/web/common/origin_visits.py
--- a/swh/web/common/origin_visits.py
+++ b/swh/web/common/origin_visits.py
@@ -35,15 +35,20 @@
 
     from swh.web.common import service
 
-    cache_entry_id = 'origin_%s_visits' % origin_info['id']
+    if 'url' in origin_info:
+        origin_url = origin_info['url']
+    else:
+        origin_url = service.lookup_origin(origin_info)['url']
+
+    cache_entry_id = 'origin_visits_%s' % origin_url
     cache_entry = cache.get(cache_entry_id)
 
     if cache_entry:
         last_visit = cache_entry[-1]['visit']
-        new_visits = list(service.lookup_origin_visits(origin_info['id'],
+        new_visits = list(service.lookup_origin_visits(origin_url,
                                                        last_visit=last_visit))
         if not new_visits:
-            last_snp = service.lookup_latest_origin_snapshot(origin_info['id'])
+            last_snp = service.lookup_latest_origin_snapshot(origin_url)
             if not last_snp or last_snp['id'] == cache_entry[-1]['snapshot']:
                 return cache_entry
 
@@ -52,7 +57,7 @@
     per_page = service.MAX_LIMIT
     last_visit = None
     while 1:
-        visits = list(service.lookup_origin_visits(origin_info['id'],
+        visits = list(service.lookup_origin_visits(origin_url,
                                                    last_visit=last_visit,
                                                    per_page=per_page))
         origin_visits += visits
@@ -105,10 +110,9 @@
     visits = get_origin_visits(origin_info)
 
     if not visits:
-        if 'type' in origin_info and 'url' in origin_info:
+        if 'url' in origin_info:
             message = ('No visit associated to origin with'
-                       ' type %s and url %s!' % (origin_info['type'],
-                                                 origin_info['url']))
+                       ' url %s!' % origin_info['url'])
         else:
             message = ('No visit associated to origin with'
                        ' id %s!' % origin_info['id'])
@@ -119,9 +123,8 @@
         if len(visit) == 0:
             if 'type' in origin_info and 'url' in origin_info:
                 message = ('Visit for snapshot with id %s for origin with type'
-                           ' %s and url %s not found!' %
-                           (snapshot_id, origin_info['type'],
-                            origin_info['url']))
+                           ' url %s not found!' %
+                           (snapshot_id, origin_info['url']))
             else:
                 message = ('Visit for snapshot with id %s for origin with'
                            ' id %s not found!' %
@@ -133,9 +136,9 @@
         visit = [v for v in visits if v['visit'] == int(visit_id)]
         if len(visit) == 0:
             if 'type' in origin_info and 'url' in origin_info:
-                message = ('Visit with id %s for origin with type %s'
+                message = ('Visit with id %s for origin with'
                            ' and url %s not found!' %
-                           (visit_id, origin_info['type'], origin_info['url']))
+                           (visit_id, origin_info['url']))
             else:
                 message = ('Visit with id %s for origin with id %s'
                            ' not found!' % (visit_id, origin_info['id']))
@@ -168,9 +171,9 @@
         return visit
     else:
         if 'type' in origin_info and 'url' in origin_info:
-            message = ('Visit with timestamp %s for origin with type %s '
+            message = ('Visit with timestamp %s for origin with '
                        'and url %s not found!' %
-                       (visit_ts, origin_info['type'], origin_info['url']))
+                       (visit_ts, origin_info['url']))
         else:
             message = ('Visit with timestamp %s for origin with id %s '
                        'not found!' % (visit_ts, origin_info['id']))
diff --git a/swh/web/common/service.py b/swh/web/common/service.py
--- a/swh/web/common/service.py
+++ b/swh/web/common/service.py
@@ -212,8 +212,7 @@
     """Return information about the origin matching dict origin.
 
     Args:
-        origin: origin's dict with keys either 'id' or
-        ('type' AND 'url')
+        origin: origin's dict with keys either 'id' or 'url'
 
     Returns:
         origin information as dict.
@@ -221,11 +220,8 @@
     """
     origin_info = storage.origin_get(origin)
     if not origin_info:
-        if 'id' in origin and origin['id']:
-            msg = 'Origin with id %s not found!' % origin['id']
-        else:
-            msg = 'Origin with type %s and url %s not found!' % \
-                (origin['type'], origin['url'])
+        msg = 'Origin %s not found!' % \
+            (origin.get('id') or origin['url'])
         raise NotFoundExc(msg)
     return converters.from_origin(origin_info)
 
@@ -814,11 +810,11 @@
     return storage.stat_counters()
 
 
-def _lookup_origin_visits(origin_id, last_visit=None, limit=10):
+def _lookup_origin_visits(origin_url, last_visit=None, limit=10):
     """Yields the origin origin_ids' visits.
 
     Args:
-        origin_id (int): origin to list visits for
+        origin_url (str): origin to list visits for
         last_visit (int): last visit to lookup from
         limit (int): Number of elements max to display
 
@@ -827,8 +823,10 @@
 
     """
     limit = min(limit, MAX_LIMIT)
-    yield from storage.origin_visit_get(
-        origin_id, last_visit=last_visit, limit=limit)
+    for visit in storage.origin_visit_get(
+            origin_url, last_visit=last_visit, limit=limit):
+        visit['origin'] = origin_url
+        yield visit
 
 
 def lookup_origin_visits(origin_id, last_visit=None, per_page=10):
@@ -841,28 +839,28 @@
        Dictionaries of origin_visit for that origin
 
     """
-    lookup_origin({'id': origin_id})
     visits = _lookup_origin_visits(origin_id, last_visit=last_visit,
                                    limit=per_page)
     for visit in visits:
         yield converters.from_origin_visit(visit)
 
 
-def lookup_origin_visit(origin_id, visit_id):
+def lookup_origin_visit(origin_url, visit_id):
     """Return information about visit visit_id with origin origin_id.
 
     Args:
-        origin_id: origin concerned by the visit
+        origin (str): origin concerned by the visit
         visit_id: the visit identifier to lookup
 
     Yields:
        The dict origin_visit concerned
 
     """
-    visit = storage.origin_visit_get_by(origin_id, visit_id)
+    visit = storage.origin_visit_get_by(origin_url, visit_id)
     if not visit:
-        raise NotFoundExc('Origin with id %s or its visit '
-                          'with id %s not found!' % (origin_id, visit_id))
+        raise NotFoundExc('Origin %s or its visit '
+                          'with id %s not found!' % (origin_url, visit_id))
+    visit['origin'] = origin_url
     return converters.from_origin_visit(visit)
 
 
diff --git a/swh/web/tests/api/views/test_origin.py b/swh/web/tests/api/views/test_origin.py
--- a/swh/web/tests/api/views/test_origin.py
+++ b/swh/web/tests/api/views/test_origin.py
@@ -30,7 +30,8 @@
 
         mock_get_origin_visits.side_effect = ValueError(err_msg)
 
-        url = reverse('api-1-origin-visits', url_args={'origin_id': 2})
+        url = reverse(
+            'api-1-origin-visits', url_args={'origin_url': 'http://foo'})
         rv = self.client.get(url)
 
         self.assertEqual(rv.status_code, 400, rv.data)
@@ -47,7 +48,8 @@
 
         mock_get_origin_visits.side_effect = StorageDBError(err_msg)
 
-        url = reverse('api-1-origin-visits', url_args={'origin_id': 2})
+        url = reverse(
+            'api-1-origin-visits', url_args={'origin_url': 'http://foo'})
         rv = self.client.get(url)
 
         self.assertEqual(rv.status_code, 503, rv.data)
@@ -65,7 +67,8 @@
 
         mock_get_origin_visits.side_effect = StorageAPIError(err_msg)
 
-        url = reverse('api-1-origin-visits', url_args={'origin_id': 2})
+        url = reverse(
+            'api-1-origin-visits', url_args={'origin_url': 'http://foo'})
         rv = self.client.get(url)
 
         self.assertEqual(rv.status_code, 503, rv.data)
@@ -94,7 +97,7 @@
                 (all_visits[1]['visit'], all_visits[2:4])):
 
             url = reverse('api-1-origin-visits',
-                          url_args={'origin_id': origin_id},
+                          url_args={'origin_url': new_origin['url']},
                           query_params={'per_page': 2,
                                         'last_visit': last_visit})
 
@@ -106,11 +109,53 @@
             for expected_visit in expected_visits:
                 origin_visit_url = reverse(
                     'api-1-origin-visit',
-                    url_args={'origin_id': origin_id,
+                    url_args={'origin_url': new_origin['url'],
                               'visit_id': expected_visit['visit']})
                 snapshot_url = reverse(
                     'api-1-snapshot',
                     url_args={'snapshot_id': expected_visit['snapshot']})
+                expected_visit['origin'] = new_origin['url']
+                expected_visit['origin_visit_url'] = origin_visit_url
+                expected_visit['snapshot_url'] = snapshot_url
+
+            self.assertEqual(rv.data, expected_visits)
+
+    @given(new_origin(), visit_dates(3), new_snapshots(3))
+    def test_api_lookup_origin_visits_by_id(self, new_origin, visit_dates,
+                                            new_snapshots):
+
+        origin_id = self.storage.origin_add_one(new_origin)
+        new_origin['id'] = origin_id
+        for i, visit_date in enumerate(visit_dates):
+            origin_visit = self.storage.origin_visit_add(origin_id, visit_date)
+            self.storage.snapshot_add(origin_id, origin_visit['visit'],
+                                      new_snapshots[i])
+
+        all_visits = list(reversed(get_origin_visits(new_origin)))
+
+        for last_visit, expected_visits in (
+                (None, all_visits[:2]),
+                (all_visits[1]['visit'], all_visits[2:4])):
+
+            url = reverse('api-1-origin-visits',
+                          url_args={'origin_url': new_origin['url']},
+                          query_params={'per_page': 2,
+                                        'last_visit': last_visit})
+
+            rv = self.client.get(url)
+
+            self.assertEqual(rv.status_code, 200, rv.data)
+            self.assertEqual(rv['Content-Type'], 'application/json')
+
+            for expected_visit in expected_visits:
+                origin_visit_url = reverse(
+                    'api-1-origin-visit',
+                    url_args={'origin_url': new_origin['url'],
+                              'visit_id': expected_visit['visit']})
+                snapshot_url = reverse(
+                    'api-1-snapshot',
+                    url_args={'snapshot_id': expected_visit['snapshot']})
+                expected_visit['origin'] = new_origin['url']
                 expected_visit['origin_visit_url'] = origin_visit_url
                 expected_visit['snapshot_url'] = snapshot_url
 
@@ -128,6 +173,39 @@
             self.storage.snapshot_add(origin_id, origin_visit['visit'],
                                       new_snapshots[i])
             url = reverse('api-1-origin-visit',
+                          url_args={'origin_url': new_origin['url'],
+                                    'visit_id': visit_id})
+
+            rv = self.client.get(url)
+            self.assertEqual(rv.status_code, 200, rv.data)
+            self.assertEqual(rv['Content-Type'], 'application/json')
+
+            expected_visit = self.origin_visit_get_by(origin_id, visit_id)
+
+            origin_url = reverse('api-1-origin',
+                                 url_args={'origin_url': new_origin['url']})
+            snapshot_url = reverse(
+                'api-1-snapshot',
+                url_args={'snapshot_id': expected_visit['snapshot']})
+
+            expected_visit['origin'] = new_origin['url']
+            expected_visit['origin_url'] = origin_url
+            expected_visit['snapshot_url'] = snapshot_url
+
+            self.assertEqual(rv.data, expected_visit)
+
+    @given(new_origin(), visit_dates(3), new_snapshots(3))
+    def test_api_lookup_origin_visit_by_id(self, new_origin, visit_dates,
+                                           new_snapshots):
+
+        origin_id = self.storage.origin_add_one(new_origin)
+        new_origin['id'] = origin_id
+        for i, visit_date in enumerate(visit_dates):
+            origin_visit = self.storage.origin_visit_add(origin_id, visit_date)
+            visit_id = origin_visit['visit']
+            self.storage.snapshot_add(origin_id, origin_visit['visit'],
+                                      new_snapshots[i])
+            url = reverse('api-1-origin-visit',
                           url_args={'origin_id': origin_id,
                                     'visit_id': visit_id})
 
@@ -138,11 +216,12 @@
             expected_visit = self.origin_visit_get_by(origin_id, visit_id)
 
             origin_url = reverse('api-1-origin',
-                                 url_args={'origin_id': origin_id})
+                                 url_args={'origin_url': new_origin['url']})
             snapshot_url = reverse(
                 'api-1-snapshot',
                 url_args={'snapshot_id': expected_visit['snapshot']})
 
+            expected_visit['origin'] = new_origin['url']
             expected_visit['origin_url'] = origin_url
             expected_visit['snapshot_url'] = snapshot_url
 
@@ -156,6 +235,27 @@
         max_visit_id = max([v['visit'] for v in all_visits])
 
         url = reverse('api-1-origin-visit',
+                      url_args={'origin_url': origin['url'],
+                                'visit_id': max_visit_id + 1})
+
+        rv = self.client.get(url)
+
+        self.assertEqual(rv.status_code, 404, rv.data)
+        self.assertEqual(rv['Content-Type'], 'application/json')
+        self.assertEqual(rv.data, {
+            'exception': 'NotFoundExc',
+            'reason': 'Origin %s or its visit with id %s not found!' %
+            (origin['url'], max_visit_id+1)
+        })
+
+    @given(origin())
+    def test_api_lookup_origin_visit_not_found_by_id(self, origin):
+
+        all_visits = list(reversed(get_origin_visits(origin)))
+
+        max_visit_id = max([v['visit'] for v in all_visits])
+
+        url = reverse('api-1-origin-visit',
                       url_args={'origin_id': origin['id'],
                                 'visit_id': max_visit_id + 1})
 
@@ -165,8 +265,8 @@
         self.assertEqual(rv['Content-Type'], 'application/json')
         self.assertEqual(rv.data, {
             'exception': 'NotFoundExc',
-            'reason': 'Origin with id %s or its visit with id %s not found!' %
-            (origin['id'], max_visit_id+1)
+            'reason': 'Origin %s or its visit with id %s not found!' %
+            (origin['url'], max_visit_id+1)
         })
 
     @given(origin())
@@ -179,7 +279,25 @@
         expected_origin = self.origin_get(origin)
 
         origin_visits_url = reverse('api-1-origin-visits',
-                                    url_args={'origin_id': origin['id']})
+                                    url_args={'origin_url': origin['url']})
+
+        expected_origin['origin_visits_url'] = origin_visits_url
+
+        self.assertEqual(rv.status_code, 200, rv.data)
+        self.assertEqual(rv['Content-Type'], 'application/json')
+        self.assertEqual(rv.data, expected_origin)
+
+    @given(origin())
+    def test_api_origin_by_url(self, origin):
+
+        url = reverse('api-1-origin',
+                      url_args={'origin_url': origin['url']})
+        rv = self.client.get(url)
+
+        expected_origin = self.origin_get(origin)
+
+        origin_visits_url = reverse('api-1-origin-visits',
+                                    url_args={'origin_url': origin['url']})
 
         expected_origin['origin_visits_url'] = origin_visits_url
 
@@ -198,7 +316,7 @@
         expected_origin = self.origin_get(origin)
 
         origin_visits_url = reverse('api-1-origin-visits',
-                                    url_args={'origin_id': origin['id']})
+                                    url_args={'origin_url': origin['url']})
 
         expected_origin['origin_visits_url'] = origin_visits_url
 
@@ -218,8 +336,7 @@
         self.assertEqual(rv['Content-Type'], 'application/json')
         self.assertEqual(rv.data, {
             'exception': 'NotFoundExc',
-            'reason': 'Origin with type %s and url %s not found!' %
-            (new_origin['type'], new_origin['url'])
+            'reason': 'Origin %s not found!' % new_origin['url']
         })
 
     @given(origin())
@@ -395,7 +512,7 @@
         for expected_origin in expected_origins:
             expected_origin['origin_visits_url'] = reverse(
                 'api-1-origin-visits',
-                url_args={'origin_id': expected_origin['id']})
+                url_args={'origin_url': expected_origin['url']})
 
         self.assertEqual(rv.data, expected_origins)
 
diff --git a/swh/web/tests/api/views/test_revision.py b/swh/web/tests/api/views/test_revision.py
--- a/swh/web/tests/api/views/test_revision.py
+++ b/swh/web/tests/api/views/test_revision.py
@@ -109,7 +109,7 @@
         self.assertEqual(rv['Content-Type'], 'application/json')
         self.assertEqual(rv.data, {
             'exception': 'NotFoundExc',
-            'reason': 'Origin with id %s not found!' %
+            'reason': 'Origin %s not found!' %
             unknown_origin_id_})
 
     @given(origin())
@@ -222,7 +222,7 @@
         self.assertEqual(rv['Content-Type'], 'application/json')
         self.assertEqual(rv.data, {
             'exception': 'NotFoundExc',
-            'reason': 'Origin with id %s not found!' %
+            'reason': 'Origin %s not found!' %
             unknown_origin_id_
         })
 
diff --git a/swh/web/tests/common/test_origin_visits.py b/swh/web/tests/common/test_origin_visits.py
--- a/swh/web/tests/common/test_origin_visits.py
+++ b/swh/web/tests/common/test_origin_visits.py
@@ -80,7 +80,6 @@
                                      visit_id=visit_id)
         exception_text = cm.exception.args[0]
         self.assertIn('Visit with id %s' % visit_id, exception_text)
-        self.assertIn('type %s' % origin_info['type'], exception_text)
         self.assertIn('url %s' % origin_info['url'], exception_text)
 
         visit = get_origin_visit(origin_info, visit_id=2)