This document is also available in these non-normative format: pdf
This document is licensed under
Creative Commons Attribution 4.0 International Public License
This document contains a normative standard for designing APIs in the Dutch Public Sector.
The Governance of this standard is described in a separate repository and published by Logius.
This document is part of the Nederlandse API Strategie, which consists of three distinct documents.
This is a proposed recommendation approved by TO. Comments regarding this document may be sent to api@logius.nl
This section is non-normative.
More and more governmental organizations offer REST APIs (henceforth abbreviated as APIs), in addition to existing interfaces like SOAP and WFS. These APIs aim to be developer-friendly and easy to implement. While this is a commendable aim, it does not shield a developer from a steep learning curve getting to know every new API, in particular when every individual API is designed using different patterns and conventions.
This document aims to describe a widely applicable set of design rules for the unambiguous provisioning of REST APIs. The primary goal is to offer guidance for organizations designing new APIs, with the purpose of increasing developer experience (DX) and interoperability between APIs. Hopefully, many organizations will adopt these design rules in their corporate API strategies and provide feedback about exceptions and additions to subsequently improve these design rules.
This version of the design rules has been submitted to Forum Standaardisatie for inclusion on the Comply or Explain list of mandatory standards in the Dutch Public Sector. This document originates from the document API Strategie voor de Nederlandse Overheid, which was recently split into separate sub-documents.
This document is part of the Nederlandse API Strategie.
The Nederlandse API Strategie consists of three layers of distinct documents.
Part | Description | Status | Link |
---|---|---|---|
I | General description of the API Strategy | Informative | https://docs.geostandaarden.nl/api/API-Strategie/ |
IIa | Standard for designing APIs | Normative | https://gitdocumentatie.logius.nl/publicatie/api/adr/ |
IIb | Extension on the Standard for designing APIs | Informative | https://docs.geostandaarden.nl/api/API-Strategie-ext/ |
Before reading this document it is advised to gain knowledge of the three documents, in particular the architecture section of part I.
An overview of all current documents is available in this Dutch infographic:
Design rules can be technical rules, which should be tested automatically and functional rules which should be considerd when designing and building the api.
The REST architectural style is centered around the concept of a resource. A resource is the key abstraction of information, where every piece of information is named by assigning a globally unique URI (Uniform Resource Identifier). Resources describe things, which can vary between physical objects (e.g. a building or a person) and more abstract concepts (e.g. a permit or an event).
/core/naming-resources: Use nouns to name resources
This is different than RPC-style APIs, where verbs are often used to perform certain actions:
A resource describing a single thing is called a singular resource. Resources can also be grouped into collections, which are resources in their own right and can typically be paged, sorted and filtered. Most often all collection members have the same type, but this is not necessarily the case. A resource describing multiple things is called a collection resource. Collection resources typically contain references to the underlying singular resources.
/core/naming-collections: Use plural nouns to name collection resources
Example collection resources, describing a list of things:
https://api.example.org/v1/gebouwen
https://api.example.org/v1/vergunningen
Singular resources contained within a collection resource are generally named by appending a path segment for the identification of each individual resource.
Example singular resource, contained within a collection resource:
https://api.example.org/v1/gebouwen/3b9710c4-6614-467a-ab82-36822cf48db1
https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f
Singular resources that stand on their own, i.e. which are not contained within a collection resource, must be named with a path segment that is written in the singular form.
Example singular resource describing the profile of the currently authenticated user:
https://api.example.org/v1/gebruikersprofiel
/core/interface-language: Define interfaces in Dutch unless there is an official English glossary available
/core/no-trailing-slash: Leave off trailing slashes from URIs
404
(not found) error response and not a redirect. This enforces API consumers to use the correct URI.
URI without a trailing slash (correct):
https://api.example.org/v1/gebouwen
URI with a trailing slash (incorrect):
https://api.example.org/v1/gebouwen/
/core/hide-implementation: Hide irrelevant implementation details
Although the REST architectural style does not impose a specific protocol, REST APIs are typically implemented using HTTP [rfc7231].
/core/http-methods: Only apply standard HTTP methods
Method | Operation | Description |
---|---|---|
GET |
Read | Retrieve a resource representation for the given URI. Data is only retrieved and never modified. |
POST |
Create | Create a subresource as part of a collection resource. This operation is not relevant for singular resources. This method can also be used for exceptional cases. |
PUT |
Create/update | Create a resource with the given URI or replace (full update) a resource when the resource already exists. |
PATCH |
Update | Partially updates an existing resource. The request only contains the resource modifications instead of the full resource representation. |
DELETE |
Delete | Remove a resource with the given URI. |
Request | Description |
---|---|
GET /rijksmonumenten |
Retrieves a list of national monuments. |
GET /rijksmonumenten/12 |
Retrieves an individual national monument. |
POST /rijksmonumenten |
Creates a new national monument. |
PUT /rijksmonumenten/12 |
Modifies national monument #12 completely. |
PATCH /rijksmonumenten/12 |
Modifies national monument #12 partially. |
DELETE /rijksmonumenten/12 |
Deletes national monument #12. |
The HTTP specification [rfc7231] and the later introduced PATCH
method specification [rfc5789] offer a set of standard methods, where every method is designed with explicit semantics. HTTP also defines other methods, e.g. HEAD
, OPTIONS
, TRACE
, and CONNECT
.
The OpenAPI Specification 3.x Path Item Object also supports these methods, except for CONNECT
.
According to RFC 7231 4.1 the GET
and HEAD
HTTP methods MUST be supported by the server, all other methods are optional.
In addition to the standard HTTP methods, a server may support other optional methods as well, e.g. PROPFIND
, COPY
, PURGE
, VIEW
, LINK
, UNLINK
, LOCK
, UNLOCK
, etc.
If an optional HTTP request method is sent to a server and the server does not support that HTTP method for the target resource, an HTTP status code 405 Method Not Allowed
shall be returned and a list of allowed methods for the target resource shall be provided in the Allow
header in the response as stated in RFC 7231 6.5.5.
Test case 1:
405 Method Not Allowed
.Test case 2:
405 Method Not Allowed
.Test case 3:
405 Method Not Allowed
. The response MUST contain an Allow
header with a list of supported methods for the target resource.
/core/http-safety: Adhere to HTTP safety and idempotency semantics for operations
Method | Safe | Idempotent |
---|---|---|
GET |
Yes | Yes |
HEAD |
Yes | Yes |
OPTIONS |
Yes | Yes |
POST |
No | No |
PUT |
No | Yes |
PATCH |
No | No |
DELETE |
No | Yes |
One of the key constraints of the REST architectural style is stateless communication between client and server. It means that every request from client to server must contain all of the information necessary to understand the request. The server cannot take advantage of any stored session context on the server as it didn’t memorize previous requests. Session state must therefore reside entirely on the client.
To properly understand this constraint, it's important to make a distinction between two different kinds of state:
It's a misconception that there should be no state at all. The stateless communication constraint should be seen from the server's point of view and states that the server should not be aware of any session state.
Stateless communication offers many advantages, including:
/core/stateless: Do not maintain session state on the server
The client of a REST API could be a variety of applications such as a browser application, a mobile or desktop application and even another server serving as a backend component for another client. REST APIs should therefore be completely client-agnostic.
Resources are often interconnected by relationships. Relationships can be modelled in different ways depending on the cardinality, semantics and more importantly, the use cases and access patterns the REST API needs to support.
/core/nested-child: Use nested URIs for child resources
When modelling resources for a news platform including the ability for users to write comments, it might be a good strategy to model the collection resources hierarchically:
https://api.example.org/v1/articles/123/comments
The platform might also offer a photo section, where the same commenting functionality is offered. In the same way as for articles, the corresponding sub-collection resource might be published at:
https://api.example.org/v1/photos/456/comments
These nested sub-collection resources can be used to post a new comment (POST
method) and to retrieve a list of comments (GET
method) belonging to the parent resource, i.e. the article or photo. An important consideration is that these comments could never have existed without the existence of the parent resource.
From the consumer's perspective, this approach makes logical sense, because the most obvious use case is to show comments below the parent article or photo (e.g. on the same web page) including the possibility to paginate through the comments. The process of posting a comment is separate from the process of publishing a new article. Another client use case might also be to show a global latest comments section in the sidebar. For this use case, an additional resource could be provided:
https://api.example.org/v1/comments
If this would have not been a meaningful use case, this resource should not exist at all. Because it doesn't make sense to post a new comment from a global context, this resource would be read-only (only GET
method is supported) and may possibly provide a more compact representation than the parent-specific sub-collections.
The singular resources for comments, referenced from all 3 collections, could still be modelled on a higher level to avoid deep nesting of URIs (which might increase complexity or problems due to the URI length):
https://api.example.org/v1/comments/123
https://api.example.org/v1/comments/456
Although this approach might seem counterintuitive from a technical perspective (we simply could have modelled a single /comments
resource with optional filters for article and photo) and might introduce partially redundant functionality, it makes perfect sense from the perspective of the consumer, which increases developer experience.
/core/resource-operations: Model resource operations as a sub-resource or dedicated resource
goedgekeurd
that can be modified by issuing a PATCH
request against the resource. Drawback of this approach is that the resource does not contain any metadata about the operation (when and by whom was the approval given? Was the submission declined in an earlier stage?). Furthermore, this requires a fine-grained authorization model, since approval might require a specific role./inzendingen/12/beoordelingen
and add an approval or declination by issuing a POST
request. To be able to retrieve the review history (and to consistently adhere to the REST principles), also support the GET
method for this resource. The /inzendingen/12
resource might still provide a goedgekeurd
boolean attribute (same as approach 1) which gets automatically updated on the background after adding a review. This attribute should however be read-only./search
or /_search
. Depending on the operation characteristics, GET
and/or POST
method may be supported for such a resource.An API is as good as the accompanying documentation. The documentation has to be easily findable, searchable and publicly accessible. Most developers will first read the documentation before they start implementing. Hiding the technical documentation in PDF documents and/or behind a login creates a barrier for both developers and search engines.
/core/doc-openapi: Use OpenAPI Specification for documentation
/core/doc-language: Publish documentation in Dutch unless there is existing documentation in English
/core/publish-openapi: Publish OAS document at a standard location in JSON-format
Clients (such as Swagger UI or ReDoc) must be able to retrieve the document without having to authenticate. Furthermore, the CORS policy for this URI must allow external domains to read the documentation from a browser environment.
The standard location for the OAS document is a URI called openapi.json
or openapi.yaml
within the base path of the API. This can be convenient, because OAS document updates can easily become part of the CI/CD process.
At least the JSON format must be supported. When having multiple (major) versions of an API, every API should provide its own OAS document(s).
An API having base path https://api.example.org/v1/
must publish the OAS document at:
https://api.example.org/v1/openapi.json
Optionally, the same OAS document may be provided in YAML format:
https://api.example.org/v1/openapi.yaml
Changes in APIs are inevitable. APIs should therefore always be versioned, facilitating the transition between changes.
/core/deprecation-schedule: Include a deprecation schedule when deprecating features or versions
/core/transition-period: Schedule a fixed transition period for a new major API version
/core/uri-version: Include the major version number in the URI
v
. This allows the exploration of multiple versions of an API in the browser. The minor and patch version numbers are not part of the URI and may not have any impact on existing client implementations.
An example of a base path for an API with current version 1.0.2:
https://api.example.org/v1/
version: '1.0.2'
servers:
- description: test environment
url: https://api.test.example.org/v1/
- description: production environment
url: https://api.example.org/v1/
/core/changelog: Publish a changelog for API changes between versions
When releasing new (major, minor or patch) versions, all API changes must be documented properly in a publicly available changelog.
/core/semver: Adhere to the Semantic Versioning model when releasing API changes
major.minor.patch
template (examples: 1.0.2, 1.11.0). Pre-release versions may be denoted by appending a hyphen and a series of dot separated identifiers (examples: 1.0.2-rc.1, 2.0.0-beta.3). When releasing a new version which contains backwards-incompatible changes, a new major version must be released. Minor and patch releases may only contain backwards compatible changes (e.g. the addition of an endpoint or an optional attribute).
/core/version-header: Return the full version number in a response header
Since the URI only contains the major version, it's useful to provide the full version number in the response headers for every API call. This information could then be used for logging, debugging or auditing purposes. In cases where an intermediate networking component returns an error response (e.g. a reverse proxy enforcing access policies), the version number may be omitted.
The version number must be returned in an HTTP response header named API-Version
(case-insensitive) and should not be prefixed.
An example of an API version response header:
API-Version: 1.0.2
Transport security is essential to safeguard the confidentiality, integrity, and authenticity of data during its transmission.
/core/transport-security: Apply the transport security module
Geospatial data refers to information that is associated with a physical location on Earth, often expressed by its 2D/3D coordinates.
/core/geospatial: Apply the geospatial module for geospatial data
Referenced in: