# Tutorial: creating an API endpoint

This section of the document details how to create an endpoint for CloudKitty’s v2 API. The v1 API is frozen, no endpoint should be added.

## Setting up the layout for a new resource

In this section, we will create an `example` endpoint. Create the following files and subdirectories in `cloudkitty/api/v2/`:

    cloudkitty/api/v2/
    └── example
        ├── example.py
        └── __init__.py

## Creating a custom resource

Each v2 API endpoint is based on a Flask Blueprint and one Flask-RESTful resource per sub-endpoint. This allows to have a logical grouping of the resources. Let’s take the `/rating/hashmap` route as an example. Each of the hashmap module’s resources should be a Flask-RESTful resource (eg. `/rating/hashmap/service`, `/rating/hashmap/field` etc…).

Note

There should be a distinction between endpoints refering to a single resource and to several ones. For example, if you want an endpoint allowing to list resources of some kind, you should implement the following:

- A `MyResource` resource with support for `GET`, `POST` and `PUT` HTTP methods on the `/myresource/<uuid:>` route.

- A `MyResourceList` resource with support for the `GET` HTTP method on the `/myresource` route.

- A blueprint containing these resources.

### Basic resource

We’ll create an `/example/` endpoint, used to manipulate fruits. We’ll create an `Example` resource, supporting `GET` and `POST` HTTP methods. First of all, we’ll create a class with `get` and `post` methods in `cloudkitty/api/v2/example/example.py`:

    from cloudkitty.api.v2 import base

    class Example(base.BaseResource):

        def get(self):
            pass

        def post(self):
            pass

### Validating a method’s parameters and output

A `GET` request on our resource will simply return {“message”: “This is an example endpoint”}. The `add_output_schema` decorator adds voluptuous validation to a method’s output. This allows to set defaults.

cloudkitty.api.v2.utils.add_output_schema(schema)

  Add a voluptuous schema validation on a method’s output

  Example usage:

      class Example(base.BaseResource):

          @api_utils.add_output_schema({
              voluptuous.Required(
                  'message',
                  default='This is an example endpoint',
              ): str,
          })
          def get(self):
              return {}

  Parameters:

    schema (dict) – Schema to apply to the method’s output

Let’s update our `get` method in order to use this decorator:

    import voluptuous

    from cloudkitty.api.v2 import base
    from cloudkitty import validation_utils

    class Example(base.BaseResource):

        @api_utils.add_output_schema({
            voluptuous.Required(
                'message',
                default='This is an example endpoint',
            ): str,
        })
        def get(self):
            return {}

    $ curl 'http://cloudkitty-api:8889/v2/example'
    {"message": "This is an example endpoint"}

It is now time to implement the `post` method. This function will take a parameter. In order to validate it, we’ll use the `add_input_schema` decorator:

cloudkitty.api.v2.utils.add_input_schema(location, schema)

  Add a voluptuous schema validation on a method’s input

  Takes a dict which can be converted to a voluptuous schema as parameter, and validates the parameters with this schema. The “location” parameter is used to specify the parameters’ location. Note that for query parameters, a `MultiDict` is returned by Flask. Thus, each dict key will contain a list. In order to ease interaction with unique query parameters, the `SingleQueryParam` voluptuous validator can be used:

      from cloudkitty.api.v2 import utils as api_utils
      @api_utils.add_input_schema('query', {
          voluptuous.Required('fruit'): api_utils.SingleQueryParam(str),
      })
      def put(self, fruit=None):
          return fruit

  To accept a list of query parameters, a `MultiQueryParam` can be used:

      from cloudkitty.api.v2 import utils as api_utils
      @api_utils.add_input_schema('query', {
          voluptuous.Required('fruit'): api_utils.MultiQueryParam(str),
      })
      def put(self, fruit=[]):
          for f in fruit:
              # Do something with the fruit

  Parameters:

    - location (str) – Location of the args. Must be one of [‘body’, ‘query’]

    - schema (dict) – Schema to apply to the method’s kwargs

Arguments validated by the input schema are passed as named arguments to the decorated function. Let’s implement the post method. We’ll use Werkzeug exceptions for HTTP return codes.

    @api_utils.add_input_schema('body', {
        voluptuous.Required('fruit'): str,
    })
    def post(self, fruit=None):
        policy.authorize(flask.request.context, 'example:submit_fruit', {})
        if not fruit:
            raise http_exceptions.BadRequest(
                'You must submit a fruit',
            )
        if fruit not in ['banana', 'strawberry']:
            raise http_exceptions.Forbidden(
                'You submitted a forbidden fruit',
            )
        return {
            'message': 'Your fruit is a ' + fruit,
        }

Here, `fruit` is expected to be found in the request body:

    $ curl -X POST -H 'Content-Type: application/json' 'http://cloudkitty-api:8889/v2/example' -d '{"fruit": "banana"}'
    {"message": "Your fruit is a banana"}

In order to retrieve `fruit` from the query, the function should have been decorated like this:

    @api_utils.add_input_schema('query', {
        voluptuous.Required('fruit'): api_utils.SingleQueryParam(str),
    })
    def post(self, fruit=None):

Note that a `SingleQueryParam` is used here: given that query parameters can be specified several times (eg `xxx?groupby=a&groupby=b`), Flask provides query parameters as lists. The `SingleQueryParam` helper checks that a parameter is provided only once, and returns it.

class cloudkitty.api.v2.utils.SingleQueryParam(param_type)

  Voluptuous validator allowing to validate unique query parameters.

  This validator checks that a URL query parameter is provided only once, verifies its type and returns it directly, instead of returning a list containing a single element.

  Parameters:

    param_type – Type of the query parameter

### Authorising methods

The `Example` resource is still missing some authorisations. We’ll create a policy per method, configurable via the `policy.yaml` file. Create a `cloudkitty/common/policies/v2/example.py` file with the following content:

    from oslo_policy import policy

    from cloudkitty.common.policies import base

    example_policies = [
        policy.DocumentedRuleDefault(
            name='example:get_example',
            check_str=base.UNPROTECTED,
            description='Get an example message',
            operations=[{'path': '/v2/example',
                         'method': 'GET'}]),
        policy.DocumentedRuleDefault(
            name='example:submit_fruit',
            check_str=base.UNPROTECTED,
            description='Submit a fruit',
            operations=[{'path': '/v2/example',
                         'method': 'POST'}]),
    ]

    def list_rules():
        return example_policies

Add the following lines to `cloudkitty/common/policies/__init__.py`:

    # [...]
    from cloudkitty.common.policies.v2 import example as v2_example

    def list_rules():
        return itertools.chain(
            base.list_rules(),
            # [...]
            v2_example.list_rules(),
        )

This registers two documented policies, `get_example` and `submit_fruit`. They are unprotected by default, which means that everybody can access them. However, they can be overriden in `policy.yaml`. Call them the following way:

    # [...]
    import flask

    from cloudkitty.common import policy
    from cloudkitty.api.v2 import base

    class Example(base.BaseResource):
        # [...]
        def get(self):
            policy.authorize(flask.request.context, 'example:get_example', {})
            return {}

        # [...]
        def post(self):
            policy.authorize(flask.request.context, 'example:submit_fruit', {})
            # [...]

### Loading drivers

Most of the time, resources need to load some drivers (storage, SQL…). As the instantiation of these drivers can take some time, this should be done only once.

Some drivers (like the storage driver) are loaded in `BaseResource` and are thus available to all resources.

Resources requiring some additional drivers should implement the `reload` function:

    class BaseResource(flask_restful.Resource):

        @classmethod
        def reload(cls):
            """Reloads all required drivers"""

Here’s an example taken from `cloudkitty.api.v2.scope.state.ScopeState`:

    @classmethod
    def reload(cls):
        super(ScopeState, cls).reload()
        cls._client = messaging.get_client()
        cls._storage_state = storage_state.StateManager()

## Registering resources

Each endpoint should provide an `init` method taking a Flask app as only parameter. This method should call `do_init`:

cloudkitty.api.v2.utils.do_init(app, blueprint_name, resources)

  Registers a new Blueprint containing one or several resources to app.

  Parameters:

    - app (flask.Flask) – Flask app in which the Blueprint should be registered

    - blueprint_name (str) – Name of the blueprint to create

    - resources (list of dicts matching `cloudkitty.api.v2.RESOURCE_SCHEMA`) – Resources to add to the Blueprint’s Api

Add the following to `cloudkitty/api/v2/example/__init__.py`:

    from cloudkitty.api.v2 import utils as api_utils

    def init(app):
        api_utils.do_init(app, 'example', [
            {
                'module': __name__ + '.' + 'example',
                'resource_class': 'Example',
                'url': '',
            },
        ])
        return app

Here, we call `do_init` with the flask app passed as parameter, a blueprint name, and a list of resources. The blueprint name will prefix the URLs of all resources. Each resource is represented by a dict with the following attributes:

- `module`: name of the python module containing the resource class

- `resource_class`: class of the resource

- `url`: url suffix

In our case, the `Example` resource will be served at `/example` (blueprint name + URL suffix).

Note

In case you need to add a resource to an existing endpoint, just add it to the list.

Warning

If you created a new module, you’ll have to add it to `API_MODULES` in `cloudkitty/api/v2/__init__.py`:

    API_MODULES = [
        'cloudkitty.api.v2.example',
    ]

## Documenting your endpoint

The v2 API is documented with os_api_ref . Each v2 API endpoint must be documented in `doc/source/api-reference/v2/<endpoint_name>/`.

this page last updated: 2026-08-16 04:39:41

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.

found an error? report a bug