diff --git a/swh/web/api/apidoc.py b/swh/web/api/apidoc.py
index d8885fed..ebb9bc1b 100644
--- a/swh/web/api/apidoc.py
+++ b/swh/web/api/apidoc.py
@@ -1,391 +1,398 @@
 # Copyright (C) 2015-2019  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU Affero General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 import docutils.nodes
 import docutils.parsers.rst
 import docutils.utils
 import functools
 import os
 import re
 import textwrap
 
 from functools import wraps
 from rest_framework.decorators import api_view
 
 from swh.web.common.utils import parse_rst
 from swh.web.api.apiurls import APIUrls
 from swh.web.api.apiresponse import make_api_response, error_response
 
 
 class _HTTPDomainDocVisitor(docutils.nodes.NodeVisitor):
     """
     docutils visitor for walking on a parsed rst document containing sphinx
     httpdomain roles. Its purpose is to extract relevant info regarding swh
     api endpoints (for instance url arguments) from their docstring written
     using sphinx httpdomain.
     """
 
     # httpdomain roles we want to parse (based on sphinxcontrib.httpdomain 1.6)
     parameter_roles = ('param', 'parameter', 'arg', 'argument')
 
     response_json_object_roles = ('resjsonobj', 'resjson', '>jsonobj', '>json')
 
     response_json_array_roles = ('resjsonarr', '>jsonarr')
 
     query_parameter_roles = ('queryparameter', 'queryparam', 'qparam', 'query')
 
     request_header_roles = ('<header', 'reqheader', 'requestheader')
 
     response_header_roles = ('>header', 'resheader', 'responseheader')
 
     status_code_roles = ('statuscode', 'status', 'code')
 
     def __init__(self, document, urls, data):
         super().__init__(document)
         self.urls = urls
         self.url_idx = 0
         self.data = data
         self.args_set = set()
         self.params_set = set()
         self.returns_set = set()
         self.status_codes_set = set()
         self.reqheaders_set = set()
         self.resheaders_set = set()
         self.field_list_visited = False
 
     def process_paragraph(self, par):
         """
         Process extracted paragraph text before display.
         Cleanup document model markups and transform the
         paragraph into a valid raw rst string (as the apidoc
         documentation transform rst to html when rendering).
         """
         par = par.replace('\n', ' ')
         # keep emphasized, strong and literal text
         par = par.replace('<emphasis>', '*')
         par = par.replace('</emphasis>', '*')
         par = par.replace('<strong>', '**')
         par = par.replace('</strong>', '**')
         par = par.replace('<literal>', '``')
         par = par.replace('</literal>', '``')
         # remove parsed document markups
         par = re.sub('<[^<]+?>', '', par)
         # api urls cleanup to generate valid links afterwards
         par = re.sub('\(\w+\)', '', par) # noqa
         par = re.sub('\[.*\]', '', par) # noqa
         par = par.replace('//', '/')
         # transform references to api endpoints into valid rst links
-        par = re.sub(':http:get:`(.*)`', r'`<\1>`_', par)
+        par = re.sub(':http:get:`([^,]*)`', r'`<\1>`_', par)
         # transform references to some elements into bold text
         par = re.sub(':http:header:`(.*)`', r'**\1**', par)
         par = re.sub(':func:`(.*)`', r'**\1**', par)
         return par
 
     def visit_field_list(self, node):
         """
         Visit parsed rst field lists to extract relevant info
         regarding api endpoint.
         """
         self.field_list_visited = True
         for child in node.traverse():
             # get the parsed field name
             if isinstance(child, docutils.nodes.field_name):
                 field_name = child.astext()
             # parse field text
             elif isinstance(child, docutils.nodes.paragraph):
                 text = self.process_paragraph(str(child))
                 field_data = field_name.split(' ')
                 # Parameters
                 if field_data[0] in self.parameter_roles:
                     if field_data[2] not in self.args_set:
                         self.data['args'].append({'name': field_data[2],
                                                   'type': field_data[1],
                                                   'doc': text})
                         self.args_set.add(field_data[2])
                 # Query Parameters
                 if field_data[0] in self.query_parameter_roles:
                     if field_data[2] not in self.params_set:
                         self.data['params'].append({'name': field_data[2],
                                                     'type': field_data[1],
                                                     'doc': text})
                         self.params_set.add(field_data[2])
                 # Response type
                 if field_data[0] in self.response_json_array_roles or \
                         field_data[0] in self.response_json_object_roles:
                     # array
                     if field_data[0] in self.response_json_array_roles:
                         self.data['return_type'] = 'array'
                     # object
                     else:
                         self.data['return_type'] = 'object'
                     # returned object field
                     if field_data[2] not in self.returns_set:
                         self.data['returns'].append({'name': field_data[2],
                                                      'type': field_data[1],
                                                      'doc': text})
                         self.returns_set.add(field_data[2])
                 # Status Codes
                 if field_data[0] in self.status_code_roles:
                     if field_data[1] not in self.status_codes_set:
                         self.data['status_codes'].append({'code': field_data[1], # noqa
                                                           'doc': text})
                         self.status_codes_set.add(field_data[1])
                 # Request Headers
                 if field_data[0] in self.request_header_roles:
                     if field_data[1] not in self.reqheaders_set:
                         self.data['reqheaders'].append({'name': field_data[1],
                                                         'doc': text})
                         self.reqheaders_set.add(field_data[1])
                 # Response Headers
                 if field_data[0] in self.response_header_roles:
                     if field_data[1] not in self.resheaders_set:
                         resheader = {'name': field_data[1],
                                      'doc': text}
                         self.data['resheaders'].append(resheader)
                         self.resheaders_set.add(field_data[1])
                         if resheader['name'] == 'Content-Type' and \
                                 resheader['doc'] == 'application/octet-stream':
                             self.data['return_type'] = 'octet stream'
 
     def visit_paragraph(self, node):
         """
         Visit relevant paragraphs to parse
         """
         # only parsed top level paragraphs
         if isinstance(node.parent, docutils.nodes.block_quote):
             text = self.process_paragraph(str(node))
             # endpoint description
-            if not text.startswith('**') and self.data['description'] != text:
+            if (not text.startswith('**') and
+                    text not in self.data['description']):
                 self.data['description'] += '\n\n' if self.data['description'] else '' # noqa
                 self.data['description'] += text
             # http methods
             elif text.startswith('**Allowed HTTP Methods:**'):
                 text = text.replace('**Allowed HTTP Methods:**', '')
                 http_methods = text.strip().split(',')
                 http_methods = [m[m.find('`')+1:-1].upper()
                                 for m in http_methods]
                 self.data['urls'].append({'rule': self.urls[self.url_idx],
                                           'methods': http_methods})
                 self.url_idx += 1
 
     def visit_literal_block(self, node):
         """
         Visit literal blocks
         """
         text = node.astext()
         # literal block in endpoint description
         if not self.field_list_visited:
             self.data['description'] += \
                 ':\n\n%s\n' % textwrap.indent(text, '\t')
         # extract example url
         if ':swh_web_api:' in text:
             self.data['examples'].append(
                 '/api/1/' + re.sub('.*`(.*)`.*', r'\1', text))
 
     def visit_bullet_list(self, node):
         # bullet list in endpoint description
         if not self.field_list_visited:
             self.data['description'] += '\n\n'
             for child in node.traverse():
                 # process list item
                 if isinstance(child, docutils.nodes.paragraph):
                     line_text = self.process_paragraph(str(child))
                     self.data['description'] += '\t* %s\n' % line_text
 
+    def visit_warning(self, node):
+        text = self.process_paragraph(str(node))
+        rst_warning = '\n\n.. warning::\n%s\n' % textwrap.indent(text, '\t')
+        if rst_warning not in self.data['description']:
+            self.data['description'] += rst_warning
+
     def unknown_visit(self, node):
         pass
 
     def depart_document(self, node):
         """
         End of parsing extra processing
         """
         default_methods = ['GET', 'HEAD', 'OPTIONS']
         # ensure urls info is present and set default http methods
         if not self.data['urls']:
             for url in self.urls:
                 self.data['urls'].append({'rule': url,
                                           'methods': default_methods})
 
     def unknown_departure(self, node):
         pass
 
 
 def _parse_httpdomain_doc(doc, data):
     doc_lines = doc.split('\n')
     doc_lines_filtered = []
     urls = []
     # httpdomain is a sphinx extension that is unknown to docutils but
     # fortunately we can still parse its directives' content,
     # so remove lines with httpdomain directives before executing the
     # rst parser from docutils
     for doc_line in doc_lines:
         if '.. http' not in doc_line:
             doc_lines_filtered.append(doc_line)
         else:
             url = doc_line[doc_line.find('/'):]
             # emphasize url arguments for html rendering
             url = re.sub(r'\((\w+)\)', r' **\(\1\)** ', url)
             urls.append(url)
     # parse the rst docstring and do not print system messages about
     # unknown httpdomain roles
     document = parse_rst('\n'.join(doc_lines_filtered), report_level=5)
     # remove the system_message nodes from the parsed document
     for node in document.traverse(docutils.nodes.system_message):
         node.parent.remove(node)
     # visit the document nodes to extract relevant endpoint info
     visitor = _HTTPDomainDocVisitor(document, urls, data)
     document.walkabout(visitor)
 
 
 class APIDocException(Exception):
     """
     Custom exception to signal errors in the use of the APIDoc decorators
     """
 
 
 def api_doc(route, noargs=False, need_params=False, tags=[],
             handle_response=False, api_version='1'):
     """
     Decorate an API function to register it in the API doc route index
     and create the corresponding DRF route.
 
     Args:
         route (str): documentation page's route
         noargs (boolean): set to True if the route has no arguments, and its
             result should be displayed anytime its documentation
             is requested. Default to False
         need_params (boolean): specify the route requires query parameters
             otherwise errors will occur. It enables to avoid displaying the
             invalid response in its HTML documentation. Default to False.
         tags (list): Further information on api endpoints. Two values are
             possibly expected:
 
                 * hidden: remove the entry points from the listing
                 * upcoming: display the entry point but it is not followable
 
         handle_response (boolean): indicate if the decorated function takes
             care of creating the HTTP response or delegates that task to the
             apiresponse module
         api_version (str): api version string
 
     """
     urlpattern = '^' + api_version + route + '$'
     tags = set(tags)
 
     # @api_doc() Decorator call
     def decorator(f):
 
         # If the route is not hidden, add it to the index
         if 'hidden' not in tags:
             doc_data = get_doc_data(f, route, noargs)
             doc_desc = doc_data['description']
             first_dot_pos = doc_desc.find('.')
             APIUrls.add_route(route, doc_desc[:first_dot_pos+1],
                               tags=tags)
 
         # If the decorated route has arguments, we create a specific
         # documentation view
         if not noargs:
 
             @api_view(['GET', 'HEAD'])
             @wraps(f)
             def doc_view(request):
                 doc_data = get_doc_data(f, route, noargs)
                 return make_api_response(request, None, doc_data)
 
             view_name = 'api-%s-%s' % \
                 (api_version, route[1:-1].replace('/', '-'))
             APIUrls.add_url_pattern(urlpattern, doc_view, view_name)
 
         @wraps(f)
         def documented_view(request, **kwargs):
             doc_data = get_doc_data(f, route, noargs)
 
             try:
                 response = f(request, **kwargs)
             except Exception as exc:
                 if request.accepted_media_type == 'text/html' and \
                         need_params and not request.query_params:
                     response = None
                 else:
                     return error_response(request, exc, doc_data)
 
             if handle_response:
                 return response
             else:
                 return make_api_response(request, response, doc_data)
 
         return documented_view
 
     return decorator
 
 
 @functools.lru_cache(maxsize=32)
 def get_doc_data(f, route, noargs):
     """
     Build documentation data for the decorated api endpoint function
     """
     data = {
         'description': '',
         'response_data': None,
         'urls': [],
         'args': [],
         'params': [],
         'resheaders': [],
         'reqheaders': [],
         'return_type': '',
         'returns': [],
         'status_codes': [],
         'examples': [],
         'route': route,
         'noargs': noargs
     }
 
     if not f.__doc__:
         raise APIDocException('apidoc: expected a docstring'
                               ' for function %s'
                               % (f.__name__,))
 
     # use raw docstring as endpoint documentation if sphinx
     # httpdomain is not used
     if '.. http' not in f.__doc__:
         data['description'] = f.__doc__
     # else parse the sphinx httpdomain docstring with docutils
     # (except when building the swh-web documentation through autodoc
     # sphinx extension, not needed and raise errors with sphinx >= 1.7)
     elif 'SWH_WEB_DOC_BUILD' not in os.environ:
         _parse_httpdomain_doc(f.__doc__, data)
         # process returned object info for nicer html display
         returns_list = ''
         for ret in data['returns']:
             returns_list += '\t* **%s (%s)**: %s\n' %\
                 (ret['name'], ret['type'], ret['doc'])
         data['returns_list'] = returns_list
 
     return data
 
 
 DOC_COMMON_HEADERS = '''
         :reqheader Accept: the requested response content type,
             either ``application/json`` (default) or ``application/yaml``
         :resheader Content-Type: this depends on :http:header:`Accept`
             header of request'''
 DOC_RESHEADER_LINK = '''
         :resheader Link: indicates that a subsequent result page is
             available and contains the url pointing to it
 '''
 
 DEFAULT_SUBSTITUTIONS = {
     'common_headers': DOC_COMMON_HEADERS,
     'resheader_link': DOC_RESHEADER_LINK,
 }
 
 
 def format_docstring(**substitutions):
     def decorator(f):
         f.__doc__ = f.__doc__.format(**{
             **DEFAULT_SUBSTITUTIONS, **substitutions})
         return f
     return decorator
diff --git a/swh/web/api/views/origin.py b/swh/web/api/views/origin.py
index 890d060e..30029138 100644
--- a/swh/web/api/views/origin.py
+++ b/swh/web/api/views/origin.py
@@ -1,590 +1,595 @@
 # Copyright (C) 2015-2019  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU Affero General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 from distutils.util import strtobool
 from functools import partial
 
 from swh.web.common import service
 from swh.web.common.exc import BadInputExc
 from swh.web.common.origin_visits import get_origin_visits
 from swh.web.common.utils import reverse
 from swh.web.api.apidoc import api_doc, format_docstring
 from swh.web.api.apiurls import api_route
 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_url': origin['url']})
         return o
 
     return origin
 
 
 def _enrich_origin_visit(origin_visit, *,
                          with_origin_link, with_origin_visit_link):
     ov = origin_visit.copy()
     if with_origin_link:
         ov['origin_url'] = reverse('api-1-origin',
                                    url_args={'origin_url': ov['origin']})
     if with_origin_visit_link:
         ov['origin_visit_url'] = reverse('api-1-origin-visit',
                                          url_args={'origin_url': ov['origin'],
                                                    'visit_id': ov['visit']})
     snapshot = ov['snapshot']
     if snapshot:
         ov['snapshot_url'] = reverse('api-1-snapshot',
                                      url_args={'snapshot_id': snapshot})
     else:
         ov['snapshot_url'] = None
     return ov
 
 
 @api_route(r'/origins/', 'api-1-origins')
 @api_doc('/origins/', noargs=True)
 @format_docstring(return_origin_array=DOC_RETURN_ORIGIN_ARRAY)
 def api_origins(request):
     """
     .. http:get:: /api/1/origins/
 
         Get list of archived software origins.
 
         Origins are sorted by ids before returning them.
 
         :query int origin_from: The first origin id that will be included
             in returned results (default to 1)
         :query int origin_count: The maximum number of origins to return
             (default to 100, can not exceed 10000)
 
         {return_origin_array}
 
         {common_headers}
         {resheader_link}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
             :http:method:`options`
 
         :statuscode 200: no error
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`origins?origin_from=50000&origin_count=500`
     """
     origin_from = int(request.query_params.get('origin_from', '1'))
     origin_count = int(request.query_params.get('origin_count', '100'))
     origin_count = min(origin_count, 10000)
     results = api_lookup(
         service.lookup_origins, origin_from, origin_count+1,
         enrich_fn=_enrich_origin)
     response = {'results': results, 'headers': {}}
     if len(results) > origin_count:
         origin_from = results.pop()['id']
         response['headers']['link-next'] = reverse(
             'api-1-origins',
             query_params={'origin_from': origin_from,
                           'origin_count': origin_count})
     return response
 
 
 @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(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.
 
         .. warning::
 
-            As all endpoints using an ``origin_id``, this endpoint is
-            deprecated and will be removed in the near future.
-            Use :http:get:`/api/1/origin/(origin_url)/get/` instead.
+            All endpoints using an ``origin_id`` or an ``origin_type`` are
+            deprecated and will be removed in the near future. Only those
+            using an ``origin_url`` will remain available.
+            You should use :http:get:`/api/1/origin/(origin_url)/get/` instead.
 
         :param int origin_id: a software origin identifier
 
         {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/1/`
 
     .. http:get:: /api/1/origin/(origin_type)/url/(origin_url)/
 
         Get information about a software origin.
 
         .. warning::
 
-            This endpoint is deprecated. You should use
-            :http:get:`/api/1/origin/(origin_url)/get/` instead.
+            All endpoints using an ``origin_id`` or an ``origin_type`` are
+            deprecated and will be removed in the near future. Only those
+            using an ``origin_url`` will remain available.
+            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
 
         {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/`
     """
     ori_dict = {
         'id': int(origin_id) if origin_id else None,
         'type': origin_type,
         'url': origin_url
     }
     ori_dict = {k: v for k, v in ori_dict.items() if ori_dict[k]}
     error_msg = 'Origin %s not found.' % \
         (ori_dict.get('id') or ori_dict['url'])
 
     return api_lookup(
         service.lookup_origin, ori_dict,
         notfound_msg=error_msg,
         enrich_fn=_enrich_origin)
 
 
 @api_route(r'/origin/search/(?P<url_pattern>.+)/',
            'api-1-origin-search')
 @api_doc('/origin/search/')
 @format_docstring(return_origin_array=DOC_RETURN_ORIGIN_ARRAY)
 def api_origin_search(request, url_pattern):
     """
     .. http:get:: /api/1/origin/search/(url_pattern)/
 
         Search for software origins whose urls contain a provided string
         pattern or match a provided regular expression.
         The search is performed in a case insensitive way.
 
         :param string url_pattern: a string pattern or a regular expression
         :query int offset: the number of found origins to skip before returning
             results
         :query int limit: the maximum number of found origins to return
         :query boolean regexp: if true, consider provided pattern as a regular
             expression and search origins whose urls match it
         :query boolean with_visit: if true, only return origins with at least
             one visit by Software heritage
 
         {return_origin_array}
 
         {common_headers}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
             :http:method:`options`
 
         :statuscode 200: no error
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`origin/search/python/?limit=2`
     """
     result = {}
     offset = int(request.query_params.get('offset', '0'))
     limit = int(request.query_params.get('limit', '70'))
     regexp = request.query_params.get('regexp', 'false')
     with_visit = request.query_params.get('with_visit', 'false')
 
     results = api_lookup(service.search_origin, url_pattern, offset, limit,
                          bool(strtobool(regexp)), bool(strtobool(with_visit)),
                          enrich_fn=_enrich_origin)
 
     nb_results = len(results)
     if nb_results == limit:
         query_params = {}
         query_params['offset'] = offset + limit
         query_params['limit'] = limit
         query_params['regexp'] = regexp
 
         result['headers'] = {
             'link-next': reverse('api-1-origin-search',
                                  url_args={'url_pattern': url_pattern},
                                  query_params=query_params)
         }
 
     result.update({
         'results': results
     })
 
     return result
 
 
 @api_route(r'/origin/metadata-search/',
            'api-1-origin-metadata-search')
 @api_doc('/origin/metadata-search/', noargs=True, need_params=True)
 @format_docstring(return_origin_array=DOC_RETURN_ORIGIN_ARRAY)
 def api_origin_metadata_search(request):
     """
     .. http:get:: /api/1/origin/metadata-search/
 
         Search for software origins whose metadata (expressed as a
         JSON-LD/CodeMeta dictionary) match the provided criteria.
         For now, only full-text search on this dictionary is supported.
 
         :query str fulltext: a string that will be matched against origin
             metadata; results are ranked and ordered starting with the best
             ones.
         :query int limit: the maximum number of found origins to return
             (bounded to 100)
 
         {return_origin_array}
 
         {common_headers}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
             :http:method:`options`
 
         :statuscode 200: no error
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`origin/metadata-search/?limit=2&fulltext=Jane%20Doe`
     """
     fulltext = request.query_params.get('fulltext', None)
     limit = min(int(request.query_params.get('limit', '70')), 100)
 
     if not fulltext:
         content = '"fulltext" must be provided and non-empty.'
         raise BadInputExc(content)
 
     results = api_lookup(service.search_origin_metadata, fulltext, limit)
 
     return {
         'results': results,
     }
 
 
 @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(
     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.
         Visits are returned sorted in descending order according
         to their date.
 
         .. warning::
 
-            As all endpoints using an ``origin_id``, this endpoint is
-            deprecated and will be removed in the near future.
+            All endpoints using an ``origin_id`` are  deprecated and will be
+            removed in the near future. Only those using an ``origin_url``
+            will remain available.
             Use :http:get:`/api/1/origin/(origin_url)/visits/` instead.
 
         :param int origin_id: a software origin identifier
         :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/1/visits/`
     """
     result = {}
     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_query, last_visit=last_visit, per_page=per_page):
         all_visits = get_origin_visits(origin_query)
         all_visits.reverse()
         visits = []
         if not last_visit:
             visits = all_visits[:per_page]
         else:
             for i, v in enumerate(all_visits):
                 if v['visit'] == last_visit:
                     visits = all_visits[i+1:i+1+per_page]
                     break
         for v in visits:
             yield v
 
     results = api_lookup(_lookup_origin_visits, origin_query,
                          notfound_msg=notfound_msg,
                          enrich_fn=partial(_enrich_origin_visit,
                                            with_origin_link=False,
                                            with_origin_visit_link=True))
 
     if results:
         nb_results = len(results)
         if nb_results == per_page:
             new_last_visit = results[-1]['visit']
             query_params = {}
             query_params['last_visit'] = new_last_visit
 
             if request.query_params.get('per_page'):
                 query_params['per_page'] = per_page
 
             result['headers'] = {
                 'link-next': reverse('api-1-origin-visits',
                                      url_args=url_args_next,
                                      query_params=query_params)
             }
 
     result.update({
         'results': results
     })
 
     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(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.
 
         .. warning::
 
-            As all endpoints using an ``origin_id``, this endpoint is
-            deprecated and will be removed in the near future.
+            All endpoints using an ``origin_id`` are  deprecated and will be
+            removed in the near future. Only those using an ``origin_url``
+            will remain available.
             Use :http:get:`/api/1/origin/(origin_url)/visit/(visit_id)`
             instead.
 
         :param int origin_id: a software origin identifier
         :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/1500/visit/1/`
     """
     if not origin_url:
         origin_url = service.lookup_origin({'id': int(origin_id)})['url']
     return api_lookup(
         service.lookup_origin_visit, origin_url, int(visit_id),
         notfound_msg=('No visit {} for origin {} found'
                       .format(visit_id, origin_url)),
         enrich_fn=partial(_enrich_origin_visit,
                           with_origin_link=True,
                           with_origin_visit_link=False))
 
 
 @api_route(r'/origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)'
            '/intrinsic-metadata', 'api-origin-intrinsic-metadata')
 @api_doc('/origin/intrinsic-metadata/')
 @format_docstring()
 def api_origin_intrinsic_metadata(request, origin_type, origin_url):
     """
     .. http:get:: /api/1/origin/(origin_type)/url/(origin_url)/intrinsic-metadata
 
         Get intrinsic metadata of a software origin (as a JSON-LD/CodeMeta dictionary).
 
         :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 string ???: intrinsic metadata field of the 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/intrinsic-metadata`
     """ # noqa
     ori_dict = {
         'type': origin_type,
         'url': origin_url
     }
 
     error_msg = 'Origin with URL %s not found' % ori_dict['url']
 
     return api_lookup(
         service.lookup_origin_intrinsic_metadata, ori_dict,
         notfound_msg=error_msg,
         enrich_fn=_enrich_origin)
diff --git a/swh/web/api/views/revision.py b/swh/web/api/views/revision.py
index aea0b3c6..0e5e27d3 100644
--- a/swh/web/api/views/revision.py
+++ b/swh/web/api/views/revision.py
@@ -1,476 +1,479 @@
 # Copyright (C) 2015-2019  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU Affero General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 from django.http import HttpResponse
 
 from swh.web.common import service
 from swh.web.common.utils import reverse
 from swh.web.common.utils import parse_timestamp
 from swh.web.api import utils
 from swh.web.api.apidoc import api_doc, format_docstring
 from swh.web.api.apiurls import api_route
 from swh.web.api.views.utils import api_lookup
 
 
 DOC_RETURN_REVISION = '''
         :>json object author: information about the author of the revision
         :>json string author_url: link to
             :http:get:`/api/1/person/(person_id)/` to get information about the
             author of the revision
         :>json object committer: information about the committer of the
             revision
         :>json string committer_url: link to
             :http:get:`/api/1/person/(person_id)/` to get information about the
             committer of the revision
         :>json string committer_date: ISO representation of the commit date
             (in UTC)
         :>json string date: ISO representation of the revision date (in UTC)
         :>json string directory: the unique identifier that revision points to
         :>json string directory_url: link to
             :http:get:`/api/1/directory/(sha1_git)/[(path)/]` to get
             information about the directory associated to the revision
         :>json string id: the revision unique identifier
         :>json boolean merge: whether or not the revision corresponds to a
             merge commit
         :>json string message: the message associated to the revision
         :>json array parents: the parents of the revision, i.e. the previous
             revisions that head directly to it, each entry of that array
             contains an unique parent revision identifier but also a link to
             :http:get:`/api/1/revision/(sha1_git)/` to get more information
             about it
         :>json string type: the type of the revision
 ''' # noqa
 
 DOC_RETURN_REVISION_ARRAY = \
     DOC_RETURN_REVISION.replace(':>json', ':>jsonarr')
 
 
 def _revision_directory_by(revision, path, request_path,
                            limit=100, with_data=False):
     """
     Compute the revision matching criterion's directory or content data.
 
     Args:
         revision: dictionary of criterions representing a revision to lookup
         path: directory's path to lookup
         request_path: request path which holds the original context to
         limit: optional query parameter to limit the revisions log
         (default to 100). For now, note that this limit could impede the
         transitivity conclusion about sha1_git not being an ancestor of
         with_data: indicate to retrieve the content's raw data if path resolves
         to a content.
 
     """
     def enrich_directory_local(dir, context_url=request_path):
         return utils.enrich_directory(dir, context_url)
 
     rev_id, result = service.lookup_directory_through_revision(
         revision, path, limit=limit, with_data=with_data)
 
     content = result['content']
     if result['type'] == 'dir':  # dir_entries
         result['content'] = list(map(enrich_directory_local, content))
     elif result['type'] == 'file':  # content
         result['content'] = utils.enrich_content(content)
     elif result['type'] == 'rev':  # revision
         result['content'] = utils.enrich_revision(content)
 
     return result
 
 
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)/log/',
            'api-1-revision-origin-log')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/log/',
            'api-1-revision-origin-log')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/ts/(?P<ts>.+)/log/',
            'api-1-revision-origin-log')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)'
            r'/ts/(?P<ts>.+)/log/',
            'api-1-revision-origin-log')
 @api_doc('/revision/origin/log/')
 @format_docstring(return_revision_array=DOC_RETURN_REVISION_ARRAY)
 def api_revision_log_by(request, origin_id,
                         branch_name='HEAD',
                         ts=None):
     """
     .. http:get:: /api/1/revision/origin/(origin_id)[/branch/(branch_name)][/ts/(timestamp)]/log
 
         Show the commit log for a revision, searching for it based on software origin,
         branch name, and/or visit timestamp.
 
         This endpoint behaves like :http:get:`/api/1/revision/(sha1_git)[/prev/(prev_sha1s)]/log/`,
         but operates on the revision that has been found at a given software origin,
         close to a given point in time, pointed by a given branch.
 
         .. warning::
 
-            As all endpoints using an ``origin_id``, this endpoint is
-            deprecated and will be removed in the near future.
+            All endpoints using an ``origin_id`` are  deprecated and will be
+            removed in the near future. Only those using an ``origin_url``
+            will remain available.
             You should instead use successively
             :http:get:`/api/1/origin/(origin_url)/visits/`,
             :http:get:`/api/1/snapshot/(snapshot_id)/`, and
             :http:get:`/api/1/revision/(sha1_git)[/prev/(prev_sha1s)]/log/`.
 
 
         :param int origin_id: a software origin identifier
         :param string branch_name: optional parameter specifying a fully-qualified branch name
             associated to the software origin, e.g., "refs/heads/master". Defaults to the HEAD branch.
         :param string timestamp: optional parameter specifying a timestamp close to which the revision
             pointed by the given branch should be looked up. The timestamp can be expressed either
             as an ISO date or as a Unix one (in UTC). Defaults to now.
 
         {common_headers}
 
         {return_revision_array}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options`
 
         :statuscode 200: no error
         :statuscode 404: no revision matching the given criteria could be found in the archive
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`revision/origin/723566/ts/2016-01-17T00:00:00+00:00/log/`
     """ # noqa
     result = {}
     per_page = int(request.query_params.get('per_page', '10'))
 
     def lookup_revision_log_by_with_limit(o_id, br, ts, limit=per_page+1):
         return service.lookup_revision_log_by(o_id, br, ts, limit)
 
     error_msg = 'No revision matching origin %s ' % origin_id
     error_msg += ', branch name %s' % branch_name
     error_msg += (' and time stamp %s.' % ts) if ts else '.'
 
     rev_get = api_lookup(
         lookup_revision_log_by_with_limit, int(origin_id), branch_name, ts,
         notfound_msg=error_msg,
         enrich_fn=utils.enrich_revision)
 
     nb_rev = len(rev_get)
     if nb_rev == per_page+1:
         revisions = rev_get[:-1]
         last_sha1_git = rev_get[-1]['id']
 
         params = {k: v for k, v in {'origin_id': origin_id,
                                     'branch_name': branch_name,
                                     'ts': ts,
                                     }.items() if v is not None}
 
         query_params = {}
         query_params['sha1_git'] = last_sha1_git
 
         if request.query_params.get('per_page'):
             query_params['per_page'] = per_page
 
         result['headers'] = {
             'link-next': reverse('api-1-revision-origin-log', url_args=params,
                                  query_params=query_params)
         }
 
     else:
         revisions = rev_get
 
     result.update({'results': revisions})
 
     return result
 
 
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/directory/',
            'api-1-revision-origin-directory')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/directory/(?P<path>.+)/',
            'api-1-revision-origin-directory')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)/directory/',
            'api-1-revision-origin-directory')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)/ts/(?P<ts>.+)/directory/',
            'api-1-revision-origin-directory')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)/directory/(?P<path>.+)/',
            'api-1-revision-origin-directory')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)/ts/(?P<ts>.+)'
            r'/directory/(?P<path>.+)/',
            'api-1-revision-origin-directory')
 @api_doc('/revision/origin/directory/', tags=['hidden'])
 def api_directory_through_revision_origin(request, origin_id,
                                           branch_name='HEAD',
                                           ts=None,
                                           path=None,
                                           with_data=False):
     """
     Display directory or content information through a revision identified
     by origin/branch/timestamp.
 
         .. warning::
 
-            As all endpoints using an ``origin_id``, this endpoint is
-            deprecated and will be removed in the near future.
+            All endpoints using an ``origin_id`` are  deprecated and will be
+            removed in the near future. Only those using an ``origin_url``
+            will remain available.
             You should instead use successively
             :http:get:`/api/1/origin/(origin_url)/visits/`,
             :http:get:`/api/1/snapshot/(snapshot_id)/`,
             :http:get:`/api/1/revision/(sha1_git)/`,
             :http:get:`/api/1/directory/(sha1_git)/[(path)/]`
     """
     if ts:
         ts = parse_timestamp(ts)
 
     return _revision_directory_by({'origin_id': int(origin_id),
                                    'branch_name': branch_name,
                                    'ts': ts
                                    },
                                   path, request.path,
                                   with_data=with_data)
 
 
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/',
            'api-1-revision-origin')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)/',
            'api-1-revision-origin')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)'
            r'/branch/(?P<branch_name>.+)/ts/(?P<ts>.+)/',
            'api-1-revision-origin')
 @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/ts/(?P<ts>.+)/',
            'api-1-revision-origin')
 @api_doc('/revision/origin/')
 @format_docstring(return_revision=DOC_RETURN_REVISION)
 def api_revision_with_origin(request, origin_id,
                              branch_name='HEAD',
                              ts=None):
     """
     .. http:get:: /api/1/revision/origin/(origin_id)/[branch/(branch_name)/][ts/(timestamp)/]
 
         Get information about a revision, searching for it based on software origin,
         branch name, and/or visit timestamp.
 
         This endpoint behaves like :http:get:`/api/1/revision/(sha1_git)/`,
         but operates on the revision that has been found at a given software origin,
         close to a given point in time, pointed by a given branch.
 
         .. warning::
 
-            As all endpoints using an ``origin_id``, this endpoint is
-            deprecated and will be removed in the near future.
+            All endpoints using an ``origin_id`` are  deprecated and will be
+            removed in the near future. Only those using an ``origin_url``
+            will remain available.
             You should instead use successively
             :http:get:`/api/1/origin/(origin_url)/visits/`,
             :http:get:`/api/1/snapshot/(snapshot_id)/`, and
             :http:get:`/api/1/revision/(sha1_git)/`.
 
         :param int origin_id: a software origin identifier
         :param string branch_name: optional parameter specifying a fully-qualified branch name
             associated to the software origin, e.g., "refs/heads/master". Defaults to the HEAD branch.
         :param string timestamp: optional parameter specifying a timestamp close to which the revision
             pointed by the given branch should be looked up. The timestamp can be expressed either
             as an ISO date or as a Unix one (in UTC). Defaults to now.
 
         {common_headers}
 
         {return_revision}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options`
 
         :statuscode 200: no error
         :statuscode 404: no revision matching the given criteria could be found in the archive
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`revision/origin/13706355/branch/refs/heads/2.7/`
     """ # noqa
     return api_lookup(
         service.lookup_revision_by, int(origin_id), branch_name, ts,
         notfound_msg=('Revision with (origin_id: {}, branch_name: {}'
                       ', ts: {}) not found.'.format(origin_id,
                                                     branch_name, ts)),
         enrich_fn=utils.enrich_revision)
 
 
 @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/', 'api-1-revision',
            checksum_args=['sha1_git'])
 @api_doc('/revision/')
 @format_docstring(return_revision=DOC_RETURN_REVISION)
 def api_revision(request, sha1_git):
     """
     .. http:get:: /api/1/revision/(sha1_git)/
 
         Get information about a revision in the archive. Revisions are
         identified by **sha1** checksums, compatible with Git commit
         identifiers.
         See :func:`swh.model.identifiers.revision_identifier` in our data model
         module for details about how they are computed.
 
         :param string sha1_git: hexadecimal representation of the revision
             **sha1_git** identifier
 
         {common_headers}
 
         {return_revision}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`,
             :http:method:`options`
 
         :statuscode 200: no error
         :statuscode 400: an invalid **sha1_git** value has been provided
         :statuscode 404: requested revision can not be found in the archive
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`revision/aafb16d69fd30ff58afdd69036a26047f3aebdc6/`
     """ # noqa
     return api_lookup(
         service.lookup_revision, sha1_git,
         notfound_msg='Revision with sha1_git {} not found.'.format(sha1_git),
         enrich_fn=utils.enrich_revision)
 
 
 @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/raw/',
            'api-1-revision-raw-message', checksum_args=['sha1_git'])
 @api_doc('/revision/raw/', tags=['hidden'], handle_response=True)
 def api_revision_raw_message(request, sha1_git):
     """Return the raw data of the message of revision identified by sha1_git
     """
     raw = service.lookup_revision_message(sha1_git)
     response = HttpResponse(raw['message'],
                             content_type='application/octet-stream')
     response['Content-disposition'] = \
         'attachment;filename=rev_%s_raw' % sha1_git
     return response
 
 
 @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/directory/',
            'api-1-revision-directory', checksum_args=['sha1_git'])
 @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/directory/(?P<dir_path>.+)/',
            'api-1-revision-directory', checksum_args=['sha1_git'])
 @api_doc('/revision/directory/')
 @format_docstring()
 def api_revision_directory(request, sha1_git,
                            dir_path=None,
                            with_data=False):
     """
     .. http:get:: /api/1/revision/(sha1_git)/directory/[(path)/]
 
         Get information about directory (entry) objects associated to revisions.
         Each revision is associated to a single "root" directory.
         This endpoint behaves like :http:get:`/api/1/directory/(sha1_git)/[(path)/]`,
         but operates on the root directory associated to a given revision.
 
         :param string sha1_git: hexadecimal representation of the revision **sha1_git** identifier
         :param string path: optional parameter to get information about the directory entry
             pointed by that relative path
 
         {common_headers}
 
         :>json array content: directory entries as returned by :http:get:`/api/1/directory/(sha1_git)/[(path)/]`
         :>json string path: path of directory from the revision root one
         :>json string revision: the unique revision identifier
         :>json string type: the type of the directory
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options`
 
         :statuscode 200: no error
         :statuscode 400: an invalid **sha1_git** value has been provided
         :statuscode 404: requested revision can not be found in the archive
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`revision/f1b94134a4b879bc55c3dacdb496690c8ebdc03f/directory/`
     """ # noqa
     return _revision_directory_by({'sha1_git': sha1_git},
                                   dir_path, request.path,
                                   with_data=with_data)
 
 
 @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/log/', 'api-1-revision-log',
            checksum_args=['sha1_git'])
 @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)'
            r'/prev/(?P<prev_sha1s>[0-9a-f]*/*)/log/',
            'api-1-revision-log', checksum_args=['sha1_git', 'prev_sha1s'])
 @api_doc('/revision/log/')
 @format_docstring(return_revision_array=DOC_RETURN_REVISION_ARRAY)
 def api_revision_log(request, sha1_git, prev_sha1s=None):
     """
     .. http:get:: /api/1/revision/(sha1_git)[/prev/(prev_sha1s)]/log/
 
         Get a list of all revisions heading to a given one, in other words show the commit log.
 
         :param string sha1_git: hexadecimal representation of the revision **sha1_git** identifier
         :param string prev_sha1s: optional parameter representing the navigation breadcrumbs
             (descendant revisions previously visited). If multiple values, use / as delimiter.
             If provided, revisions information will be added at the beginning of the returned list.
         :query int per_page: number of elements in the returned list, for pagination purpose
 
         {common_headers}
         {resheader_link}
 
         {return_revision_array}
 
         **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options`
 
         :statuscode 200: no error
         :statuscode 400: an invalid **sha1_git** value has been provided
         :statuscode 404: requested revision can not be found in the archive
 
         **Example:**
 
         .. parsed-literal::
 
             :swh_web_api:`revision/e1a315fa3fa734e2a6154ed7b5b9ae0eb8987aad/log/`
     """ # noqa
     result = {}
     per_page = int(request.query_params.get('per_page', '10'))
 
     def lookup_revision_log_with_limit(s, limit=per_page+1):
         return service.lookup_revision_log(s, limit)
 
     error_msg = 'Revision with sha1_git %s not found.' % sha1_git
     rev_get = api_lookup(lookup_revision_log_with_limit, sha1_git,
                          notfound_msg=error_msg,
                          enrich_fn=utils.enrich_revision)
 
     nb_rev = len(rev_get)
     if nb_rev == per_page+1:
         rev_backward = rev_get[:-1]
         new_last_sha1 = rev_get[-1]['id']
         query_params = {}
 
         if request.query_params.get('per_page'):
             query_params['per_page'] = per_page
 
         result['headers'] = {
             'link-next': reverse('api-1-revision-log',
                                  url_args={'sha1_git': new_last_sha1},
                                  query_params=query_params)
         }
 
     else:
         rev_backward = rev_get
 
     if not prev_sha1s:  # no nav breadcrumbs, so we're done
         revisions = rev_backward
 
     else:
         rev_forward_ids = prev_sha1s.split('/')
         rev_forward = api_lookup(
             service.lookup_revision_multiple, rev_forward_ids,
             notfound_msg=error_msg,
             enrich_fn=utils.enrich_revision)
         revisions = rev_forward + rev_backward
 
     result.update({
         'results': revisions
     })
     return result
diff --git a/swh/web/assets/src/bundles/webapp/webapp.css b/swh/web/assets/src/bundles/webapp/webapp.css
index 76cf4a68..15b24e88 100644
--- a/swh/web/assets/src/bundles/webapp/webapp.css
+++ b/swh/web/assets/src/bundles/webapp/webapp.css
@@ -1,489 +1,504 @@
 /**
  * Copyright (C) 2018-2019  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 html {
     height: 100%;
     overflow-x: hidden;
 }
 
 body {
     min-height: 100%;
     margin: 0;
     position: relative;
     padding-bottom: 120px;
 }
 
 a:active,
 a.active {
     outline: none;
 }
 
 code {
     background-color: #f9f2f4;
 }
 
 pre code {
     background-color: transparent;
 }
 
 footer {
     background-color: #262626;
     color: #fff;
     font-size: 0.8rem;
     position: absolute;
     bottom: 0;
     width: 100%;
     padding-top: 20px;
     padding-bottom: 20px;
 }
 
 footer a,
 footer a:visited,
 footer a:hover {
     color: #fecd1b;
 }
 
 footer a:hover {
     text-decoration: underline;
 }
 
 .link-color {
     color: #fecd1b;
 }
 
 pre {
     background-color: #f5f5f5;
     border: 1px solid #ccc;
     border-radius: 4px;
     padding: 9.5px;
     font-size: 0.8rem;
 }
 
 .btn.active {
     background-color: #e7e7e7;
 }
 
 .card {
     margin-bottom: 5px !important;
     overflow-x: auto;
 }
 
 .navbar-brand {
     padding: 5px;
     margin-right: 0;
 }
 
 .table {
     margin-bottom: 0;
 }
 
 .swh-table thead {
     background-color: #f2f4f5;
     border-top: 1px solid rgba(0, 0, 0, 0.2);
     font-weight: normal;
 }
 
 .swh-table-striped th {
     border-top: none;
 }
 
 .swh-table-striped tbody tr:nth-child(even) {
     background-color: #f2f4f5;
 }
 
 .swh-table-striped tbody tr:nth-child(odd) {
     background-color: #fff;
 }
 
 .swh-web-app-link a {
     text-decoration: none;
     border: none;
 }
 
 .swh-web-app-link:hover {
     background-color: #efeff2;
 }
 
 .table > thead > tr > th {
     border-top: none;
     border-bottom: 1px solid #e20026;
 }
 
 .table > tbody > tr > td {
     border-style: none;
 }
 
 .sitename .first-word,
 .sitename .second-word {
     color: rgba(0, 0, 0, 0.75);
     font-weight: normal;
     font-size: 1.2rem;
 }
 
 .sitename .first-word {
     font-family: 'Alegreya Sans', sans-serif;
 }
 
 .sitename .second-word {
     font-family: 'Alegreya', serif;
 }
 
 .swh-counter {
     font-size: 150%;
 }
 
 .swh-http-error {
     margin: 0 auto;
     text-align: center;
 }
 
 .swh-http-error-head {
     color: #2d353c;
     font-size: 30px;
 }
 
 .swh-http-error-code {
     bottom: 60%;
     color: #2d353c;
     font-size: 96px;
     line-height: 80px;
     margin-bottom: 10px !important;
 }
 
 .swh-http-error-desc {
     font-size: 12px;
     color: #647788;
     text-align: center;
 }
 
 .swh-http-error-desc pre {
     display: inline-block;
     text-align: left;
     max-width: 800px;
     white-space: pre-wrap;
 }
 
 .popover {
     max-width: 97%;
     z-index: 40000;
 }
 
 .modal {
     text-align: center;
     padding: 0 !important;
 }
 
 .modal::before {
     content: '';
     display: inline-block;
     height: 100%;
     vertical-align: middle;
     margin-right: -4px;
 }
 
 .modal-dialog {
     display: inline-block;
     text-align: left;
     vertical-align: middle;
 }
 
 .dropdown-submenu {
     position: relative;
 }
 
 .dropdown-submenu .dropdown-menu {
     top: 0;
     left: -100%;
     margin-top: -5px;
     margin-left: -2px;
 }
 
 .dropdown-item:hover,
 .dropdown-item:focus {
     background-color: rgba(0, 0, 0, 0.1);
 }
 
 a.dropdown-left::before {
     content: "\f0d9";
     font-family: 'FontAwesome';
     display: block;
     width: 20px;
     height: 20px;
     float: left;
     margin-left: 0;
 }
 
 #swh-navbar {
     border-top-style: none;
     border-left-style: none;
     border-right-style: none;
     border-bottom-style: solid;
     border-bottom-width: 5px;
     border-image: linear-gradient(to right, rgb(226, 0, 38) 0%, rgb(254, 205, 27) 100%) 1 1 1 1;
     width: 100%;
     padding: 5px;
     margin-bottom: 10px;
     margin-top: 30px;
     justify-content: normal;
     flex-wrap: nowrap;
     height: 72px;
     overflow: hidden;
 }
 
 #back-to-top {
     display: none;
     position: fixed;
     bottom: 30px;
     right: 30px;
     z-index: 10;
 }
 
 #back-to-top a img {
     display: block;
     width: 32px;
     height: 32px;
     background-size: 32px 32px;
     text-indent: -999px;
     overflow: hidden;
 }
 
 .swh-top-bar {
     direction: ltr;
     height: 30px;
     position: fixed;
     top: 0;
     left: 0;
     width: 100%;
     z-index: 99999;
     background-color: #262626;
     color: #fff;
     text-align: center;
     font-size: 14px;
 }
 
 .swh-top-bar ul {
     margin-top: 4px;
     padding-left: 0;
     white-space: nowrap;
 }
 
 .swh-top-bar li {
     display: inline-block;
     margin-left: 10px;
     margin-right: 10px;
 }
 
 .swh-top-bar a,
 .swh-top-bar a:visited {
     color: white;
 }
 
 .swh-top-bar a.swh-current-site,
 .swh-top-bar a.swh-current-site:visited {
     color: #fecd1b;
 }
 
 .swh-position-right {
     position: absolute;
     right: 0;
 }
 
 .swh-donate-link {
     border: 1px solid #fecd1b;
     background-color: #e20026;
     color: white !important;
     padding: 3px;
     border-radius: 3px;
 }
 
 .swh-navbar-content h4 {
     padding-top: 7px;
 }
 
 .swh-navbar-content .bread-crumbs {
     display: block;
     margin-left: -40px;
 }
 
 .swh-navbar-content .bread-crumbs li.bc-no-root {
     padding-top: 7px;
 }
 
 .main-sidebar {
     margin-top: 30px;
 }
 
 .content-wrapper {
     background: none;
 }
 
 .brand-image {
     max-height: 40px;
 }
 
 .brand-link {
     padding-top: 18.5px;
     padding-bottom: 18px;
     padding-left: 4px;
     border-bottom: 5px solid #e20026 !important;
 }
 
 .navbar-header a,
 ul.dropdown-menu a,
 ul.navbar-nav a,
 ul.nav-sidebar a {
     border-bottom-style: none;
     color: #323232;
 }
 
 .swh-sidebar .nav-link.active {
     color: #323232 !important;
     background-color: #e7e7e7 !important;
 }
 
 .swh-image-error {
     width: 80px;
     height: auto;
 }
 
 @media (max-width: 600px) {
     .swh-image-error {
         width: 40px;
         height: auto;
     }
 
     .swh-navbar-content h4 {
         font-size: 1rem;
     }
 
     .swh-donate-link {
         display: none;
     }
 }
 
 .form-check-label {
     padding-top: 4px;
 }
 
 .swh-id-option {
     display: inline-block;
     margin-right: 5px;
     line-height: 1rem;
 }
 
 .nav-pills .nav-link:not(.active):hover {
     color: rgba(0, 0, 0, 0.55);
 }
 
 .swh-heading-color {
     color: #e20026 !important;
 }
 
 .sidebar-mini.sidebar-collapse .main-sidebar:hover {
     width: 4.6rem;
 }
 
 .sidebar-mini.sidebar-collapse .main-sidebar:hover .user-panel > .info,
 .sidebar-mini.sidebar-collapse .main-sidebar:hover .nav-sidebar .nav-link p,
 .sidebar-mini.sidebar-collapse .main-sidebar:hover .brand-text {
     visibility: hidden !important;
 }
 
 .sidebar .nav-link p,
 .main-sidebar .brand-text,
 .sidebar .user-panel .info {
     transition: none;
 }
 
 .sidebar-mini.sidebar-mini.sidebar-collapse .sidebar {
     padding-right: 0;
 }
 
 .swh-words-logo {
     position: absolute;
     top: 0;
     left: 0;
     width: 73px;
     height: 73px;
     text-align: center;
     font-size: 10pt;
     color: rgba(0, 0, 0, 0.75);
 }
 
 .swh-words-logo:hover {
     text-decoration: none;
 }
 
 .swh-words-logo-swh {
     line-height: 1;
     padding-top: 13px;
     visibility: hidden;
 }
 
 hr.swh-faded-line {
     border: 0;
     height: 1px;
     background-image: linear-gradient(to left, #f0f0f0, #8c8b8b, #f0f0f0);
 }
 
 .swh-readme-txt pre {
     background: none;
     border: none;
 }
 
 .swh-coverage-col {
     padding-left: 10px;
     padding-right: 10px;
 }
 
 .swh-coverage {
     height: calc(65px + 1em);
     padding-top: 0.3rem;
     border: none;
 }
 
 .swh-coverage a {
     text-decoration: none;
 }
 
 .swh-coverage-logo {
     display: block;
     width: 100%;
     height: 50px;
     margin-left: auto;
     margin-right: auto;
     object-fit: contain;
 
     /* polyfill for old browsers, see https://github.com/bfred-it/object-fit-images */
     font-family: 'object-fit: contain;';
 }
 
 .swh-coverage-list {
     width: 100%;
     height: 320px;
     border: none;
 }
 
 tr.swh-tr-hover-highlight:hover td {
     background: #ededed;
 }
 
 tr.swh-api-doc-route a {
     text-decoration: none;
 }
 
 .swh-apidoc .col {
     margin: 10px;
 }
 
 a.toggle-col {
     text-decoration: none;
 }
 
 a.toggle-col.col-hidden {
     text-decoration: line-through;
 }
+
+.admonition.warning {
+    background: #fcf8e3;
+    border: 1px solid #faebcc;
+    padding: 15px;
+    border-radius: 4px;
+}
+
+.admonition.warning p {
+    margin-bottom: 0;
+}
+
+.admonition.warning .first {
+    font-size: 1.5rem;
+}