Creating an API with Cloud Endpoints Frameworks for App Engine

An API is a remote procedure call (RPC) service that provides remote methods accessible to external clients. Each backend API consists of an RPC service class that subclasses the ProtoRPC remote.Service class, and one or more methods. When you define a method, you must also define message classes for the requests coming into that method and the responses returned by it. A message class performs a mapping function so the incoming data can be extracted and supplied to the service method properly, or supplied properly to the outgoing response.

If a request has path or querystring arguments, you'll use a ResourceContainer class for the mapping, instead of a simple message class.

Finally, you will need to decorate the API service class and class methods, and you will need to define message classes for the requests and responses, as described below:

Creating the API

To create an API:

  1. Add the following required imports:

    import endpoints
    from protorpc import message_types
    from protorpc import messages
    from protorpc import remote

  2. Define a subclass of remote.Service and decorate it with @endpoints.api as follows:

    @endpoints.api(name='echo', version='v1')
    class EchoApi(remote.Service):

    Notice that your API name and the name of your service class do not need to be the same. The version number applies to the version of the API.

  3. Determine what data your method expects from the request and what data will be returned, and create a Message class for the request and response.

    class EchoRequest(messages.Message):
        content = messages.StringField(1)
    
    
    class EchoResponse(messages.Message):
        """A proto Message that contains a simple string field."""
        content = messages.StringField(1)
    
    
    ECHO_RESOURCE = endpoints.ResourceContainer(
        EchoRequest,
        n=messages.IntegerField(2, default=1))

    Note that if no arguments will appear in the request body, such as in a GET request, you could omit the message class for the request and simply use the value message_types.VoidMessage.

    If your request has path or querystring arguments, replace YourRequestMessageClass with an appropriate ResourceContainer.

    For complete information on forming and using message classes, see the documentation for the Google Protocol RPC response and request message classes.

  4. Create the desired method for your API, and decorate it with @endpoints.method as follows:

    @endpoints.method(
            # This method takes a ResourceContainer defined above.
            ECHO_RESOURCE,
            # This method returns an Echo message.
            EchoResponse,
            path='echo',
            http_method='POST',
            name='echo')
        def echo(self, request):

    If your request has path or querystring data, replace the request message type with an appropriate ResourceContainer.

    Each of the decorators (@endpoints.api and @endpoints.method) is described in more detail below.

  5. Add the API server code, as described in Creating an API Server.

Defining the API (@endpoints.api)

You can supply several arguments to @endpoints.api to define your API. The following table describes the available arguments:

@endpoints.api Arguments Description Example
allowed_client_ids Required if your API uses authentication. List of client IDs for clients allowed to request tokens. For more information, see Allowed Client IDs and Audiences. allowed_client_ids=['1-web-apps.apps.googleusercontent.com','2-android-apps.apps.googleusercontent.com', endpoints.API_EXPLORER_CLIENT_ID]
api_key_required Optional. Used to restrict access to requests that supply an Api key. api_key_required=True
audiences Required if your API requires authentication and if you are supporting Android clients. For Google ID tokens, this can be a list of client IDs on behalf of which tokens are requested. If the tokens are issued by third-party auth providers (e.g., Auth0), this needs to be a dictionary mapping from auth issuer names to audience lists. For more information, see Allowed Client IDs and Audiences. audiences=['1-web-apps.apps.googleusercontent.com'] or audiences={"auth0": ["aud-1.auth0.com", "aud-2.auth0.com"]}
canonical_name Optional. Used to specify a different or more readable name for the API in the client library. This name is used to generate names in the client library; the backend API continues to use the value specified in the name property.

For example, if your API has the name set to dfaanalytics, you could use this property to specifiy a canonical name of DFA Group Analytics; the generated client classes would then contain the name DfaGroupAnalytics.

You should include the relevant spaces between the names as shown above; these will be replaced by the appropriate camel casing or underscores.
canonical_name='DFA Analytics'
description A short description of the API. This is exposed in the discovery service to describe your API, and may optionally also be used to generate documentation as described in Generating Client Libraries. description='Sample API for a simple game'
documentation Optional. The URL where users can find documentation about this version of the API. This will be surfaced in the API Explorer "Learn More" highlight at the top of the API Explorer page to allow users to learn about your service. documentation='http://link_to/docs'
hostname Optional. The host name of your app engine application. hostname='your_app_id.appspot.com'
issuers Optional. The custom JWT issuer configurations. This should be a dictionary mapping from issuer names to endpoints.Issuer objects. issuers={"auth0": endpoints.Issuer("https://test.auth0.com", "https://test.auth0.com/.well-known/jwks.json")}
name Required. The name of the API, which is used as the prefix for all of the API's methods and paths. The name value:
  • Must begin with lowercase.
  • Must match the regular expression [a-z]+[A-Za-z0-9]*.
name='yourApi'
limit_definitions Optional. Used to define quotas for your API. See limit_definitions for more information.
owner_domain Optional. The domain name of the entity that owns the API. Used together with owner_name to provides hints to properly name the client library when it is generated for this API. (The package path will be the reverse of the owner_domain and package_path if supplied. The default is to use appid.apppost.com owner_domain='your-company.com'
owner_name Optional. The name of the entity that owns the API. Used together with owner_domain to provides hints to properly name the client library when it is generated for this API. owner_name='Your-Company'
package_path Optional. Is used to further scope The "package" this API belongs to, with values separated by / specifying logical groupings of APIs.

For example, specifying cloud/platform will result in the client library path set to cloud/platform/<ApiName> and client library package set to cloud.plaform.<ApiName>.
package_path='cloud/platform'
scopes If not supplied, the default is the email scope (https://www.googleapis.com/auth/userinfo.email), which is required for OAuth. You can override this to specify more OAuth 2.0 scopes if you wish. You can also override the scopes specified here for a particular API method by specifying different scopes in the @endpoints.method decorator. However, if you do define more than one scope, note that the scope check will pass if the token is minted for any of the specified scopes. To require multiple scopes, a single string should be specified with a space between each scope. scopes=['ss0', 'ss1 and_ss2']
title Optional. The text displayed in API Explorer as the title of your API, and exposed in the discovery and the directory services. title='My Backend API'
version Required. Specifies your Endpoint’s version. version='v1'

limit_definitions

To define quotas for your API, you specify the optional limit_definitions argument to @endpoints.api. To configure a quota, you must also:

  • Install version 2.4.5 or later of the Endpoints Frameworks library.
  • Add the metric_costs argument to the method decorator for each method that you want to apply a quota to.

See Configuring Quotas for all the steps required to setup a quota.

You specify a list of one or more LimitDefinition instances, like the following:

quota_limits = [
              endpoints.LimitDefinition(
                "name",
                "Display name",
                limit)
]

Each LimitDefinition instance must have the following values:

Element Description
name The name for the API request counter. Typically, this is the type of request (for example, “read-requests” or “write-requests”) that uniquely identifies the quota.
Display name

The text displayed to identify the quota on the Quotas page in the Endpoints dashboard. This text is also displayed to consumers of your API on the Quotas page of IAM & admin and API & services. The display name must be a maximum of 40 characters.

For readability purposes, the text “per minute per project” is automatically appended to the display name on the Quotas pages in Cloud Console. To maintain consistency with the display names of Google services listed on the Quotas pages that consumers of your API see, we recommend the following for the display name:

  • Use "Requests" when you only have one metric.
  • When you have multiple metrics, each should describe the type of request and contain the word “requests” (for example “Read requests” or “Write requests”).
  • Use "quota units" instead of “requests” when any of the costs for this quota is greater than 1.

limit An integer value that is the maximum number of requests per minute per consumer project for the quota.

Example

quota_limits = [
  endpoints.LimitDefinition('read-requests', 'Read Requests', 1000),
  endpoints.LimitDefinition('list-requests', 'List Requests', 100),
  endpoints.LimitDefinition('write-requests', 'Write Requests', 50)
]

@endpoints.api(name='bookstore',
               version='v1',
               limit_definitions=quota_limits)

Allowed client IDs and audiences

For OAuth2 authentication, an OAuth2 token is issued to a specific client ID, which means that this client ID can be used for restricting access to your APIs. When you register Android applications in the Google Cloud Platform Console, you create a client ID for it. This client ID is the one requesting an OAuth2 token from Google for authentication purposes. When the backend API is protected by auth, an OAuth2 access token is sent and opened by Cloud Endpoints Frameworks for App Engine, the client ID is extracted from the token, and then the ID is compared to the backend's declared acceptable Client ID list (the allowed_client_ids list).

The allowed_client_ids list should consist of the all client IDs you have obtained through the Google Cloud Platform Console for your web, Android and other client apps. (This means that the clients must be known at API build-time.) If you specify an empty list, no clients can access the API.

Note that in each API method where you want to check for proper authentication, you must call endpoints.get_current_user(). See Authenticating Users for more information.

If you use the allowed_client_ids argument and you want to test authenticated calls to your API using the Google API Explorer, you must supply its client ID in the list of allowed_client_ids: the value to use is endpoints.API_EXPLORER_CLIENT_ID. Notice that if allowed_client_ids contains only the endpoints.API_EXPLORER_CLIENT_ID, and you deploy your API, your API will still be publicly discoverable and can be publicly accessed using the API Explorer.

About audiences

The allowed_client_ids list protects the backend API from unauthorized clients. But further protection is needed to protect the clients, so that their auth token will work only for the intended backend API. For Android clients, this mechanism is the audiences argument, in which you specify the client ID of the backend API.

Note that when you create a Google Cloud Platform Console Project, a default client ID is automatically created and named for use by the project. When you upload your backend API into App Engine, it uses that client ID. This is the web client ID mentioned in API auth.

Third-Party Authentication Token Issuer

If your application accepts authentication tokens that are not Google ID tokens and are issued by third-party issuers, you need to properly set audiences and issuers arguments in @endpoints.api to provide the information about the third-party issuers. For example:

@endpoints.api(
        audiences={'auth0': ['aud-1.auth0.com', 'aud-2.auth0.com']},
        issuers={'auth0': endpoints.Issuer('https://test.auth0.com',
                                           'https://test.auth0.com/.well-known/jwks.json')})
class GreetingApi(remote.Service):

Defining an API method (@endpoints.method)

The audiences, scopes, and allowed_client_ids settings can be set for the entire API via @endpoints.api, or for a method, via @endpoints.method. If these settings are specified at both the API and the method level, the method setting overrides.

To create a method in your API, decorate the corresponding Python method with @endpoints.method, supplying arguments to configure the use of the method. For example, you'll specify the request and response message classes to be used.

The available arguments are listed in the following table:

@endpoints.method Arguments Description Example
allowed_client_ids This setting overrides the equivalent attribute specified in @endpoints.api. For more information, see Allowed Client IDs and Audiences. ['1-web-apps.apps.googleusercontent.com', '2-android-apps.apps.googleusercontent.com']
api_key_required Optional. Used to restrict access to requests that supply an Api key. api_key_required=True
audiences Overrides the equivalent argument specified in @endpoints.api. For more information, see Allowed Client IDs and Audiences. ['1-web-apps.apps.googleusercontent.com']
metric_costs Optional. Indicates that the method has a quota limit. This is a dictionary with the following key:value pairs:
  • name: A name that you specified in the limit_definitions argument to the API decorator.
  • cost: An integer that specifies the cost for each request. The cost allows methods to consume at different rates from the same quota. For example, if a quota has a limit of 1000 and a cost of 1, the calling application can make 1000 requests per minute before going over the limit. With a cost of 2 for the same quota, a calling application can make only 500 requests per minute before going over the limit.
metric_costs={'read-requests': 1}
http_method The HTTP method to use. If you don't set this, 'POST' is used by default. 'GET'
name An alternative name for this method. The name value:
  • Must begin with lowercase.
  • Must match the regular expression [a-z]+[A-Za-z0-9]*.
'yourApi'
path The URI path to use to access this method. If you don't set this, the empty string is the path. 'yourapi/path'
Request Message Class The Google Protocol RPC request message class to be used in the method call. Alternatively, you can supply the name of the class. YourRequestClass
Response Message Class The Google Protocol RPC response message class to be used in the method call. Alternatively, you can supply the name of the class. YourResponseClass

Using ResourceContainer for path or querystring arguments

If the request contains path or querystring arguments, you cannot use a simple Message class as described under Create the API. Instead, you must use a ResourceContainer class, as follows:

  1. Define a message class that has all the arguments that will be passed in the request body. (If no arguments will appear in the request body, you don't need to define a message class: simply use message_types.VoidMessage.) For example:

    class EchoRequest(messages.Message):
        content = messages.StringField(1)

  2. Define a ResourceContainer with the above message class as its first parameter, and subsequent parameters for the path and querystring arguments. For example:

    MULTIPLY_RESOURCE = endpoints.ResourceContainer(
        Greeting,
        times=messages.IntegerField(2, variant=messages.Variant.INT32,
                                    required=True))

    where the first argument is the message class for the data in the request body and times is a number expected in the path or querystring accompanying the request.

  3. Supply the ResourceContainer to the method handling the request, in the first parameter replacing the request message class that would otherwise be supplied in that location. This snippets show both the ResourceContainer and the endpoints.method:

    # This ResourceContainer is similar to the one used for get_greeting, but
    # this one also contains a request body in the form of a Greeting message.
    MULTIPLY_RESOURCE = endpoints.ResourceContainer(
        Greeting,
        times=messages.IntegerField(2, variant=messages.Variant.INT32,
                                    required=True))
    
    @endpoints.method(
        # This method accepts a request body containing a Greeting message
        # and a URL parameter specifying how many times to multiply the
        # message.
        MULTIPLY_RESOURCE,
        # This method returns a Greeting message.
        Greeting,
        path='greetings/multiply/{times}',
        http_method='POST',
        name='greetings.multiply')
    def multiply_greeting(self, request):
        return Greeting(message=request.message * request.times)

  4. Add the path parameter as shown, to include your API.

  5. If your ResourceContainer has a required argument, a client request must include it either in a querystring (for example, yourApi?times=2), or the URL path (for example, yourApi/2). However, in order for your API to receive an argument value via the URL path, you must also add the argument name to the API path as shown above for the {times} argument in path='yourApi/{times}.

Creating an API Implemented with Multiple Classes

If you implement your API using more than one class, you use a slightly different decoration scheme. For example:

api_collection = endpoints.api(name='library', version='v1.0')


@api_collection.api_class(resource_name='shelves')
class Shelves(remote.Service):

    @endpoints.method(Request, Response, path='list')
    def list(self, request):
        return Response()


@api_collection.api_class(resource_name='books', path='books')
class Books(remote.Service):

    @endpoints.method(Request, Response, path='bookmark')
    def bookmark(self, request):
        return Response()

where you replace api_collection with any name you want, so long as you use the same name for each class in the API. You must precede each class in the API with the decorator as shown in the snippet above.

About the resource name argument

The optional resource name argument for api_class is the name of the class that you want to exposed in the API; this is the name that will show up in the API Explorer, prepended to any methods exposed in the class.

About the path argument

You don't have to explicitly specify a path argument, which indicates the relative location at which the class methods will appear. Thus, in the snippet above, no path is specified for the class Shelves, so its methods will be accessible under /_ah/api/library/v1.

If you do specify a path, the path is appended to the root, but prepended to any paths provided within the class. It is the prefix that appears by default before any paths specified in the class.

In the snippet above, notice that the path books is specified for class Books. This means that any paths specified for its class methods will be appended to the class, as shown below:

@api_collection.api_class(resource_name='books', path='books')
class Books(remote.Service):

    @endpoints.method(Request, Response, path='bookmark')
    def bookmark(self, request):
        return Response()

where bookmark is accessible via the path /_ah/api/library/v1/books/bookmark

Serving a multi-class API

In your endpoints.api_server code that creates the API server, you supply the name you assigned for your api_class collection. For example, where the collection name is api_collection you would create the server as follows:

api = endpoints.api_server([api_collection])

Send feedback about...

Cloud Endpoints Frameworks for App Engine