1. Introduction

BAHAG’s software architecture centers around decoupled microservices that provide functionality via RESTful APIs with a JSON payload. Small teams own, deploy and operate these microservices in their (team) accounts. Our APIs most purely express what our systems do, and are therefore highly valuable business assets.

The API Vision

Our strategy emphasizes developing lots of public APIs that can be used by all BAHAG teams or in the future by external business partners.

Designing high-quality, long-lasting APIs will become even more critical for us when we start to develop our business capabilities in the new product teams.

But also non-public APIs, i.e. those that are only used in one application, should meet the same high quality standards.

With this in mind, we’ve adopted "API First" as one of our key engineering principles. Microservices development begins with API definition outside the code and ideally involves ample  peer-review feedback to achieve high-quality APIs. API First encompasses a set of quality-related standards and fosters a peer review culture including a lightweight review procedure. We encourage our teams to follow them to ensure that our APIs:

  • are easy to understand and learn

  • are general and abstracted from specific implementation and use cases

  • are robust and easy to use

  • have a common look and feel

  • follow a consistent RESTful style and syntax

  • are consistent with other teams’ APIs and our global architecture

Ideally, all BAHAG APIs will look like the same author created them.

Conventions used in these guidelines

The requirement level keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" used in this document (case insensitive) are to be interpreted as described in  RFC 2119.

BAHAG specific information

The BAHAG Teams are responsible to fulfill these guidelines during API development and are encouraged to contribute to guideline evolution via pull requests. The documentation is written in  asciidoc, a further evolution of  markdown.

These guidelines will, to some extent, remain work in progress as our work evolves, but teams can confidently follow and trust them.

In case guidelines are changing, following rules apply:

  • existing APIs don’t have to be changed, but we recommend it

  • clients of existing APIs have to cope with these APIs based on outdated rules

  • new APIs have to respect the current guidelines

Furthermore you should keep in mind that once an API becomes public externally available, it has to be re-reviewed and changed according to current guidelines - for sake of overall consistency.

This guideline is based on the  Zalando REST API guidelines. But every rule has been evaluated and adopted in the BAHAG context.

2. Principles

API design principles

REST is centered around business (data) entities exposed as resources that are identified via URIs and can be manipulated via standardized CRUD-like methods using different representations, and hypermedia. RESTful APIs tend to be less use-case specific and come with less rigid client / server coupling and are more suitable for an ecosystem of (core) services providing a platform of APIs to build diverse new business services. We apply the RESTful web service principles to all kind of application (micro-) service components, independently from whether they provide functionality via the internet or intranet. Furthermore we apply the RESTful web service principles to all kind of appliation specific communication between frontend and backend.

We prefer REST-based APIs with JSON payloads. Be liberal in what you accept, be conservative in what you send

API as a product

As mentioned above, BAHAG is transforming its services and applications to a rich set of products following a Software as a Platform (SaaP) model for all internal teams and future external business partners. As a company we want to deliver products to our (internal and external) customers which can be consumed like a service.

Platform products provide their functionality via (public) APIs; hence, the design of our APIs should be based on the API as a Product principle:

  • Treat your API as product and act like a product owner

  • Put yourself into the place of your customers; be an advocate for their needs

  • Emphasize simplicity, comprehensibility, and usability of APIs to make them irresistible for client engineers

  • Actively improve and maintain API consistency over the long term

  • Make use of customer feedback and provide service level support

Embracing 'API as a Product' facilitates a service ecosystem, which can be evolved more easily and used to experiment quickly with new business ideas by recombining core capabilities. It makes the difference between agile, innovative product service business built on a platform of APIs and ordinary enterprise integration business where APIs are provided as "appendix" of existing products to support system integration and optimised for local server-side realization.

Understand the concrete use cases of your customers and carefully check the trade-offs of your API design variants with a product mindset. Avoid short-term implementation optimizations at the expense of unnecessary client side obligations, and have a high attention on API quality and client developer experience.

API first

In a nutshell API First requires two aspects:

  • define APIs first, before coding its implementation, using a standard specification language

  • get early review feedback from peers and client developers

By defining APIs outside the code, we want to facilitate early review feedback and also a development discipline that focus service interface design on…​

  • profound understanding of the domain and required functionality

  • generalized business entities / resources, i.e. avoidance of use case specific APIs (except the frontend-backend interface in one specific application)

  • clear separation of WHAT vs. HOW concerns, i.e. abstraction from implementation aspects — APIs should be stable even if we replace complete service implementation including its underlying technology stack

Moreover, API definitions with standardized specification format also facilitate…​

  • single source of truth for the API specification; it is a crucial part of a contract between service provider and client users

  • infrastructure tooling for API discovery, API GUIs, API documents, automated quality checks

Elements of API First are also this API Guidelines and a standardized API review process as to get early review feedback from peers and client developers. Peer review is important for us to get high quality APIs, to enable architectural and design alignment and to supported development of client applications decoupled from service provider engineering life cycle.

It is important to learn, that API First is not in conflict with the agile development principles that we love. Service applications should evolve incrementally — and so its APIs. Of course, our API specification will and should evolve iteratively in different cycles; however, each starting with draft status and early team and peer review feedback. API may change and profit from implementation concerns and automated testing feedback. API evolution during development life cycle may include breaking changes for not yet productive features and as long as we have aligned the changes with the clients. Hence, API First does not mean that you must have 100% domain and requirement understanding and can never produce code before you have defined the complete API and get it confirmed by peer review.

It is crucial to request and get early feedback — as early as possible, but not before the API changes are comprehensive with focus to the next evolution step and have a certain quality (including API Guideline compliance), already confirmed via team internal reviews.

API Internationalization

We as BAUHAUS are an international company and our APIs should be provided for all our touchpoints and in all BAUHAUS countries. That means we have

  • one API for all countries. No country specific APIs.

  • one central business logic for all countries. No country specific business logic.

  • the APIs must support all languages of the BAUHAUS countries.

3. General guidelines

We live the API first principle

The titles are marked with the corresponding labels: MUST, SHOULD, MAY.

MUST follow API first principle [B100]

You must follow the API First Principle, more specifically:

MUST provide API specification using Open API [B101] COVERED BY API-LINTER LINTER SUPPORT

We use the  Open API specification as standard to define API specification files. API designers are required to provide the API specification using a single self-contained YAML file to improve readability. We use the version from Open API 3.0 or above.

The API specification files should be subject to version control using a source code management system.

Hint: A good way to explore Open API 3.0 is to navigate through the  Open API specification mind map and use a plugin for your IDE to create your first API. To explore and validate/evaluate existing APIs the  Swagger Editor may be a good starting point.

Hint: We do not yet provide guidelines for  GraphQL. We currently focus on resource oriented HTTP/REST API style (and related tooling and infrastructure support) for general purpose peer-to-peer microservice communication. We will evaluate GraphQL in the future but at the moment, it will not be part of the guideline.

MUST only use durable and immutable remote references [B102]

Normally, API specification files must be self-contained, i.e. files should not contain references to local or remote content, e.g. ../fragment.yaml#/element. The reason is, that the content referred to is in general not durable and not immutable. As a consequence, the semantic of an API may change in unexpected ways.

However, you may use remote references to resources accessible by the following service URLs.

As we control these URLs, we ensure that their content is durable and immutable. This allows to define API specifications by using fragments published via this sources, as suggested in MUST specify success and error responses [B121] COVERED BY API-LINTER LINTER SUPPORT.

MUST provide API examples and documentation [B229] COVERED BY API-LINTER

Inside the API Specification, for improving client developer experience, especially of engineers that are less experienced in using this API, the specification must include the following API aspects:

  • API scope, purpose, and use cases

  • examples of every parameter, request body and response

  • edge cases, error situation details with status code and repair hints

MUST write APIs using U.S. English [B104]

Since English will be the predominant language in all development teams in the future, all specifications and documentation must be written in U.S. English. It is not necessary to translate existing documentation into U.S. English.

4. Meta information

All important meta information is provided via the API

MUST/SHOULD contain API meta information [B105] COVERED BY API-LINTER LINTER SUPPORT

API specifications must contain the following Open API meta information to allow for API management (see also  info object):

  • #/info/title as (unique) identifying, functional descriptive name of the API

  • #/info/version to distinguish API specifications versions following MUST use semantic versioning [B106] COVERED BY API-LINTER LINTER SUPPORT

  • #/info/description containing a proper description of the API

  • #/info/contact containing the responsible team as an object {name,url,email, channel}, extending  contact object. The name of the team must be the official abbreviation of the product team.

  • #/info/contact/name - name of the responsible team

  • #/info/contact/url - optional link to the homepage of the team or the api if there is any

  • #/info/contact/email - optional email address of the contact person of the API

  • #/info/x-sunset-date - optional date in UTC when the API will become deprecated from production

  • #/info/x-shutdown-date - optional date in UTC when the API will be disabled or undeployed from production

Following Open API extension properties must be provided in addition:

  • #/info/x-channel - link to the teams channel for API announcements

  • #/info/x-monitoring - link to the monitoring dashboard of the API

  • #/info/x-alerting - link to teams channel for API alertings

  • #/info/x-audience MUST provide API audience [B108] COVERED BY API-LINTER LINTER SUPPORT

  • #/info/x-app - name of the Apigee App that is used in the implementaion of the API for having access to other APIs (should only be one). Must be provided if the API is calling other APIs in the implementation.

the following extensions are mandatory and needed by team security for classifying the API

  • #/info/x-apigee-proxy - the name of the proxy in apigee

  • #/info/x-business-critical - can be either 'false' or 'true'

  • #/info/x-authentication-method - multiple values are allowed. possible values are 'apikey' and 'oauth2' (If the API is using both authentication methods, the value has to be set to 'apikey,oauth2').

  • #/info/x-data-types - multiple values are allowed. possible values are 'customer', 'article', 'order', 'price', 'employee', 'invoice', 'store', 'other'

  • #/info/x-gdpr - if the data managed by this API conations GDPR relevant data, the value has to be set to 'tru'. Can be either 'false' or 'true'

  • #/info/x-touchpoints-types - contains the main touchpoints the API is designedf for. multiple values are allowed. possible values are 'consumerapp', 'salesapp', 'onlineshop'

  • #/info/x-restrictions - if the access to your API is restricted to certain doamins or ip ranges, the value has to bet se et to 'tru'. Can be either 'false' or 'true'

Info: For audience company-internal, external-partner and external-public, you must provide the API meta information as described above. For audience component-internal and business-unit-internal you should provide the information.

MUST use semantic versioning [B106] COVERED BY API-LINTER LINTER SUPPORT

Open API allows to specify the API specification version in #/info/version. To share a common semantic of version information we expect API designers to comply to  Semantic Versioning 2.0 rules 1 to 8 and 11 restricted to the format <MAJOR>.<MINOR>.<PATCH> for versions as follows:

  • Increment the MAJOR version when you make incompatible API changes after having aligned this changes with consumers,

  • Increment the MINOR version when you add new functionality in a backwards-compatible manner, and

  • Optionally increment the PATCH version when you make backwards-compatible bug fixes or editorial changes not affecting the functionality.

Additional Notes:

  • Pre-release versions ( rule 9) and build metadata ( rule 10) must not be used in API version information.

  • While patch versions are useful for fixing typos etc, API designers are free to decide whether they increment it or not.

  • API designers should consider to use API version 0.y.z ( rule 4) for initial API design.

Example:

openapi: 3.0.1
info:
  title: Order Service API
  description: API for <...>
  version: 1.3.7
  <...>

MUST provide API audience [B108] COVERED BY API-LINTER LINTER SUPPORT

A marketplace Zalando has an interesting approach to classifying their APIs based upon who is consuming them. It isn’t just about APIs being published publicly, or privately, they actually have standardized their definition, and have established an OpenAPI vendor extension, so that the definition is machine readable and available via their OpenAPI.

Providing a pretty interesting way of establishing the scope and reach of each API in a way that makes each API owner think deeply about who they are/should be targeting with the service. Done in a way that makes the audience focus machine readable, and available as part of it’s OpenAPI definition which can be then used across discovery, documentation, and through API governance and security.

Based on the Zalando approach, each API must be classified with respect to the intended target audience supposed to consume the API, to facilitate differentiated standards on APIs for discoverability, changeability, quality of design and documentation, as well as permission granting. We differentiate the following API audience groups with clear organisational and legal boundaries:

component-internal

This is often referred to as a team internal API or a product internal API. The API consumers with this audience are restricted to applications of the same functional component which typically represents a specific product with clear functional scope and ownership. All services of a functional component / product are owned by a specific dedicated owner and engineering team(s). Typical examples of component-internal APIs are APIs being used by internal helper and worker services or that support service operation. As an example, the  vif-admin API is an component-internal API, because it should only be used by one frontend or application (in this case  vif-admin application).

business-unit-internal

The API consumers with this audience are restricted to applications of a specific product portfolio owned by the same business unit. Thinking of the new product teams, an API used by multiple services and applications in only one product team is referred to be business-unit-internal.

company-internal

The API consumers with this audience are restricted to applications owned by the business units of the same the company (BAUHAUS, BAHAG etc.)

external-partner

The API consumers with this audience are restricted to applications of business partners of the company owning the API and the company itself. This usecase is planned for the future.

external-public

APIs with this audience can be accessed by anyone with Internet access. This usecase is planned for the future.

Note: a smaller audience group is intentionally included in the wider group and thus does not need to be declared additionally.

The API audience is provided as API meta information in the info-block of the Open API specification and must conform to the following specification:

/info/x-audience:
  type: string
  x-extensible-enum:
    - component-internal
    - business-unit-internal
    - company-internal
    - external-partner
    - external-public
  description: |
    Intended target audience of the API. Relevant for standards around
    quality of design and documentation, reviews, discoverability,
    changeability, and permission granting.

Note: Exactly one audience per API specification is allowed. For this reason a smaller audience group is intentionally included in the wider group and thus does not need to be declared additionally. If parts of your API have a different target audience, we recommend to split API specifications along the target audience — even if this creates redundancies.

Example:

openapi: 3.0.1
info:
  x-audience: company-internal
  title: Order Helper Service API
  description: API for <...>
  version: 1.2.4
  <...>

5. Security

We use OAuth 2.0 for securing the endpoints

MUST secure endpoints with OAuth 2.0 [B180] COVERED BY API-LINTER LINTER SUPPORT

Every API endpoint needs to be secured using OAuth 2.0. Please refer to the  Authentication section of the official Open API specification on how to specify security definitions in your API.

The following code snippet shows how to define the authorization scheme using a bearer token (e.g. JWT token).

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

The next code snippet applies this security scheme to all API endpoints. The bearer token of the client must have additionally the scopes scope_1 and scope_2.

security:
  - BearerAuth: [ scope_1, scope_2 ]

SHOULD define and assign permissions (scopes) [B181] COVERED BY API-LINTER LINTER SUPPORT

We dont use permissions in our APIs as long we don’t have a concept and an infrastructure for mapping permissions to roles. This rule will be a MUST once we have established the infrastructure for mapping permissions to roles.

APIs should define permissions to protect their resources. Thus, at least one permission should be assigned to each endpoint. Permissions are defined as shown in the MUST secure endpoints with OAuth 2.0 [B180] COVERED BY API-LINTER LINTER SUPPORT.

The naming schema for permissions corresponds to the naming schema for MUST follow naming convention for hostnames [B141] COVERED BY API-LINTER LINTER SUPPORT. Please refer to SHOULD follow naming convention for permissions (scopes) [B182] for designing permission names.

APIs should stick to component specific permissions without resource extension to avoid governance complexity of too many fine grained permissions. For the majority of use cases, restricting access to specific API endpoints using read and write is sufficient for controlling access for client types like merchant or retailer business partners, customers or operational staff. However, in some situations, where the API serves different types of resources for different owners, resource specific scopes may make sense.

Some examples for standard and resource-specific permissions:

Domain Resource Access Type Example

product-compliance

guidelines

write

product-compliance.guidelines.write

product-information

products

read

product-information.products.read

product-price

prices

read

product-price.prices.read

order

write

order.write

After permission names are defined and the permission is declared in the security definition at the top of an API specification, it should be assigned to each API operation by specifying a  security requirement like this:

paths:
 /business-partners/{partner-id}:
    get:
      summary: Retrieves information about a business partner
      security:
        - BearerAuth: [ business-partner.read ]

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 permission in this case. It is the user id and always available as OAuth2 default scope.

paths:
  /public-information:
    get:
      summary: Provides public information about ...
               Accessible by any user; no permissions needed.
      security:
        - BearerAuth: [ uid ]

Hint: you need not explicitly define the "Authorization" header; it is a standard header so to say implicitly defined via the security section.

SHOULD follow naming convention for permissions (scopes) [B182]

We dont use permissions in our APIs as long we don’t have a concept and an infrastructure for mapping permissions to roles. This rule will be a MUST once we have established the infrastructure for mapping permissions to roles.

Permission names in APIs must conform to the following naming pattern:

<permission> ::= <standard-permission> |  -- should be sufficient for majority of use cases
                 <resource-permission> |  -- for special security access differentiation use cases
                 <pseudo-permission>      -- used to explicitly indicate that access is not restricted

<standard-permission> ::= <domain>.<access-mode>
<resource-permission> ::= <domain>.<resource>.<access-mode>
<pseudo-permission>   ::= uid

<application-id>      ::= [a-z][a-z0-9-]*  -- application identifier
<resource-name>       ::= [a-z][a-z0-9-]*  -- free resource identifier
<access-mode>         ::= read | write    -- might be extended in future

This pattern is compatible with the previous definition.

6. Compatibility

Make your API robust and avoid changes that break compatibility

MUST not break backward compatibility [B183]

Change APIs, but keep all consumers running. Consumers usually have independent release lifecycles, focus on stability, and avoid changes that do not provide additional value. APIs are contracts between service providers and service consumers that cannot be broken via unilateral decisions.

There are two techniques to change APIs without breaking them:

  • follow rules for compatible extensions

  • introduce new API versions and still support older versions

We strongly encourage using compatible API extensions and discourage versioning (see SHOULD avoid versioning [B190] and [B191] below). The following guidelines for service providers (SHOULD prefer compatible extensions [B184] LINTER SUPPORT) and consumers (MUST prepare clients accept compatible API extensions [B185] ) enable us (having Postel’s Law in mind) to make compatible changes without versioning.

Note: There is a difference between incompatible and breaking changes. Incompatible changes are changes that are not covered by the compatibility rules below. Breaking changes are incompatible changes deployed into operation, and thereby breaking running API consumers. Usually, incompatible changes are breaking changes when deployed into operation. However, in specific controlled situations it is possible to deploy incompatible changes in a non-breaking way, if no API consumer is using the affected API aspects (see also Deprecation guidelines).

Hint: Please note that the compatibility guarantees are for the "on the wire" format. Binary or source compatibility of code generated from an API definition is not covered by these rules. If client implementations update their generation process to a new version of the API definition, it has to be expected that code changes are necessary.

SHOULD prefer compatible extensions [B184] LINTER SUPPORT

API designers should apply the following rules to evolve RESTful APIs for services in a backward-compatible way:

  • Add only optional, never mandatory fields.

  • Never change the semantic of fields

  • Input fields may have (complex) constraints being validated via server-side business logic. Never change the validation logic to be more restrictive and make sure that all constraints are clearly defined in description.

  • Enum ranges can be reduced when used as input parameters, only if the server is ready to accept and handle old range values too. Enum range can be reduced when used as output parameters.

  • Enum ranges cannot be extended when used for output parameters — clients may not be prepared to handle it. However, enum ranges can be extended when used for input parameters.

  • Use x-extensible-enum, if range is used for output parameters and likely to be extended with growing functionality. It defines an open list of explicit values and clients must be agnostic to new values.

  • Support redirection in case an URL has to change 301 (Moved Permanently).

MUST prepare clients accept compatible API extensions [B185]

Service clients should apply the robustness principle:

  • Be conservative with API requests and data passed as input, e.g. avoid to exploit definition deficits like passing megabytes of strings with unspecified maximum length.

  • Be tolerant in processing and reading data of API responses, more specifically…​

Service clients must be prepared for compatible API extensions of service providers:

  • Be tolerant with unknown fields in the payload (see also Fowler’s  "TolerantReader" post), i.e. ignore new fields but do not eliminate them from payload if needed for subsequent PUT requests.

  • Be prepared that x-extensible-enum return parameter may deliver new values; either be agnostic or provide default behavior for unknown values.

  • Be prepared to handle HTTP status codes not explicitly specified in endpoint definitions. Note also, that status codes are extensible. Default handling is how you would treat the corresponding 2xx code (see RFC 7231 Section 6).

  • Follow the redirect when the server returns HTTP status code 301 (Moved Permanently).

SHOULD design APIs conservatively [B186]

Designers of service provider APIs should be conservative and accurate in what they accept from clients:

  • Unknown input fields (regardless if they are mandatory or optional) in payload or URL should not be ignored; servers should provide error feedback to clients via an HTTP 400 response code.

  • Be accurate in defining input data constraints (like formats, ranges, lengths etc.) — and check constraints and return dedicated error information in case of violations.

  • Prefer being more specific and restrictive (if compliant to functional requirements), e.g. by defining length range of strings. It may simplify implementation while providing freedom for further evolution as compatible extensions.

Not ignoring unknown input fields is a specific deviation from Postel’s Law (e.g. see also
 The Robustness Principle Reconsidered) and a strong recommendation. Servers might want to take different approach but should be aware of the following problems and be explicit in what is supported:

  • Ignoring unknown input fields is actually not an option for PUT, since it becomes asymmetric with subsequent GET response and HTTP is clear about the PUT replace semantics and default roundtrip expectations (see RFC 7231 Section 4.3.4). Note, accepting (i.e. not ignoring) unknown input fields and returning it in subsequent GET responses is a different situation and compliant to PUT semantics.

  • Future extensions of the input data structure might be in conflict with already ignored fields and, hence, will not be compatible, i.e. break clients that already use this field but with different type.

In specific situations, where a (known) input field is not needed anymore, it either can stay in the API definition with "not used anymore" description or can be removed from the API definition as long as the server ignores this specific parameter.

MUST always return JSON objects as top-level data structures [B187]

In a response body, you must always return a JSON object (and not e.g. an array) as a top level data structure to support future extensibility. JSON objects support compatible extension by additional attributes. This allows you to easily extend your response and e.g. add pagination later, without breaking backwards compatibility. See SHOULD use pagination links where applicable [B172] for an example.

Maps (see SHOULD define maps using additionalProperties [B112] ), even though technically objects, are also forbidden as top level data structures, since they don’t support compatible, future extensions.

SHOULD avoid versioning [B190]

When changing your RESTful APIs, do so in a compatible way and avoid generating additional API versions. Multiple versions can significantly complicate understanding, testing, maintaining, evolving, operating and releasing our systems ( supplementary reading).

If changing an API can’t be done in a compatible way, then proceed in one of these three ways:

  • create a new resource (variant) in addition to the old resource variant

  • create a new service endpoint — i.e. a new application with a new API (with a new domain name)

  • create a new API version supported in parallel with the old API by the same microservice

MUST use URI versioning [B192]

With URI versioning a (major) version number is included in the path, e.g. /v1/customer-masterdata/1 or /v1/customer-masterdata/2, see also MUST use semantic versioning [B106] COVERED BY API-LINTER LINTER SUPPORT. We always point to a specific version, there is no path thats leads to the latest version.

7. Deprecation

We have a well defined process to shutdown an existing API

Sometimes it is necessary to phase out an API endpoint, an API version, or an API feature, e.g. if a field or parameter is no longer supported or a whole business functionality behind an endpoint is supposed to be shut down. As long as the API endpoints and features are still used by consumers these shut downs are breaking changes and not allowed. To progress the following deprecation rules have to be applied to make sure that the necessary consumer changes and actions are well communicated and aligned using deprecation and sunset dates.

MUST obtain approval of clients before API shut down [B173]

Before shutting down an API, version of an API, or API feature the producer must make sure, that all clients have given their consent on a sunset date. Producers should help consumers to migrate to a potential new API or API feature by providing a migration manual and clearly state the time line for replacement availability and sunset (see also SHOULD add Deprecation and Sunset header to responses [B177] ). Once all clients of a sunset API feature are migrated, the producer may shut down the deprecated API feature.

MUST collect external partner consent on deprecation time span [B174]

If the API is consumed by any external partner, the API owner must define a reasonable time span that the API will be maintained after the producer has announced deprecation. All external partners must state consent with this after-deprecation-life-span, i.e. the minimum time span between official deprecation and first possible sunset, before they are allowed to use the API.

MUST reflect deprecation in API specifications [B175]

The API deprecation must be part of the API specification.

If an API endpoint (operation object), an input argument (parameter object), an in/out data object (schema object), or on a more fine grained level, a schema attribute or property should be deprecated, the producers must set deprecated: true for the affected element and add further explanation to the description section of the API specification. If a future shut down is planned, the producer must provide a sunset date and document in details what consumers should use instead and how to migrate.

MUST monitor usage of deprecated API scheduled for sunset [B176]

Owners of an API, API version, or API feature used in production that is scheduled for sunset must monitor the usage of the sunset API, API version, or API feature in order to observe migration progress and avoid uncontrolled breaking effects on ongoing consumers. See also SHOULD monitor API usage [B131] .

SHOULD add Deprecation and Sunset header to responses [B177]

During the deprecation phase, the producer should add a Deprecation: <date-time> (see  draft: RFC Deprecation HTTP Header) and - if also planned - a Sunset: <date-time> (see RFC 8594) header on each response affected by a deprecated element (see MUST reflect deprecation in API specifications [B175] ).

The Deprecation header can either be set to true - if a feature is retired -, or carry a deprecation time stamp, at which a replacement will become/became available and consumers must not on-board any longer (see MUST not start using deprecated APIs [B179] ). The optional Sunset time stamp carries the information when consumers latest have to stop using a feature. The sunset date should always offer an eligible time interval for switching to a replacement feature.

Deprecation: Tue, 31 Dec 2024 23:59:59 GMT
Sunset: Wed, 31 Dec 2025 23:59:59 GMT

If multiple elements are deprecated the Deprecation and Sunset headers are expected to be set to the earliest time stamp to reflect the shortest interval consumers are expected to get active.

Note: adding the Deprecation and Sunset header is not sufficient to gain client consent to shut down an API or feature.

Hint: You must not use the Warning header to provide the deprecation info to clients. However, Warning header has a less specific semantics, will be obsolete with  draft: RFC HTTP Caching, and our syntax was not compliant with RFC 7234 — Warning header.

SHOULD add monitoring for Deprecation and Sunset header [B178]

Clients should monitor the Deprecation and Sunset headers in HTTP responses to get information about future sunset of APIs and API features (see SHOULD add Deprecation and Sunset header to responses [B177] ). We recommend that client owners build alerts on this monitoring information to ensure alignment with service owners on required migration task.

MUST not start using deprecated APIs [B179]

Clients must not start using deprecated APIs, API versions, or API features.

8. JSON Guidelines

We are using JSON payloads for encoding structured data

These guidelines provides recommendations for defining JSON data at BAHAG. JSON here refers to RFC 7159 (which updates RFC 4627), the "application/json" media type and custom JSON media types defined for APIs. The guidelines clarifies some specific cases to allow BAHAG JSON data to have an idiomatic form across teams and services.

The first some of the following guidelines are about property names, the later ones about values.

MUST property names must be ASCII snake_case (and never camelCase): ^[a-z_][a-z_0-9]*$ [B109] COVERED BY API-LINTER LINTER SUPPORT

Property names are restricted to ASCII strings. The first character must be a letter, or an underscore, and subsequent characters can be a letter, an underscore, or a number.

(It is recommended to use _ at the start of property names only for keywords like _links.)

Rationale: No established industry standard exists, but many popular Internet companies prefer snake_case: e.g. GitHub, Stack Exchange, Twitter. Others, like Google and Amazon, use both - but not only camelCase. It’s essential to establish a consistent look and feel such that JSON looks as if it came from the same hand.

SHOULD represent enumerations as strings [B110]

Strings are a reasonable target for values that are by design enumerations.

SHOULD declare enum values using UPPER_SNAKE_CASE format [B111] COVERED BY API-LINTER

Enum values (using enum or x-extensible-enum) need to consistently use the upper-snake case format, e.g. VALUE or YET_ANOTHER_VALUE. This approach allows to clearly distinguish values from properties or other elements.

Note: This does not apply where the actual exact values are coming from some outside source, e.g. for language codes from ISO 639-1, or when declaring possible values for a sort parameter.

SHOULD define maps using additionalProperties [B112]

A "map" here is a mapping from string keys to some other type. In JSON this is represented as an object, the key-value pairs being represented by property names and property values. In Open API schema (as well as in JSON schema) they should be represented using additionalProperties with a schema defining the value type. Such an object should normally have no other defined properties.

The map keys don’t count as property names in the sense of MUST property names must be ASCII snake_case (and never camelCase): ^[a-z_][a-z_0-9]*$ [B109] COVERED BY API-LINTER LINTER SUPPORT, and can follow whatever format is natural for their domain. Please document this in the description of the map object’s schema.

Here is an example for such a map definition (the translations property):

components:
  schemas:
    Message:
      description:
        A message together with translations in several languages.
      type: object
      properties:
        message_key:
          type: string
          description: The message key.
        translations:
          description:
            The translations of this message into several languages.
            The keys are [IETF BCP-47 language tags](https://tools.ietf.org/html/bcp47).
          type: object
          additionalProperties:
            type: string
            description:
              the translation of this message into the language identified by the key.

An actual JSON object described by this might then look like this:

{ "message_key": "color",
  "translations": {
    "de": "Farbe",
    "en-US": "color",
    "en-GB": "colour",
    "eo": "koloro",
    "nl": "kleur"
  }
}

MUST pluralize array names [B113] COVERED BY API-LINTER LINTER SUPPORT

To indicate they contain multiple values prefer to pluralize array names. This implies that object names should in turn be singular.

MUST not use null for boolean properties [B114]

Schema based JSON properties that are by design booleans must not be presented as nulls. A boolean is essentially a closed enumeration of two values, true and false. If the content has a meaningful null value, strongly prefer to replace the boolean with enumeration of named values or statuses - for example accepted_terms_and_conditions with true or false can be replaced with terms_and_conditions with values yes, no and unknown.

MUST use same semantics for null and absent properties [B115]

Open API 3.x allows to mark properties as required and as nullable to specify whether properties may be absent ({}) or null ({"example":null}). If a property is defined to be not required and nullable (see 2nd row in Table below), this rule demands that both cases must be handled in the exact same manner by specification.

The following table shows all combinations and whether the examples are valid:

required nullable {} {"example":null}

true

true

No

Yes

false

true

Yes

Yes

true

false

No

No

false

false

Yes

No

While API designers and implementers may be tempted to assign different semantics to both cases, we explicitly decide against that option, because we think that any gain in expressiveness is far outweighed by the risk of clients not understanding and implementing the subtle differences incorrectly.

MUST not use null for empty arrays [B116]

Empty array values can unambiguously be represented as the empty list, [].

SHOULD name date/time properties with _at suffix [B117] COVERED BY API-LINTER LINTER SUPPORT

Dates and date-time properties should end with _at to distinguish them from boolean properties which otherwise would have very similar or even identical names:

  • created_at rather than created,

  • modified_at rather than modified,

  • occurred_at rather than occurred, and

  • returned_at rather than returned.

Hint: When defining time periods with two properties like valid from and valid until whe should use these names:

  • valid_from and

  • valid_until

SHOULD name user properties with _by suffix COVERED BY API-LINTER [B118]

Properties which specify the user together with an action should end with _by to ensure an idiomatic form.

  • created_by rather than creator,

  • modified_by rather than modifier and

  • owned_by rather than owner.

MUST define dates properties compliant with RFC 3339 [B119] 

Use the date and time formats defined by RFC 3339:

  • for "date" use strings matching date-fullyear "-" date-month "-" date-mday, for example: 2015-05-28

  • for "date-time" use strings matching full-date "T" full-time, for example 2015-05-28T14:07:17Z

Note that the  Open API format "date-time" corresponds to "date-time" in the RFC and 2015-05-28 for a date (note that the Open API format "date" corresponds to "full-date" in the RFC). Both are specific profiles, a subset of the international standard ISO 8601.

A zone offset may be used (both, in request and responses) — this is simply defined by the standards. Dates are restricted to UTC without offsets. For example 2015-05-28T14:07:17Z. From experience we have learned that zone offsets are not easy to understand and often not correctly handled. Note also that zone offsets are different from local times that might be including daylight saving time. Localization of dates should be done by the services that provide user interfaces, if required.

When it comes to storage, all dates must be consistently stored in UTC without a zone offset. Localization must be done locally by the services that provide user interfaces, if required.

SHOULD define time durations and intervals properties conform to ISO 8601 [B120]

Schema based JSON properties that are by design durations and intervals could be strings formatted as recommended by ISO 8601( Appendix A of RFC 3339 contains a grammar for durations).

Example Meaning

P3Y6M4DT12H30M5S

three years, six months, four days, twelve hours, thirty minutes, and five seconds

P1M

one month

PT1M

one minute

9. Data formats

We are using standard formats in JSON payloads

MUST use JSON to encode structured data [B150] LINTER SUPPORT

Use JSON-encoded body payload for transferring structured data. The JSON payload must follow RFC 7159 using a JSON object as top-level data structure (if possible) to allow for future extension. This also applies to collection resources, where one naturally would assume an array. See also MUST always return JSON objects as top-level data structures [B187] .

Additionally, the JSON payload must comply to RFC 7493, particularly

As a consequence, a JSON payload must

MAY use non JSON media types for binary data or alternative content representations [B151]

Other media types may be used in following cases:

  • Transferring binary data or data whose structure is not relevant. This is the case if payload structure is not interpreted and consumed by clients as is. Example of such use case is downloading images in formats JPG, PNG, GIF.

  • In addition to JSON version alternative data representations (e.g. in formats PDF, DOC, XML) may be made available through content negotiation.

SHOULD encode embedded binary data in base64url [B152]

Exposing binary data using an alternative media type is generally preferred. See MAY use non JSON media types for binary data or alternative content representations [B151] .

If an alternative content representation is not desired then binary data should be embedded into the JSON document as a base64url-encoded string property following RFC 7493 Section 4.4.

SHOULD prefer standard media type name application/json [B153] COVERED BY API-LINTER LINTER SUPPORT

Use the standard media type name application/json or application/problem+json for MUST use problem JSON [B126] COVERED BY API-LINTER LINTER SUPPORT.

Custom media types beginning with x bring no advantage compared to the standard media type for JSON, and make automated processing more difficult. They are also discouraged by RFC 6838.

SHOULD use standardized property formats [B154]

 JSON Schema and  Open API define several universally useful property formats. The following table contains some additional formats that are particularly useful in an e-commerce environment.

Please notice that the list is not exhaustive and everyone is encouraged to propose additions.

type format Specification Example

integer

int32

7721071004

integer

int64

772107100456824

integer

bigint

77210710045682438959

number

float

IEEE 754-2008

3.1415927

number

double

IEEE 754-2008

3.141592653589793

number

decimal

3.141592653589793238462643383279

string

bcp47

BCP 47

"en-DE"

string

byte

RFC 7493

"dGVzdA=="

string

date

RFC 3339

"2019-07-30"

string

date-time

RFC 3339

"2019-07-30T06:43:40.252Z"

string

email

RFC 5322

"example@bahag.com"

string

gtin-13

GTIN

"5710798389878"

string

hostname

RFC 1034

"www.bauhaus.info"

string

ipv4

RFC 2673

"104.75.173.179"

string

ipv6

RFC 2673

"2600:1401:2::8a"

string

iso-3166

ISO 3166-1 alpha-2

"DE"

string

iso-4217

ISO 4217

"EUR"

string

iso-639

ISO 639-1

"de"

string

json-pointer

RFC 6901

"/items/0/id"

string

password

"secret"

string

regex

ECMA 262

"^[a-z0-9]+$"

string

time

RFC 3339

"06:43:40.252Z"

string

uri

RFC 3986

"https://www.bauhaus.info/"

string

uri-template

RFC 6570

"/users/{id}"

string

uuid

RFC 4122

"e2ab873e-b295-11e9-9c02-…​"

MUST use standard date and time formats [B155]

JSON payload

Read more about date and time format in MUST define dates properties compliant with RFC 3339 [B119] .

HTTP headers

Http headers including the proprietary headers use the HTTP date format defined in RFC 7231.

SHOULD use standards for country, language and currency codes [B156]

Use the following standard formats for country, language and currency codes:

MUST define format for number and integer types [B157] COVERED BY API-LINTER LINTER SUPPORT

Whenever an API defines a property of type number or integer, the precision must be defined by the format as follows to prevent clients from guessing the precision incorrectly, and thereby changing the value unintentionally:

type format specified value range

integer

int32

integer between -231 and 231-1

integer

int64

integer between -263 and 263-1

integer

bigint

arbitrarily large signed integer number

number

float

IEEE 754-2008/ISO 60559:2011 binary32 decimal number

number

double

IEEE 754-2008/ISO 60559:2011 binary64 decimal number

number

decimal

arbitrarily precise signed decimal number

The precision must be translated by clients and servers into the most specific language types. E.g. for the following definitions the most specific language types in Java will translate to BigDecimal for Money.amount and int or Integer for the OrderList.page_size:

components:
  schemas:
    Money:
      type: object
      properties:
        amount:
          type: number
          description: Amount expressed as a decimal number of major currency units
          format: decimal
          example: 99.95
       ...

    OrderList:
      type: object
      properties:
        page_size:
          type: integer
          description: Number of orders in list
          format: int32
          example: 42

MUST Provide unit names in international format [B225]

In order to make the use / consumption of multiple APIs within an application as stable and consistent as possible every API that exposes units such as length, volumes, weight etc. must expose the unit by the international version.

ISO-Unit Sign English German

MTR

m

meter

Meter

CMT

cm

centimeter

Zentimeter

KGM

kg

kilogram

Kilogramm

GRM

g

gram

Gramm

LFM

lfm

running meter

laufender Meter

CT

-

Box (packaging unit)

Karton (Verpackungseinheit)

BE

-

bundle

Bündel (Verpackungseinheit)

DZN

-

dozen

Dutzend (Verpackungseinheit)

MAY Provide units as special [B226]

In addtion to the iso code an API may include the scientific notation / sign of a unit as a separate attribute (eg. SQM ⇒ m², EUR ⇒ €)

MAY Provide units in local language [B227]

An API may provide localized names for units in a separate attribute. This should be done in accordance with the rules stated for I18N and L10n.

10. Common data types

Use the same business object structures across all APIs without reinventing them new#

Definitions of data objects that are good candidates for wider usage. Below you can find a list of common data types used in the guideline:

MUST use the common money object [B128]

Use the following common money structure:

Money:
  type: object
  properties:
    amount:
      type: number
      description: >
        The amount describes unit and subunit of the currency in a single value,
        where the integer part (digits before the decimal point) is for the
        major unit and fractional part (digits after the decimal point) is for
        the minor unit.
      format: decimal
      example: 99.95
    currency:
      type: string
      description: 3 letter currency code as defined by ISO-4217
      format: iso-4217
      example: EUR
  required:
    - amount
    - currency

APIs are encouraged to include a reference to the global schema for Money.

SalesOrder:
  properties:
    grand_total:
      $ref: 'https://stash.bahag.com/projects/BAHAG/repos/bahag-api-guideline/browse/models/problem-1.0.0.yaml#/Money'

Please note that APIs have to treat Money as a closed data type, i.e. it’s not meant to be used in an inheritance hierarchy. That means the following usage is not allowed:

{
  "amount": 19.99,
  "currency": "EUR",
  "discounted_amount": 9.99
}

Cons

  • Less flexible since both amounts are coupled together, e.g. mixed currencies are impossible

A better approach is to favor  composition over inheritance:

{
    "price": {
      "amount": 19.99,
      "currency": "EUR"
    },
    "discounted_price": {
      "amount": 9.99,
      "currency": "EUR"
    }
}

Pros

  • No inheritance, hence no issue with the substitution principle

  • No coupling, i.e. mixed currencies is an option

  • Prices are now self-describing, atomic values

Notes

Please be aware that some business cases (e.g. transactions in Bitcoin) call for a higher precision, so applications must be prepared to accept values with unlimited precision, unless explicitly stated otherwise in the API specification.

Examples for correct representations (in EUR):

  • 42.20 or 42.2 = 42 Euros, 20 Cent

  • 0.23 = 23 Cent

  • 42.0 or 42 = 42 Euros

  • 1024.42 = 1024 Euros, 42 Cent

  • 1024.4225 = 1024 Euros, 42.25 Cent

Make sure that you don’t convert the "amount" field to float / double types when implementing this interface in a specific language or when doing calculations. Otherwise, you might lose precision. Instead, use exact formats like Java’s BigDecimal to avoid rounding errors. See Stack Overflow for more info.

Some JSON parsers (NodeJS’s, for example) convert numbers to floats by default. We’ve decided on "decimal" as our amount format. It is not a standard Open API format, but should help us to avoid parsing numbers as float / doubles.

MUST use common field names and semantics [B129] COVERED BY API-LINTER LINTER SUPPORT

There exist a variety of field types that are required in multiple places. To achieve consistency across all API implementations, you must use common field names and semantics whenever applicable.

Generic fields

There are some data fields that come up again and again in API data:

Example JSON schema:

tree_node:
  type: object
  properties:
    id:
      description: the identifier of this node
      type: string
    created_at:
      description: when got this node created
      type: string
      format: 'date-time'
    modified_at:
      description: when got this node last updated
      type: string
      format: 'date-time'
    type:
      type: string
      enum: [ 'LEAF', 'NODE' ]
    parent_node_id:
      description: the identifier of the parent node of this node
      type: string
  example:
    id: '123435'
    created_at: '2017-04-12T23:20:50.52Z'
    modified_at: '2017-04-12T23:20:50.52Z'
    type: 'LEAF'
    parent_node_id: '534321'

These properties are not always strictly necessary, but making them idiomatic allows API client developers to build up a common understanding of BAHAG’s resources. There is very little utility for API consumers in having different names or value types for these fields across APIs.

To foster a consistent look and feel using simple hypertext controls for paginating and iterating over collection values the response objects should follow a common pattern using the below field semantics:

  • self: the link or cursor in a pagination response or object pointing to the same collection object or page.

  • first: the link or cursor in a pagination response or object pointing to the first collection object or page.

  • prev: the link or cursor in a pagination response or object pointing to the previous collection object or page.

  • next: the link or cursor in a pagination response or object pointing to the next collection object or page.

  • last: the link or cursor in a pagination response or object pointing to the last collection object or page.

Pagination responses should contain the following additional array field to transport the page content:

  • items: array of resources, holding all the items of the current page (items may be replaced by a resource name).

To simplify user experience, the applied query filters may be returned using the following field (see also GET with body):

  • query: object containing the query filters applied in the search request to filter the collection resource.

As Result, the standard response page using Link TODOpagination links is defined as follows:

ResponsePage:
  type: object
  properties:
    self:
      description: Pagination link pointing to the current page.
      type: string
      format: uri
    first:
      description: Pagination link pointing to the first page.
      type: string
      format: uri
    prev:
      description: Pagination link pointing to the previous page.
      type: string
      format: uri
    next:
      description: Pagination link pointing to the next page.
      type: string
      format: uri
    last:
      description: Pagination link pointing to the last page.
      type: string
      format: uri

     query:
       description: >
         Object containing the query filters applied to the collection resource.
       type: object
       properties: ...

     items:
       description: Array of collection items.
       type: array
       required: false
       items:
         type: ...

The response page may contain additional metadata about the collection or the current page.

11. API naming

All APIs follow the same naming conventions

MUST use functional naming schema [B140]

Functional naming is a powerful, yet easy way to align global resources as host, permission, and event names within an the application landscape. It helps to preserve uniqueness of names while giving readers meaningful context information about the addressed component. Besides, the most important aspect is, that it allows to keep APIs stable in the case of technical and organizational changes (BAHAG for example maintains an internal naming convention for backends and frontends).

A unique functional-name is assigned to each functional component serving an API. It is built of the domain name of the functional group the component is belonging to and a unique a short identifier for the functional component itself:

<functional-name>      ::= <functional-domain>-<functional-component>, e.g. "product-compliance"
<functional-domain>    ::= [a-z][a-z0-9-]* -- managed functional group of components, e.g. "product"
<functional-component> ::= [a-z][a-z0-9-]* -- name of API owning functional component, e.g. "compliance"

Example: Currently, you can access the component-internal API with the internal host qm-domain. If you choose to make your API company-internal (that means every team can build an application using your api), you should choose a host like product-compliance for providing your API like GET product-compliance.api.bauhaus.info/documents/{document-id}/. The name "product-compliance" here is just an example, in has to be set by the responsible product team of the resource.

Functional Naming

Audience

must

external-public, external-partner, company-internal, business-unit-internal

may

component-internal

Please see the following rules for detailed functional naming patterns: * MUST follow naming convention for hostnames [B141] COVERED BY API-LINTER LINTER SUPPORT

MUST follow naming convention for hostnames [B141] COVERED BY API-LINTER LINTER SUPPORT

Hostnames in APIs must, respectively should conform to the functional naming depending on the MUST provide API audience [B108] COVERED BY API-LINTER LINTER SUPPORT as follows (see MUST use functional naming schema [B140] for details and <functional-name> definition):

<hostname>             ::= <functional-hostname> | <application-hostname>

<functional-hostname>  ::= <functional-name>.api.bauhaus.info

MUST use lowercase separate words with hyphens for path segments [B142] COVERED BY API-LINTER LINTER SUPPORT

Example:

order-omnichannel.api.bauhaus.info/dashboards/ship-to-store

This applies to concrete path segments (ship-to-store) and not the names of path parameters.

MUST use lowercase separate words with hyphens for path parameters COVERED BY API-LINTER [B143]

Example:

product-compliance.api.bauhaus.info/documents/{document-id}

This applies to path parameters like {document-id}.

MUST use snake_case (never camelCase) for query parameters [B144] COVERED BY API-LINTER LINTER SUPPORT

Examples:

document_id, order_id, billing_address

SHOULD prefer hyphenated-pascal-case for HTTP header fields [B145] LINTER SUPPORT

This is for consistency in your documentation (most other headers follow this convention). Avoid camelCase (without hyphens). Exceptions are common abbreviations like "ID."

Examples:

Accept-Encoding
Apply-To-Redirect-Ref
Disposition-Notification-Options
Original-Message-ID

See Common headers and [proprietary-headers] sections for more guidance on HTTP headers.

MUST pluralize resource names [B146] COVERED BY API-LINTER LINTER SUPPORT

Usually, a collection of resource instances is provided (at least the API should be ready here).

product-asset.api.bauhaus.info/products/{product-id}/images

Exception: the pseudo identifier self used to specify a resource endpoint where the resource identifier is provided by authorization information (see MUST identify resources and sub-resources via path segments [B164] LINTER SUPPORT).

MUST not use /api as base path [B147] COVERED BY API-LINTER

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 — we encourage you to maintain two different API specifications and MUST provide API audience [B108] COVERED BY API-LINTER LINTER SUPPORT . For both APIs, you must not use /api as base path.

We see API’s base path as a part of deployment variant configuration. Therefore, this information has to be declared in the  server object.

MUST use normalized paths without empty path segments and trailing slashes [B148] COVERED BY API-LINTER LINTER SUPPORT

You must not specify paths with duplicate or trailing slashes, e.g. /customers//addresses or /customers/. As a consequence, you must also not specify or use path variables with empty string values.

Reasoning: Non standard paths have no clear semantics. As a result, behavior for non standard paths varies between different HTTP infrastructure components and libraries. This may leads to ambiguous and unexpected results during request handling and monitoring.

We recommend to implement services robust against clients not following this rule. All services should  normalize request paths before processing by removing duplicate and trailing slashes. Hence, the following requests should refer to the same resource:

GET /orders/{order-id}
GET /orders/{order-id}/
GET /orders//{order-id}

Note: path normalization is not supported by all framework out-of-the-box. Services are required to support at least the normalized path while rejecting all alternatives paths, if failing to deliver the same resource.

MUST stick to conventional query parameters [B149] 

If you provide query support for searching, sorting, filtering, and paginating, you must stick to the following naming conventions:

12. Resources

APIs are focused on domain-specific resources

MUST avoid actions — think about resources [B158]

REST is all about your resources, so consider the domain entities that take part in web service interaction, and aim to model your API around these using the standard HTTP methods as operation indicators. For instance, if an application has to lock articles explicitly so that only one user may edit them, create an article lock with PUT or POST instead of using a lock action.

Request:

PUT /article-locks/{article-id}

The added benefit is that you already have a service for browsing and filtering article locks.

SHOULD model complete business processes [B159]

An API should contain the complete business processes containing all resources representing the process. This enables clients to understand the business process, foster a consistent design of the business process, allow for synergies from description and implementation perspective, and eliminates implicit invisible dependencies between APIs.

In addition, it prevents services from being designed as thin wrappers around databases, which normally tends to shift business logic to the clients.

SHOULD define useful resources [B160]

As a rule of thumb resources should be defined to cover 90% of all its client’s use cases. A useful resource should contain as much information as necessary, but as little as possible. A great way to support the last 10% is to allow clients to specify their needs for more/less information by supporting filtering and SHOULD reduce bandwidth needs and improve responsiveness [B193] .

MUST keep URLs verb-free [B161]

The API describes resources, so the only place where actions should appear is in the HTTP methods. In URLs, use only nouns. Instead of thinking of actions (verbs), it’s often helpful to think about putting a message in a letter box: e.g., instead of having the verb cancel in the url, think of sending a message to cancel an order to the cancellations letter box on the server side.

MUST use domain-specific resource names [B162]

API resources represent elements of the application’s domain model. Using domain-specific nomenclature for resource names helps developers to understand the functionality and basic semantics of your resources. It also reduces the need for further documentation outside the API definition. For example, "sales-order-items" is superior to "order-items" in that it clearly indicates which business object it represents. Along these lines, "items" is too general.

MUST use URL-friendly resource identifiers: [a-zA-Z0-9:._\-/]* [B163]

To simplify encoding of resource IDs in URLs, their representation must only consist of ASCII strings using letters, numbers, underscore, minus, colon, period, and - on rare occasions - slash.

Note: slashes are only allowed to build and signal resource identifiers consisting of MAY expose compound keys as resource identifiers [B165] .

Note: to prevent ambiguities of MUST use normalized paths without empty path segments and trailing slashes [B148] COVERED BY API-LINTER LINTER SUPPORT resource identifiers must never be empty. Consequently, support of empty strings for path parameters is forbidden.

MUST identify resources and sub-resources via path segments [B164] LINTER SUPPORT

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 as follows:

/resources/{resource-id}/sub-resources/{sub-resource-id}

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 instance, if /partners/{partner-id}/addresses/{address-id} is valid, then, in principle, also /partners/{partner-id}/addresses, /partners/{partner-id} and /partners must be valid. Examples of concrete url paths:

/shopping-carts/de:1681e6b88ec1/items/1
/shopping-carts/de:1681e6b88ec1
/content/images/9cacb4d8
/content/images

Note: resource identifiers may be build of multiple other resource identifiers (see MAY expose compound keys as resource identifiers [B165] ).

Exception: In some situations the resource identifier is not passed as a path segment but via the authorization information, e.g. an authorization token or session cookie. Here, it is reasonable to use self as pseudo-identifier path segment. For instance, you may define /employees/self or /employees/self/personal-details as resource paths —  and may additionally define endpoints that support identifier passing in the resource path, like define /employees/{employee-id} or /employees/{employee-id}/personal-details.

MAY expose compound keys as resource identifiers [B165]

If a resource is best identified by a compound key consisting of multiple other resource identifiers, it is allowed to reuse the compound key in its natural form without containing slashes instead of technical resource identifier in the resource path without violating the above rule MUST identify resources and sub-resources via path segments [B164] LINTER SUPPORT as follows:

/resources/{compound-key-1}[delim-1]...[delim-n-1]{compound-key-n}

Example paths:

/shopping-carts/{country}:{session-id}
/shopping-carts/{country}:{session-id}/items/{item-id}
/api-specifications/{docker-image-id}/apis/{path}:{file-name}
/api-specifications/{repository-name}/{artifact-name}:{tag}
/article-size-advices/{sku}:{sales-channel}

Warning: Exposing a compound key as described above limits ability to evolve the structure of the resource identifier as it is no longer opaque.

To compensate for this drawback, APIs must apply a compound key abstraction consistently in all requests and responses parameters and attributes allowing consumers to treat these as technical resource identifier replacement. The use of independent compound key components must be limited to search and creation requests, as follows:

# compound key components passed as independent search query parameters
GET /article-size-advices?skus=sku-1,sku-2&sales_channel_id=sid-1
=> { "items": [{ "id": "id-1", ...  },{ "id": "id-2", ...  }] }

# opaque technical resource identifier passed as path parameter
GET /article-size-advices/sku-1:sid-1
=> { "id": "id-1", "sku": "sku-1", "sales_channel_id": "sid-1", "size": ... }

# compound key components passed as mandatory request fields
POST /article-size-advices { "sku": "sku-1", "sales_channel_id": "sid-1", "size": ... }
=> { "id": "id-1", "sku": "sku-1", "sales_channel_id": "sid-1", "size": ... }

Where id-1 is representing the opaque provision of the compound key sku-1:sid-1 as technical resource identifier.

Remark: A compound key component may itself be used as another resource identifier providing another resource endpoint, e.g /article-size-advices/{sku}.

MAY consider using (non-)nested URLs [B166] LINTER SUPPORT

If a sub-resource is only accessible via its parent resource and may not exist without parent resource, consider using a nested URL structure, for instance:

/shopping-carts/de/1681e6b88ec1/cart-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, customer has a collection for sales orders; however, sales orders have globally unique id and some services may choose to access the orders directly, for instance:

/customers/1637asikzec1
/sales-orders/5273gh3k525a

SHOULD only use UUIDs if necessary [B167]

Generating IDs can be a scaling problem in high frequency and near real time use cases. UUIDs solve this problem, as they can be generated without collisions in a distributed, non-coordinated way and without additional server round trips.

However, they also come with some disadvantages:

  • pure technical key without meaning; not ready for naming or name scope conventions that might be helpful for pragmatic reasons, e.g. we learned to use names for product attributes, instead of UUIDs

  • less usable, because…​

  • cannot be memorized and easily communicated by humans

  • harder to use in debugging and logging analysis

  • less convenient for consumer facing usage

  • quite long: readable representation requires 36 characters and comes with higher memory and bandwidth consumption

  • not ordered along their creation history and no indication of used id volume

  • may be in conflict with additional backward compatibility support of legacy ids

UUIDs should be avoided when not needed for large scale id generation. Instead, for instance, server side support with id generation can be preferred (POST on id resource, followed by idempotent PUT on entity resource). Usage of UUIDs is especially discouraged as primary keys of master and configuration data, like brand-ids or attribute-ids which have low id volume but widespread steering functionality.

Please be aware that sequential, strictly monotonically increasing numeric identifiers may reveal critical, confidential business information, like order volume, to non-privileged clients.

In any case, we should always use string rather than number type for identifiers. This gives us more flexibility to evolve the identifier naming scheme. Accordingly, if used as identifiers, UUIDs should not be qualified using a format property.

Hint: Usually, random UUID is used - see UUID version 4 in RFC 4122. Though UUID version 1 also contains leading timestamps it is not reflected by its lexicographic sorting. This deficit is addressed by  ULID (Universally Unique Lexicographically Sortable Identifier). You may favour ULID instead of UUID, for instance, for pagination use cases ordered along creation time.

SHOULD limit number of resource types [B168] COVERED BY API-LINTER LINTER SUPPORT

To keep maintenance and service evolution manageable, we should follow "functional segmentation" and "separation of concern" design principles and do not mix different business functionalities in same API definition. In practice this means that the number of resource types exposed via an API should be limited. In this context a resource type is defined as a set of highly related resources such as a collection, its members and any direct sub-resources.

For example, the resources below would be counted as three resource types, one for customers, one for the addresses, and one for the customers' related addresses:

/customers
/customers/{customer-id}
/customers/{customer-id}/preferences
/customers/{customer-id}/addresses
/customers/{customer-id}/addresses/{address-id}
/addresses
/addresses/{address-id}

Note that:

  • We consider /customers/{customer-id}/preferences part of the /customers resource type because it has a one-to-one relation to the customer without an additional identifier.

  • We consider /customers and /customers/{customer-id}/addresses as separate resource types because /customers/{customer-id}/addresses/{address-id} also exists with an additional identifier for the address.

  • We consider /addresses and /customers/{customer-id}/addresses as separate resource types because there’s no reliable way to be sure they are the same.

Given this definition, our experience is that well defined APIs involve no more than 4 to 8 resource types. There may be exceptions with more complex business domains that require more resources, but you should first check if you can split them into separate subdomains with distinct APIs.

Nevertheless one API should hold all necessary resources to model complete business processes helping clients to understand these flows.

SHOULD limit number of sub-resource levels [B169] COVERED BY API-LINTER LINTER SUPPORT

There are main resources (with root url paths) and sub-resources (or nested resources with non-root urls paths). Use sub-resources if their life cycle is (loosely) coupled to the main resource, i.e. the main resource works as collection resource of the subresource entities. You should use ⇐ 3 sub-resource (nesting) levels — more levels increase API complexity and url path length. (Remember, some popular web browsers do not support URLs of more than 2000 characters.)

13. HTTP requests

All APIs are models around resources and are focused on the following usecases:
  •    GET  read either a single or a collection resource

  •    PUT  update entire resources – single or collection resources

  •   POST  create single resources on a collection resource endpoint

  •   POST  execute a specified action on a single ressource

  •  PATCH  update parts of single resources

  • DELETE  delete resources

MUST use HTTP methods correctly [B132]

Be compliant with the standardized HTTP method semantics summarized as follows:

GET

GET requests are used to read either a single or a collection resource.

  • 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 collection is empty) or 404 (if the collection is missing)

  • GET requests must NOT have a request body payload (see GET with body)

Note: GET requests on collection resources should provide sufficient MUST stick to conventional query parameters [B149]  and Pagination mechanisms.

Example: GET /products/{productId}/documents

  1. will result in 404 if there is no product with id = {productId}

  2. will result in 200 if there is a product with id = {productId} but this product has no documents

GET with body payload

APIs sometimes face the problem, that they have to provide extensive structured request information with GET, that may conflict with the size limits of clients, load-balancers, and servers. As we require APIs to be standard conform (request body payload in GET must be ignored on server side), API designers have to check the following two options:

  1. 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 via multiple query parameters or by a single structured URL encoded string.

  2. POST with body payload content: when a GET with URL encoded query parameters is not possible, a POST request with body payload must be used, and explicitly documented with a hint like in the following example:

paths:
  /products:
    post:
      description: >
        [GET with body payload](api.bahag.cloud/api-guideline/#get-with-body) - no resources created:
        Returns all products matching the query passed as request input payload.
      requestBody:
        required: true
        content:
          ...

Note: It is no option to encode the lengthy structured request information using header parameters. From a conceptual point of view, the semantic of an operation should always be expressed by the resource names, as well as the involved path and query parameters. In other words by everything that goes into the URL. Request headers are reserved for general context information (see Link TODO [183]). 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.

Hint: As GET with body is used to transport extensive query parameters, the cursor cannot any longer be used to encode the query filters in case of SHOULD decide between cursor-based pagination and offset-based pagination [B171] . As a consequence, it is best practice to transport the query filters in the body payload, while using SHOULD use pagination links where applicable [B172] containing the cursor that is only encoding the page position and direction. To protect the pagination sequence the cursor may contain a hash over all applied query filters (See also SHOULD use pagination links where applicable [B172] ).

GET with multiple identifiers

It may be required that a single GET request queries for multiple ressources at the same time. This should be used to reduce the number of needed requests to an API (compared to querying for each and every identifier in a separate reuqest). Using the GET parameters also enables caching (eg. via Clodflare) to further reduce the load.

SHOULD use square bracket syntax to query for multiple identifieres [B238]

Multiple identifieres should be specified by the so called bracket syntax

https://my.url.bauhaus/myEntity?id[]=3&id[]=47&id[]=58

In such cases the requested ressource SHOULD always return an (potentially empty) array as result.

MAY use indexed square bracket syntax to query for multiple identifieres [B239]

If required the API may provide an indexed access to multiple identifier by specifying the index in the square brackets.

https://my.url.bauhaus/myEntity?id[4]=3&id[18]=47&id[foobar]=58

PUT

PUT requests are used to update (in rare cases to create) 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

  • 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)

Important: It is best practice to prefer POST over PUT for creation of (at least top-level) resources. This leaves the resource ID under control of the service and allows to concentrate on the update semantic using PUT as follows.

Note: In the rare cases where PUT is although used for resource creation, the resource IDs 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 (see MUST fulfill common method properties [B133] ).

Hint: To prevent unnoticed concurrent updates and duplicate creations when using PUT, you MAY consider to support ETag together with If-Match/If-None-Match header [B203] to allow the server to react on stricter demands that expose conflicts and prevent lost updates. See also [optimistic-locking] for details and options.

POST

POST are idiomatically used to create single resources on a collection resource endpoint, but other semantics on single resources endpoint are equally possible. The semantic for collection endpoints is best described as "please add the enclosed representation to the collection resource identified by the URL".

  • on a successful POST request, 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), 202 (if the request was accepted but has not been finished yet), and exceptionally 204 with Location header (if the actual resource is not returned).

POST requests are also used to execute a specified action on a single ressource. The semantic for single resource endpoints is best described as "please execute the given well specified request on the resource identified by the URL".

Generally: POST should be used for scenarios that cannot be covered by the other methods sufficiently. In such cases, make sure to document the fact that POST is used as a workaround (see GET with body).

Note: Resource IDs with respect to POST requests are created and maintained by server and returned with response payload.

Hint: Posting the same resource twice is not required to be idempotent (check MUST fulfill common method properties [B133] ) and may result in multiple resources. However, you SHOULD consider to design POST and PATCH idempotent [B134] to prevent this.

PATCH

PATCH requests are used to update parts 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 as patching entire collection is challenging

  • 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)

Note: since implementing PATCH correctly is a bit tricky, we strongly suggest to choose one and only one of the following patterns per endpoint, unless forced by a MUST not break backward compatibility [B183] . In preference order:

  1. use PUT with complete objects to update a resource as long as feasible (i.e. do not use PATCH at all).

  2. use PATCH with partial objects to only update parts of a resource, whenever possible. (This is basically JSON Merge Patch, a specialized media type application/merge-patch+json that is a partial resource representation.)

  3. use PATCH with JSON Patch, a specialized media type application/json-patch+json that includes instructions on how to change the resource.

  4. 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  JSON patch vs. merge).

Note: Patching the same resource twice is not required to be idempotent (check MUST fulfill common method properties [B133] ) and may result in a changing result. However, you SHOULD consider to design POST and PATCH idempotent [B134] to prevent this.

Hint: To prevent unnoticed concurrent updates when using PATCH you MAY consider to support ETag together with If-Match/If-None-Match header [B203] to allow the server to react on stricter demands that expose conflicts and prevent lost updates. See [optimistic-locking] and SHOULD consider to design POST and PATCH idempotent [B134] for details and options.

DELETE

DELETE requests 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.

  • DELETE requests can be applied to multiple resources at once using query parameters on the collection resource (see DELETE with query parameters).

  • successful DELETE requests will usually generate 200 (if the deleted resource is returned) or 204 (if no content is returned).

  • failed DELETE requests will usually generate 404 (if the resource cannot be found) or 410 (if the resource was already deleted before).

Important: After deleting a resource with DELETE, a GET request on the resource is expected to either return 404 (not found) or 410 (gone) depending on how the resource is represented after deletion. Under no circumstances the resource must be accessible after this operation on its endpoint.

DELETE with query parameters

DELETE request can have query parameters. Query parameters should be used as filter parameters on a resource and not for passing context information to control the operation behavior.

DELETE /resources?param1=value1&param2=value2...&paramN=valueN

Note: When providing DELETE with query parameters, API designers must carefully document the behavior in case of (partial) failures to manage client expectations properly.

The response status code of DELETE with query parameters requests should be similar to usual DELETE requests. In addition, it may return the status code 207 using a payload describing the operation results (see MUST use code 207 for batch or bulk requests [B124] for details).

DELETE with body payload

In rare cases DELETE may require additional information, that cannot be classified as filter parameters and thus should be transported via request body payload, to perform the operation. Since RFC-7231 states, that DELETE has an undefined semantic for payloads, we recommend to utilize POST. In this case the POST endpoint must be documented with the hint DELETE with body analog to how it is defined for GET with body. The response status code of DELETE with body requests should be similar to usual DELETE requests.

HEAD requests are used to retrieve the header information of single resources and resource collections.

  • HEAD has exactly the same semantics as GET, but returns headers only, no body.

Hint: HEAD is particular useful to efficiently lookup whether large resources or collection resources have been updated in conjunction with the etag-header.

OPTIONS

OPTIONS requests are used to inspect the available operations (HTTP methods) of a given endpoint.

  • OPTIONS responses usually either return a comma separated list of methods in the Allow 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.

MUST fulfill common method properties [B133]

Request methods in RESTful services can be…​

  • safe - the operation semantic is defined to be read-only, meaning it must not have intended side effects, i.e. changes, to the server state.

  • idempotent - the operation has the same intended effect on the server state, independently whether it is executed once or multiple times. Note: this does not require that the operation is returning the same response or status code.

  • cacheable - to indicate that responses are allowed to be stored for future reuse. In general, requests to safe methods are cachable, if it does not require a current or authoritative response from the server.

Note: The above definitions, of intended (side) effect allows the server to provide additional state changing behavior as logging, accounting, pre- fetching, etc. However, these actual effects and state changes, must not be intended by the operation so that it can be held accountable.

Method implementations must fulfill the following basic properties according to RFC 7231:

Method Safe Idempotent Cacheable

GET

Yes

Yes

Yes

HEAD

Yes

Yes

Yes

POST

No

No, but SHOULD consider to design POST and PATCH idempotent [B134]

May, but only if specific POST endpoint is safe. Hint: not supported by most caches.

PUT

No

Yes

No

PATCH

No

No, but SHOULD consider to design POST and PATCH idempotent [B134]

No

DELETE

No

Yes

No

OPTIONS

Yes

Yes

No

TRACE

Yes

Yes

No

SHOULD consider to design POST and PATCH idempotent [B134]

Taken over by Zalando, still needs further review.

In many cases it is helpful or even necessary to design POST and PATCH idempotent for clients to expose conflicts and prevent resource duplicate (a.k.a. zombie resources) or lost updates, e.g. if same resources may be created or changed in parallel or multiple times. To design an idempotent API endpoint owners should consider to apply one of the following three patterns.

Note: While conditional key and secondary key are focused on handling concurrent requests, the idempotency key is focused on providing the exact same responses, which is even a stronger requirement than the idempotency defined above. It can be combined with the two other patterns.

To decide, which pattern is suitable for your use case, please consult the following table showing the major properties of each pattern:

Conditional Key Secondary Key Idempotency Key

Applicable with

PATCH

POST

POST/PATCH

HTTP Standard

Yes

No

No

Prevents duplicate (zombie) resources

Yes

Yes

No

Prevents concurrent lost updates

Yes

No

No

Supports safe retries

Yes

Yes

Yes

Supports exact same response

No

No

Yes

Can be inspected (by intermediaries)

Yes

No

Yes

Usable without previous GET

No

Yes

Yes

Note: The patterns applicable to PATCH can be applied in the same way to PUT and DELETE providing the same properties.

If you mainly aim to support safe retries, we suggest to apply conditional key and secondary key pattern before the Idempotency Key pattern.

SHOULD use secondary key for idempotent POST design [B135] {Z231}

Taken over by Zalando, still needs further review.

The most important pattern to design POST idempotent for creation is to introduce a resource specific secondary key provided in the request body, to eliminate the problem of duplicate (a.k.a zombie) resources.

The secondary key is stored permanently in the resource as alternate key or combined key (if consisting of multiple properties) guarded by a uniqueness constraint enforced server-side, that is visible when reading the resource. The best and often naturally existing candidate is a unique foreign key, that points to another resource having one-on-one relationship with the newly created resource, e.g. a parent process identifier.

A good example here for a secondary key is the shopping cart ID in an order resource.

Note: When using the secondary key pattern without Idempotency-Key all subsequent retries should fail with status code 409 (conflict). We suggest to avoid 200 here unless you make sure, that the delivered resource is the original one implementing a well defined behavior. Using 204 without content would be a similar well defined option.

MUST define collection format of header and query parameters [B136] LINTER SUPPORT

Taken over by Zalando, still needs further review.

Header and query parameters allow to provide a collection of values, either by providing a comma-separated list of values or by repeating the parameter multiple times with different values as follows:

Parameter Type Comma-separated Values Multiple Parameters Standard

Header

Header: value1,value2

Header: value1, Header: value2

RFC 7230 Section 3.2.2

Query

?param=value1,value2

?param=value1&param=value2

RFC 6570 Section 3.2.8

As Open API does not support both schemas at once, an API specification must explicitly define the collection format to guide consumers as follows:

Parameter Type Comma-separated Values Multiple Parameters

Header

style: simple, explode: false

not allowed (see RFC 7230 Section 3.2.2)

Query

style: form, explode: false

style: form, explode: true

When choosing the collection format, take into account the tool support, the escaping of special characters and the maximal URL length.

SHOULD design simple query languages using query parameters [B137] 

Taken over by Zalando, still needs further review.

We prefer the use of query parameters to describe resource-specific query languages for the majority of APIs because it’s native to HTTP, easy to extend and has excellent implementation support in HTTP clients and web frameworks.

Query parameters should have the following aspects specified:

  • Reference to corresponding property, if any

  • Value range, e.g. inclusive vs. exclusive

  • Comparison semantics (equals, less than, greater than, etc)

  • Implications when combined with other queries, e.g. and vs. or

How query parameters are named and used is up to individual API designers. The following examples should serve as ideas:

  • name=BAUHAUS, to query for elements based on property equality

  • age=5, to query for elements based on logical properties

    • Assuming that elements don’t actually have an age but rather a birthday

  • max_length=5, based on upper and lower bounds (min and max)

  • shorter_than=5, using terminology specific e.g. to length

  • created_before=2019-07-17 or not_modified_since=2019-07-17

    • Using terminology specific e.g. to time: before, after, since and until

We don’t advocate for or against certain names because in the end APIs should be free to choose the terminology that fits their domain the best.

SHOULD design complex query languages using JSON [B138]

Taken over by Zalando, still needs further review.

Minimalistic query languages based on query parameters are suitable for simple use cases with a small set of available filters that are combined in one way and one way only (e.g. and semantics). Simple query languages are generally preferred over complex ones.

Some APIs will have a need for sophisticated and more complex query languages. Dominant examples are APIs around search (incl. faceting) and product catalogs.

Aspects that set those APIs apart from the rest include but are not limited to:

  • Unusual high number of available filters

  • Dynamic filters, due to a dynamic and extensible resource model

  • Free choice of operators, e.g. and, or and not

APIs that qualify for a specific, complex query language are encouraged to use nested JSON data structures and define them using Open API directly. The provides the following benefits:

  • Data structures are easy to use for clients

    • No special library support necessary

    • No need for string concatenation or manual escaping

  • Data structures are easy to use for servers

    • No special tokenizers needed

    • Semantics are attached to data structures rather than text tokens

  • Consistent with other HTTP methods

  • API is defined in Open API completely

    • No external documents or grammars needed

    • Existing means are familiar to everyone

JSON-specific rules and most certainly needs to make use of the GET-with-body pattern.

Example

The following JSON document should serve as an idea how a structured query might look like.

{
  "and": {
    "name": {
      "match": "Alice"
    },
    "age": {
      "or": {
        "range": {
          ">": 25,
          "<=": 50
        },
        "=": 65
      }
    }
  }
}

Feel free to also get some inspiration from:

MUST document implicit filtering [B139]

Taken over by Zalando, still needs further review.

Sometimes certain collection resources or queries will not list all the possible elements they have, but only those for which the current client is authorized to access.

Implicit filtering could be done on:

  • the collection of resources being return on a parent GET request

  • the fields returned for the resource’s detail

In such cases, the implicit filtering must be in the API specification (in its description).

Consider caching considerations when implicitly filtering.

Example:

If an employee of the company Foo accesses one of our business-to-business service and performs a GET /business-partners, it must, for legal reasons, not display any other business partner that is not owned or contractually managed by her/his company. It should never see that we are doing business also with company Bar.

Response as seen from a consumer working at FOO:

{
    "items": [
        { "name": "Foo Performance" },
        { "name": "Foo Sport" },
        { "name": "Foo Signature" }
    ]
}

Response as seen from a consumer working at BAR:

{
    "items": [
        { "name": "Bar Classics" },
        { "name": "Bar pour Elle" }
    ]
}

The API Specification should then specify something like this:

paths:
  /business-partner:
    get:
      description: >-
        Get the list of registered business partner.
        Only the business partners to which you have access to are returned.

14. HTTP status codes and errors

All APIs are using the standard HTTP status and error codes with the same semantic

MUST specify success and error responses [B121] COVERED BY API-LINTER LINTER SUPPORT

APIs should define the functional, business view and abstract from implementation aspects. Success and error responses are a vital part to define how an API is used correctly.

Therefore, you must define all success and service specific error responses in your API specification. Both are part of the interface definition and provide important information for service clients to handle standard as well as exceptional situations.

Hint: In most cases it is not useful to document all technical errors, especially if they are not under control of the service provider. Thus unless a response code conveys application-specific functional semantics or is used in a none standard way that requires additional explanation, multiple error response specifications can be combined using the following pattern (see also MUST only use durable and immutable remote references [B102] ):

responses:
  ...
  default:
    description: error occurred - see status code and problem object for more information.
    content:
      "application/problem+json":
        schema:
          $ref: 'https://stash.bahag.com/projects/BAHAG/repos/bahag-api-guideline/browse/models/problem-1.0.0.yaml#/Problem'

API designers should also think about a troubleshooting board as part of the associated online API documentation. It provides information and handling guidance on application-specific errors and is referenced via links from the API specification. This can reduce service support tasks and contribute to service client and provider performance.

MUST use standard HTTP status codes [B122] COVERED BY API-LINTER LINTER SUPPORT

You must only use standardized HTTP status codes consistently with their intended semantics. You must not invent new HTTP status codes.

RFC standards define ~60 different HTTP status codes with specific semantics (mainly RFC7231 and RFC 6585) — and there are upcoming new ones, e.g.  draft legally-restricted-status. See overview on all error codes on  Wikipedia or via  https://httpstatuses.com/) also inculding 'unofficial codes', e.g. used by popular web servers like Nginx.

Below we list the most commonly used and best understood HTTP status codes, consistent with their semantic in the RFCs. APIs should only use these to prevent misconceptions that arise from less commonly used HTTP status codes.

Important: As long as your HTTP status code usage is well covered by the semantic defined here, you should not describe it to avoid an overload with common sense information and the risk of inconsistent definitions. Only if the HTTP status code is not in the list below or its usage requires additional information aside the well defined semantic, the API specification must provide a clear description of the HTTP status code in the response.

Success codes

Code Meaning Methods

200

OK - this is the standard success response

<all>

201

Created - Returned on successful entity creation. You are free to return either an empty response or the created resource in conjunction with the Location header.

POST, PUT

202

Accepted - The request was successful and will be processed asynchronously.

POST, PUT, PATCH, DELETE

204

No content - There is no response body. If a GET request does not find a resource, this results in 404 response.

PUT, PATCH, DELETE

206

Partial content - the response code indicates that the request has succeeded and the body contains the requested ranges of data. If a GET request does not find a resource, this results in 404 response.

GET

207

Taken over by Zalando, still needs further review. Multi-Status - The response body contains multiple status informations for different parts of a batch/bulk request (see MUST use code 207 for batch or bulk requests [B124] ).

POST, (DELETE)

Redirection codes

Code Meaning Methods

301

Moved Permanently - This and all future requests should be directed to the given URI.

<all>

303

See Other - The response to the request can be found under another URI using a GET method.

POST, PUT, PATCH, DELETE

304

Not Modified - indicates that a conditional GET or HEAD request would have resulted in 200 response if it were not for the fact that the condition evaluated to false, i.e. resource has not been modified since the date or version passed via request headers If-Modified-Since or If-None-Match.

GET, HEAD

Client side error codes

Code Meaning Methods

400

Bad request - generic / unknown error. Should also be delivered in case of input payload fails business logic validation.

<all>

401

Unauthorized - the users must log in (this often means "Unauthenticated").

<all>

403

Forbidden - the user is not authorized to use this resource.

<all>

404

Not found - the resource is not found. Hint: If a search request returns an empty result, this is marked with the status code 204. If a GET request does not find a resource, this is marked with a 404.

<all>

405

Method Not Allowed - the method is not supported, see OPTIONS.

<all>

406

Not Acceptable - resource can only generate content not acceptable according to the Accept headers sent in the request.

<all>

408

Request timeout - the server times out waiting for the resource.

<all>

409

Conflict - request cannot be completed due to conflict, e.g. when two clients try to create the same resource or if there are concurrent, conflicting updates.

POST, PUT, PATCH, DELETE

410

Gone - resource does not exist any longer, e.g. when accessing a resource that has intentionally been deleted.

<all>

412

Precondition Failed - returned for conditional requests, e.g. If-Match if the condition failed. Used for optimistic locking.

PUT, PATCH, DELETE

415

Unsupported Media Type - e.g. clients sends request body without content type.

POST, PUT, PATCH, DELETE

423

Locked - Pessimistic locking, e.g. processing states.

PUT, PATCH, DELETE

428

Precondition Required - server requires the request to be conditional, e.g. to make sure that the "lost update problem" is avoided.

<all>

429

Too many requests - the client does not consider rate limiting and sent too many requests.

<all>

Server side error codes:

Code Meaning Methods

500

Internal Server Error - a generic error indication for an unexpected server execution problem (here, client retry may be sensible)

<all>

501

Not Implemented - server cannot fulfill the request (usually implies future availability, e.g. new feature).

<all>

503

Service Unavailable - service is (temporarily) not available (e.g. if a required component or downstream service is not available) — client retry may be sensible. If possible, the service should indicate how long the client should wait by setting the Retry-After header.

<all>

MUST use most specific HTTP status codes [B123]

You must use the most specific HTTP status code when returning information about your request processing status or error situations.

MUST use code 207 for batch or bulk requests [B124]

Some APIs are required to provide either batch or bulk requests using POST for performance reasons, i.e. for communication and processing efficiency. In this case services may be in need to signal multiple response codes for each part of an batch or bulk request. As HTTP does not provide proper guidance for handling batch/bulk requests and responses, we herewith define the following approach:

  • A batch or bulk request always responds with HTTP status code 207 unless a non-item-specific failure occurs.

  • A batch or bulk request may return 4xx/5xx status codes, if the failure is non-item-specific and cannot be restricted to individual items of the batch or bulk request, e.g. in case of overload situations or general service failures.

  • A batch or bulk response with status code 207 always returns as payload a multi-status response containing item specific status and/or monitoring information for each part of the batch or bulk request.

Note: These rules apply even in the case that processing of all individual parts fail or each part is executed asynchronously!

The rules are intended to allow clients to act on batch and bulk responses in a consistent way by inspecting the individual results. We explicitly reject the option to apply 200 for a completely successful batch as proposed in Nakadi’s POST /event-types/{name}/events as short cut without inspecting the result, as we want to avoid risks and expect clients to handle partial batch failures anyway.

The bulk or batch response may look as follows:

BatchOrBulkResponse:
  description: batch response object.
  type: object
  properties:
    items:
      type: array
      items:
        type: object
        properties:
          id:
            description: Identifier of batch or bulk request item.
            type: string
          status:
            description: >
              Response status value. A number or extensible enum describing
              the execution status of the batch or bulk request items.
            type: string
            x-extensible-enum: [...]
          description:
            description: >
              Human readable status description and containing additional
              context information about failures etc.
            type: string
        required: [id, status]

Note: while a batch defines a collection of requests triggering independent processes, a bulk defines a collection of independent resources created or updated together in one request. With respect to response processing this distinction normally does not matter.

MUST use code 429 with headers for rate limits [B125] LINTER SUPPORT

APIs that wish to manage the request rate of clients must use the 429 (Too Many Requests) response code, if the client exceeded the request rate (see RFC 6585). Such responses must also contain header information providing further details to the client. There are two approaches a service can take for header information:

  • Return a Retry-After header indicating how long the client ought to wait before making a follow-up request. The Retry-After header can contain a HTTP date value to retry after or the number of seconds to delay. Either is acceptable but APIs should prefer to use a delay in seconds.

  • Return a trio of X-RateLimit headers. These headers (described below) allow a server to express a service level in the form of a number of allowing requests within a given window of time and when the window is reset.

The X-RateLimit headers are:

  • X-RateLimit-Limit: The maximum number of requests that the client is allowed to make in this window.

  • X-RateLimit-Remaining: The number of requests allowed in the current window.

  • X-RateLimit-Reset: The relative time in seconds when the rate limit window will be reset. Beware that this is different to Github and Twitter’s usage of a header with the same name which is using UTC epoch seconds instead.

The reason to allow both approaches is that APIs can have different needs. Retry-After is often sufficient for general load handling and request throttling scenarios and notably, does not strictly require the concept of a calling entity such as a tenant or named account. In turn this allows resource owners to minimise the amount of state they have to carry with respect to client requests. The 'X-RateLimit' headers are suitable for scenarios where clients are associated with pre-existing account or tenancy structures. 'X-RateLimit' headers are generally returned on every request and not just on a 429, which implies the service implementing the API is carrying sufficient state to track the number of requests made within a given window for each named entity.

MUST use problem JSON [B126] COVERED BY API-LINTER LINTER SUPPORT

RFC 7807 defines a Problem JSON object and the media type application/problem+json. Operations should return it (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 error codes).

The Open API schema definition of the Problem JSON object can be found on BitBucket. You can reference it by using:

responses:
  503:
    description: Service Unavailable
    content:
      "application/problem+json":
        schema:
          $ref: 'https://stash.bahag.com/projects/BAHAG/repos/bahag-api-guideline/browse/models/problem-1.0.0.yaml#/Problem'

You may define custom problem types as extensions of the Problem JSON object if your API needs to return specific, additional and detailed error information.

Problem type and instance identifiers in our APIs are not meant to be resolved. RFC 7807 encourages that custom problem types are URI references that point to human-readable documentation, but we deliberately decided against that, as all important parts of the API must be documented using MUST provide API specification using Open API [B101] COVERED BY API-LINTER LINTER SUPPORT anyway. In addition, URLs tend to be fragile and not very stable over longer periods because of organizational and documentation changes and descriptions might easily get out of sync.

In order to stay compatible with RFC 7807 we proposed to use  relative URI references usually defined by absolute-path [ '?' query ] [ '#' fragment ] as simplified identifiers in type and instance fields:

  • /problems/out-of-stock

  • /problems/insufficient-funds

  • /problems/user-deactivated

  • /problems/connection-error#read-timeout

Note: The use of  absolute URIs is not forbidden but strongly discouraged. If you use absolute URIs, please reference problem-1.0.0.yaml#/Problem instead.

MUST not expose stack traces [B127]

Stack traces contain implementation details that are not part of an API, and on which clients should never rely. Moreover, stack traces can leak sensitive information that partners and third parties are not allowed to receive and may disclose insights about vulnerabilities to attackers.

15. Performance

Performance must always be considered from planning to implementation

SHOULD reduce bandwidth needs and improve responsiveness [B193]

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. (BAHAG is a 'Mobile First' company, so be mindful of this point.)

Common techniques include:

Each of these items is described in greater detail below.

SHOULD use gzip compression [B194]

Compress the payload of your API’s 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. The server should indicate used gzip compression via the Content-Encoding header.

SHOULD support partial responses via filtering [B193]

Depending on your use case and payload size, you can significantly reduce network bandwidth need by supporting filtering of returned entity fields. Here, the client can explicitly determine the subset of fields he wants to receive via the fields query parameter. (It is analogue to  GraphQL fields and simple queries, and also applied, for instance, for  Google Cloud API’s partial responses.)

Unfiltered

GET http://api.example.org/users/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",
  "friends": [ {
    "id": "1fb43648-dae1-11e5-aa01-1fbc3abb1cd0",
    "name": "Jane Doe",
    "address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
    "birthday": "1988-04-07"
  } ]
}

Filtered

GET http://api.example.org/users/123?fields=(name,friends(name)) HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "John Doe",
  "friends": [ {
    "name": "Jane Doe"
  } ]
}

The fields query parameter determines the fields returned with the response payload object. For instance, (name) returns users root object with only the name field, and (name,friends(name)) returns the name and the nested friends object with only its name field.

Open API doesn’t support you in formally specifying different return object schemes depending on a parameter. When you define the field parameter, we recommend to provide the following description: `Endpoint supports filtering of return object fields as described in rule B193' (SHOULD reduce bandwidth needs and improve responsiveness [B193] )

The syntax of the query fields value is defined by the following  BNF grammar.

<fields>            ::= [ <negation> ] <fields_struct>
<fields_struct>     ::= "(" <field_items> ")"
<field_items>       ::= <field> [ "," <field_items> ]
<field>             ::= <field_name> | <fields_substruct>
<fields_substruct>  ::= <field_name> <fields_struct>
<field_name>        ::= <dash_letter_digit> [ <field_name> ]
<dash_letter_digit> ::= <dash> | <letter> | <digit>
<dash>              ::= "-" | "_"
<letter>            ::= "A" | ... | "Z" | "a" | ... | "z"
<digit>             ::= "0" | ... | "9"
<negation>          ::= "!"

Note: Following the  principle of least astonishment, you should not define the fields query parameter using a default value, as the result is counter-intuitive and very likely not anticipated by the consumer.

SHOULD allow optional embedding of sub-resources [B196]

Embedding related resources (also know as Resource expansion) is a great way to reduce the number of requests. In cases where clients know upfront that they need some related resources they can instruct the server to prefetch that data eagerly. Whether this is optimized on the server, e.g. a database join, or done in a generic way, e.g. an HTTP proxy that transparently embeds resources, is up to the implementation.

See MUST stick to conventional query parameters [B149]  for naming, e.g. "embed" for steering of embedded resource expansion. Please use the  BNF grammar, as already defined above for filtering, when it comes to an embedding query syntax.

Embedding a sub-resource can possibly look like this where an order resource has its order items as sub-resource (/order/{orderId}/items):

GET /order/123?embed=(items) HTTP/1.1

{
  "id": "123",
  "_embedded": {
    "items": [
      {
        "position": 1,
        "sku": "1234-ABCD-7890",
        "price": {
          "amount": 71.99,
          "currency": "EUR"
        }
      }
    ]
  }
}

MUST document cachable GET, HEAD, and POST endpoints [B197]

Caching has to take many aspects into account, e.g. general cacheability of response information, our guideline to protect endpoints using SSL and MUST secure endpoints with OAuth 2.0 [B180] COVERED BY API-LINTER LINTER SUPPORT, resource update and invalidation rules, existence of multiple consumer instances. As a consequence, caching is in best case complex, e.g. with respect to consistency, in worst case inefficient.

As a consequence, client side as well as transparent web caching should be avoided, unless the service supports and requires it to protect itself, e.g. in case of a heavily used and therefore rate limited master data service, i.e. data items that rarely or not at all change after creation.

As default, API providers and consumers should always set the Cache-Control header set to Cache-Control: no-store and assume the same setting, if no Cache-Control header is provided.

Note: There is no need to document this default setting. However, please make sure that your framework is attaching this header value by default, or ensure this manually, e.g. using the best practice of Spring Security as shown below. Any setup deviating from this default must be sufficiently documented.

Cache-Control: no-cache, no-store, must-revalidate, max-age=0

If your service really requires to support caching, please observe the following rules:

  • Document all cacheable GET, HEAD, and POST endpoints by declaring the support of Cache-Control, Vary, and etag headers in response. Note: you must not define the Expires header to prevent redundant and ambiguous definition of cache lifetime. A sensible default documentation of these headers is given below.

  • Take care to specify the ability to support caching by defining the right caching boundaries, i.e. time-to-live and cache constraints, by providing sensible values for Cache-Control and Vary in your service. We will explain best practices below.

  • Provide efficient methods to warm up and update caches, e.g. as follows:

Hint: For proper cache support, you must return 304 without content on a failed HEAD or GET request with If-None-Match: <entity-tag> instead of 412.

components:
  headers:
  - Cache-Control:
      description: |
        The RFC 7234 Cache-Control header field is providing directives to
        control how proxies and clients are allowed to cache responses results
        for performance. Clients and proxies are free to not support caching of
        results, however if they do, they must obey all directives mentioned in
        [RFC-7234 Section 5.2.2](https://tools.ietf.org/html/rfc7234) to the
        word.

        In case of caching, the directive provides the scope of the cache
        entry, i.e. only for the original user (private) or shared between all
        users (public), the lifetime of the cache entry in seconds (max-age),
        and the strategy how to handle a stale cache entry (must-revalidate).
        Please note, that the lifetime and validation directives for shared
        caches are different (s-maxage, proxy-revalidate).

      type: string
      required: false
      example: "private, must-revalidate, max-age=300"

  - Vary:
      description: |
        The RFC 7231 Vary header field in a response defines which parts of
        a request message, aside the target URL and HTTP method, might have
        influenced the response. A client or proxy cache must respect this
        information, to ensure that it delivers the correct cache entry (see
        [RFC-7231 Section
        7.1.4](https://tools.ietf.org/html/rfc7231#section-7.1.4)).

      type: string
      required: false
      example: "accept-encoding, accept-language"

The default setting for Cache-Control should contain the private directive for endpoints with standard MUST secure endpoints with OAuth 2.0 [B180] COVERED BY API-LINTER LINTER SUPPORT, as well as the must-revalidate directive to ensure, that the client does not use stale cache entries. Last, the max-age directive should be set to a value between a few seconds (max-age=60) and a few hours (max-age=86400) depending on the change rate of your master data and your requirements to keep clients consistent.

Cache-Control: private, must-revalidate, max-age=300

The default setting for Vary is harder to determine correctly. It highly depends on the API endpoint, e.g. whether it supports compression, accepts different media types, or requires other request specific headers. To support correct caching you have to carefully choose the value. However, a good first default may be:

Vary: accept, accept-encoding

Anyhow, this is only relevant, if you encourage clients to install generic HTTP layer client and proxy caches.

Note: generic client and proxy caching on HTTP level is hard to configure. Therefore, we strongly recommend to attach the (possibly distributed) cache directly to the service (or gateway) layer of your application. This relieves from interpreting the Vary header and greatly simplifies interpreting the Cache-Control and etag headers. Moreover, is highly efficient with respect to caching performance and overhead, and allows to support more advanced cache update and warm up patterns.

Anyhow, please carefully read RFC 7234 before adding any client or proxy cache.

16. Pagination

Our APIs support pagination when providing a read functionality on collection resources

MUST support pagination [B170]

Access to lists of data items must support pagination to protect the service against overload as well as for best client side iteration and batch processing experience. This holds true for all lists that are (potentially) larger than just a few hundred entries.

There are two well known page iteration techniques:

  • numeric offset identifies the first page entry

  •  Cursor/Limit-based — aka key-based — pagination: a unique key element identifies the first page entry (see also  Facebook’s guide)

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/prev page links (See SHOULD use pagination links where applicable [B172] ). This favours cursor-based over offset-based pagination.

Note: To provide a consistent look and feel of pagination patterns, you must stick to the common query parameter names defined in MUST stick to conventional query parameters [B149] .

SHOULD decide between cursor-based pagination and offset-based pagination [B171]

Cursor-based pagination is eventually better and more efficient when compared to offset-based pagination. Especially when it comes to high-data volumes and/or storage in NoSQL databases.

But every product team should decide in according to theit usecases if they want to use cursor-based or offset-based pagination. The important point is to support pagination on collection resource endpoints.

Before choosing cursor-based pagination, consider the following trade-offs:

  • Usability/framework support:

    • Offset-based pagination is more widely known than cursor-based pagination, so it has more framework support and is easier to use for API clients

  • Use case - jump to a certain page:

    • If jumping to a particular page in a range (e.g., 51 of 100) is really a required use case, cursor-based navigation is not feasible.

  • Data changes may lead to anomalies in result pages:

    • Offset-based pagination may create duplicates or lead to missing entries if rows are inserted or deleted between two subsequent paging requests.

    • If implemented incorrectly, cursor-based pagination may fail when the cursor entry has been deleted before fetching the pages.

  • Performance considerations - efficient server-side processing using offset-based pagination is hardly feasible for:

    • Very big data sets, especially if they cannot reside in the main memory of the database.

    • Sharded or NoSQL databases.

  • Cursor-based navigation may not work if you need the total count of results.

The cursor used for pagination is an opaque pointer to a page, that must never be inspected or constructed by clients. It usually encodes (encrypts) the page position, i.e. the identifier of the first or last page element, the pagination direction, and the applied query filters - or a hash over these - to safely recreate the collection. The cursor may be defined as follows:

Cursor:
  type: object
  properties:
    position:
      description: >
        Object containing the identifier(s) pointing to the entity that is
        defining the collection resource page - normally the position is
        represented by the first or the last page element.
      type: object
      properties: ...

    direction:
      description: >
        The pagination direction that is defining which elements to choose
        from the collection resource starting from the page position.
      type: string
      enum: [ ASC, DESC ]

    query:
      description: >
        Object containing the query filters applied to create the collection
        resource that is represented by this cursor.
      type: object
      properties: ...

    query_hash:
      description: >
        Stable hash calculated over all query filters applied to create the
        collection resource that is represented by this cursor.
      type: string

  required:
    - position
    - direction

The page information for cursor-based pagination should consist of a cursor set, that besides next may provide support for prev, first, last, and self as follows (see also Link relation fields):

{
  "cursors": {
    "self": "...",
    "first": "...",
    "prev": "...",
    "next": "...",
    "last": "..."
  },
  "items": [... ]
}

Note: The support of the cursor set may be dropped in favor of SHOULD use pagination links where applicable [B172] .

Further reading:

SHOULD use pagination links where applicable [B172]

To simplify client design, APIs should support SHOULD use simple hypertext controls for pagination and self-references [B209] for pagination over collections whenever applicable. Beside next this may comprise the support for prev, first, last, and self as link relations (see also Link relation fields for details).

The page content is transported via items, while the query object may contain the query filters applied to the collection resource as follows:

{
  "self": "http://my-service.api.intern.bahag.cloud/resources?cursor=<self-position>",
  "first": "http://my-service.api.intern.bahag.cloud/resources?cursor=<first-position>",
  "prev": "http://my-service.api.intern.bahag.cloud/resources?cursor=<previous-position>",
  "next": "http://my-service.api.intern.bahag.cloud/resources?cursor=<next-position>",
  "last": "http://my-service.api.intern.bahag.cloud/resources?cursor=<last-position>",
  "query": {
    "query-param-<1>": ...,
    "query-param-<n>": ...
  },
  "items": [...]
}

Note: In case of complex search requests, e.g. when GET with body is required, the cursor may not be able to encode all query filters. In this case, it is best practice to encode only page position and direction in the cursor and transport the query filter in the body - in the request as well as in the response. To protect the pagination sequence, in this case it is recommended, that the cursor contains a hash over all applied query filters for pagination request validation.

Remark: You should avoid providing a total count unless there is a clear need to do so. Very often, there are significant system and performance implications when supporting full counts. Especially, if the data set grows and requests become complex queries and filters drive full scans. While this is an implementation detail relative to the API, it is important to consider the ability to support serving counts over the life of a service.

17. Hypermedia

MUST use REST maturity level 2 [B205]

We strive for 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.:

Although this is not HATEOAS, it should not prevent you from designing proper link relationships in your APIs as stated in rules below.

MAY use REST maturity level 3 - HATEOAS [B206]

We do not generally recommend to implement  REST Maturity Level 3. 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 our e-commerce SaaS platform.

Our major concerns regarding the promised advantages of HATEOAS (see also  RESTistential Crisis over Hypermedia APIs,  Why I Hate HATEOAS and others for a detailed discussion):

  • When using the rule MUST follow API first principle [B100] , APIs are 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 practice and does not fit to our SOA set-up. The Open API description format (and tooling based on Open API) 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.

However, we do not forbid HATEOAS; you could use it, if you checked its limitations and still see clear value for your usage scenario that justifies its additional complexity.

MUST use full, absolute URI [B207]

Links to other resource must always use full, absolute URI.

Motivation: Exposing any form of relative URI (no matter if the relative URI uses an absolute or relative path) introduces avoidable client side complexity. It also requires clarity on the base URI, which might not be given when using features like embedding subresources. The primary advantage of non-absolute URI is reduction of the payload size, which is better achievable by following the recommendation to SHOULD use gzip compression [B194]

MUST use common hypertext controls [B208]

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. As IANA link relation names use hyphen-case notation, while this guide enforces snake_case notation for attribute names, hyphens in IANA names have to be replaced with underscores (e.g. the IANA link relation type version-history would become the attribute version_history)

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 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 allow  HAL, we actually don’t recommend/enforce the usage of HAL anymore as the structural separation of meta-data and data creates more harm than value to the understandability and usability of an API.

SHOULD use simple hypertext controls for pagination and self-references [B209]

For pagination and self-references a simplified form of the MUST use common hypertext controls [B208] should be used to reduce the specification and cognitive overhead. It consists of a simple URI value in combination with the corresponding link relations, e.g. next, prev, first, last, or self.

MUST not use link headers with JSON entities [B210]

For flexibility and precision, we prefer links to be directly embedded in the JSON payload instead of being attached using the uncommon link header syntax. As a result, the use of the Link Header defined by RFC 8288 in conjunction with JSON media types is forbidden.

18. Common headers

Must use common standard headers correctly

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.

MUST use Content-* headers correctly [B198]

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:

MAY use standardized headers [B199]

Use  this list and mention its support in your Open API definition.

MAY use Content-Location header [B200]

Taken over by Zalando, still needs further review.

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

SHOULD use Location header instead of Content-Location header [B201]

Taken over by Zalando, still needs further review.

As the correct usage of Content-Location with respect to semantics and caching is difficult, we discourage the use of Content-Location. In most cases it is sufficient to direct clients to the resource location by using the Location header instead without hitting the Content-Location specific ambiguities and complexities.

More details in RFC 7231 7.1.2 Location, 3.1.4.2 Content-Location

MAY consider to support Prefer header to handle processing preferences [B202]

Taken over by Zalando, still needs further review.

The Prefer header defined in RFC 7240 allows clients to request processing behaviors from servers. It pre-defines a number of preferences and is extensible, to allow others to be defined. Support for the Prefer header is entirely optional and at the discretion of API designers, but as an existing Internet Standard, is recommended over defining proprietary "X-" headers for processing directives.

The Prefer header can defined like this in an API definition:

components:
  headers:
  - Prefer:
      description: >
        The RFC7240 Prefer header indicates that a particular server behavior
        is preferred by the client but is not required for successful completion
        of the request (see [RFC 7240](https://tools.ietf.org/html/rfc7240).
        The following behaviors are supported by this API:

        # (indicate the preferences supported by the API or API endpoint)
        * **respond-async** is used to suggest the server to respond as fast as
          possible asynchronously using 202 - accepted - instead of waiting for
          the result.
        * **return=<minimal|representation>** is used to suggest the server to
          return using 204 without resource (minimal) or using 200 or 201 with
          resource (representation) in the response body on success.
        * **wait=<delta-seconds>** is used to suggest a maximum time the server
          has time to process the request synchronously.
        * **handling=<strict|lenient>** is used to suggest the server to be
          strict and report error conditions or lenient, i.e. robust and try to
          continue, if possible.

      type: array
      items:
        type: string
      required: false

Note: Please copy only the behaviors into your Prefer header specification that are supported by your API endpoint. If necessary, specify different Prefer headers for each supported use case.

Supporting APIs may return the Preference-Applied header also defined in RFC 7240 to indicate whether a preference has been applied.

MAY consider to support ETag together with If-Match/If-None-Match header [B203]

When creating or updating resources it may be necessary to expose conflicts and to prevent the 'lost update' or 'initially created' problem. Following RFC 7232 "HTTP: Conditional Requests" this can be best accomplished by supporting the etag header together with the If-Match or If-None-Match conditional header. 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, If-None-Match: * 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 can be defined as follows in the API definition:

components:
  headers:
  - ETag:
      description: |
        The RFC 7232 ETag header field in a response provides the entity-tag of
        a selected resource. The entity-tag is an opaque identifier for versions
        and representations of the same resource over time, regardless whether
        multiple versions are valid at the same time. An entity-tag consists of
        an opaque quoted string, possibly prefixed by a weakness indicator (see
        [RFC 7232 Section 2.3](https://tools.ietf.org/html/rfc7232#section-2.3).

      type: string
      required: false
      example: W/"xy", "5", "5db68c06-1a68-11e9-8341-68f728c1ba70"

  - If-Match:
      description: |
        The RFC7232 If-Match header field in a request requires the server to
        only operate on the resource that matches at least one of the provided
        entity-tags. This allows clients express a precondition that prevent
        the method from being applied if there have been any changes to the
        resource (see [RFC 7232 Section
        3.1](https://tools.ietf.org/html/rfc7232#section-3.1).

      type: string
      required: false
      example: "5", "7da7a728-f910-11e6-942a-68f728c1ba70"

  - If-None-Match:
      description: |
        The RFC7232 If-None-Match header field in a request requires the server
        to only operate on the resource if it does not match any of the provided
        entity-tags. If the provided entity-tag is `*`, it is required that the
        resource does not exist at all (see [RFC 7232 Section
        3.2](https://tools.ietf.org/html/rfc7232#section-3.2).

      type: string
      required: false
      example: "7da7a728-f910-11e6-942a-68f728c1ba70", *

Please see [optimistic-locking] for a detailed discussion and options.

MAY consider to support Idempotency-Key header [B204]

Taken over by Zalando, still needs further review.

When creating or updating resources it can be helpful or necessary to ensure a strong idempotent behavior comprising same responses, to prevent duplicate execution in case of retries after timeout and network outages. Generally, this can be achieved by sending a client specific unique request key – that is not part of the resource – via Idempotency-Key header.

The unique request key is stored temporarily, e.g. for 24 hours, together with the response and the request hash (optionally) of the first request in a key cache, regardless of whether it succeeded or failed. The service can now look up the unique request key in the key cache and serve the response from the key cache, instead of re-executing the request, to ensure idempotent behavior. Optionally, it can check the request hash for consistency before serving the response. If the key is not in the key store, the request is executed as usual and the response is stored in the key cache.

This allows clients to safely retry requests after timeouts, network outages, etc. while receive the same response multiple times. Note: The request retry in this context requires to send the exact same request, i.e. updates of the request that would change the result are off-limits. The request hash in the key cache can protection against this misbehavior. The service is recommended to reject such a request using status code 400.

Important: To grant a reliable idempotent execution semantic, the resource and the key cache have to be updated with hard transaction semantics – considering all potential pitfalls of failures, timeouts, and concurrent requests in a distributed systems. This makes a correct implementation exceeding the local context very hard.

The Idempotency-Key header must be defined as follows, but you are free to choose your expiration time:

components:
  headers:
  - Idempotency-Key:
      description: |
        The idempotency key is a free identifier created by the client to
        identify a request. It is used by the service to identify subsequent
        retries of the same request and ensure idempotent behavior by sending
        the same response without executing the request a second time.

        Clients should be careful as any subsequent requests with the same key
        may return the same response without further check. Therefore, it is
        recommended to use an UUID version 4 (random) or any other random
        string with enough entropy to avoid collisions.

        Idempotency keys expire after 24 hours. Clients are responsible to stay
        within this limits, if they require idempotent behavior.

      type: string
      format: uuid
      required: false
      example: "7da7a728-f910-11e6-942a-68f728c1ba70"

Hint: The key cache is not intended as request log, and therefore should have a limited lifetime, else it could easily exceed the data resource in size.

Note: The Idempotency-Key header unlike other headers in this section is not standardized in an RFC. Our only reference are the usage in the Stripe API. However, as it fit not into our section about [proprietary-headers], and we did not want to change the header name and semantic, we decided to treat it as any other common header.

19. I18N and L10N (Internationalization and localization)

Our API landscape is supporting all our countries and languages

I18n support is mandatory when the API supports mutiple languages. While it is possible to start without I18N, it is highly recommended to design it with I18N support even if you dont support more than one language.

It is important to acknowledge, that locales, languages and country-codes do have some overlap in the naming, but refer to different aspects and entities. The country code is used to identify the mandator/seller (think of it as the responsible subsidary company that will take care of the fullfillment of an order). The language / locale identifies the spoken language, number-formatting, currency to be used. This is a user / consumer decision and may differ from the country, in some cases there may even be multiple locales per country:

Country-Code Country Name Locales used

de

Germany

de-DE

at

Austria

de-AT

ch

Switzerland

de-CH, fr-CH, it-CH

ca

Canda

en-CA,fr-CA

lu

Luxembourg

fr-LU,de-LU

MUST use locales as described in RFC-3066 [B211]

Locales as specified in RFC-3066 must be used to identify locales

MUST implement I18N support when the API supports more then one language[B212]

All APIs must implement I18N if the user can specify the language when using the API.

SHOULD always require an language [B214]

All APIs should require the language as a parameter to have a clear user indication which language or locale should be used.

For options of indication see further entries [B215]

Example (product API)

Used URL Meaning / Case

https://api.bauhaus/v1/<api-name>/de/<resource>/22380681?language_id=de-DE

user indication is clear

https://api.bauhaus/v1/<api-name>/de/<resource>/22380681

must throw an error with  error model 2.0.1

If the product is listed for the country code given (de in this case) the API must always provide a error response not equal to 404 (as the product exists / is sold in DE).

There is no fallback language, there has to be a clear user indication.

20. API Caching

We always are using caching for all of our APIs

SHOULD monitor API usage [B131]

Owners of APIs used in production should monitor API service to get information about its using clients. This information, for instance, is useful to identify potential review partner for API changes.

Hint: A preferred way of client detection implementation is by logging of the client-id retrieved from the OAuth token.

Owners should use the API dashboard https://dashboard.api.bauhaus/. In the dashboard you see

  • the traffic of your APIs based on cloudflare logging and the apigee proxy

  • the provided versions of your APIs for all stages

  • the test result of your APIs based on the mochas tests in the API repository in github

  • the load test results from k6 if there any defined

  • the validation results of your APIs based on the openapi specification in swaggerhub

Addionally, you can see the long-term behaviour of your API in the API View tab In the overview tab, you see the overall traffic and test quality of all APIs in our API landscape.

MUST use caching headers [B228]

Every API and endpoint must inform cloudflare how the API response should be cached by Clodflare.

Non-GET Endpoints and GET /health

header value mandatory comment

cache-control

private, no-cache

Yes

GET Endpoints except /health

header value mandatory comment

cache-control

Yes

public, s-maxage=<seconds>, max-age=0

browser caching disabled, only the cloudflare cache will be used. If the cloudflare-purge service is used this value is required for supporting ajax calls (the cloudflare-purge service can’t invalidate the browser cache)

public, max-age=<seconds>

with browser caching, the browser cache will also use the given TTL.

cache-tag

Yes

<api name>_<api version>_<country-code>_<env (dev, qa, prod)>, <env (dev, qa, prod)>

You can add more tags if you like. e.g. cache-tag: "customer-notification_1.0.0_de_qs, qs". You can purge all caches with one or more tags. When you don’t have a country code, you can skip the value in your tag.

How to check if the caching is working?

If you have set your headers correctly, in your reponse, there will be a new header called "cf-cache-status":

cf-cache-status value

HIT

response served by cloudflare from the cache

MISS

response not served by cloudflare from the cache

Further Information can be found in the cloudflare documentation

21. API Versioning

We are always calling a dedicated version of an API and support mutiple versions on every stage

Semantic Versioning

First of all, we use the semantic versioning, see also https://semver.org/

That means the version of an API is described with MAJOR.MINOR.PATCH

  • MAJOR version when you make incompatible API changes

  • MINOR version when you add functionality in a backwards compatible manner

  • PATCH version when you make backwards compatible bug fixes

When to update on which version?

Major: Breaking Change or Many Changes

  • Making optional parameters or schema properties required or not required

  • Changing the format or type

  • Removing, renaming, or moving API entities such as: —  endpoints —  HTTP methods associated with endpoints —  operation query, body, or header parameters —  schema properties —  authorization roles

  • Changing the way how existing features need to be used, e.g., by introducing new preconditions to be fulfilled

  • Changing an already present workflow

  • Changing documented functional or non-functional behavior in significant ways

  • Security related changes

Minor: No Breaking Changes

  • New features

Patch: No Breaking Changes

  • Bug-Fixes in the specification

  • Updating the specification document

  • implementation remains unchanged

  • Patch updates are corrections or improvements of descriptions or examples in the API specification

Use the full MAJOR.MINOR.PATCH version in your spec

In swaggerhub and synced to your github repo you define the version of your API. See also Create  a new version of an API

basepath of your API

In the path of your API proxy use only the MAJOR version with the following pattern

api.bauhaus/v1/<api-name>/<major>/<spec>

# deprecated
api.bauhaus/v1/<api-name>/<spec>

as an example:

api.bauhaus/v1/object-capability/1/de/resources/<resource-id>/....

For sure, as long as you have only one version on production, you don’t need the major in the path (currently no API has this included)

You only need a second version of your API on production if you have breaking changes, that means in this case you would have the following scenario

api.bauhaus/v1/object-capability/1/de/resources/<resource-id>/....
api.bauhaus/v1/object-capability/2/de/resources/<resource-id>/....

providing the version of your api

The "real" version of an API you can also retrieve via the /health endpoint (see https://app.swaggerhub.com/domains/BAHAG/Problem/1.1.0#/definitions/Health).

In our case (production stage) :

api.bauhaus/v1/object-capability/1/health   -> e.g. { .., version: "1.0.3", .. }
api.bauhaus/v1/object-capability/2/health   -> e.g. { .., version: "2.0.0", .. }
---
An example response of the /health endpoint looks like this

[,json]
---
GET https://dev.api.bauhaus/v1/object-capability/1/health
{
    "team": "team name",
    "api": "api-object-capability",
    "version": "1.0.0"
}

You will see these versions also in the  API Dashboard

versions and stages

Normally, we have a preprod stage for having the same behaviour as on production. But for minimizing the maintenance effort, we agreed to use the QA stage for deploying the same versions of an API as on production. Additionally for testing purposes we have the next version of an API on the QA stage.

  • you have to provide the same version of your API on production and QA for having the chance to reproducing issues on QA which occured on production.

  • on QA make sure that the data your API provides is in sync with all the other APIs from the data point-of-view.

  • The next version to test on QA must be accessible with the full version number so that each test setup can choose a dedicated API === Example Lifecycle with QA and PROD stage

  • bugfix in spec = e.g. new optional attribute in a model

  • feature added = e.g. new endpoint

  • breaking changes = e.g. endpoint is removed (sunset)

Start with a deployed version

stage QA stage PROD

qs.api.bauhaus/v1/<api-name>/1

1.0.2

api.bauhaus/v1/<api-name>/1

1.0.2

Develop new version (bugfix in 1.0.3)

stage QA stage PROD

qs.api.bauhaus/v1/<api-name>/1

1.0.3

api.bauhaus/v1/<api-name>/1

1.0.2

Deploy new version (bugfix in 1.0.3)

stage QA stage PROD

qs.api.bauhaus/v1/<api-name>/1

1.0.3

api.bauhaus/v1/<api-name>/1

1.0.3

Releasing the bugfix and develop a new feature (in 1.1.0)

stage QA stage PROD

qs.api.bauhaus/v1/<api-name>/1

1.1.0

api.bauhaus/v1/<api-name>/1

1.0.3

releasing the new feature and develop a new version with breaking changes (2.0.0) to the spec

stage QA stage PROD

qs.api.bauhaus/v1/<api-name>/1

1.1.0

api.bauhaus/v1/<api-name>/1

1.1.0

qs.api.bauhaus/v1/<api-name>/1

2.0.0

releasing the new version with breaking changes (2.0.0) in parallel to the exiting one in production (1.1.0)

stage QA stage PROD

qs.api.bauhaus/v1/<api-name>/1

1.1.1

api.bauhaus/v1/<api-name>/1

1.1.0

qs.api.bauhaus/v1/<api-name>/1

2.1.0

api.bauhaus/v1/<api-name>/1

2.0.0

Deploy new version (2.1.0) and prepare a bugfix (1.1.1) for the old version

stage QA stage PROD

qs.api.bauhaus/v1/<api-name>/1

1.1.1

api.bauhaus/v1/<api-name>/1

1.1.0

qs.api.bauhaus/v1/<api-name>/1

2.1.0

api.bauhaus/v1/<api-name>/1

2.1.0

22. Rule validation overview

rule rule name type api-linter

B101

MUST provide API specification using Open API [B101] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B105

MUST/SHOULD contain API meta information [B105] COVERED BY API-LINTER LINTER SUPPORT

SHOULD/MUST

Yes

B106

MUST use semantic versioning [B106] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B108

MUST provide API audience [B108] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B109

MUST property names must be ASCII snake_case (and never camelCase): ^[a-z_][a-z_0-9]*$ [B109] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B111

SHOULD declare enum values using UPPER_SNAKE_CASE format [B111] COVERED BY API-LINTER

SHOULD

Yes

B113

MUST pluralize array names [B113] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B117

SHOULD name date/time properties with _at suffix [B117] COVERED BY API-LINTER LINTER SUPPORT

SHOULD

Yes

B118

SHOULD name user properties with _by suffix COVERED BY API-LINTER [B118]

SHOULD

Yes

B121

MUST specify success and error responses [B121] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B122

MUST use standard HTTP status codes [B122] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B125

MUST use code 429 with headers for rate limits [B125] LINTER SUPPORT

MUST

-

B126

MUST use problem JSON [B126] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B129

MUST use common field names and semantics [B129] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B136

MUST define collection format of header and query parameters [B136] LINTER SUPPORT

MUST

-

B141

MUST follow naming convention for hostnames [B141] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B142

MUST use lowercase separate words with hyphens for path segments [B142] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B143

MUST use lowercase separate words with hyphens for path parameters COVERED BY API-LINTER [B143]

MUST

Yes

B144

MUST use snake_case (never camelCase) for query parameters [B144] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B145

SHOULD prefer hyphenated-pascal-case for HTTP header fields [B145] LINTER SUPPORT

SHOULD

-

B146

MUST pluralize resource names [B146] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B147

MUST not use /api as base path [B147] COVERED BY API-LINTER

MUST

Yes

B148

MUST use normalized paths without empty path segments and trailing slashes [B148] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B150

MUST use JSON to encode structured data [B150] LINTER SUPPORT

MUST

-

B153

SHOULD prefer standard media type name application/json [B153] COVERED BY API-LINTER LINTER SUPPORT

SHOULD

Yes

B157

MUST define format for number and integer types [B157] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B164

MUST identify resources and sub-resources via path segments [B164] LINTER SUPPORT

MUST

-

B166

MAY consider using (non-)nested URLs [B166] LINTER SUPPORT

MAY

-

B168

SHOULD limit number of resource types [B168] COVERED BY API-LINTER LINTER SUPPORT

SHOULD

Yes

B169

SHOULD limit number of sub-resource levels [B169] COVERED BY API-LINTER LINTER SUPPORT

SHOULD

Yes

B180

MUST secure endpoints with OAuth 2.0 [B180] COVERED BY API-LINTER LINTER SUPPORT

MUST

Yes

B181

SHOULD define and assign permissions (scopes) [B181] COVERED BY API-LINTER LINTER SUPPORT

SHOULD

Yes

B184

SHOULD prefer compatible extensions [B184] LINTER SUPPORT

SHOULD

-

23. Calling an API

Every touchpoint or service has its own service account

Introduction

This article describes how to access an API with oauth 2.0 authentication.

Simple workflow of generating an OAuth 2 accesstoken and using the token in a call

If you want to use another stage then production, just exchange api.bauhaus with qa.api.bauhaus or dev.api.bauhaus.

Generate Access Token User/Password for basic authentication are the Key and Secret of your Apigee App. For every service or touchpoint there exists one Apigee App (“DEV” postfix for having access to dev.api.bauhaus and qa.api.bauhaus and “PROD” postfix for having access to api.bauhaus).

For further information about generating access tokens, see   swaggerhub

You dont need (and shouldn’t) generate an accesstoken only when your current token is expired! If you attempt to use an expired token, you’ll receive a "401 Unauthorized HTTP" response.

Response of the generate access token request (example)

{
    ...
    "access_token": "Q2xzauyPm0i6daAP86EFhYpnbETA",
    ...
}

Call your API

The use of the header x-apigee-app is mandatory, the header x-client-version is optional. With these headers we monitor the whole traffic.

POST api.bauhaus/v1/<api>/<major-version>/<country>/<ressource>/<ressource-id>
Header fields:
  Authorization: Bearer <access_token>
  accept: application/json
  # example for service account "Touchpoint DEV" you can use either "Touchpoint" or "Touchpoint DEV"
  x-apigee-app: <Apigee App with or without 'DEV/PROD'>
  # semver versioning is recommended like "1.0.0"
  x-client-version: <optional, version number of your service>

When the header “x-apigee-app” is set, you also see additional information in the   API Dashboard