1. Introduction
This is a design guide for networked APIs, in particular REST and gRPC APIs, shared here to inform internal and external developers and to make it easier for us all to work together.
By providing a set of guidelines, we want to answer many common questions encountered along the way of API development, following RESTful principles and also want to inspire additional discussion and refinement within and among our teams. We seek to define a common resource-oriented design for Infront’s services platform, which should promote API adoption, reduce friction and enable proper usage.
1.1. Usage
Feel free to use these guidelines as a guidance for your own development. Also consider this to be a living, evolving document. We will revise and update based on our learnings and experiences.
1.2. Requirement Levels
The requirement level keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" used in this document are to be interpreted as described in RFC 2119.
1.3. Design Principles
We want to provide RESTful and gRPC APIs, which can be used by internal and external clients, whereas only the gRPC API enables efficient communication between microservices. The RESTful API however enables robust hypermedia REST clients.
This guide borrows from other design guides, which should be introduced briefly in the following. Their updated versions should be considered for possible inclusion in this document.
As discussed in Hypermedia, we figured that providing a complete RESTful API including hypermedia can have beneficial effects for clients, when they make use of it. In order to give us full flexibility in designing the RESTful API we MUST separate it from the gRPC API.
We differ from Zalando’s guidelines, since we SHOULD use hypermedia formats for expressing client side state. Also Zalando’s API is only intended for use by external web clients, not other services unlike most of our APIs.
2. Security
2.1. Endpoints security
Note
|
Applies to RESTful APIs |
Every API endpoint MUST be secured using OAuth 2.0. The following examples show how to specify security definitions in you API:
securityDefinitions:
# OAuth2 password flow (default)
OAuth2:
description: OAuth2 with password flow
type: oauth2
authorizationUrl: 'https://idm.eu.infrontfinance.com/auth/realms/vwd-internal/protocol/openid-connect/auth'
tokenUrl: 'https://idm.eu.infrontfinance.com/auth/realms/vwd-internal/protocol/openid-connect/token'
flow: password
scopes:
read:<service-name>: Access right needed to read from <service-name>
write:<service-name>: Access right needed to write to <service-name>
read:<service-name>.<endpoint>: Access right needed to read from the <service-name>.endpoint
write:<service-name>.<endpoint>: Access right needed to write to the <service-name>.<endpoint>
The keycloak server is used for authorization and requesting a token and reachable under the following (actually dev) address:
Authorization endpoints:
-
"https://idm.staging.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/auth"
-
"https://idm.preview.eu.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/auth"
-
"https://idm.eu.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/auth"
-
"https://idm.preview.ch.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/auth"
-
"https://idm.ch.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/auth"
Token endpoints:
-
"https://idm.staging.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/token"
-
"https://idm.preview.eu.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/token"
-
"https://idm.eu.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/token"
-
"https://idm.preview.ch.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/token"
-
"https://idm.ch.infrontfinance.com/auth/realms/%realm%/protocol/openid-connect/token"
Hint: %realm% is a variable and depends on the application
The example defines OAuth2 with password flow as security standard used for authentication when accessing endpoints; additionally, there are two API access rights defined via the scopes section for later endpoint authorization usage - please see next section.
It makes little sense specifying the flow to retrieve OAuth tokens in the securityDefinitions section, as API endpoints should not care, how OAuth tokens were created. Unfortunately the flow field is mandatory and cannot be ommited. API endpoints SHOULD always set flow: password and ignore this information.
2.2. Access Rights (Scopes)
Every API needs to define access rights, called scopes here, and every endpoint needs to have at least one scope assigned. Scopes are defined by name and description per API specification, as shown in the previous section. Please refer to the following rules when creating scope names:
<api-scope> ::= <api-standard-scope> | -- should be sufficient for majority of use cases
<api-resource-specific-scope> | -- for special security access differentiation use cases
<api-pseudo-scope> -- used to explicitly indicate that access is not restricted
<api-standard-scope> ::= <application-id>.<access-type>
<api-resource-specific-scope> ::= <application-id>.<resource-id>.<access-type>
<api-pseudo-scope> ::= uid
<application-id> ::= <as defined via STUPS>
<access-type> ::= read | write -- might be extended in future
<resource-id> ::= <free identifier following application-id syntax>
APIs SHOULD stick to standard scopes by default — for the majority of use cases, restricting access to specific APIs (with read vs. write differentiation) is sufficient for controlling access for client types like merchant or retailer business partners, customers or operational staff. Too many fine grained scopes increasing governance complexity without real value add SHOULD be avoided. In some situations, where the API serves different types of resources for different owners, resource specific scopes MAY make sense.
Application ID | Resource ID | Access Type | *Example |
---|---|---|---|
fulfillment-order |
read |
fulfillment-order.read |
|
fulfillment-order |
write |
fulfillment-order.write |
|
sales-order |
sales_order |
read |
sales-order.sales_order.read |
sales_order |
shipment_order |
read |
shipment-order.shipment_order.read |
After scope names are defined and the scope is declared in the security definition at the top of an API specification, it SHOULD be assigned to each API operation by specifiying a security requirement.
paths:
/product-profiles:
get:
summary: Retrieves a product profiles list
security:
- OAuth2: [read.product_profiles]
responses:
200:
description: ...
In very rare cases a whole API or some selected endpoints may not require specific access control. However, to make this explicit you SHOULD assign the uid pseudo access right scope in this case. It is the user id and always available as OAuth2 default scope.
Hint: you need not explicitly define the "Authorization" header; it is a standard header so to say implicitly defined via the security section.
In cases you want to grant access to certain resources without giving the user access to the keycloak server, API keys SHOULD be used:
securityDefinitions:
# X-API-Key: abcdef12345
APIKeyHeader:
description: Provide API-Key as header
type: apiKey
in: header
name: X-API-Key
# /path?api_key=abcdef12345
APIKeyQueryParam:
description: Provide API-Key as query parameter
type: apiKey
in: query
name: api_key
paths:
/information:
get:
summary: Retrieves information
security:
- APIKeyHeader: []
- APIKeyQueryParam: []
responses:
200:
description: Test
2.3. Security with gRPC
Note
|
Applies to gRPC APIs. |
-
For internal and external APIs, the API MUST be secured with OAuth2. The client has to send the token within the header (Metadata) of the request - so no additional request field has to be defined within the message itself.
The token has to be sent in a metadata field named ACCESS_TOKEN
.
3. Resource Oriented Design
Note
|
Applies to RESTful and gRPC APIs |
The architectural style of REST was primarily designed to work well
with HTTP/1.1. Its core principle is to define named resources that
can be manipulated using a small number of methods. The resources and
methods are known as nouns and verbs of APIs. With the HTTP protocol,
the resource names naturally map to URLs, and methods naturally map to
HTTP methods POST
, GET
, PUT
, PATCH
, and DELETE
.
In order to keep RESTful and gRPC APIs in sync it is REQUIRED to apply the resource-oriented design also to gRPC services.
3.1. Design Flow
The Design Guide suggests taking the following steps when designing resource-oriented APIs (more details are covered in specific sections below):
-
Determine what types of resources an API provides.
-
Determine the relationships between resources.
-
Decide the resource name schemes based on types and relationships.
-
Decide the resource schemas.
-
Attach minimum set of methods to resources.
3.2. Resources
A resource-oriented API is generally modeled as a resource hierarchy, where each node is either a simple resource or a collection resource. For convenience, they are often called as a resource and a collection, respectively.
-
A collection contains a list of resources of the same type. For example, a user has a collection of contacts.
-
A resource has some state and zero or more sub-resources. Each sub-resource can be either a simple resource or a collection resource.
3.2.1. Resource Types
-
Predefined one-off resources for especially important aspects of the application.
This includes top-level directories of other available resources. Most services expose few or no one-off resources. For example a web site’s homepage. It’s a one-of-a-kind resource, at a well-known URI, which acts as a portal to other resources.
-
A resource for every object exposed through the service.
One service may expose many kinds of objects, each with its own resource set. Most services expose a large or infinite number of these resources.
-
Resources representing the results of algorithms applied to the data set.
This includes collection resources, which are usually the results of queries. Most expose an infinite number of algorithmic resources, or they don’t expose any. Algorithmically-generated resources, because they rely on the client providing an arbitrary search string (e.g. an ISIN) or combining unrelated elements (e.g. "issuername = Deka" or "country = Deutschland").
3.3. Methods
The key characteristic of a resource-oriented API is that it
emphasizes resources (data model) over the methods performed on the
resources (see functionality). A typical resource-oriented API exposes a
large number of resources with a small number of methods. The methods
can be either the standard methods or custom methods. For this guide,
the standard methods are: List
, Get
, Create
, Update
, and
Delete
.
Where API functionality naturally maps to one of the standard methods, that method SHOULD be used in the API design. For functionality that does not naturally map to one of the standard methods, custom methods MAY be used. Custom methods offer the same design freedom as traditional RPC APIs, which can be used to implement common programming patterns, such as database transactions or data analysis.
Whenever it seems tempting to add a method as noun to the API like "subscribe", it is RECOMMENDED to consider defining a new resource, e.g. "subscription".
4. Naming Conventions
Note
|
Applies to RESTful and gRPC APIs |
Since many developers are not native English speakers, one goal of these naming conventions is to ensure that the majority of developers can easily understand an API. It does this by encouraging the use of a simple, consistent, and small vocabulary when naming methods and resources.
-
Names used in APIs SHOULD be in correct American English. For example, license (instead of licence), color (instead of colour).
-
Commonly accepted short forms or abbreviations of long words MAY be used for brevity. For example, API is preferred over Application Programming Interface.
-
Use intuitive, familiar terminology where possible. For example, when describing removing (and destroying) a resource,
delete
is preferred overerase
. -
Use the same name or term for the same concept, including for concepts shared across APIs.
-
Avoid name overloading. Use different names for different concepts.
-
Avoid overly general names that are ambiguous within the context of the API. They can lead to misunderstanding of API concepts. Rather, choose specific names that accurately describe the API concept. This is particularly important for names that define first-order API elements, such as resources. There is no definitive list of names to avoid, as every name must be evaluated in the context of other names.
Instance
,info
, andservice
are examples of names that have been problematic in the past. Names chosen should describe the API concept clearly (for example: instance of what?) and distinguish it from other relevant concepts (for example: does "alert" mean the rule, the signal, or the notification?). -
Carefully consider use of names that may conflict with keywords in common programming languages. Such names MAY be used but will likely trigger additional scrutiny during API review. Use them judiciously and sparingly.
Important
|
As the REST and gRPC API definitions should be generated by a shared source definition, the naming of both API types depend on each other. Please bear this dependency in mind when you create an API definition. |
4.1. Field names
Field name definitions MUST use lower_case_underscore_separated_names. These names will be mapped to the native naming convention in generated code for each programming language.
Field names SHOULD avoid prepositions (e.g. "for"), for example:
-
reason_for_error
should instead beerror_reason
-
cpu_usage_at_time_of_failure
should instead befailure_time_cpu_usage
Field names SHOULD also avoid using postpositive adjectives (modifiers placed after the noun), for example:
-
items_collected
should instead becollected_items
-
objects_imported
should instead beimported_objects
4.2. Query / Method Parameters
The naming of query parameters (REST) and method parameters (gRPC) should match.
It is REQUIRED to use snake_case (never camelCase) for query/method parameters.
customer_number, order_id, billing_address
Use Conventional Query / Method Parameters
If you provide query support for sorting, pagination, filtering functions or other actions, you SHOULD use the following standardized naming conventions:
Parameter | Description |
---|---|
|
to restrict the number of entries. See Pagination section below. |
|
key-based page start. See Pagination section below. |
|
numeric offset page start. See Pagination section below. |
|
comma-separated list of fields to sort. To indicate sorting direction, fields my prefixed with + (ascending) or - (descending, default), e.g. /sales-orders?sort=+id |
|
to retrieve a subset of fields. See Support Filtering of Resource Fields. |
When using query parameters, you SHOULD reference the parameter definitions under https://api.development.vwd.com/definitions/queryparameters.openapi-2.0.yaml.
4.3. gRPC Specific Naming
gRPC specific naming rules regarding packages, interfaces, resources, collections, methods, and messages.
4.3.1. Package names
Package names declared in the API .proto files SHOULD be consistent with Product and Service Names. For example:
package infrontfinance.ms.services.calendar;
An abstract API that isn’t directly associated with a service, should use proto package names consistent with the Product name:
package infrontfinance.ms.services.portfoliomanager;
Java package names specified in the API .proto
files must match the
proto package names with standard Java package name prefix (com.,
edu., net., etc). For example:
package infrontfinance.ms.services.portfoliomanager;
// Specifies Java package name, using the standard prefix "com."
option java_package = "com.infrontfinance.ms.services.portfoliomanager";
4.3.2. Collection IDs
Collection IDs should use plural form and lowerCamelCase
, and American
English spelling and semantics. For example: events
, children
, or
deletedEvents
.
4.3.3. Interface names
To avoid confusion with Service Names such as pubsub.infrontfinance.com
,
the term interface name refers to the name used when defining a
service in a .proto
file:
// Library is the interface name.
service Library {
rpc ListBooks(...) returns (...);
rpc ...
}
You can think of the service name as a reference to the actual implementation of a set of APIs, while the interface name refers to the abstract definition of an API.
An interface name SHOULD use an intuitive noun such as Calendar or Blob. The name SHOULD NOT not conflict with any well-established concepts in programming languages and their runtime libraries (for example, File).
In the rare case where an interface name would conflict with another
name within the API, a suffix (for example Api
or Service
) SHOULD be
used to disambiguate.
4.3.4. Method names
A service MAY, in its IDL specification, define one or more RPC
methods that correspond to methods on collections and resources. The
method names SHOULD follow the naming convention of VerbNoun
in upper
camel case, where the noun is typically the resource type.
The verb portion of the method name SHOULD use the imperative mood, which is for orders or commands rather than the indicative mood which is for questions.
This is easily confused when the verb is asking a question about a
sub-resource in the API, which is often expressed in the indicative
mood. For example, ordering the API to create a book is clearly
CreateBook
(in the imperative mood), but asking the API about the
state of the book’s publisher might use the indicative mood, such as
IsBookPublisherApproved
or NeedsPublisherApproval
. To remain in the
imperative mood in situations like this, rely on commands such as
"check" (CheckBookPublisherApproved
) and "validate"
(ValidateBookPublisher
).
4.3.5. Message names
The request and response messages for RPC methods SHOULD be named
after the method names with the suffix Request
and Response
,
respectively, unless the method request or response type is:
-
an empty message (use
google.protobuf.Empty
), -
a resource type, or
-
a resource representing an operation
This typically applies to requests or responses used in standard
methods Get
, Create
, Update
, or Delete
.
4.3.6. Enum names
Enum types MUST use UpperCamelCase names.
Enum values MUST use CAPITALIZED_NAMES_WITH_UNDERSCORES
. Each enum
value MUST end with a semicolon, not a comma. The first value MUST
be named UNSPECIFIED
as it is returned when an enum value is
not explicitly specified.
enum FooBar {
// The first value represents the default and must be == 0.
UNSPECIFIED = 0;
FIRST_VALUE = 1;
SECOND_VALUE = 2;
}
4.4. RESTful Specific Naming
4.4.1. Paths
-
MUST use lowercase separate words with hyphens for path segments.
Listing 5. Example:/stock-staticdata/{stock-id}
This applies to concrete path segments and not the names of path parameters. For example
{stock_staticdata_id}
would be ok as a path parameter. -
MUST pluralize resource names.
Usually, a collection of resource instances is provided (at least API should be ready here). The special case of a resource singleton is a collection with cardinality 1.
-
MAY: Use /api as first path segment (for internal APIs).
-
MAY: Use /public as first path segment. This is used to mark this resource as public (in terms of authentication); it can be accessed without any authentication.
In most cases, all resources provided by a service are part of the public API, and therefore should be made available under the root "/" base path. If the service should also support non-public, internal APIs - for specific operational support functions, for example - add "/api" as base path to clearly separate public and non-public API resources.
-
MUST: Identify resources and Sub-Resources via Path Segments
Some API resources may contain or reference sub-resources. Embedded sub-resources, which are not top-level resources, are parts of a higher-level resource and cannot be used outside of its scope. Sub-resources should be referenced by their name and identifier in the path segments.
Listing 6. Basic URL structure: (authenticated, public and private APIs)/{resources}/[resource-id]/{sub-resources}/[sub-resource-id] /api/{resources}/[resource-id]/{sub-resources}/[sub-resource-id]
Listing 7. Basic URL structure: (unauthenticated)/public/{resources}/[resource-id]/{sub-resources}/[sub-resource-id]
-
MUST: Use (Non-) Nested URLs
If a sub-resource is only accessible via its parent resource and may not exists without parent resource, use a nested URL structure, for instance:
/carts/1681e6b88ec1/items/1
However, if the resource can be accessed directly via its unique id, then the API should expose it as a top-level resource. For example, product-profiles contain a collection of configs; however, configs have globally unique id and some services may choose to access the configs directly, for instance:
/product-profiles/{pp_id}/configs/{cfg_id} /product-profile-configs/{cfg_id}
To satisfy the basic URL structure, every second path level MUST be a path parameter. So never use the following path style for sub-resources:
/product-profiles/configs/{cfg_id}
-
All sub-paths SHOULD be valid references
In order to improve the consumer experience, you should aim for intuitively understandable URLs, where each sub-path is a valid reference to a resource or a set of resources. For example, all of these paths should be valid:
/customers/12ev123bv12v/addresses/DE_100100101 /customers/12ev123bv12v/addresses /customers/12ev123bv12v /customers
-
MUST: Use separate paths for single resources and collections.
Paths that end on an resource identifier MUST always return a single resource. For example
/customers/12ev123bv12v
returns only the customer with the given ID.To request the collection of all customers, use the path
/customers
(use pagination if these lists can get large). -
MUST avoid trailing slashes
The trailing slash must not have specific semantics. Resource paths must deliver the same results whether they have the trailing slash or not.
4.4.2. HTTP Headers
-
REQUIRED to use hyphenated HTTP headers
-
SHOULD prefer hyphenated-pascal-case for HTTP header fields
-
MAY use standardized headers
-
MUST when using own, non-standardized headers, always use the prefix X-VWD(legacy) or X-INFRONTFINANCE
This is for consistency in your documentation (most other headers follow this convention). Avoid camelCase (without hyphens). Exceptions are common abbreviations like "ID."
Accept-Encoding Apply-To-Redirect-Ref Disposition-Notification-Options Original-Message-ID X-VWD-Deeplink X-INFRONTFINANCE-Dochub
4.5. Single response format
All single responses MUST have the following basic structure:
-
entity (e.g. product_profile) with these attributes:
-
_meta
-
_attributes
-
_links
-
all real attributes of the entity
-
_meta
is an optional property containing additional meta information in a key value map. You MUST
use the given definition "EntityMeta" under https://api.development.vwd.com/definitions/collections/basedefinitions.yaml#/EntityMeta
_attributes
is an optional property and contains additional information for all attributes. You MUST
use the given definition "EntityAttributes" under https://api.development.vwd.com/definitions/collections/basedefinitions.yaml#/EntityAttributes
_links
is an optional property and contains links that lead to further information of a requested resource. You MUST
use the given definition "EntityLinks" under https://api.development.vwd.com/definitions/collections/basedefinitions.yaml#/EntityLinks
{
"product_profile": {
"id": "ca68619b-205d-11e7-b09e-0030486074ca",
"update_timestamp": "2017-03-20T16:36:30.415Z",
"isin": "DE0007100000",
"product_profile_attributes": {...},
"_meta": {...},
"_attributes": {...},
"_links": {
"self": {
"name": "string",
"href": "string",
"templated": false
}
}
}
}
4.6. Collection response format
All collection responses MUST have the following basic structure:
-
_meta
-
_pagination
-
_data
_meta
is an optional property containing additional meta information in a key value map. You MUST
use the given definition "Meta" under https://api.development.vwd.com/definitions/collections/basedefinitions.yaml#/Meta
_pagination
is an optional property and contains information about pagination. You MUST use the given
definition "Pagination" under https://api.development.vwd.com/definitions/collections/basedefinitions.yaml#/Pagination
_data
MUST contain a collection of objects with all information from the requested resource.
Important
|
Per row you MUST return a map including all requested entities via parameter fields. This is necessary to aggregate/compose results of different services! |
{
"_meta": {...},
"_data": [
{
"product_profile": {
"id": "ca68619b-205d-11e7-b09e-0030486074ca",
"update_timestamp": "2017-03-20T16:36:30.415Z",
"product_profile_attributes": {...},
"_meta": {...},
"_attributes": {...},
"_links": {
"self": {
"name": "string",
"href": "string",
"templated": false
}
}
},
"producer_product_profile": {
"id": "ca68619b-205d-11e7-b09e-0030486074ca",
"isin": "DE0007100000",
"product_profile_attributes": {...},
"_meta": {...},
"_attributes": {...},
"_links": {
"self": {
"name": "string",
"href": "string",
"templated": false
}
}
},
"security": {
"instrument_id": 99949,
"instrument_key": "519000.ETR",
"isin": "DE0005190003",
"description": "Bayerische Motorenwerke AG",
"description_short": "BMW",
"security_category_code": "CERT",
"security_group_code": "CERT_BONUS",
"security_type_code": "CERT_BONUS_CAP",
"_meta": {...},
"_attributes": {...},
"_links": {
"self": {
"name": "string",
"href": "string",
"templated": false
}
}
}
}
],
"_pagination": {
"index": 0,
"page_size": 0,
"self": {
"name": "string",
"href": "string",
"templated": false
},
"first": {...},
"next": {...},
"prev": {...},
"last": {...}
}
}
5. Methods
Note
|
Applies to RESTful and gRPC APIs |
This chapter defines the concept of standard methods for gRPC and how they map to HTTP Operations for REST services.
The standard gRPC methods are: List
, Get
, Create
, Update
, and Delete
.
These methods define a common set of CRUD operations to reduce complexity and increase consistency between APIs.
The following table describes how to map standard methods to HTTP methods:
gRPC Method | HTTP Mapping | HTTP Request Body | HTTP Response Body |
---|---|---|---|
|
GET <collection URL> |
Empty |
|
|
GET <resource URL> |
Empty |
|
|
POST <collection URL> |
Resource |
|
|
PUT or PATCH <resource URL> |
Resource |
|
|
DELETE <resource URL> |
Empty |
The resource returned from List, Get, Create, and Update methods may contain partial data if the methods support field masks, which specify a subset of fields to be returned. In some cases, the API platform natively supports field masks for all methods.
The response returned from a Delete method that doesn’t immediately remove the resource (such as updating a flag or creating a long-running delete operation) should contain either the long-running operation or the modified resource.
A standard method may also return a long running operation for requests that do not complete within the time-span of the single API call.
The following sections describe each of the standard methods in
detail. The examples show the methods defined in .proto
files with
special annotations for the HTTP mappings.
5.1. Standard Methods
Note
|
Applies to gRPC APIs only |
5.1.1. List
The List
method takes a collection name and zero or more parameters as
input, and returns a list of resources that match the input.
List
is commonly used to search for resources. List
is suited for data
from a single collection that is bounded in size and not cached.
A batch get (such as a method that takes multiple resource IDs and
returns an object for each of those IDs) should be implemented as a
custom BatchGet method, rather than a List. However, if you have an
already-existing List method that provides the same functionality, you
MAY reuse the List method for this purpose instead. If you are using a
custom BatchGet method, it SHOULD be mapped to HTTP GET
.
The following code example defines a standard List method that lists a collection of books:
// Lists all the books on a given shelf.
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {}
message ListBooksRequest {
// The parent resource name, for example, "shelves/shelf1".
string parent = 1;
// The maximum number of items to return.
int32 page_size = 2;
// The next_page_token value returned from a previous List request, if any.
string page_token = 3;
}
message ListBooksResponse {
// The field name should match the noun "books" in the method name. There
// will be a maximum number of items returned based on the page_size field
// in the request.
repeated Book books = 1;
// Token to retrieve the next page of results, or empty if there are no
// more results in the list.
string next_page_token = 2;
}
5.1.2. Get
The Get
method takes a resource name, zero or more parameters, and
returns the specified resource.
The following code example defines a standard Get
method that gets a
specified book:
// Gets the specified book.
rpc GetBook(GetBookRequest) returns (Book) {}
message GetBookRequest {
// The field will contain name of the resource requested, for example:
// "shelves/shelf1/books/book2"
string name = 1;
}
5.1.3. Create
The Create
method takes a collection name, a resource, and zero or
more parameters. It creates a new resource in the specified
collection, and returns the newly created resource.
If the API supports creating resources, it SHOULD have a
Create
method for each type of resource that can be created.
If the Create
method supports client-assigned resource name and the
resource already exists, the request SHOULD either fail with error
code google.rpc.Code.ALREADY_EXISTS
or use a different server-assigned
resource name and the documentation should be clear that the created
resource name may be different from that passed in.
The following code example defines a standard Create
method that
creates a book inside a parent shelf:
rpc CreateBook(CreateBookRequest) returns (Book) {}
message CreateBookRequest {
// The parent resource name where the book to be created.
string parent = 1;
// The book id to use for this book.
string book_id = 3;
// The book resource to create.
// The field name should match the Noun in the method name.
Book book = 2;
}
rpc CreateShelf(CreateShelfRequest) returns (Shelf) {}
message CreateShelfRequest {
Shelf shelf = 1;
}
5.1.4. Update
The Update
method takes a request message containing a resource and
zero or more parameters. It updates the specified resource and its
properties, and returns the updated resource.
Mutable resource properties SHOULD be mutable by the Update method, except the properties that contain the resource’s name or parent. Any functionality to rename or move a resource MUST not happen in the Update method and instead SHALL be handled by a custom method.
If the API accepts client-assigned resource names, the server may
allow the client to specify a non-existent resource name and create a
new resource. Otherwise, the Update method should fail with
non-existent resource name. The error code NOT_FOUND
should be used if
it is the only error condition.
An API with an Update
method that supports resource creation SHOULD
also provide a Create
method. Rationale is that it is not clear how to
create resources if the Update
method is the only way to do it.
The following code example defines a standard Update method that updates a specified book:
rpc UpdateBook(UpdateBookRequest) returns (Book) {}
message UpdateBookRequest {
// The book resource which replaces the resource on the server.
Book book = 1;
// The update mask applies to the resource. For the `FieldMask` definition,
// see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask
FieldMask update_mask = 2;
}
5.1.5. Delete
The Delete
method takes a resource name and zero or more parameters,
and deletes or schedules for deletion the specified resource. The
Delete
method should return google.protobuf.Empty
.
An API SHOULD NOT rely on any information returned by a Delete
method,
as it CANNOT be invoked repeatedly.
Calls to the Delete
method SHOULD be idempotent in effect, but do not
need to yield the same response. Any number of Delete requests should
result in a resource being (eventually) deleted, but only the first
request should result in a success code. Subsequent requests should
result in a google.rpc.Code.NOT_FOUND
.
The following code example defines a standard Delete
method that
deletes a specified book:
rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {}
message DeleteBookRequest {
// The resource name of the book to be deleted, for example:
// "shelves/shelf1/books/book2"
string name = 1;
}
5.2. HTTP Verbs
Note
|
Applies to RESTful APIs only |
5.2.1. GET
GET
requests are used to read a single resource or query set of resources.
-
GET
requests for individual resources will usually generate a 404 if the resource does not exist -
GET
requests for collection resources may return either 200 (if the listing is empty) or 404 (if the list is missing) -
GET
requests MUST NOT have request body payload -
GET
requests on collection resources SHOULD provide a sufficient filter mechanism as well as pagination.
GET with Body
APIs sometimes face the problem, that they have to provide extensive structured request information with GET, that may even conflicts with the size limits of clients, load-balancers, and servers. As we require APIs to be standard conform (body in GET must be ignored on server side), API designers have to check the following two options:
-
GET with URL encoded query parameters: when it is possible to encode the request information in query parameters, respecting the usual size limits of clients, gateways, and servers, this should be the first choice. The request information can either be provided distributed to multiple query parameters or a single structured URL encoded string.
-
POST with body content: when a GET with URL encoded query parameters is not possible, a POST with body content must be used. In this case the endpoint must be documented with the hint
GET with body
to transport the GET semantic of this call.
Note
|
It is no option to encode the lengthy structured request information in header parameters. From a conceptual point of view, the semantic of an operation should always be expressed by resource name and query parameters, i.e. what goes into the URL. Request headers are reserved for general context information, e.g. FlowIDs. In addition, size limits on query parameters and headers are not reliable and depend on clients, gateways, server, and actual settings. Thus, switching to headers does not solve the original problem. |
GET-Examples
-
Single Resource
{ "product_profile": { "id": "ca68619b-205d-11e7-b09e-0030486074ca", "update_timestamp": "2017-03-20T16:36:30.415Z", "isin": "DE0007100000", "product_profile_attributes": {...}, "_meta": {...}, "_attributes": {...}, "_links": { "self": { "name": "string", "href": "string", "templated": false } } } }
-
Collection Resource
{ "_meta": {...}, "_data": [ { "product_profile": { "id": "ca68619b-205d-11e7-b09e-0030486074ca", "update_timestamp": "2017-03-20T16:36:30.415Z", "product_profile_attributes": {...}, "_meta": {...}, "_attributes": {...}, "_links": { "self": { "name": "string", "href": "string", "templated": false } } }, "producer_product_profile": { "id": "ca68619b-205d-11e7-b09e-0030486074ca", "isin": "DE0007100000", "product_profile_attributes": {...}, "_meta": {...}, "_attributes": {...}, "_links": { "self": { "name": "string", "href": "string", "templated": false } } }, "security": { "instrument_id": 99949, "instrument_key": "519000.ETR", "isin": "DE0005190003", "description": "Bayerische Motorenwerke AG", "description_short": "BMW", "security_category_code": "CERT", "security_group_code": "CERT_BONUS", "security_type_code": "CERT_BONUS_CAP", "_meta": {...}, "_attributes": {...}, "_links": { "self": { "name": "string", "href": "string", "templated": false } } } } ], "_pagination": { "index": 0, "page_size": 0, "self": { "name": "string", "href": "string", "templated": false }, "first": {...}, "next": {...}, "prev": {...}, "last": {...} } }
5.2.2. PUT
PUT
requests are used to create or update entire resources - single or collection resources. The
semantic is best described as please put the enclosed representation at the resource mentioned by
the URL, replacing any existing resource.
-
PUT requests are usually applied to single resources, and not to collection resources, as this would imply replacing the entire collection (because of that be careful when using PUT for collections)
-
PUT requests are usually robust against non-existence of resources by implicitly creating before updating
-
on successful PUT requests, the server will replace the entire resource addressed by the URL with the representation passed in the payload (subsequent reads will deliver the same payload)
-
successful PUT requests will usually generate 200 or 204 (if the resource was updated - with or without actual content returned), and 201 (if the resource was created)
-
therefore PUT is idempotent
Note
|
Resource IDs with respect to PUT requests are maintained by the client and passed as a URL path segment. Putting the same resource twice is required to be idempotent and to result in the same single resource instance. If PUT is applied for creating a resource, only URIs should be allowed as resource IDs. If URIs are not available POST should be preferred. |
To prevent unnoticed concurrent updates when using PUT, the combination of ETag and If-(None-)Match header headers should be considered to signal the server stricter demands to expose conflicts and prevent lost updates.
5.2.3. POST
POST
requests are idiomatically used to create single resources on a collection resource endpoint,
but other semantics on single resources endpoint are equally possible, but discouraged.
So POST
SHOULD NOT be used for single resources, use PUT
instead.
The semantic for collection endpoints is best described as please add the enclosed representation to the collection resource identified by the URL. The semantic for single resource endpoints is best described as please execute the given well specified request on the collection resource identified by the URL.
-
POST request should only be applied to collection resources, and normally not on single resource, as this has an undefined semantic
-
on successful POST requests, the server will create one or multiple new resources and provide their URI/URLs in the response
-
successful POST requests will usually generate 200 (if resources have been updated), 201 (if resources have been created), and 202 (if the request was accepted but has not been finished yet)
-
the server must respond with 409 if the resource already exists (when using with single resource endpoints).
More generally: POST should be used for scenarios that cannot be covered by the other methods sufficiently. For instance, GET with complex (e.g. SQL like structured) query that needs to be passed as request body payload because of the URL-length constraint. In such cases, make sure to document the fact that POST is used as a workaround.
Note
|
Resource IDs with respect to POST requests are created and maintained by server and returned with response payload. Posting the same resource twice is by itself not required to be idempotent and may result in multiple resource instances. Anyhow, if external URIs are present that can be used to identify duplicate requests, it is best practice to implement POST in an idempotent way. |
5.2.4. PATCH
PATCH
request are only used for partial update of single resources, i.e. where only a specific
subset of resource fields should be replaced. The semantic is best described as please change
the resource identified by the URL according to my change request. The semantic of the change
request is not defined in the HTTP standard and must be described in the API specification by
using suitable media types.
-
PATCH requests are usually applied to single resources, and not on collection resources, as this would imply patching on the entire collection
-
PATCH requests are usually not robust against non-existence of resource instances
-
on successful PATCH requests, the server will update parts of the resource addressed by the URL as defined by the change request in the payload
-
successful PATCH requests will usually generate 200 or 204 (if resources have been updated
-
with or without updated content returned)
-
In preference order:
-
use PUT with complete objects to update a resource as long as feasible (i.e. do not use PATCH at all).
-
use PATCH with partial objects to only update parts of a resource, when ever possible. (This is basically JSON Merge Patch, a specialized media type
application/merge-patch+json
that is a partial resource representation.) -
use PATCH with JSON Patch, a specialized media type
application/json-patch+json
that includes instructions on how to change the resource. -
use POST (with a proper description of what is happening) instead of PATCH if the request does not modify the resource in a way defined by the semantics of the media type.
In practice JSON Merge Patch quickly turns out to be too limited, especially when trying to update single objects in large collections (as part of the resource). In this cases JSON Patch can shown its full power while still showing readable patch requests (see also).
5.2.5. DELETE
DELETE
request are used to delete resources. The semantic is best described as please delete the
resource identified by the URL.
-
DELETE requests are usually applied to single resources, not on collection resources, as this would imply deleting the entire collection
-
successful DELETE request will usually generate 200 (if the deleted resource is returned) or 204 (if no content is returned)
-
failed DELETE request will usually generate 404 (if the resource cannot be found) or 410 (if the resource was already deleted before)
5.2.6. HEAD
HEAD
requests are used retrieve to header information of single resources and resource collections.
-
HEAD has exactly the same semantics as
GET
, but returns headers only, no body. -
can be used to check for existance of a resource
5.2.7. OPTIONS
OPTIONS
are used to inspect the available operations (HTTP methods) of a given endpoint.
-
OPTIONS
requests usually either return a comma separated list of methods (provided by anAllow:
-Header) or as a structured list of link templates
Note
|
OPTIONS is rarely implemented, though it could be used to self-describe the full
functionality of a resource.
|
5.2.8. Fulfill Safeness and Idempotency Properties
An operation MUST be either …
-
idempotent, i.e. operation will produce the same results if executed once or multiple times (note: this does not necessarily mean returning the same status code)
or…
-
safe, i.e. must not have side effects such as state changes
Method implementations MUST fulfill the following basic properties:
HTTP method | safe | idempotent |
---|---|---|
OPTIONS |
Yes |
Yes |
HEAD |
Yes |
Yes |
GET |
Yes |
Yes |
PUT |
No |
Yes |
POST |
No |
No |
DELETE |
No |
Yes |
PATCH |
No |
No |
6. HTTP Status Codes
Note
|
This section relates applies to RESTful APIs only. |
This guideline groups the following rules for HTTP status codes usage:
-
You MUST not invent new HTTP status codes; only use standardized HTTP status codes and consistent with its intended semantics.
-
You SHOULD use the most specific HTTP status code for your concrete resource request processing status or error situation.
-
You SHOULD provide good documentation in the API definition when using HTTP status codes that are less commonly used and not listed below.
Our list of most commonly used and best understood HTTP status codes:
6.1. Success Codes
Code | Meaning | Methods |
---|---|---|
200 |
OK. RFC 7231 |
All |
201 |
Created. MAY return created resource. But MUST return created resource when Hypermedia is used. RFC 7231 |
|
202 |
Accepted. RFC 7231 |
|
204 |
No Content. RFC 7231 |
|
207 |
Multi-Status. MUST NOT be used when Hypermedia is applied. RFC 2518 |
|
6.2. Redirection Codes
Code | Meaning | Methods |
---|---|---|
301 |
Moved Permanently. RFC 7231 |
All |
303 |
See Other. RFC 7231 |
|
304 |
Not Modified. RFC 7232 |
|
6.3. Client Side Error Codes
Code | Meaning | Methods |
---|---|---|
400 |
Bad Request. RFC 7231 |
All |
401 |
Unauthorized. RFC 7235 |
All |
403 |
Forbidden. RFC 7231 |
All |
404 |
Not found. RFC 7231 |
All |
405 |
Method Not Allowed. RFC 7231 |
All |
406 |
Not Acceptable. RFC 7231 |
All |
408 |
Request Timeout. RFC 7231 |
All |
409 |
Conflict RFC 7231 |
|
410 |
Gone. RFC 7231 |
All |
412 |
Precondition Failed. RFC 7232 |
|
415 |
Unsupported Media Type. RFC 7231 |
|
423 |
Locked. RFC 2518 |
|
428 |
Precondition Required. RFC 6585 |
All |
429 |
Too Many Requests. RFC 6585 |
All |
7. Error Handling
7.1. gRPC Error Handling
Note
|
This section applies to gRPC APIs only. |
7.1.1. Error Model
The error model is logically defined by google.rpc.Status
, an instance
of which is returned to the client when an API error occurs. The
following code snippet shows the overall design of the error model:
package google.rpc;
message Status {
// A simple error code that can be easily handled by the client. The
// actual error code is defined by `google.rpc.Code`.
int32 code = 1;
// A developer-facing human-readable error message in English. It should
// both explain the error and offer an actionable resolution to it.
string message = 2;
// Additional error information that the client code can use to handle
// the error, such as retry delay or a help link.
repeated google.protobuf.Any details = 3;
}
7.1.2. Error Codes
gRPC APIs MUST use the canonical error codes defined by
google.rpc.Code
. Individual APIs should avoid defining additional
error codes, since developers are very unlikely to write logic to
handle a large number of error codes.
7.1.3. Error Messages
The error message should help users understand and resolve the API error easily and quickly. In general, consider the following guidelines when writing error messages:
-
Do not assume the user is an expert user of your API. Users could be client developers, operations people, IT staff, or end-users of apps.
-
Do not assume the user knows anything about your service implementation or is familiar with the context of the errors (such as log analysis).
-
When possible, error messages should be constructed such that a technical user (but not necessarily a developer of your API) can respond to the error and correct it.
-
Keep the error message brief. If needed, provide a link where a confused reader can ask questions, give feedback, or get more information that doesn’t cleanly fit in an error message. Otherwise, use the details field to expand.
7.1.4. Error Details
gRPC APIs define a set of standard error payloads for error details,
which you can find in google/rpc/error_details.proto
. These cover
the most common needs for API errors. Like error codes, error details
should use these standard payloads whenever possible.
Additional error detail types should only be introduced if they can assist application code to handle the errors. If the error information can only be handled by humans, rely on the error message content and let developers handle it manually rather than introducing new error detail types. Note that if additional error detail types are introduced, they must be explicitly registered.
7.1.5. RPC Mapping
Different RPC protocols map the error model differently. For gRPC, the
error model is natively supported by the generated code and the
runtime library in each supported language. You can find out more in
gRPC’s API documentation (for example, see gRPC Java’s
io.grpc.Status
).
7.1.6. Error Localization
The message field in google.rpc.Status
is developer-facing and must be
in English.
If a user-facing error message is needed, use
google.rpc.LocalizedMessage
as your details field. While the message
field in google.rpc.LocalizedMessage
can be localized, ensure that the
message field in google.rpc.Status
is in English.
By default, the API service should use the authenticated user’s locale
or HTTP Accept-Language
header to determine the language for the
localization.
7.1.7. Handling Errors
Below is a table containing all of the gRPC error codes defined in
google.rpc.Code
and a short description of their cause. To handle an
error, you can check the description for the returned status code and
modify your call accordingly.
HTTP | RPC | Description |
---|---|---|
200 |
OK |
No error. |
400 |
INVALID_ARGUMENT |
Client specified an invalid argument. Check error message and error details for more information. |
400 |
FAILED_PRECONDITION |
Request can not be executed in the current system state, such as deleting a non-empty directory. |
400 |
OUT_OF_RANGE |
Client specified an invalid range. |
401 |
UNAUTHENTICATED |
Request not authenticated due to missing, invalid, or expired OAuth token. |
403 |
PERMISSION_DENIED |
Client does not have sufficient permission. This can happen because the OAuth token does not have the right scopes, the client doesn’t have permission, or the API has not been enabled for the client project. |
404 |
NOT_FOUND |
A specified resource is not found, or the request is rejected by undisclosed reasons, such as whitelisting. |
409 |
ABORTED |
Concurrency conflict, such as read-modify-write conflict. |
409 |
ALREADY_EXISTS |
The resource that a client tried to create already exists. |
429 |
RESOURCE_EXHAUSTED |
Either out of resource quota or reaching rate limiting. The client should look for google.rpc.QuotaFailure error detail for more information. |
499 |
CANCELLED |
Request cancelled by the client. |
500 |
DATA_LOSS |
Unrecoverable data loss or data corruption. The client should report the error to the user. |
500 |
UNKNOWN |
Unknown server error. Typically a server bug. |
500 |
INTERNAL |
Internal server error. Typically a server bug. |
501 |
NOT_IMPLEMENTED |
API method not implemented by the server. |
503 |
UNAVAILABLE |
Service unavailable. Typically the server is down. |
504 |
DEADLINE_EXCEEDED |
Request deadline exceeded. If it happens repeatedly, consider reducing the request complexity. |
7.2. REST Error Handling
Use Problem JSON in case of an error is a MUST.
RFC 7807 defines the media type application/problem+json. Operations should return that (together with a suitable status code) when any problem occurred during processing and you can give more details than the status code itself can supply, whether it be caused by the client or the server (i.e. both for 4xx or 5xx errors).
APIs MAY define custom problems types with extension properties, according to their specific needs.
Example
responses:
...
'418':
description: I am a Teapot
schema:
$ref: 'https://api.development.vwd.com/definitions/collections/basedefinitions.yaml#/ProblemResponse'
8. Hypermedia
Note
|
Applies to RESTful APIs only |
The server can guide the client from one application state (in terms of RESTful services: resource) to another by sending links and forms in its representations. R. Fielding’s thesis [rfielding] calls this "Hypermedia as the engine of application state" (HATEOAS). Other publications also talk about just "Hypermedia" or even "Connectedness" [richruby].
In a well-connected service, the client can make a path through the application by following links and filling out forms. In a service that’s not connected, the client must use predefined rules to construct every URI it wants to visit. Therefor the client needs to maintain its own application state. With hypermedia the client state (not the server state) is contained in the server’s representation.
Forms guide the client through the process of modifying resource state with a PUT or POST request, by giving hints about what representations are acceptable.
8.1. Reasons for Hypermedia
-
Clients that rely on rules for URI construction are more brittle
-
No need or out-of-band documentation that describes how the client should work
-
Rules for URI construction change
-
Rules or URI construction can be complex // e.g. in Google Maps tile-based navigation system, which uses coordinates as paramerters. Consider that this is necessary, since the server should contain no state.
8.2. Reasons for Hypermedia in Client-Side Apps
For many people it doesn’t make sense to include hypermedia links and forms in responses because hypermedia clients won’t know what to do with them anyway. This is true and hypermedia clients need human understanding to understand the goals these links represent and to make a choice. The interaction between hypermedia clients and and the human can be summarized in Bill Verplank’s Do-Feel-Know model [verplank]:
WHILE WITH-USER: FEEL(previously-rendered-display) KNOW(select-action) WITH-MACHINE: DO(RUN selected-action on TARGET website and RENDER) END-WHILE
The human (user) is required to feel (sense) the information presented by the hypermedia client (machine) and needs to know what he wants to do next. The machine is then instructed to run the HTTP action on the resource and present the result to the human.
8.3. Internal vs. External API
This shows in what circumstances hypermedia is a RECOMMENDED choice. Whenever a client application should be developed, that is human assisted, it makes sense to maintain the client side state by using hypermedia links. When an API is designed without a user facing application in mind, hypermedia is less useful but can still be a valuable choice by eliminating error-prone URI construction.
Therefor we recommend…
8.4. Use REST Maturity Level 2
REQUIRED is a good implementation of REST Maturity Level 2 as it enables us to build resource-oriented APIs that make full use of HTTP verbs and status codes. You can see this expressed by many rules throughout these guidelines, e.g.:
-
Avoid Actions - Think About Resources
-
Keep URLs Verb-Free
-
Use HTTP Methods Correctly
-
Use Meaningful HTTP Status Codes
Although this is not HATEOAS, it should not prevent you from designing proper link relationships in your APIs as stated in rules below.
8.5. Use REST Maturity Level 3
We RECOMMEND to implement REST Maturity Level 3 in APIs for user-facing web applications. HATEOAS comes with additional API complexity without real value in our SOA context where client and server interact via REST APIs and provide complex business functions as part of an SaaS platform.
Our major concerns regarding the promised advantages of HATEOAS:
-
We follow the API First principle with APIs explicitly defined outside the code with standard specification language. HATEOAS does not really add value for SOA client engineers in terms of API self-descriptiveness: a client engineer finds necessary links and usage description (depending on resource state) in the API reference definition anyway.
-
Generic HATEOAS clients which need no prior knowledge about APIs and explore API capabilities based on hypermedia information provided, is a theoretical concept that we haven’t seen working in practise and does not fit to our SOA set-up. The OpenAPI description format (and tooling based on OpenAPI) doesn’t provide sufficient support for HATEOAS either.
-
In practice relevant HATEOAS approximations (e.g. following specifications like HAL or JSON API) support API navigation by abstracting from URL endpoint and HTTP method aspects via link types. So, Hypermedia does not prevent clients from required manual changes when domain model changes over time.
-
Hypermedia make sense for humans, less for SOA machine clients. We would expect use cases where it may provide value more likely in the frontend and human facing service domain.
-
Hypermedia does not prevent API clients to implement shortcuts and directly target resources without discovering them
8.6. Use Common Hypertext Controls
When embedding links to other resources into representations you must use the common hypertext control object. It contains at least one attribute:
-
href
: The URI of the resource the hypertext control is linking to. All our API are using HTTP(s) as URI scheme.
In API that contain any hypertext controls, the attribute name href
is reserved for usage within hypertext controls.
The schema for hypertext controls can be derived from this model:
HttpLink:
description: A base type of objects representing links to resources.
type: object
properties:
href:
description: Any URI that is using http or https protocol
type: string
format: uri
required: [ "href" ]
The name of an attribute holding such a HttpLink
object specifies
the relation between the object that contains the link and the linked
resource. Implementations should use names from the
IANA
Link Relation Registry whenever appropriate.
Specific link objects may extend the basic link type with additional attributes, to give additional information related to the linked resource or the relationship between the source resource and the linked one.
E.g. a service providing "Person" resources could model a person who
is married with some other person with a hypertext control that
contains attributes which describe the other person (id
, name
) but
also the relationship "spouse" between between the two persons
(since
):
{
"id": "446f9876-e89b-12d3-a456-426655440000",
"name": "Peter Mustermann",
"spouse": {
"href": "https://...",
"since": "1996-12-19",
"id": "123e4567-e89b-12d3-a456-426655440000",
"name": "Linda Mustermann"
}
}
Hypertext controls are allowed anywhere within a JSON model. While this specification would RECOMMEND HAL, we actually don’t enforce the usage of HAL above other hypermedia formats.
8.7. Use Simple Hypertext Controls for Pagination and Self-References
Hypertext controls for pagination inside collections and
self-references SHOULD use a simple URI value in combination with
their corresponding
link
relations (next
, prev
, first
, last
, self
) instead of the
extensible common hypertext control
See Pagination for information how to best represent pageable collections.
9. Data Formats
Note
|
This section applies to RESTful APIs only. |
9.1. Use JSON as the Body Payload
It is REQUIRED to JSON-encode the body payload. The JSON payload must follow RFC-7159 by having (if possible) a serialized object as the top-level structure, since it would allow for future extension. This also applies for collection resources where one naturally would assume an array. See the Pagination section for an example.
10. Data Types
Note
|
This section applies to gRPC APIs only. |
10.1. Use Protobuf wrapper types for scalar data types
It is REQUIRED to wrap scalar data types (string, int32, float, etc.) to their respective wrapped data types (StringValue, Int32Value, FloatValue) when it is necessary to check fields for absence.
When using the wrapped types, the generated code has a hasXXX() function to check for the presence of a field
Below is a list with all wrapped data types. See also google/protobuf/wrappers.proto
for the definition.
Scalar | Wrapper type |
---|---|
bool |
BoolValue |
int32 |
Int32Value |
int64 |
Int64Value |
uint32 |
UInt32Value |
uint64 |
UInt64Value |
float |
FloatValue |
double |
DoubleValue |
string |
StringValue |
Background: gRPC uses Protobuf version 3 as transport protocol. This version of protobuf does not support optional fields anymore - all fields are required. So for scalar data types, a missing field is filled up with the respective default value, e.g. 0 for int32 or an empty string for strings. When it is important to distinguish between the absence of a field or default value of a field, the only technical way to determine is to use this wrapper types.
11. Performance
11.1. Reduce Bandwidth Needs and Improve Responsiveness
APIs SHOULD support techniques for reducing bandwidth based on client needs. This holds for APIs that (might) have high payloads and/or are used in high-traffic scenarios like the public Internet and telecommunication networks. Typical examples are APIs used by mobile web app clients with (often) less bandwidth connectivity.
Common techniques include:
-
gzip compression
-
querying field filters to retrieve a subset of resource attributes (see Support Filtering of Resource Fields below)
-
paginate lists of data items (see Pagination)
-
ETag
andIf-(None-)Match
headers to avoid re-fetching of unchanged resources (see Consider using ETag together with If-(None-)Match header) -
pagination for incremental access of larger (result) lists
Each of these items is described in greater detail below.
11.2. Use gzip Compression
It is RECOMMENDED to compress the payload of your APIs responses with gzip, unless there’s a good reason not to - for example, you are serving so many requests that the time to compress becomes a bottleneck. This helps to transport data faster over the network (fewer bytes) and makes frontends respond faster.
Though gzip compression might be the default choice for server payload, the server should also support payload without compression and its client control via Accept-Encoding request header — see also [RFC 7231 Section 5.3.4](http://tools.ietf.org/html/rfc7231#section-5.3.4). The server should indicate used gzip compression via the Content-Encoding header.
11.3. Support Filtering of Resource Fields
Depending on your use case and payload size, you SHOULD significantly reduce network bandwidth need by supporting filtering of returned entity fields. Here, the client can determine the subset of fields he wants to receive via the fields query parameter - example see Google AppEngine APIs partial response:
11.3.1. Unfiltered
GET http://api.example.org/resources/123 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "cddd5e44-dae0-11e5-8c01-63ed66ab2da5",
"name": "John Doe",
"address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
"birthday": "1984-09-13",
"partner": {
"id": "1fb43648-dae1-11e5-aa01-1fbc3abb1cd0",
"name": "Jane Doe",
"address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
"birthday": "1988-04-07"
}
}
11.3.2. Filtered
GET http://api.example.org/resources/123?fields=name,partner.name HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"name": "John Doe",
"partner": {
"name": "Jane Doe"
}
}
The following listing shows a more complex example and how to use a filter if your response object contains e.g. a security and performance object:
GET http://api.example.org/resources/123?fields=security.isin,security.description,performance.performance_1_year HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"security": {
"isin": "DE0007100000"
"description": "Daimler AG NA O.N."
},
"performance": {
"performance_1_year": "13,776"
}
}
As illustrated by the above examples, field filtering should be done via request parameter "fields" with value range defined by the following BNF grammar.
<fields> ::= <field_path> | <field_path> <FIELD_SEPARATOR> <fields> <field_path> ::= <field_id> | <field_id> <PATH_SEPARATOR> <field_path> <field_id> ::= <LETTER> | <LETTER> <LETTER_DIGIT> | <LETTER> <field_id_inner> <LETTER_DIGIT> <field_id_inner> ::= <DASH_LETTER_DIGIT> | <DASH_LETTER_DIGIT> <field_id_inner> <FIELD_SEPARATOR> ::= "," <PATH_SEPARATOR> ::= "." <DASH_LETTER_DIGIT> ::= <DASH> | <LETTER> | <DIGIT> <LETTER_DIGIT> ::= <LETTER> | <DIGIT> <DASH> ::= "-" | "_" <LETTER> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" <DIGIT> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
A fields_expression
as defined by the grammar describes the properties of an object, i.e. name
returns only the name
property of the root object. name,partner.name
returns the name
and partner
properties where partner
itself is also an object and only its name
property is returned.
Note
|
OpenAPI doesn’t allow you to formally specify whether depending on a given parameter will return different parts of the specified result schema. Explain this in English in the parameter description. |
12. Pagination
Note
|
This section relates applies to RESTful APIs only. |
12.1. Support Pagination
Access to lists of data items MUST support pagination for best client side batch processing and iteration experience. This holds true for all lists that are (potentially) larger than just a few hundred entries.
There are two page iteration techniques:
-
Offset/Limit-based pagination: numeric offset identifies the first page entry
-
Cursor-based - aka key-based - pagination: a unique key element identifies the first page entry
There’re some rules and restrictions:
-
if you don’t specify a limit on your request the default-limit is set to 10
-
you can get max. 100 items per request (with limit=100) 
-
you can get max. 1000 items at all (e.g. with offset=900&limit=100 or offset=975&limit=25). You should provide additional filter if you reach this limit of 1000 items. The number of max items can be increased e.g. for private collections (portfolio-positions, portfolio-accounts…) where you MUST show more content - BUT DON’T DO THAT PER DEFAULT. If you hit the max-items you can give a hint like "more rows are available - filter your result". An additional export-endpoint can be offered if necessary.
The technical conception of pagination should also consider user experience related issues. As mentioned in this article, jumping to a specific page is far less used than navigation via next/previous page links. This favours cursor-based over offset-based pagination.
12.2. Use Pagination Links Where Applicable
-
API implementing HATEOS MAY use simplified hypertext controls) for pagination within collections.
The collection should contain additional metadata about the collection or the current page (e.g. index
, page_size
) when necessary. Links to the single items of you collection should be defined at every item.
You should avoid providing a total count in your API unless there’s a clear need to do so. Very often, there are systems and performance implications to supporting full counts, especially as datasets grow and requests become complex queries or filters that drive full scans (e.g., your database might need to look at all candidate items to count them). While this is an implementation detail relative to the API, it’s important to consider your ability to support serving counts over the life of a service.
If the collection consists of links to other resources, the collection name should use IANA registered link relations as names whenever appropriate, but use plural form.
12.3. Deliver the absolute number of items in the response
Optionally you MAY return the number of items which would be returned by the query max. This means that we either get the actual number of the items. This value is an integer.
-
page_size: the number of items on the current page
-
accessible_size: the maximum number of items accessible via the api (1000 (or higher on special endpoints) or size if size < 1000)
-
size: the total number of items available (but perhaps not reachable if > accessible_size)
12.4. Example
E.g. a service for articles could represent the collection of hyperlinks to an article’s authors
like that:
{ _meta: {...}, _data: [ { "article": { "id": "ca68619b-205d-11e7-b09e-0030486074ca", ... "_meta": {...}, "_attributes": {...}, "_links": { "self": { "name": "string", "href": "string", "templated": false } }, }, }, {...}, ], _pagination: { "self": { "href": "https://.../articles/xyz/authors/", "templated": "123e4567-e89b-12d3-a456-426655440000", "name": "Kent Beck" }, "first": {...}, "next": {...}, "prev": {...}, "last": {...}, "index": 0, "page_size": 5, "accessible_size": 1000, "size": 17563 } }
13. Grouping
Note
|
This section relates applies to RESTful APIs only. |
13.1. Support Grouping
Access to lists of data items CAN support grouping for best client side drill down into groups. You only have to implement that if requested.
If you want to support grouping you MUST do it that way.
13.2. Request a group
You MUST add the optional query parameter groupings
to the api request. You MAY have multible groupings at one request.
13.3. Group Responses
The group result items are of the same type as the not grouped list items.
Every single item in the list MUST have _meta
(Hashmap of meta data) and _links
.
Only the properties, whitch are grouped are filled. All other are empty.
You MUST fill into the _meta
the count
value, that contains the size of the elements in that group.
You Must fill the _links' with a `self
link, that shows how to drill down into that group.
13.4. Examples
13.4.1. Flat List
E.g. a service for articles with the authors name could response the flat list like that:
Request
https://.../articles
Response
{ _meta: {...}, _data: [ { "article": { "id": "ca68619b-205d-11e7-b09e-0030486074ca", "author": "John" ... "_meta": {...}, "_links": {...} }, { "article": { "id": "ca68619b-205d-11e7-b09e-0030486074cb", "author": "John" ... "_meta": {...}, "_links": {...} }, { "article": { "id": "ca68619b-205d-11e7-b09e-0030486074cc", "author": "Paul" ... "_meta": {...}, "_links": {...} }, }, {...}, ], _pagination: {...} }
13.4.2. Grouped List
Request
https://.../articles?groupings=author
Response
{ _meta: {...}, _data: [ { "article": { "author": "John" "_meta": { "count": 2 }, "_links": { "self": { "name": "self", "href": "https://.../article?author=John", "templated": false } }, }, }, { "article": { "author": "Paul" "_meta": { "count": 1 }, "_links": { "self": { "name": "self", "href": "https://.../article?author=Paul", "templated": false } }, }, }, ], _pagination: {...} }
14. Common Headers
This section describes a handful of headers, which we found raised the most questions in our daily usage, or which are useful in particular circumstances but not widely known.
14.1. Use Content Headers Correctly
REQUIRED are content or entity headers are headers with a Content-
prefix. They describe the content of the body of the message and they can be used in both, HTTP requests and responses. Commonly used content headers include but are not limited to:
-
Content-Disposition
can indicate that the representation is supposed to be saved as a file, and the proposed file name. -
Content-Encoding
indicates compression or encryption algorithms applied to the content. -
Content-Length
indicates the length of the content (in bytes). -
Content-Language
indicates that the body is meant for people literate in some human language(s). -
Content-Location
indicates where the body can be found otherwise. -
Content-Range
is used in responses to range requests to indicate which part of the requested resource representation is delivered with the body. -
Content-Type
indicates the media type of the body content.
14.2. Use Content-Location Header
The Content-Location header is OPTIONAL and can be used in successful write operations (PUT, POST or PATCH) or read operations (GET, HEAD) to guide caching and signal a receiver the actual location of the resource transmitted in the response body. This allows clients to identify the resource and to update their local copy when receiving a response with this header.
The Content-Location header can be used to support the following use cases:
-
For reading operations GET and HEAD, a different location than the requested URI can be used to indicate that the returned resource is subject to content negotiations, and that the value provides a more specific identifier of the resource.
-
For writing operations PUT and PATCH, an identical location to the requested URI, can be used to explicitly indicate that the returned resource is the current representation of the newly created or updated resource.
-
For writing operations POST and DELETE, a content location can be used to indicate that the body contains a status report resource in response to the requested action, which is available at provided location.
Note
|
When using the Content-Location header, the Content-Type header has to be set as well. For example: |
GET /products/123/images HTTP/1.1
HTTP/1.1 200 OK
Content-Type: image/png
Content-Location: /products/123/images?format=raw
14.3. Consider using ETag together with If-(None-)Match header
When creating or updating resources it MAY be necessary to expose conflicts and to prevent the lost update
problem. This can be best accomplished by using the ETag
header together with the If-Match
and
If-None-Match
. The contents of an ETag: <entity-tag>
header is either (a) a hash of the response body, (b) a hash of the last modified field of the entity, or
(c) a version number or identifier of the entity version.
To expose conflicts between concurrent update operations via PUT, POST, or PATCH, the If-Match: <entity-tag>
header can be used to force the server to check whether the version of the updated entity is conforming to the
requested <entity-tag>
. If no matching entity is found, the operation is supposed a to respond with status
code 412 - precondition failed.
Beside other use cases, the If-None-Match:
header with parameter *
can be used in a similar way to expose
conflicts in resource creation. If any matching entity is found, the operation is supposed a to respond with
status code 412 - precondition failed.
The ETag
, If-Match
, and If-None-Match
headers definitions can be used from https://api.development.vwd.com/definitions/headers.openapi-2.0.yaml
15. Versioning
15.1. Practice
Versioning MUST NOT be used.
Sometimes APIs can undergo changes without causing major problems, because Hypermedia connected RESTful APIs use link relations to hide changes in the URL format or because the message format of gRPC APIs allow for optional fields. Breaking changes are changes introduced into the resource, request and response design that cause errors on the consumer side.
So it is RECOMMENDED to rely on backwards-compatible changes in order to avoid unnecessary new endpoints and force users of the API to update. gRPC and RESTful APIs have different capabilities in terms of backwards compatibility so we have to differ between those in the following.
15.2. Deprecation
In order to make it transparent for the consumer how long an API will be supported it is REQUIRED to make a statement how long APIs will be maintained, when a newer version under a new endpoint is released. Old endpoints MUST be marked as deprecated.
15.3. RESTful Compatibility
15.3.1. Backwards-compatible changes
-
Adding of properties is backwards compatible if the added fields have a valid absent value semantic and the
PATCH
instead ofPUT
is used to update a resource. Otherwise thePUT
semantics are violated. -
Removing fields is a breaking change, since the passed data cannot be consumed by the server properly.
-
Adding of properties to the request body is backwards compatible if the added fields have a valid absent value semantic and the
PATCH
instead ofPUT
is used to update a resource. Otherwise thePUT
semantics are violated. -
Removing fields from the request body is a breaking change, since the passed data cannot be consumed by the server properly.
15.3.2. Backwards-incompatible changes
-
Changes in the URL format (resource path, query parameters) introduce a new API and thus a new version when consumers do not rely on Hypermedia.
-
Changes in the request and response body might possibly break a consumer.
JSON and XML request and response bodies might contain non backwards compatible changes if the client is not designed in a way to deal with added or removed properties.
15.4. gRPC Compatibility
The detailed list of gRPC backwards-compatible and non backwards-compatible can be obtained from the Google Cloud APIs Design Guide which can be applied to all gRPC APIs.
Appendix B: Literature and references
Appendix C: Copyright notices
- Frontpage Image
-
api-design-guideline.jpg taken from https://hackernoon.com/restful-api-designing-guidelines-the-best-practices-60e1d954e7c9
Appendix D: Changelog
- v1.0.0
-
Promoted current status as first official version
- v0.9.0 (draft)
-
Added collections types, included base definitions as served schemata
- v0.2.0 (draft)
-
Reviewed and partially revised by API team
- v0.1.0 (draft)
-
Initial API spec based on Google gRPC and Zalando REST API guidelines