1. Benoit Boissinot
  2. ckan - i18n - fr

Commits

johnbywater  committed 0525981

Added support for using package ID as a package reference in all operations of the API.

  • Participants
  • Parent commits 06c9492
  • Branches default

Comments (0)

Files changed (3)

File ckan/controllers/rest.py

View file
             return self._finish_ok(results)
         elif register == u'package' and subregister == 'relationships':
             #TODO authz stuff for this and related packages
-            pkg = model.Package.by_name(id)
+            pkg = self._get_pkg(id)
             if not pkg:
                 response.status_int = 404
                 return 'First package named in request was not found.'
             response.status_int = 400
             return ''
 
+    def _get_pkg(self, id):
+        pkg = model.Session.query(model.Package).get(id)
+        if pkg == None:
+            pkg = model.Package.by_name(id)
+            # Todo: Make sure package names can't be changed to look like package IDs?
+        return pkg
+
     def show(self, register, id, subregister=None, id2=None):
         if register == u'revision':
             # Todo: Implement access control for revisions.
             _dict = changeset.as_dict()
             return self._finish_ok(_dict)
         elif register == u'package' and not subregister:
-            pkg = model.Session.query(model.Package).get(id)
-            if pkg == None:
-                pkg = model.Package.by_name(id)
-                # Todo: Make sure package names can't be changed to look like package IDs?
+            pkg = self._get_pkg(id)
             if pkg == None:
                 response.status_int = 404
                 return ''
                 _dict['license'] = _dict.get('license_id', '')
             return self._finish_ok(_dict)
         elif register == u'package' and (subregister == 'relationships' or subregister in model.PackageRelationship.get_all_types()):
-            pkg1 = model.Package.by_name(id)
-            pkg2 = model.Package.by_name(id2)
+            pkg1 = self._get_pkg(id)
+            pkg2 = self._get_pkg(id2)
             if not pkg1:
                 response.status_int = 404
                 return 'First package named in address was not found.'
                 request_fa_dict = ckan.forms.edit_package_dict(ckan.forms.get_package_dict(fs=fs), request_data)
                 fs = fs.bind(model.Package, data=request_fa_dict, session=model.Session)
             elif register == 'package' and subregister in model.PackageRelationship.get_all_types():
-                pkg1 = model.Package.by_name(id)
-                pkg2 = model.Package.by_name(id2)
+                pkg1 = self._get_pkg(id)
+                pkg2 = self._get_pkg(id2)
                 if not pkg1:
                     response.status_int = 404
                     return 'First package named in address was not found.'
             model.Session.rollback()
             raise
         obj = fs.model
+        location = "%s/%s" % (request.path, obj.id)
+        response.headers['Location'] = location
         return self._finish_ok(obj.as_dict())
             
     def update(self, register, id, subregister=None, id2=None):
         if register == 'package' and not subregister:
-            entity = model.Package.by_name(id)
+            entity = self._get_pkg(id)
+            if entity == None:
+                response.status_int = 404
+                return 'Package was not found.'
         elif register == 'package' and subregister in model.PackageRelationship.get_all_types():
-            pkg1 = model.Package.by_name(id)
-            pkg2 = model.Package.by_name(id2)
+            pkg1 = self._get_pkg(id)
+            pkg2 = self._get_pkg(id2)
             if not pkg1:
                 response.status_int = 404
                 return 'First package named in address was not found.'
 
     def delete(self, register, id, subregister=None, id2=None):
         if register == 'package' and not subregister:
-            entity = model.Package.by_name(id)
+            entity = self._get_pkg(id)
             if not entity:
                 response.status_int = 404
                 return 'Package was not found.'
             revisioned_details = 'Package: %s' % entity.name
         elif register == 'package' and subregister in model.PackageRelationship.get_all_types():
-            pkg1 = model.Package.by_name(id)
-            pkg2 = model.Package.by_name(id2)
+            pkg1 = self._get_pkg(id)
+            pkg2 = self._get_pkg(id2)
             if not pkg1:
                 response.status_int = 404
                 return 'First package named in address was not found.'
                               'rating':5}
         """
         # check options
-        package_name = params.get('package')
+        package_ref = params.get('package')
         rating = params.get('rating')
         user = self.rest_api_user
         opts_err = None
-        if not package_name:
-            opts_err = gettext('You must supply a package name (parameter "package").')
+        if not package_ref:
+            opts_err = gettext('You must supply a package id or name (parameter "package").')
         elif not rating:
             opts_err = gettext('You must supply a rating (parameter "rating").')
         else:
             except ValueError:
                 opts_err = gettext('Rating must be an integer value.')
             else:
-                package = model.Package.by_name(package_name)
+                package = self._get_pkg(package_ref)
                 if rating < ckan.rating.MIN_RATING or rating > ckan.rating.MAX_RATING:
                     opts_err = gettext('Rating must be between %i and %i.') % (ckan.rating.MIN_RATING, ckan.rating.MAX_RATING)
                 elif not package:
-                    opts_err = gettext('Package with name %r does not exist.') % package_name
+                    opts_err = gettext('Package with name %r does not exist.') % package_ref
         if opts_err:
             self.log.debug(opts_err)
             response.status_int = 400
         ckan.rating.set_rating(user, package, rating_int)
 
         response.headers['Content-Type'] = 'application/json'
-        package = model.Package.by_name(package_name)
+        package = self._get_pkg(package_ref)
         ret_dict = {'rating average':package.get_average_rating(),
                     'rating count': len(package.ratings)}
         return self._finish_ok(ret_dict)

File ckan/tests/functional/test_rest.py

View file
 
 class TestRest(TestController):
 
-    @classmethod
-    def setup_class(self):
+#    @classmethod
+#    def setup_class(self):
+    def setup(self):
         try:
             CreateTestData.delete()
         except:
         model.Session.remove()
         self.extra_environ={'Authorization' : str(self.user.apikey)}
 
-    @classmethod
-    def teardown_class(self):
+#    @classmethod
+#    def teardown_class(self):
+    def tearDown(self):
         model.Session.remove()
         model.repo.rebuild_db()
         model.Session.remove()
 
+    def teardown_method(self, name):
+        pkg = model.Package.by_name(u'testpkg')
+        if pkg:
+            pkg.purge()
+
     def test_01_register_post_noauth(self):
         # Test Packages Register Post 401.
         offset = '/api/rest/package'
         assert '"Index of the novel"' in res, res
         assert '"id": "%s"' % anna.id in res, res
 
-
-
     def _test_04_ckan_url(self):
         # NB This only works if run on its own
         config['ckan_host'] = 'test.ckan.net'
         postparams = '%s=1' % json.dumps(self.testpackagevalues)
         res = self.app.post(offset, params=postparams, status=[200],
                 extra_environ=self.extra_environ)
+        # Check the value of the Location header.
+        location = res.header('Location')
+        assert offset in location
+        res = self.app.get(location, status=[200])
+        # Check the database record.
         model.Session.remove()
         pkg = model.Package.by_name(self.testpackagevalues['name'])
         assert pkg
         #        extra_environ=self.extra_environ)
         model.Session.remove()
 
-    def test_10_edit_pkg_values(self):
+    def test_10_edit_pkg_values_by_id(self):
+        # Test Packages Entity Put 200.
+
+        # create a package with testpackagevalues
+        tag_names = [u'tag1', u'tag2', u'tag3']
+        pkg = model.Package.by_name(self.testpackagevalues['name'])
+        if not pkg:
+            pkg = model.Package()
+            model.Session.add(pkg)
+        rev = model.repo.new_revision()
+        pkg.name = self.testpackagevalues['name']
+        pkg.url = self.testpackagevalues['url']
+        tags = [model.Tag(name=tag_name) for tag_name in tag_names]
+        for tag in tags:
+            model.Session.add(tag)
+        pkg.tags = tags
+        pkg.extras = {u'key1':u'val1', u'key2':u'val2'}
+        model.Session.commit()
+
+        pkg = model.Package.by_name(self.testpackagevalues['name'])
+        model.setup_default_user_roles(pkg, [self.user])
+        rev = model.repo.new_revision()
+        model.repo.commit_and_remove()
+
+        # edit it
+        pkg_vals = {
+            'name':u'somethingnew',
+            'title':u'newtesttitle',
+            'resources': [
+                {
+                    u'url':u'http://blah.com/file2.xml',
+                    u'format':u'xml',
+                    u'description':u'Appendix 1',
+                    u'hash':u'def123',
+                },
+                {
+                    u'url':u'http://blah.com/file3.xml',
+                    u'format':u'xml',
+                    u'description':u'Appenddic 2',
+                    u'hash':u'ghi123',
+                },
+            ],
+            'extras':{u'key3':u'val3', u'key2':None},
+            'tags':[u'tag1', u'tag2', u'tag4', u'tag5'],
+        }
+        offset = '/api/rest/package/%s' % pkg.id
+        postparams = '%s=1' % json.dumps(pkg_vals)
+        res = self.app.post(offset, params=postparams, status=[200],
+                            extra_environ=self.extra_environ)
+
+        # Check submitted field have changed.
+        model.Session.remove()
+        pkg = model.Session.query(model.Package).filter_by(name=pkg_vals['name']).one()
+        # - title
+        assert pkg.title == pkg_vals['title']
+        # - tags
+        pkg_tagnames = [tag.name for tag in pkg.tags]
+        for tagname in pkg_vals['tags']:
+            assert tagname in pkg_tagnames, 'tag %r not in %r' % (tagname, pkg_tagnames)
+        # - resources
+        assert len(pkg.resources), "Package has no resources: %s" % pkg
+        assert len(pkg.resources) == 2, len(pkg.resources)
+        resource = pkg.resources[0]
+        assert resource.url == u'http://blah.com/file2.xml', resource.url
+        assert resource.format == u'xml', resource.format
+        assert resource.description == u'Appendix 1', resource.description
+        assert resource.hash == u'def123', resource.hash
+        resource = pkg.resources[1]
+        assert resource.url == 'http://blah.com/file3.xml', resource.url
+        assert resource.format == u'xml', resource.format
+        assert resource.description == u'Appenddic 2', resource.description
+        assert resource.hash == u'ghi123', resource.hash
+
+        # Check unsubmitted fields have not changed.
+        # - url
+        assert pkg.url == self.testpackagevalues['url'], pkg.url
+        # - extras
+        assert len(pkg.extras) == 2, pkg.extras
+        for key, value in {u'key1':u'val1', u'key3':u'val3'}.items():
+            assert pkg.extras[key] == value, pkg.extras
+
+    def test_10_edit_pkg_values_by_name(self):
         # Test Packages Entity Put 200.
 
         # create a package with testpackagevalues

File doc/api.rst

View file
-=========================
-CKAN API (including REST)
-=========================
-
-
-Introduction
+============
+The CKAN API
 ============
 
-A CKAN server's data catalog is not only available in a web browser, but also via its 
-Application Programming Interface (API). The API can be used to view and change
-the CKAN data.
 
-The API has two sections:
+A CKAN server's data catalog is not only available in a web browser, but also
+via its Application Programming Interface (API). The API can be used to view
+and change the catalog.
 
-* a RESTful (Representational State Transfer) style interface for accessing 
-  CKAN database objects
+This document describes the CKAN API, so that anyone can create software
+applications that use CKAN API services.
 
-* a Search API
 
-This document specifies the API so that anyone can create software applications
-to use the CKAN service. The specification describes the RESTful API in terms
-of which resources are available, what their locations are, what methods each
-resource supports, and what the responses might be. It also species the usage
-and responses to the Search API.
-
-Code modules
-============
+Code Modules for Client Applications
+====================================
 
 There are also some code modules (Python, PHP, Drupal, Perl etc.) that provide 
 convenient wrappers around much of the CKAN API. For full details of these, 
 please consult: http://wiki.okfn.org/ckan/related
 
 
-Example
-=======
+Example of Usage
+================
 
 You're using ckan.net and want a list of all the packages. If you GET
 ``http://ckan.net/api/rest/package`` then it will return the list of the package
 
 "tags": ["navigation", "openstreetmap", "map", "geo", "geodata", "xml", "publicdomain", "osm", "my-new-tag"]
 
-So that the system knows who is making this change, you need to send your API key in the headers.
+So that the system knows who is making this change, you need to send your API key in the headers (see below).
 
 
-API Locations
-=============
+Overview
+========
 
-A REST interface presents resources at published locations. Here are the named
-locations of the CKAN REST API resources:
+The CKAN API is separated into two parts:
+
+* the Model API; and
+* the Search API.
+
+The CKAN API follows the RESTful (Representational State Transfer) style.
+Published resources are separated both from the methods supported by the
+resources, and from the data formats and status codes used by the methods.
+
+At the same time, clients can proceed by following related resources
+identified in server responses. For example, after successfully POSTing data
+to a model register, the location of the newly created entity is indicated in
+the method response's 'Location' header.
+
+
+CKAN Model API
+==============
+
+Model resources are available at published locations. They are represented with
+a variety of data formats. Each resource location supports a number of methods.
+
+The data formats of the requests and the responses are defined below.
+
+
+Model API Resources
+-------------------
+
+Here are the resources of the Model API.
 
 +--------------------------------+-------------------------------------------------------------------+
-| Resource Name                  | Location                                                          |
+| Resource                       | Location                                                          |
 +================================+===================================================================+
 | Package Register               | ``/api/rest/package``                                             |
 +--------------------------------+-------------------------------------------------------------------+
-| Package Entity                 | ``/api/rest/package/PACKAGE-NAME``                                |
+| Package Entity                 | ``/api/rest/package/PACKAGE-REF``                                 |
 +--------------------------------+-------------------------------------------------------------------+
 | Group Register                 | ``/api/rest/group``                                               |
 +--------------------------------+-------------------------------------------------------------------+
 +--------------------------------+-------------------------------------------------------------------+
 | Rating Register                | ``/api/rest/rating``                                              |
 +--------------------------------+-------------------------------------------------------------------+
-| Rating Entity                  | ``/api/rest/rating/PACKAGE-NAME``                                 |
+| Rating Entity                  | ``/api/rest/rating/PACKAGE-REF``                                  |
 +--------------------------------+-------------------------------------------------------------------+
-| Package Relationships Register | ``/api/rest/package/PACKAGE-NAME/relationships``                  |
+| Package Relationships Register | ``/api/rest/package/PACKAGE-REF/relationships``                   |
 +--------------------------------+-------------------------------------------------------------------+
-| Package Relationships Register | ``/api/rest/package/PACKAGE-NAME/relationships/PACKAGE-NAME``     |
+| Package Relationships Register | ``/api/rest/package/PACKAGE-REF/relationships/PACKAGE-REF``       |
 +--------------------------------+-------------------------------------------------------------------+
-| Package Relationship Entity    | ``/api/rest/package/PACKAGE-NAME/RELATIONSHIP-TYPE/PACKAGE-NAME`` |
+| Package Relationship Entity    | ``/api/rest/package/PACKAGE-REF/RELATIONSHIP-TYPE/PACKAGE-REF``   |
 +--------------------------------+-------------------------------------------------------------------+
 | Revision Register              | ``/api/rest/revision``                                            |
 +--------------------------------+-------------------------------------------------------------------+
 | License List                   | ``/api/rest/licenses``                                            |
 +--------------------------------+-------------------------------------------------------------------+
 
-Possible values for RELATIONSHIP-TYPE are given below for Data Formats - Relationship-Type.
+Possible values for PACKAGE-REF are the package id, or the current package name.
 
-Here are the non-REST API locations:
+Possible values for RELATIONSHIP-TYPE are described in the Relationship-Type data format.
 
-+-------------------+--------------------------+
-| API functions     | Location                 |
-+===================+==========================+
-| Package Search    | ``/api/search/package``  |
-+-------------------+--------------------------+
-| Tag Counts        | ``/api/tag_counts``      |
-+-------------------+--------------------------+
-| Revision Search   | ``/api/search/revision`` |
-+-------------------+--------------------------+
 
-See below for more information about package and revision search parameters.
+Model API Methods
+-----------------
 
-
-Methods and data formats
-========================
-
-Each resource location supports a number of methods, which may send or receive
-a piece of data. Standard http status codes are used to signal the outcome of
-the operation.
+Here are the methods of the Model API.
 
 +-------------------------------+--------+------------------+-------------------+
 | Resource                      | Method | Request          | Response          |
 +-------------------------------+--------+------------------+-------------------+
 | Tag Register                  | GET    |                  | Tag-List          |  
 +-------------------------------+--------+------------------+-------------------+
-| Tag Entity                    | GET    | Tag              | Package-List      | 
+| Tag Entity                    | GET    |                  | Package-List      | 
 +-------------------------------+--------+------------------+-------------------+
 | Rating Register               | POST   | Rating           |                   | 
 +-------------------------------+--------+------------------+-------------------+
 +-------------------------------+--------+------------------+-------------------+
 | Package Relationship Entity   | GET    |                  | Pkg-Relationship  |
 +-------------------------------+--------+------------------+-------------------+
-| Package Relationship Entity   | POST   | Pkg-Relationship |                   | 
-+-------------------------------+--------+------------------+-------------------+
 | Package Relationship Entity   | PUT    | Pkg-Relationship |                   | 
 +-------------------------------+--------+------------------+-------------------+
-| Search                        | GET    |                  | Search-Response   | 
-+-------------------------------+--------+------------------+-------------------+
-| Search                        | POST   | Query-String     | Search-Response   | 
-+-------------------------------+--------+------------------+-------------------+
-| Tag Counts                    | GET    |                  | Tag-Count-List    | 
-+-------------------------------+--------+------------------+-------------------+
 | Revision Entity               | GET    |                  | Revision          | 
 +-------------------------------+--------+------------------+-------------------+
 | License List                  | GET    |                  | License-List      | 
 +-------------------------------+--------+------------------+-------------------+
 
-Notes:
+* The location of new entity resources will be indicated in the 'Location' header containing the resource location of the new entity.
 
-* 'PUT' operations may instead use the HTTP POST method.
+* PUT operations may instead use the HTTP POST method with the same.
 
-* To search, there are two ways to provide parameters - you can use either or
-  both ways in each search request. The first method is to provide them as
-  parameters in the URL, (e.g. /api/rest/search?q=geodata&amp;allfields=1 ). The
-  second way is to encode the parameters as a JSON dictionary and supply them
-  in the POST request.
+* POSTing data to a register resource will create a new entity, whilst PUT/POSTing data to an entity resource will update an existing entity.
 
 
-Data Formats
-============
+
+Model API Data Formats
+----------------------
+
+Here are the data formats for the Model API.
 
 +-----------------+------------------------------------------------------------+
 | Name            | Format                                                     |
 |                 | 'derives_from', 'has_derivation',                          |
 |                 | 'child_of', 'parent_of'.                                   |
 +-----------------+------------------------------------------------------------+
-| Search-Response | { count: Count-int, results: [Package, Package, ...] }     |
-+-----------------+------------------------------------------------------------+
-| Query-String    | [ q: String ]                                              |
-+-----------------+------------------------------------------------------------+
-| Tag-Count-List  | [ [Name-String, Integer], [Name-String, Integer], ... ]    |
-+-----------------+------------------------------------------------------------+
 | Revision        | { id: Uuid, message: String, author: String,               |
 |                 | timestamp: Date-Time, packages: Package-List }             |
 +-----------------+------------------------------------------------------------+
 
  * To delete an 'extra' key-value pair, supply the key with a None value.
 
- * When you read a package then some additional information is supplied that cannot be edited in the REST style. This includes info on Package Relationship ('relationships'), Group membership ('groups'), ratings ('ratings_average' and 'ratings_count') and Package ID ('id'). This is purely a convenience for clients, and only forms part of the Package on GET.
+ * When you read a package then some additional information is supplied that cannot current be adjusted throught the CKAN API. This includes info on Package Relationship ('relationships'), Group membership ('groups'), ratings ('ratings_average' and 'ratings_count') and Package ID ('id'). This is purely a convenience for clients, and only forms part of the Package on GET.
 
-API Keys
-========
 
-You will need to supply an API Key for certain requests to the REST API:
+CKAN Search API
+===============
 
-* For any action which makes a change to a resource (i.e. all non-GET methods)
+Search resources are available at published locations. They are represented with
+a variety of data formats. Each resource location supports a number of methods.
 
-* If the particular resource's authorization set-up is not open to 
-  visitors for the action.
+The data formats of the requests and the responses are defined below.
 
-To obtain your API key:
 
-1. Log-in to the particular CKAN website: /user/login
+Search API Resources
+--------------------
 
-2. The user page has a link to the API Key: /user/apikey
+Here are the published resources of the CKAN Search API.
 
-The key should be passed in the API request header:
++-------------------+--------------------------+
+| Resource          | Location                 |
++===================+==========================+
+| Package Search    | ``/api/search/package``  |
++-------------------+--------------------------+
+| Revision Search   | ``/api/search/revision`` |
++-------------------+--------------------------+
+| Tag Counts        | ``/api/tag_counts``      |
++-------------------+--------------------------+
 
-====================== =====
-Header                 Example value
-====================== =====
-HTTP_AUTHORIZATION     fde34a3c-b716-4c39-8dc4-881ba115c6d4
-====================== =====
+See below for more information about package and revision search parameters.
 
-If requests that are required to be authorized are not sent with a currently 
-valid Authorization header, or the user associated with the key is not 
-authorized for the operation, then the requested operation will not be carried
-out and the CKAN REST API will respond with status code 403.
+
+Search API Methods
+------------------
+
+Here are the methods of the CKAN Search API.
+
++-------------------------------+--------+------------------+-------------------+
+| Resource                      | Method | Request          | Response          |
++===============================+========+==================+===================+ 
+| Package Search                | POST   | Query-String     | Search-Response   | 
++-------------------------------+--------+------------------+-------------------+
+| Revision Search               | POST   | Query-String     | Search-Response   | 
++-------------------------------+--------+------------------+-------------------+
+| Tag Counts                    | GET    |                  | Tag-Count-List    | 
++-------------------------------+--------+------------------+-------------------+
+
+It is also possible to supply the search parameters in the URL of a GET request, 
+for example ``/api/rest/search?q=geodata&amp;allfields=1``.
+
+
+Search API Data Formats
+-----------------------
+
+Here are the data formats for the Search API.
+
++-----------------+------------------------------------------------------------+
+| Name            | Format                                                     |
++=================+============================================================+
+| Query-String    | { Query-Key: Query-Value, Query-Key: Query-Value, ... }    |
++-----------------+------------------------------------------------------------+
+| Search-Response | { count: Count-int, results: [Package, Package, ... ] }    |
++-----------------+------------------------------------------------------------+
+| Tag-Count-List  | [ [Name-String, Integer], [Name-String, Integer], ... ]    |
++-----------------+------------------------------------------------------------+
+
+The ``Package`` data format is defined in the CKAN Model API.
 
 
 Package Search Parameters
-=========================
+~~~~~~~~~~~~~~~~~~~~~~~~~
 
 +-----------------------+---------------+----------------------------------+----------------------------------+
-| Key                   |    Value      | Example                          |  Notes                           |
+| Query-Key             | Query-Value   | Example                          |  Notes                           |
 +=======================+===============+==================================+==================================+
 | q                     | Search-String || q=geodata                       | Criteria to search the package   |
 |                       |               || q=government+sweden             | fields for. URL-encoded search   |
 +-----------------------+---------------+----------------------------------+----------------------------------+
 | qjson                 | JSON encoded  | ['q':'geodata']                  | All search parameters can be     |
 |                       | options       |                                  | json-encoded and supplied to this|
-|                       |               |                                  | URL parameter as a more flexible |
-|                       |               |                                  | alternative.                     |
+|                       |               |                                  | parameter as a more flexible     |
+|                       |               |                                  | alternative in GET requests.     |
 +-----------------------+---------------+----------------------------------+----------------------------------+
 |title,                 | Search-String | title=uk&amp;tags=health+census  | Search a particular a field. Note|
 |tags, notes, groups,   |               |                                  | that the latter fields mentioned |
 
 
 Revision Search Parameters
-==========================
+~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 +-----------------------+---------------+-----------------------------------------------------+----------------------------------+
 | Key                   |    Value      | Example                                             |  Notes                           |
 +-----------------------+---------------+-----------------------------------------------------+----------------------------------+
 
 
-Status Codes
-============
+CKAN API Status Codes
+=====================
+
+Standard HTTP status codes are used to signal method outcomes.
 
 ===== =====
 Code  Name
 409   Conflict (e.g. name already exists)
 500   Service Error           
 ===== =====
+
+
+CKAN API Keys
+=============
+
+You will need to supply an API Key for certain requests to the CKAN API:
+
+* For any action which makes a change to a resource (i.e. all POST methods on register resources, and PUT/POST methods on entity resources).
+
+* If the particular resource's authorization set-up is not open to 
+  visitors for the action.
+
+To obtain your API key:
+
+1. Log-in to the particular CKAN website: /user/login
+
+2. The user page has a link to the API Key: /user/apikey
+
+The key should be passed in the API request header:
+
+====================== =====
+Header                 Example value
+====================== =====
+HTTP_AUTHORIZATION     fde34a3c-b716-4c39-8dc4-881ba115c6d4
+====================== =====
+
+If requests that are required to be authorized are not sent with a currently 
+valid Authorization header, or the user associated with the key is not 
+authorized for the operation, then the requested operation will not be carried
+out and the CKAN API will respond with status code 403.
+
+