REST Guidelines

2024-12-02

1. Introduction

This style guide for RESTful services is a collaborative effort by several Belgian government institutions, originally under the G-Cloud umbrella, before moving to Belgif, the Belgian Interoperability Framework. Its goal is to improve compatibility between RESTful services offered by government institutions or any other organization adopting these guidelines.

This guide is a living document, updated when new interoperability issues arise or when REST-related standards evolve. All changes are reviewed by the REST design workgroup, in which the Belgian government organizations are represented. If you find errors or omissions, or want to contribute, you can open an issue or pull request on GitHub.

Why choose RESTful web services?

The main benefit for choosing RESTful services is to increase flexibility and to offer web service support to client platforms not able to communicate using SOAP web services.

  • REST is the defacto standard to communicate with web services from JavaScript and native mobile applications.

  • While SOAP is strictly linked to XML and needs complex standards (MTOM, Soap-with-Attachments) to work with other media formats, RESTful services can support this natively.

  • WS-* specifications added to SOAP are often overly complex and redundant to the possibilities over the underlying communication protocol (e.g. HTTP).

  • REST has become the industry standard for developing APIs on the web (Google, Facebook, Amazon, Twitter, etc).

These Belgif resources may also be useful when developing RESTful webservices:

Links

For brevity most URLs in JSON examples are shortened, but in practice they should be in absolute notation.

License

This work is licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

2. Changelog

  • 2024-12-02

    • add a list of Related Belgif initiatives

    • new rule [prop-req] on declaring properties as required

    • new rule [openapi-opid]: recommend the use of operationId

    • update rule [jsn-null]: clarify use of null

    • new rule [doc-patch] on JSON Merge Patch, illustrating the use of nullable properties

    • update rule [err-problem]: provide structured data in additional properties and recommend against parsing title or description

    • clarify that time offsets are mandatory for date-time values in JSON

    • update rule: [oas-exampl] recommending to add examples, and clarify specifics of OpenAPI 2.0 and 3.0

  • 2024-04-05

    • update [oas-tags]: add new guidelines on tags (declared, style, no more than one tag on an operation)

    • new: rule [oas-comp] applies naming guidelines on all types of components (previously in [oas-types] only for schemas)

    • added motivation for [oas-rdonly] why required properties shouldn’t be readOnly

    • clarify that health status should be from client point-of-view in Health

  • 2024-01-31

    • new: a document may also be a singleton that’s not part of a collection (Singleton documents)

    • new: path parameter names should use lowerCamelCase [path-param]

    • update: clarify that query parameters should use lowerCamelCase [uri-notat]

    • update: the name of a multi-value query parameter should be singular if it’s a noun [qry-multi]

    • new: explicit rule and clearer guidelines on GET /health resource [hlth-res]

  • 2023-10-05

    • update: [req-valid] issue type for unknown input is renamed

    • new: rule [hdr-case] on case-insensitivity of HTTP headers

    • update: explicit rules for guidelines: [meth-http] and [stat-codes] (use of appropriate HTTP methods and status codes), [col-sort] (sorting a collection)

    • update: improved overview table for the use of HTTP methods [meth-http]

    • update: corrected use of status 303: allowed on all methods in overview table [stat-codes]

    • update: links to the releases of common OpenAPI definitions are added to [oas-comdef]

    • update: Problem Details for HTTP APIs is updated to RFC 9457 which obsoletes RFC 7807 throughout the guide

  • 2023-06-14

    • add /doc/openapi.[json,yaml] to list of reserved resources Resource names

    • updated [id-name]: naming convention for OpenAPI types for identifiers and codes

    • updated [hyp-links]: avoid cross-API links, make self links optional

    • new: [req-valid]: handling unknown request parameters

    • updated list of OpenAPI tools

  • 2023-03-17

    • Rule identifier with anchor added for each rule in the guide

    • Updated [jsn-naming]: naming of JSON properties

    • Updated [id-name] with new naming conventions for identifiers and codes

Older changes

3. REST API

3.1. Uniform interface

The REST uniform interface embraces all aspects of the HyperText Transfer Protocol, version 1.1 (HTTP/1.1) including its request methods, response codes, and message headers.​

The REST uniform interface is based on three fundamental elements:

  1. Resources – How can we express where the data is being transferred to or from?

  2. HTTP Methods – What are the protocol mechanisms used to transfer the data?

  3. Media Types – What type of data is being transferred?

3.2. API

Rule: API scope [api-scope]

An API bundles a set of resources which are functionally related and form an autonomous unit of business logic.

Compare an API to the concept of Services in service-orientation or the concept of Bounded Contexts in Domain-Driven Development.

Rule: API format [uri-format]

URI = https://host/pathPrefix/apiName/vmajorVersion/resources

example: https://api.socialsecurity.be/REST/employerProfile/v1/profiles

https://

Services are at least secured on transport level using a one-way TLS connection. The implicit port is 443.

host

Hostname is determined by the environment where the service is deployed

pathPrefix

Optional path prefix to discriminate between REST APIs and other resources on the same host. (example: /REST)

apiName

Name of the API that groups a functional consistent set of resources. The API name consists of one or multiple path segments and SHOULD be written in lowerCamelCase (example: /referenceData/geography).

majorVersion

Major version numbers are integers and start at 1. See API versioning.

resources

All path segments identifying the actual resources.

3.3. Richardson Maturity Model

The Richardson Maturity Model (developed by Leonard Richardson) breaks down the principal elements of a RESTful interface into three steps. In order to be compliant to the uniform interface as described by Roy Fielding, all three levels must be fulfilled.

  1. Resources — Level 1 tackles the question of handling complexity by using divide and conquer, breaking a large service endpoint down into multiple resources, each represented by a unique URI.

  2. HTTP Methods — Level 2 introduces a standard set of verbs so that we handle similar situations in the same way, removing unnecessary variation.

  3. Hypermedia controls — Level 3 introduces discoverability, providing a way of making a protocol more self-documenting.

Rule: Maturity level [api-matur]

All REST services MUST at least respect level 2 and desirably achieve level 3.

References

4. Resources

A REST APIs exposes a set of resources. Resources model the data and functionality of a system. A resource can be data, processing logic, files, or anything else that a service may have access to.

4.1. Resource URI

Any resource is uniquely identified by a Uniform Resource Identifier or URI (RFC 3986).

URI = scheme "://" authority "/" path [ "?" query ] [ "#" fragment ]
example: https://api.socialsecurity.be/demo/v1/employers/406798006?lang=nl

URIs must be intuitively structured.

URI fragments point to a part of a resource’s representation, and are never sent to the API, but handled locally by clients. They are thereby not specified in a REST API’s specifications (OpenAPI).

Rule: URI notation [uri-notat]

Path segments and query parameters within an API SHOULD use lowerCamelCase notation to enhance readability and to separate compound names.

As lowerCamelCase is used for JSON property names as well, the casing is consistent throughout the API. Trailing slashes MUST NOT be used.

Example 1. URI notation
GOOD BAD

https://api.socialsecurity.be/demo/v1/socialSecretariats/331

https://api.socialsecurity.be/demo/v1/Social_Secretariats/331
https://api.socialsecurity.be/demo/v1/social-secretariats/331

https://api.socialsecurity.be/demo/v1/socialSecretariats/331

https://api.socialsecurity.be/demo/v1/socialSecretariats/331/

https://api.socialsecurity.be/demo/v1/socialSecretariats?country=BE

https://api.socialsecurity.be/demo/v1/Social_Secretariats?Country=BE

Rule: URI extensions [uri-extens]

The URI SHOULD NOT contain a file extension.

A notable exception to this rule is the swagger/OpenAPI file (see doc resource).

Instead, look at how to express Media Types using HTTP headers.

Example 2. URI extensions

GOOD: https://api.socialsecurity.be/demo/v1/socialSecretariats/331
BAD: https://api.socialsecurity.be/demo/v1/socialSecretariats/331.json

Rule: Query parameters [qry-filter]

The query part of a URI may be used to filter the resource output.

The query component of a URI contains a set of parameters to be interpreted as a variation or derivative of the resource. The query component can provide clients with additional interaction capabilities such as ad hoc searching and filtering.

Example 3. Query parameters

https://api.socialsecurity.be/demo/v1/socialSecretariats*?q=sdworx*
Filter the resource collection using a search string.

https://api.socialsecurity.be/demo/v1/socialSecretariats/331*?select=(name,address)*
Filter the resource properties to the ones specified in the select query parameter.

https://api.socialsecurity.be/demo/v1/socialSecretariats/331*?lang=nl*
Only return translatable properties in Dutch.

Rule: Multiple values for the same query parameter [qry-multi]

When a single query parameter can have multiple values, the parameter SHOULD be repeated for each value.

If the parameter’s name is a noun, it SHOULD be singular.

Example 4. Multi-valued parameter
Request
GET /cars?color=black&color=blue
OpenAPI 3.0
parameters:
- name: color
  in: query
  # defaults for style and explode properties are OK (style: form, explode: true)
  schema:
    type: array
    items:
      type: string
OpenAPI 2.0
parameters:
- name: color
  in: query
  collectionFormat: multi
  type: array
  items:
    type: string

4.2. Resource Archetypes

There are three different resource archetypes:

4.3. Document

A document resource is a singular concept that is referring to a business entity or object instance. A document’s state representation typically includes both fields with values and links to other related resources.

Documents in a collection

Most document resources are contained in a parent collection resource to which a variable path segment with identity key is added.

collection
Example 5. Document resource part of a collection
Location Document URI Resource representation Path template

part of collection

/persons/01234567890

{
  "ssin": "01234567890",
  "givenName": "John",
  "familyName": "Doe"
}

/persons/{ssin}

Singleton documents

Documents may also be singletons, of which only a single instance exists within the context of its parent, if any. A singleton document’s URI ends with a fixed path segment that is a singular noun.

Example 6. Singleton document resources
Location Document URI Resource representation Path template

singleton with document as parent

/persons/01234567890/profilePicture

image file (binary)

/persons/{ssin}/profilePicture

singleton without a parent

/endUserLicenseAgreement

{ "license": "Any information obtained from this API may not be made public, nor ..."}

/endUserLicenseAgreement

Child resources of a document

A document may have child resources of any type (document, collection or controller) that represent its specific subordinate concepts. When defining child resources, stick to concepts within the same API. Occasionally complex resources have so many related resources that putting all these different concepts in a single API becomes unmanageable. For example, an employer resource could link to related resources describing employees, debts, taxes, mandates, declarations, risks, etc. In that case, model the related resources in a separate API and link to the principal resource not by including a link (URL) across APIs (see [hyp-links]) but just by adding the identifier of the principal resource in the representation.

4.3.1. Identifier

Rule: Choice of identifier [id-choice]

The identity key of a document resource is preferably a natural business identifier uniquely identifying the business resource like an ISBN or SSIN. If such key does not exist, a surrogate or technical key can be created.

New types of identifiers should be designed carefully. Once an identifier has been introduced, it may get widespread usage in various systems even beyond the scope for which it was initially designed, making it very hard to change its structure later on.

Designing identifiers in a URI structure, as specified in the ICEG URI standard, is useful as it makes the identifier context-independent and more self-descriptive. A REST API may choose to use a shorter API-local form of a URI identifier because of practical considerations.

When designing an identifier, various requirements may be of importance specific to the use case:

  • the governance and lifecycle of identifiers and the entities they represent

  • easy to memorize (e.g. textual identifier like problem types)

  • input by user (e.g. web form, over phone/mail)

    • easy to type (ignore special separator chars, difference between lower/capital case), limited length

    • validation of typing errors, e.g. by checksum, fixed length, …​

    • hint on format to recognize purpose of identifier based on its value

  • printable (restricted length)

  • open to evolve structure for new use cases

  • ability to generate identifiers collision-free at multiple independent sources, e.g. by adding a source-specific prefix, using UUIDs, …​

  • stable across different deployment environments (e.g. problem type codes)

  • hide any business information (e.g. no sequential number that indicates number of resources created)

  • not easy to guess a valid identifier, especially for unsecured resources (e.g. no sequentially generated identifier)

  • easy to represent in URL parameter without escaping

  • sortable (for technical reasons e.g. pagination)

Rule: Designing new identifiers [id-design]

For new identifiers, a string based format SHOULD be used: textual lowerCamelCase string codes, UUID, URI or other custom formats. Take into account the requirements that follow from the ways the identifier will be used.

Each identifier MUST be represented by only one single string value, so that a string equality check can be used to test if two identifiers are identical. This means that capitalization, whitespace or leading zeroes are significant.

In the OpenAPI data type for the identifier, a regular expression may be specified if helpful for input validation or as hint of the structure (e.g. to avoid whitespace or wrong capitalization), but shouldn’t be too restrictive in order to be able to evolve the format.

No business meaning SHOULD be attributed to parts of the identifier. This should be captured in separate data fields. Parts with technical meaning like a checksum are allowed.

Parts of an identifier may carry some business meaning for easier readability, like the problem type identifiers in this guide, but no application logic should parse and interpret these parts.
Don’t use database-generated keys as identity keys in your public API to avoid tight coupling between the database schema and API. Having the key independent of all other columns insulates the database relationships from changes in data values or database design (agility) and guarantees uniqueness.
Example 7. Identifiers

The table below lists some examples of identifiers, though it does not list all possibilities or considerations when designing a new identifier.

identifier structure example OpenAPI type considerations

UUID

"d9e35127-e9b1-4201-a211-2b52e52508df"

Type defined in common-v1.yaml

Uuid:
  description: Universally Unique Identifier, as standardized in RFC 4122 and ISO/IEC 9834-8
  type: string
  pattern: '^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$'

long identifier, not easy to memorize or input by user, easy to generate, resistant to brute-force guessing

URI (URN)

"urn:problem-type:belgif:resourceNotFound"

type: string
format: uri
pattern: "^urn:problem-type:.+$" # further restrictions may be possible

can be human-readable, long, not easy to input by user

URI (http)

"https://www.waterwegen.be/id/rivier/schelde"

type: string
format: uri

can be human-readable, long, not easy to input by user, requires character escaping when used as URL parameter can be generated collision-free by multiple sources (different domain name)

custom format

"ab12347895"

type: string
pattern: "^[a-z0-9]{1-20}$"

short, easy to encode

A code is a special type of identifier:

  • it has an exhaustive list of possible values that doesn’t change frequently over time

  • each value identifies a concept (examples: a country, a gender, …​).

Rule: Designing new codes [cod-design]

New code types SHOULD be represented as string values in lowerCamelCase.

Depending on context, the OpenAPI data type may enumerate the list of allowed values (see Enum values rule).

Example 8. Code

GET /refData/paymentMethods/{code} with code of type PaymentMethodCode

As string with enumeration:

PaymentMethodCode:
  type: string
  enum:
  - cash
  - wireTransfer
  - creditCard
  - debitCard

As string with regular expression:

PaymentMethodCode:
  type: string
  pattern: "^[A-Za-z0-9]+$"
  example: "debitCard"
Rule: Representing existing numerical identifiers [id-numer]

When defining the type for a property representing an existing numerical code or identifier:

  • Identifiers that are commonly represented (e.g. when displayed or inputted by a user) with leading zeros present SHOULD be represented using a string type. A regular expression SHOULD be specified in the OpenAPI data type to avoid erroneous values (i.e. without leading zeros).

  • Otherwise, use an integer based type. It is RECOMMENDED to further restrict the format of the type (e.g. format: int32 and using minimum/maximum).

For new identifiers, it is not recommended to use a number type however as stated in [new-identifiers]

Example 9. Representing existing numerical identifiers

An employer ID may be of variable length. Leading zeroes are ignored and most of the time not displayed.

EmployerId:
  description: Definitive or provisional NSSO number, assigned to each registered employer or local or provincial administration.
  type: integer
  format: int64
  minimum: 0
  maximum: 5999999999
  example: 21197

If SSIN has a zero as first digit, it is always displayed.

Ssin:
  description: Social Security Identification Number issued by the National Register or CBSS
  type: string
  pattern: '^\d{11}$'

Country NIS code is a three-digit code, the first digit cannot be a zero.

CountryNisCode:
  description: NIS code representing a country as defined by statbel.fgov.be
  type: integer
  minimum: 100
  maximum: 999
  example: 150 # represents Belgium
Rule: Identifier name [id-name]

Following naming guidelines should be applied when using an identifier or code in a REST API:

  • JSON property:

    • within an object that represents the entire or part of a resource: use id or code

    • to reference a resource within another one’s representation: property name should designate the relation between the resources (see [jsn-naming]); no need to suffix with id or code

  • path parameter: use id or code, OPTIONALLY prefixed with the resource type to disambiguate when there are multiple identifiers as path parameters

  • query search parameter: use same name as the property in the JSON resource representation of the response (see Filtering)

  • OpenAPI type: add suffix Id or Code to distinguish it from the type of the full resource representation

As an exception, use the standardized name for the business identifier if one exists, rather than id or code.

If multiple identifiers or coding schemes may be used within the same context, a suffix can be added to the name to disambiguate.

Example 10. Identifier name

Request:

GET /stores/{storeId}/orders/{orderId} (1)

Response:

{
  "id": 123,  (2)
  "customer": 456, (3)
  "store": {
     "id": 789, (4)
     "href": "/stores/789"
  },
  "paymentMethod": "creditCard",
  "deliveryMethod": {
     "code": "deliveredAtHome", (5)
     "href": "/refData/deliveryMethods/deliveredAtHome"
  }
}

OpenAPI types (not all are listed for brevity):

Order: (6)
  type: object
  properties:
    id:
      $ref: "#/components/schemas/OrderId" (7)
    customer:
      $ref: "#/components/schemas/CustomerId" (7)
    store:
      $ref: "#/components/schemas/StoreReference" (8)
    paymentMethod:
      $ref: "#/components/schemas/PaymentMethodCode" (7)
    deliveryMethod:
      $ref: "#/components/schemas/DeliveryMethodReference" (8)

OrderId: (7)
  type: integer
  format: int32
  minimum: 1

StoreReference: (8)
  allOf:
  - $ref: "./common/v1/common-v1.yaml#/components/schemas/HttpLink"
  type: object
  properties:
    id:
      $ref: "#/components/schemas/StoreId" (7)

StoreId: (7)
  type: integer
  format: int32
  minimum: 1
1 path parameter: id prefixed with resource type to be able to distinguish both parameters
2 id as property of the consulted resource
3 identifier used to reference another resource. JSON property name designates relation to the other resource.
4 id as property in a partial representation of a store resource
5 code as property in a partial representation of a deliveryMethod resource
6 The type of the full resource representation doesn’t have a suffix
7 Types of the identifiers have the suffix Id or Code
8 Partial resource representations, which may link to the full resource
GET /refData/deliveryMethods/{code} (1)
{
  "code": "deliveredAtHome",
  "description": {
     "nl": "Geleverd aan huis",
     "en": "Delivered at home",
     "fr": "Livré à domicile"
  }
}
1 Since there are no child resources with other path parameters, simply code suffices
Example 11. Standardized business identifiers
GET /persons/{ssin} (1)
{
  "ssin": "12345678901", (1)
  "partner": "2345678902", (2)
  "civilStatus": 1
}
1 Standardized business identifier name Ssin is preferred over id.
2 JSON property name designates relation to the other resource. The OpenAPI specification declares the expected value to be of type Ssin.
Example 12. Multiple types of identifiers
GET /addresses/{addressId}
{
  "municipality": {
    "code": 10000,
    "name": "Brussels"
  },
  "country": {
    "nisCode": 150, (1)
    "isoCode": "BE", (1)
    "name": {
      "nl": "België",
      "fr": "Belgique"
    }
  }
}
1 Prefixes nis and iso disambiguate between two types of country identifiers used in a single context

4.3.2. Consult

Example 13. Consulting a document

GET

/employers/{employerId}

Consult a single employer

Parameters

employerId

path-param

NSSO number uniquely identifying the employer.

Response

body

The response properties and links to other resources.

{
  "self": "/employers/93017373",
  "name": "Belgacom",
  "employerId": 93017373,
  "company": {
    "enterpriseNumber": "0202239951",
    "href": "/companies/202239951"
  }
}

Most used response codes

200

OK

Default success code if the document exists.

400

Bad request

The dynamic path segment containing the identity key has a wrong data format:

{
  "type": "urn:problem-type:belgif:badRequest",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/badRequest.html",
  "status": 400,
  "title": "Bad Request",
  "instance": "urn:uuid:d9e35127-e9b1-4201-a211-2b52e52508df",
  "detail": "The input message is incorrect",
  "issues": [
    {
      "type": "urn:problem-type:belgif:input-validation:schemaViolation",
      "in": "path",
      "name": "employerId",
      "value": "abc",
      "detail": "This value should be numeric"
    }
  ]
}

404

Not Found

The document resource does not exist.

204 No content should not be used with GET.
Rule: Retrieve partial resource representation [res-repres]

The select query parameter is reserved to return a resource representation with only the specified properties.

The value of this parameter SHOULD follow this BNF grammar:

<selects>            ::= [ <negation> ] <selects_struct>
<selects_struct>     ::= "(" <select_items> ")"
<select_items>       ::= <select> [ "," <select_items> ]
<select>             ::= <select_name> | <selects_substruct>
<selects_substruct>  ::= <select_name> <selects_struct>
<select_name>        ::= <dash_letter_digit> [ <select_name> ]
<dash_letter_digit> ::= <dash> | <letter> | <digit>
<dash>              ::= "-" | "_"
<letter>            ::= "A" | ... | "Z" | "a" | ... | "z"
<digit>             ::= "0" | ... | "9"
<negation>          ::= "!"
Example 14. Select query parameter
GET /employers/93017373?select=(name)
{
  "self": "/employers/93017373?select=(name)",
  "name": "Proximus"
}

Note that parentheses around the value of the select parameter are required, even when selecting a single property.

This notation can also be used for nested properties:

GET /employers/93017373?select=(name,address(street(name,code)))
{
  "self": "/employers/93017373",
  "name": "Proximus",
  "address": {
    "street": {
      "name": "Koning Albert II laan",
      "code": 2177
    }
  }
}

4.3.3. Update

Updating a resource may be done in one of several ways. One and only one of following patterns should be chosen per resource, unless forced by a backwards compatible change.

In order of preference:

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

    This option is preferred when clients are likely to always take into account the entire resource representation. If a client ignores some of a resource’s properties returned by a consultation, they are likely to be omitted from the PUT request and thus lost. This scenario may occur when new properties were added during the API lifecycle. In this case, use of PUT isn’t advised.

  2. Use PATCH with partial objects to only update parts of a resource, whenever possible, using the JSON Merge Patch standard.

    JSON Merge Patch is limited however, e.g. it doesn’t allow for an update of a single element of an array. If this proves to be an issue, this might however indicate that the array elements might be better modeled as separate sub-resources.

  3. use POST on a child resource instead of PATCH if the request does not modify the resource in a way defined by the semantics of the media type. See Controller for more information.

Use of the JSON Patch standard, an alternative to JSON Merge Patch, is not recommended, as it proves to be difficult to implement.

Full update

Use PUT when you like to do a complete update of a document resource. All values are replaced by the values submitted by the client. Absent optional values in the request are set to their default value if one is specified in the OpenAPI specification.

Example 15. PUT on a document resource
PUT https://api.socialsecurity.be/demo/v1/employers/93017373 HTTP/1.1

{
    "employerId": 93017373,
    "name": "Belgacom"
}

PUT

/employers/{employerId}

Replace the entire employer resource with the client data. This implies a full update of the resource. Via PUT the client submits new values for all the data.

Request

body

Full representation of the resource to persist.

Parameters

employerId

path-param

employer ID of NSSO uniquely identifying the employer.

Response

body

either empty or resource after update

{
    "employerId": 93017373,
    "name": "Belgacom"
}

Most used response codes

200

OK

The update is successful and updated resource is returned.

204

No Content

The update is successful but updated resource is not returned.

400

Bad request

The input data is not valid according the data schema.

404

Not Found

The resource does not exist and thus cannot be updated.

409

Conflict

The client data is in conflict with the data on the server e.g. optimistic locking issues.

Partial update
Rule: JSON Merge patch [doc-patch]

Use PATCH when you like to do a partial update of a document resource.

The PATCH message SHOULD conform to the JSON Merge Patch (RFC 7386) specification:

  • JSON properties in the request overwrite the ones in the previous resource state

  • properties with value null in the request are removed from the resource

  • properties not present in the request are preserved

APIs should support both the media type of JSON merge patch application/merge-patch+json as the generic application/json JSON media type.

Properties that can be removed, must have nullable: true in the OpenAPI 3.0 schema describing the JSON Merge Patch request, which is an exception to [jsn-null]. JSON Merge Patch requests SHOULD be represented by a different schema than other document operations (create, consult or full update), because in a JSON Merge Patch request, required properties may be absent or optional properties may be removed using a null value.

OpenAPI 2.0 does not allow to document the use of null values. Instead, a MergePatch marker type should be used, defined in common-v1.yaml.

Example 16. JSON merge patch

PATCH

/employers/{employerId}

Performs a partial update of an existing employer.

Request

body

application/merge-patch+json or application/json

{
  "bankrupt": false,
  "bankruptDate": null
}

schema

OpenAPI 3.0
EmployerPatch:
  type: object
  properties:
    bankrupt:
      type: boolean  # mandatory property, shouldn't be nullable
    bankruptDate:
      type: string
      format: date
      nullable: true # optional property, use null to remove it
OpenAPI 2.0
# Schema from https://github.com/belgif/openapi-common/blob/master/src/main/swagger/common/v1/common-v1.yaml
MergePatch:
  description: JSON Merge Patch (RFC 7386) request body
  type: object
  additionalProperties: true
  # Marker type as JSON Merge Patch requests can not be fully specified as a JSON Schema type in OpenAPI 2.0

Parameters

employerId

path-param

employer ID of NSSO uniquely identifying the employer.

Response

body

empty or the complete resource after applying PATCH

{
    "employerId": 93017373,
    "name": "Belgacom",
    "bankrupt": false
}

Most used response codes

200

OK

Success code with resource after applying PATCH returned.

204

No Content

Success code without returning the resource.

400

Bad request

The input data is not valid according the data schema.

404

Not Found

The resource does not exist and thus cannot be updated.

409

Conflict

The client data is in conflict with the data on the server e.g. optimistic locking issues.

4.3.4. Remove

Use DELETE when you like to delete a document resource.

Example 17. Deleting a document resource

DELETE

/employers/{employerId}

Deletes an employer.

Parameters

employerId

path-param

employer ID of NSSO uniquely identifying the employer.

Response

body

empty or a message indicating success

204 No Content

or

200 OK
{
 "message": "Employer is deleted."
}

Most used response codes

200

OK

Success code with the deleted resource returned.

204

No Content

Success code with the deleted resource not returned.

400

Bad request

Generic error on client side. For example, the syntax of the request is invalid.

404

Not Found

The resource does not exist and thus cannot be deleted.

409

Conflict

A constraint on the resource is violated. The resource could not be deleted because the request is in conflict with the current state of the resource.

For example, a REST API may return this response code when a client tries to DELETE a non-empty store resource.

4.3.5. Long-running tasks

Some operations need to be performed asynchronously, as they take too long to complete.

Rule: Long-running tasks [lng-task]

Long-running tasks MUST be represented as a resource. The task resource is created using a POST action returning a 202 Accepted response containing the URL of the task in the Location HTTP header. It can then be fetched by the client to get the processing status of the task.

When GETting the task resource, the response can be:

  • Still processing: status 200 OK and a representation of the task’s current status

  • Success: status 303 See Other with the Location header containing the URL of the task’s output.

  • Failure: status 200 OK with a representation of the task’s status, including the reason of the failure

Variations on the above may be required, e.g. if the task has no output, the response on success may be 200 OK without a Location header. The schema common-v1.yaml defines the representation of a task’s status.

Example 18. Long-running task

Submitting the task

POST /images/tasks

HTTP/1.1 202 Accepted
Content-Type: application/json;charset=UTF-8
Location: http://www.example.org/images/tasks/1
Date: Sun, 13 Sep 2018 01:49:27 GMT
{
  "self": "/images/tasks",
  "status": {
    "state": "processing",
    "pollAfter": "2018-09-13T01:59:27Z"
  }
}

The response 202 Accepted status indicates that the server accepted the request for processing. pollAfter hints when to check for an updated status at a later time.

Getting the processing status

GET /images/tasks/1

When the server is still processing the task

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
  "self": "/images/tasks/1",
  "status": {
    "state": "processing",
    "pollAfter": "2018-09-13T02:09:27Z"
  }
}

When processing has completed

HTTP/1.1 303 See Other
Location: http://www.example.org/images/1
Content-Type: application/json;charset=UTF-8
{
  "self": "/images/tasks/1",
  "status": {
    "state": "done",
    "completed":"2018-09-13T02:10:00Z"
  }
}

The Location header refers to the result of the task.

In case of failure during processing

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
  "self": "/images/tasks/1",
  "status": {
    "state": "failed",
    "completed":"2018-09-13T02:10:00Z",
    "problem": {
      "instance": "urn:uuid:d9e35127-e9b1-4201-a211-2b52e52508df",
      "title": "Bad Request",
      "status": 400,
      "type": "urn:problem-type:example:invalidImageFormat",
      "href": "https://example.org/example/v1/refData/problemTypes/urn:problem-type:example:invalidImageFormat",
      "detail": "Invalid image format"
    }
  }
}

Note that the status code is 200 OK as the retrieval of the task’s status succeeded. The cause of failure is represented using an embedded Problem object, as defined in Error handling.

4.4. Collection

A collection resource is a server-managed list of document resources.

Rule: Collection name [col-name]

A plural noun SHOULD be used for collection names, for example 'employers' or 'children'.

Example 19. Collection name

https://api.socialsecurity.be/demo/v1/persons/{ssin}/children - a collection of children as a child resource of a person

4.4.1. Consult

Rule: Representation of a collection [col-repres]

The representation of a collection MUST contain a list of links to child resources:

  • A collection result MUST contain an items property with an array of objects, each one representing an item in the collection.

  • In these objects, a href property MUST be present with a link to the resource.

  • The unique business identifier SHOULD be present for each item.

  • Each item object MAY be extended with some key business properties, needed for display in a master view.

  • In case the collection is empty, the items property MUST have an empty array as value.

  • The title property MAY be used to provide a human-readable description for an item, usable as display text for the link.

  • The number of items in the collection result set MAY be included in the response using the total property. If the response is paginated, its value MUST correspond to the number of items regardless of pagination, rather than only the number of items in the current page.

A collection resource, like other resources, MUST always return a JSON object as top-level data structure to support extensibility ([evo-object]). Do not return a JSON array, because the moment you like to add paging, hypermedia links, etc. your API will break.
Table 1. Most used response codes

200

OK

Default response code, also when the collection is empty.

400

Bad Request

Generic error on client side. For example, the syntax of the request is invalid.

404

Not found

The URI provided cannot be mapped to a resource.

204 No content should not be used with GET.
Rule: Sorting a collection [col-sort]

A sort multi-value query parameter MAY be provided to allow a client to sort the items of a collection resource on one or more specified properties. Descending sorting order can be indicated by prefixing the property name with -.

The API SHOULD document which properties can be used for sorting, and whether descending order is allowed.

Table 2. Sort query parameter

sort

Multi-value query param with list of properties to sort on. Direction is ascending by default. To indicate descending, prefix property with -.

?sort=age&sort=-name

Example 20. Consulting a collection
Response
{
 "self": "https://api.socialsecurity.be/demo/v1/employers",
 "items":[
   {
     "href":"/employers/93017373",
     "title":"Belgacom",
     "employerId": 93017373,
     "enterpriseNumber": "0202239951"
   },
   {
     "href":"/employers/20620259",
     "title":"Partena VZW",
     "employerId": 20620259,
     "enterpriseNumber": "0409536968"
   }
 ],
 "total":2
}
JSON data types
EmployersCollection:
  allOf:
  - $ref: common/v1/common-v1.yaml#/components/schemas/SelfLink
  - type: object
    properties:
      items:
        type: array
        items:
          $ref: "#/components/schemas/EmployerLink"
      total:
        type: integer
    required:
      - items
      - total
EmployerLink:
  allOf:
  - $ref: common/v1/common-v1.yaml#/components/schemas/HttpLink
  - type: object
    properties:
      employerId:
        $ref: "./employer/identifier/v1/employer-identifier-v1beta.yaml#/components/schemas/EmployerId"
      enterpriseNumber:
        $ref: "./organization/identifier/v1/organization-identifier-v1.yaml#/components/schemas/EnterpriseNumber"
Rule: Getting full representations of collection items [col-embed]

When the collection items contain few data, you may want to retrieve them in full when getting the collection. In this case, the full representations MUST be included in an 'embedded' property as described in Embedding resources.

Example 21. Consulting a collection with embedded items
Response
{
 "self": "/appendices/employerclasses?embed=items",
 "items": [
  {
   "value": "0",
   "href": "/appendices/employerclasses/0"
  }, {
   "value": "2",
   "href": "/appendices/employerclasses/2"
  }
 ],
 "total":2,
 "embedded": {
   "/appendices/employerclasses/2": {
     "self": "/appendices/employerclasses/2",
     "value": "2",
     "description": {
       "nl": "Bijzondere categorie voor werkgevers die voor hun arbeiders een speciale bijdrage verschuldigd zijn.",
       "fr": "Catégorie particulière pour les employeurs redevables pour les ouvriers d’une cotisation spéciale."
      }
   },
   "/appendices/employerclasses/0": {
     "self": "/appendices/employerclasses/0",
     "value": "0",
     "description": {
      "nl": "Algemene categorie voor werkgevers van commerciële of niet-commerciële aard.",
      "fr": "Catégorie générale pour les employeurs, de type commercial ou non-commercial."
     }
   }
 }
}
JSON data types
AppendixCodesCollection:
  description: A collection of appendix codes
  type: object
  properties:
    items:
      type: array
      items:
        $ref: '#/components/schemas/AppendixCodeLink'
    total:
      type: integer
    embedded:
      type: object
      additionalProperties:
        $ref: 'appendixCode.yaml#/components/schemas/AppendixCode'
  required:
    - items
    - total
AppendixCodeLink:
  allOf:
  - $ref: 'common/v1/common-v1.yaml#/components/schemas/HttpLink'
  - type: object
    properties:
      value:
        $ref: 'appendixCode.yaml#/components/schemas/AppendixCodeValue'

4.4.2. Filtering

Rule: Filtering items in a collection [col-filter]

A collection may support query parameters to filter its items:

  • a query parameter with the name of a document property, filters items on the specified value

  • the query parameter q is reserved for a full text search on all the document’s content

  • other filter parameters may be supported, e.g. to look up items within a search period

Unless the API documentation explicitly states otherwise, returned collection items:

  • should satisfy ALL filter query parameters (AND-logic)

  • have to match ANY of the values of a multi-valued filter query parameter (OR-logic).

For example, the query GET /cars?doors=5&color=black&color=blue should be interpreted by default as: "search for all cars that have 5 doors AND are of color blue OR black".

GET

/employers

get all the employers documents in the collection

​​​Parameters

name

query-param

Filter only employers that have a specific name.

Response

body

{
  "self": "/companies?name=belg",
        "items": [{
                "href": "/companies/202239951",
                "title": "Belgacom"
        }, {
                "href": "/companies/448826918",
                "title": "Carrefour Belgium SA"
        }],
        "total": 2
}

Most used response codes ​​

200

OK

Default response code, also when the filtered collection is empty.

400

Bad Request

Generic error on client side. For example, the syntax of the request is invalid.

404

Not found

The URI provided cannot be mapped to a resource. ​

204 No content should not be used with GET.

4.4.3. Pagination

Rule: Paging over a large collection​ [col-paging]

Collection with too many items MUST support pagination. There are two pagination techniques:

  • offset-based pagination: numeric offset identifies a page

  • cursor-based (aka key-based or luke index): a unique key element identifies a page

Cursor-based pagination has some advantages, especially for high volumes. Take into account the considerations listed in the Zalando API guidelines before choosing a pagination technique.

Table 3. Reserved JSON properties:

next

MANDATORY (except for the last page)

hyperlink to the next page

prev

OPTIONAL

hyperlink to the previous page

pageSize

RECOMMENDED

Maximum number of items per page. For the last page, its value should be independent of the number of actually returned items.

page

MANDATORY (offset-based); N/A (cursor-based)

index of the current page of items, should be 1-based (the default and first page is 1)

first

OPTIONAL

hyperlink to the first page

last

OPTIONAL

hyperlink to the last page

total

OPTIONAL

Total number of items across all pages. If query parameters are used to filter the result set, this is the total of the collection result set, not of the entire collection.

The names of the properties with hyperlink values and the items property are derived from the IANA registered link relations.

Table 4. Reserved query parameters:

pageSize

OPTIONAL

maximum number of items per page desired by client; must have a default value if absent.

page

MANDATORY with default value 1 (offset-based); N/A (cursor-based)

the index of page to be retrieved

Example 22. Offset-based pagination
{
  "self": "/companies?page=2&pageSize=2",
  "items": [
    {
      "href": "/companies/202239951",
      "title": "Belgacom"
    },
    {
      "href": "/companies/212165526",
      "title": "CPAS de Silly"
    }
  ],
  "pageSize": 2,
  "page": 2,
  "total": 7,
  "first": "/companies?pageSize=2",
  "last": "/companies?page=4&pageSize=2",
  "prev": "/companies?page=1&pageSize=2",
  "next": "/companies?page=3&pageSize=2"
}
Example 23. Cursor-based pagination
{
  "self": "/companies?afterCompany=0244640631&pageSize=2",
  "items": [
    {
      "href": "/companies/202239951",
      "title": "Belgacom"
    },
    {
      "href": "/companies/212165526",
      "title": "CPAS de Silly"
    }
  ],
  "pageSize": 2,
  "total": 7,
  "first": "/companies?pageSize=2",
  "next": "/companies?afterCompany=0212165526&pageSize=2"
}

4.4.4. Create a new resource​

The collection resource can be used to create new document resources from the POST request body. Absent optional values are set to their default value if one is specified in the OpenAPI specification.

​​​​​​​​​POST

/employers

create a new employer in the collection

​​​Request

body

​The data of the resource to create.

{
  "name": "Belgacom",
  "employerId": 93017373,
  "company": {
    "enterpriseNumber": "0202239951"
  }
}

Response headers

Location

http-header

The URI of the newly created resource e.g. /employers/93017373

Response

body

The API should specify for each creation operation, if it returns:

  • an empty body,

  • a partial resource representation (e.g. only a generated resource identifier),

  • or a full resource representation.

Most used response codes ​​

201

Created

Default response code if the resource is created.

409

Conflict

The resource could not be created because the request is in conflict with the current state of the resource. E.g. the resource already exists (duplicate key violation).

303

See Other

The resource already exists. May be returned instead of 409 Conflict if it is considered a normal use case to perform the operation for an already existing resource. The Location header refers to the resource.

200 OK should not be used with POST for creating resources.
POST /employers HTTP/1.1

HTTP/1.1 201 Created
Location: /employers/93017373
Content-Length: 0
Date: Wed, 06 Jan 2016 15:37:16 GMT

4.4.5. Remove

A selection of items can be removed from a collection using the DELETE method. In fact, the collection itself cannot be removed, but it can be emptied if all its items are removed. By using query parameters, the items to be removed can be filtered. In order to remove a single specific item from a collection, use DELETE on the document resource.

DELETE

/employers

Delete all the employers documents in the collection.

Parameters

name

query-param

Remove only employers that have a specific name.

Response

body

Empty or a message incidating success.

Most used response codes

200

OK

The items are successfully removed from the collection and returned.

204

No content

The items are successfully removed from the collection but no additional content is included in the response body.

400

Bad Request

Generic error on client side. For example, the syntax of the request is invalid.

404

Not found

The collection resource does not exist and thus cannot be deleted.

4.5. Controller

A controller resource models a procedural concept. Controller resources are like executable functions, with parameters and return values; inputs and outputs.

A REST API can rely on controller resources to perform application-specific actions that cannot be logically mapped to one of the standard methods (standard CRUD-operations).

Rule: Controller resource [ctl-res]

A controller resource name is a verb instead of a noun.

The HTTP method on a controller SHOULD be:

  • POST for actions with side effects (state change) or actions without side effects but requiring a request body

  • GET for idempotent actions without side effects

Example 24. Controller examples

Withdrawal from an account:

POST /accounts/123/withdraw

Sending a notification to an employer:

POST /employers/93017373/sendNotification

Converting money from one currency to another (using GET because of no side effects):

GET /convertMoney?from=EUR&amount=45&to=USD

4.5.1. Controller vs document

Rule: Reification of controller resource [ctl-reific]

Before using a controller resource to represent an action, consider reifying the action as a collection or document resource (noun) describing the intent of the action.

Long-running tasks are an example of actions modelled as a collection/document instead of a controller.

Example 25. Reification examples

Withdrawal from an account as a controller resource:

POST /account/123/withdraw

or as a collection resource:

POST /account/123/withdrawals

Using a noun improves extensibility in this case. For instance, a GET operation could be added to consult a history of all withdrawals executed on the account.

5. HTTP Methods

5.1. GET

GET must be used to retrieve a representation of a resource. It is a strict read-only method, which should never modify the state of the resource.

5.2. POST

POST must be used to create a new resource in a collection or to execute a controller resource.

When using for resource creation, the POST request’s body contains the suggested state representation of the new resource to be added to the server-owned collection. The response should contain a Location HTTP header containing the URI of the newly created resource, while the body contains either its (partial or full) representation or is empty.

POST operations may have side effects (i.e. modify state), and are not required to be idempotent.

5.3. PUT

PUT must be used to update a stored resource under a consumer supplied URI. It could be used to insert a new resource in case the client application decides on the resource identifier.

If the URI refers to an already existing resource, the enclosed entity SHOULD be considered as a new version to replace the one residing on the server. If the target resource is successfully modified in accordance with the state of the enclosed representation, then a 200 (OK)​ response SHOULD be sent to indicate successful completion of the request.

If the URI does not point to an existing resource, and that URI is capable of being defined as a new resource, the server can create the resource with that URI. The server MUST inform the client by sending a 201 (Created)​ response to indicate succesful creation.

PUT operations may have side effects (i.e. modify state), but MUST be idempotent.

5.4. PATCH

PATCH must be used to update partially a representation of a resource.

The enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version. The PATCH method affects the resource identified by the Request-URI, and it also MAY have side effects on other resources; i.e., new resources may be created, or existing ones modified, by the application of a PATCH.​ PATCH operations are not required to be idempotent, however they will often be in pratice.

See Partial update on how to specify the set of instructions describing how a resource on the server should be modified.

Compatibility

The PATCH http method may not be supported in all components participating in the communication (e.g. HTTP client, intermediary proxy). If these can’t be upgraded to add support, client and server may agree to work around this by implementing PATCH as POST operations with HTTP header 'X-HTTP-Method-Override: PATCH'.

5.5. DELETE

DELETE must be used to remove a resource from its parent. Once a DELETE request has been processed for a given resource, the resource can no longer be found by clients. Therefore, any future attempt to retrieve the resource’s state representation, using either GET or HEAD, must result in a 404 (“Not Found”)​ status returned by the API.

HEAD should be used to only retrieve the HTTP response headers​. HEAD returns the same response as GET, except that the API returns an empty body. It is strictly read-only.

5.7. OPTIONS

OPTIONS should be used to retrieve metadata that describes a resource’s available interactions.

5.8. How to use each method

Rule: Use appropriate HTTP method [meth-http]

The HTTP methods SHOULD be used that are appropriate for the type of action performed on the resource.

The table below lists the usages of HTTP methods for each resource archetype. Combinations explicitly excluded ( -) SHOULD NOT be used.

Method Document Collection Controller Request body Response body

GET

consult

consult

idempotent actions without side effects

Empty

Resource(s)

POST

-

create document

action with side effects or requiring request body

Representation of the created resource or controller info

Optional

PUT

full update

-

-

Representation of the updated resource

Optional

PATCH

partial update

-

-

Fields of the resource to update

Optional

DELETE

remove

remove selection of items

-

Empty

Optional

HEAD

X

X

X

Empty

Empty

OPTIONS

X

X

X

Empty

Optional

6. Status codes

6.1. Overview

Rule: Use appropriate HTTP status codes [stat-codes]

The HTTP response status code SHOULD be returned that best describes the outcome of the treatment of the request.

The table below list most common combinations of a status code for each HTTP method, though may not be exhaustive. Combinations explicitly excluded ( -) SHOULD NOT be used.

The full list of HTTP status codes is documented here.

This HTTP status codes chart (takes a while to load) shows a decision tree to determine the usage of the correct HTTP status code.

Code GET HEAD PUT POST PATCH DELETE OPTIONS

200 OK

X

X

X

X (controller only)

X

X

X

201 Created

-

-

X (creation only)

X

-

-

-

202 Accepted

-

-

-

X

-

-

X

204 No Content

-

X

X

X

X

X

-

301 Moved Permanently

X

X

X

X

X

X

X

303 See Other

X

X

X

X

X

X

X

304 Not Modified

X

X

-

-

-

-

-

307 Temporary Redirect

X

X

X

X

X

X

X

400 Bad Request

X

X

X

X

X

X

X

401 Unauthorized

X

X

X

X

X

X

X

403 Forbidden

X

X

X

X

X

X

X

404 Not Found

X

X

X

X

X

X

X

405 Method Not Allowed

X

X

X

X

X

X

-

406 Not Acceptable

X

X

X

X

X

X

X

409 Conflict

-

-

X

X

X

X

-

412 Precondition Failed

-

-

X

X

X

X

-

413 Payload Too Large

-

-

X

X

X

-

-

415 Unsupported Media Type

X

X

X

X

X

X

X

429 Too Many Requests

X

X

X

X

X

X

X

500 Internal Server Error

X

X

X

X

X

X

X

502 Bad Gateway

X

X

X

X

X

X

X

503 Service Unavailable

X

X

X

X

X

X

X

6.2. List of status codes

6.2.1. 1xx Informational

Request received, continuing process.

6.2.2. 2xx Success

The action was successfully received, understood, and accepted.

Code Description Methods

200 OK

200 (“OK”) should be used to indicate nonspecific success

In most cases, 200 is the code the client hopes to see. It indicates that the REST API successfully carried out whatever action the client requested, and that no more specific code in the 2xx series is appropriate. Unlike the 204 status code, a 200 response should include a response body.

GET, HEAD, PUT, PATCH, DELETE, OPTIONS

201 Created

201 (“Created”) must be used to indicate successful resource creation

A REST API responds with the 201 status code whenever a collection creates, or a store adds, a new resource at the client’s request. The response body may be empty or contain a partial or full representation of the new resource. There may also be times when a new resource is created as a result of some custom POST action, in which case 201 would also be an appropriate response.

POST

PUT only in case it is used to create a new document resource.

202 Accepted

202 (“Accepted”) must be used to indicate successful start of an asynchronous action

A 202 response indicates that the client’s request will be handled asynchronously. This response status code tells the client that the request appears valid, but it still may have problems once it’s finally processed. A 202 response is typically used for actions that take a long while to process (see Long-running tasks). A POST method may send 202 responses, but other methods should not.

POST

204 No Content

204 (“No Content”) should be used when the response body is intentionally empty

The 204 status code is usually sent out in response to a PUT, POST, PATCH or DELETE request, when the REST API declines to send back any status message or representation in the response message’s body.

POST, HEAD, PUT, PATCH, DELETE, OPTIONS

6.2.3. 3xx Redirection

Further action must be taken in order to complete the request.

Code Description Methods

301 Moved Permanently

301 (“Moved Permanently”) should be used to relocate resources

The 301 status code indicates that the REST API’s resource model has been significantly redesigned and a new permanent URI has been assigned to the client’s requested resource. The REST API should specify the new URI in the response’s Location header.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

303 See Other

303 (“See Other”) should be used to refer the client to a different URI

The 303 status code allows a REST API to send a reference to a resource without forcing the client to download its state. Instead, the client may send a GET request to the value of the Location header.

It can be used when a long-running action has finished its work, but instead of sending a potentially unwanted response body, it sends the client the URI of a response resource. This can be the URI of a temporary status message, or the URI to some already existing, more permanent, resource.

All methods are acceptable.

Mostly used with POST.

304 Not Modified

304 (“Not Modified”) should be used to preserve bandwidth

This status code is similar to 204 (“No Content”) in that the response body must be empty. The key distinction is that 204 is used when there is nothing to send in the body, whereas 304 is used when there is state information associated with a resource but the client already has the most recent version of the representation. This status code is used in conjunction with conditional HTTP requests.

GET, HEAD

307 Temporary Redirect

307 (“Temporary Redirect”) should be used to tell clients to resubmit the request to another URI

A 307 response indicates that the REST API is not going to process the client’s request. Instead, the client should resubmit the request to the URI specified by the response message’s Location header.

A REST API can use this status code to assign a temporary URI to the client’s requested resource. For example, a 307 response can be used to shift a client request over to another host.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

6.2.4. 4xx Client Error

The request contains bad syntax or cannot be fulfilled.

Code Description Method

400 Bad Request

400 (“Bad Request”) may be used to indicate nonspecific failure 400 is the generic client-side error status, used when no other 4xx error code is appropriate.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

401 Unauthorized

401 (“Unauthorized”) must be used when there is a problem with the client’s credentials.

A 401 error response indicates that the client tried to operate on a protected resource without providing the proper authorization. It may have provided the wrong credentials or none at all.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

403 Forbidden

403 (“Forbidden”) should be used to forbid access regardless of authorization state.

A 403 error response indicates that the client’s request is formed correctly, but the REST API refuses to honor it. A 403 response is not a case of insufficient client credentials; that would be 401 (“Unauthorized”).

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

404 Not Found

404 (“Not Found”) must be used when a client’s URI cannot be mapped to a resource.

The 404 error status code indicates that the REST API can’t map the client’s URI to a resource.

All: GET, HEAD, PUT, PATCH, DELETE, OPTIONS, POST (if parent resource not found)

405 Method Not Allowed

405 (“Method Not Allowed”) must be used when the HTTP method is not supported.

The API responds with a 405 error to indicate that the client tried to use an HTTP method that the resource does not allow. For example, when a PUT or POST action is performed on a read-only resource supporting only GET and HEAD.

A 405 response must include the Allow header, which lists the HTTP methods that the resource supports. For example: Allow: GET, POST

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

406 Not Acceptable

406 (“Not Acceptable”) must be used when the requested media type cannot be served

The 406 error response indicates that the API is not able to generate any of the client’s preferred media types, as indicated by the Accept request header. For example, a client request for data formatted as application/xml will receive a 406 response if the API is only willing to format data as application/json.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

409 Conflict

409 (“Conflict”) should be used when a request conflicts with the current state of the target resource

The 409 error response tells the client that they tried to put the REST API’s resources into an impossible or inconsistent state. For example, a REST API may return this response code when a client tries to DELETE a non-empty store resource.

POST, PUT, PATCH, DELETE

412 Precondition Failed

412 (“Precondition Failed”) should be used to support conditional operations

The 412 error response indicates that the client specified one or more preconditions in its request headers, effectively telling the REST API to carry out its request only if certain conditions were met. A 412 response indicates that those conditions were not met, so instead of carrying out the request, the API sends this status code.

Only use for conditional HTTP requests, not constraints expressed in the HTTP payload. Use 409 Conflict instead.

POST, PUT, PATCH, DELETE

413 Payload Too Large

413 (“Payload Too Large”) should be used when a request is refused because its payload is too large

The 413 error response indicates that the request is larger than the server is willing or able to process.

POST, PUT, PATCH

415 Unsupported Media Type

415 (“Unsupported Media Type”) must be used when the media type of a request’s payload cannot be processed

The 415 error response indicates that the API is not able to process the client’s supplied media type, as indicated by the Content-Type request header. For example, a client request including data formatted as application/xml will receive a 415 response if the API is only willing to process data formatted as application/json.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

429 Too Many Requests

429 (“Too Many Requests”) should be used to indicate that the user has sent too many requests in a given amount of time.

The response representations SHOULD include details explaining the condition, and MAY include a Retry-After header indicating how long to wait before making a new request. Note that this specification does not define how the origin server identifies the user, nor how it counts requests.

Responses with the 429 status code MUST NOT be stored by a cache.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

6.2.5. 5xx Server Error

The server failed to fulfill an apparently valid request.

Code Description Methods

500 Internal Server Error

500 (“Internal Server Error”) should be used to indicate API malfunction

500 is the generic REST API error response. Most web frameworks automatically respond with this response status code whenever they execute some request handler code that raises an exception.

A 500 error is never the client’s fault and therefore it is reasonable for the client to retry the exact same request that triggered this response, and hope to GET a different response.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

502 Bad Gateway

The 502 ("Bad Gateway") status code indicates that the server, while acting as a gateway or proxy, received an invalid response from an inbound server it accessed while attempting to fulfill the request.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

503 Service Unavailable

503 (“Service Unavailable”) indicates that the server is currently unable to handle the request due to a temporary overload or scheduled maintenance, which will likely be alleviated after some delay.

The server MAY send a Retry-After header field to suggest an appropriate amount of time for the client to wait before retrying the request.

All: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS

7. Media Types

The HTTP protocol supports content negotiation for media types of the response.

Rule: Content negotiation [cnt-negot]

REST clients SHOULD specify the media type(s) that is(/are) acceptable for the response in the Accept HTTP header.

A REST response or request with payload MUST include the Content-Type HTTP header to indicate the media type of the HTTP payload.

Status code 406 MUST be used to indicate that the requested media type could not be provided.

Status code 415 MUST be used to indicate an unsupported media type in the request payload.

If a resource supports multiple content types through negotation, the response header Vary: Accept MUST be added to avoid undesired cache hits (also see Caching).

Example 26. Content negotiation

Request HTTP headers:

Accept: image/jpeg, image/png

Response HTTP headers:

Content-Type: image/png
Vary: Accept

JSON

The primary media type to support is JSON (Javascript Object Notation). The choice for JSON is particularly made for supporting JavaScript clients.

Rule: Representation of structured data [rep-struct]

Structured data MUST be made available in JSON format (RFC 8259) with media type application/json. JSON text MUST be encoded using UTF-8.

Some legacy clients erroneously decode JSON payloads using a non-UTF-8 encoding by default. To work around this, a charset parameter may be added to the Content-Type HTTP header, even though it has no meaning according to the JSON RFC.

Content-Type: application/json;charset=UTF-8

Media types of format application/<subtype>+json may be used for standardized JSON formats, though REST APIs should also accept requests using the generic application/json media type:

  • application/merge-patch+json for JSON Merge Patch payloads (see Partial update)

  • application/problem+json for Problem Detail for HTTP APIs (RFC 9457) payloads (see Error handling)

XML

On specific client request, a REST service can expose XML messages defined by XML Schema.

Content-Type: application/xml;charset=UTF-8

Always stick to UTF-8 encoding and specify the charset in the Content-Type HTTP header.

It is not recommended to implement both JSON / XML in each REST service. It would require to define and implement two representations of the same data. Automatic conversion between the two standards will almost never give a satisfying result.

Media types in OpenAPI

Payload media types can be specified in an OpenAPI specification.

For JSON, the media type specified in OpenAPI should not contain a charset parameter, because this is known to lead to problems in some server software when treating requests without the parameter in the Accept header.

OpenAPI 3.0

OpenAPI 3.0 allows to specify a different media type for each request or response format. Default media types cannot be specified.

Example 27. Specifying OpenAPI 3.0 media types
  /persons/{ssin}/photo:
    get:
      summary: Get the photo of the person
      operationId: getPhoto
      parameters:
      - $ref: "person/identifier/v1/person-identifier-v1.yaml#/components/parameters/SsinQueryParameter"
      responses:
        "200":
          description: successful operation
          content:
            image/jpeg:
              schema:
                type: string
                format: binary
        "400":
          description: SSIN has invalid structure
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/InvalidSsinProblem"
        "404":
          description: SSIN was not found
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/UnknownSsinProblem"
        default:
          "$ref": "./problem/v1/problem-v1.yaml#/components/responses/ProblemResponse"

OpenAPI 2.0

The request and response body’s media type are specified in an OpenAPI 2.0 specification respectively by the consumes and produces keywords:

  • at operation level

  • at root level, as default media types for the entire specification

Example 28. Specifying media types at operation level
  /persons/{ssin}/photo:
    get:
      summary: Get the photo of the person
      operationId: getPhoto
      produces:
      - image/jpeg  # for photo
      - application/problem+json # for problems
      parameters:
      - $ref: "person/identifier/v1/person-identifier-v1.yaml#/parameters/SsinQueryParameter"
      responses:
        "200":
          description: successful operation
          schema:
            type: file
        "400":
          $ref: "#/responses/InvalidSsinResponse"
        "404":
          $ref: "#/responses/UnknownSsinResponse"
OpenAPI 2.0 doesn’t support specifying a different media type per status code.
Rule: Default media types [med-types]

OpenAPI 2.0 specifications SHOULD specify following default media types:

consumes:
- application/json

produces:
- application/json
- application/problem+json

8. Performance

8.1. Embedding resources

In order to reduce the number of requests, optional embedding of subresources may be used.

Rule: Embedding subresources [prf-embed]

Embedded subresources MUST be put in a JSON object named embedded at root level of the JSON document. In this object, each property key is the URI and the value the full JSON representation of the subresource. The URI MUST be the same as the href value by which the subresource is referenced in the representation of the main resource.

Embedded resources are returned at the client’s request if the embed query parameter is specified. The name of the JSON property referencing the subresource can be used as value of the embed parameter.

This way of embedding resources limits the changes required when a clients starts to use it, and only impacts the JSON Schema type of the root document. It also works well when there are multiple occurrences of the same subresource, and when embedding resources more than one level deep.

In JSON Schema, additionalProperties can be used to specify the data types allowed in embedded. Note that it not possible to specify more than one data type this way in OpenAPI, so the only restriction that can be specified is type: object when there are multiple embedded data types.

Example 29. Embedding subresources
{
  "self": "http://myrestapi/family/32456",
  "family": {
    "parents": [{
      "ssin": "12345678901",
      "href": "http://myrestapi/people/12345678901"
    }],
    "children": [{
      "ssin": "98765432101",
      "href": "http://myrestapi/people/98765432101"
    }]
  },
  "embedded": {
    "http://myrestapi/people/12345678901": {
      "self": "http://myrestapi/people/12345678901",
      "ssin": "12345678901",
      "givenName": "Jane",
      "lastName": "Doe"
    },
    "http://myrestapi/people/98765432101": {
      "self": "http://myrestapi/people/98765432101",
      "ssin": "98765432101",
      "givenName": "Bobby",
      "lastName": "Doe"
    }
  }
}
JSON data type
Family:
  description: A family
  type: object
  properties:
    parents:
      type: array
      items:
        $ref: '#/components/schemas/PersonRef'
    children:
      type: array
      items:
        $ref: '#/components/schemas/PersonRef'
    embedded:
      type: object
      additionalProperties:
        $ref: 'person.yaml#/components/schemas/Person'
Example 30. Query parameters for subresources more than one level deep

8.2. Caching

Rule: HTTP Caching [prf-cach]

HTTP caching SHOULD be considered for resource types for which the same resources are repeatedly retrieved by clients.

The following schema shows a decision tree for the caching setup for a given resource:

http cache decision tree

8.2.1. Conditional requests

Conditional requests are those where the client can ask the server if it has an updated copy of the resource. The client will send some information about the cached resource it holds and the server will determine whether updated content should be returned or the client’s copy is already up to date. In the latter case, an HTTP status of 304 Not Modified is returned.

conditional

Though conditional requests do invoke a call across the network, unmodified resources result in an empty response body – saving the cost of transferring the resource back to the end client. The backend service is also often able to very quickly determine a resource’s last version or modification date without accessing the resource which itself saves non-trivial processing time.

Time-based

A time-based conditional request is based on the the last modified time of the resource. If the cached copy’s last modification time is still the most recent one, the server returns the 304 response code. To enable time-based conditional requests, the application specifies the last modified time of a resource via the Last-Modified response header.

Last-Modified: Mon, 03 Jan 2011 17:45:57 GMT

The next time the browser requests this resource it will only ask for the contents of the resource if they’re unchanged since this date using the If-Modified-Since request header

If-Modified-Since: Mon, 03 Jan 2011 17:45:57 GMT

If the resource hasn’t changed since Mon, 03 Jan 2011 17:45:57 GMT the server will return with an empty body with the 304 response code.

Rule: Precision of Last-Modified header [prf-lstmod]

The Last-Modified header doesn’t allow subsecond precision, which may lead to incorrect cache hits if there are two updates within a second.

Because of this, the ETag SHOULD be preferred over the Last-Modified header.

Content-based

The ETag (or Entity Tag) works in a similar way as the Last-Modified header except its value is a free-form tag identifying a resource version. Valid ETags can be a MD5 hash, a version number or a modification timestamp, allowing more precision than when using a Last-Modified-header.

This allows the server to identify if the cached contents of the resource are different to the most recent version.

ETag: "15f0fff99ed5aae4edffdd6496d7131f"

On subsequent browser requests, the If-None-Match request header is sent with the ETag value of the last requested version of the resource.

If-None-Match: "15f0fff99ed5aae4edffdd6496d7131f"

As with the If-Modified-Since header, if the current version has the same ETag value as the browser’s cached copy, then an HTTP status of 304 is returned.

8.2.2. Client caching directives

Cache-Control header

An HTTP client cache may cache server responses and decide to not even contact the server when the resource is requested again, saving the round trip to the server. The Cache-Control response header specifies directives for the client under which conditions and how long it should cache the response contents. This is useful for resources which don’t change frequently, and a client doesn’t need to be always synchronized with the latest version of the resource.

Example 31. Cache-Control response header
Cache-Control:public, max-age=86400

The response data may be cached by clients and intermediary servers as it is public, and should expire from the cache after 1 day (86400 seconds).

Vary header

The Vary response header describes which request headers, aside from the method, Host header field, and request target, influence the origin server’s process for selecting and representing this response. It is used to prevent unwanted cache hits.

See Best Practices for Using the Vary Header for more guidelines on the usage of the Vary header.

Example 32. Vary response header
Vary: Accept

This avoids using a cached XML response when a second request asks for JSON.

Never use Vary: * as it will result in a cache hit of 0.

9. JSON

All RESTful services must support JSON (Javascript Object Notation RFC 7159).

For extensibility, the payload (if any) of a RESTful request or response should always be a JSON object instead of an array, string, number, etc ([evo-object]). This way the request or response can always be extended with new properties ( [evo-compat]).

Rule: Naming of JSON properties [jsn-naming]

Following guidelines SHOULD be respected when determining a name for a JSON property:

  • use lowerCamelCase notation

    • also for abbreviations: all letters after the first should be lowercase

  • use American English

  • do not use underscores (_), hyphens (-) or dots (.) in a property name, nor use a digit as first letter

  • don’t use overly generic terms like info(rmation) and data as property name or as part of it

  • the name should refer to the business meaning of its value in relation to the object in which it is specified, rather than how it is defined

  • omit parts of the name that are already clear from the context

Properties used from other standards, like OpenID Connect and OAuth2, are allowed to deviate from this rule.

KO OK

SSIN

ssin

street_RRN

streetRrn

customerInformation

customer

descriptionStringLength140

description

"country": {
  "countryNisCode": 150,
  "countryIsoCode": "BE",
  "countryName": "Belgium"
}
"country": {
  "nisCode": 150,
  "isoCode": "BE",
  "name": "Belgium"
}

In below example, enterpriseNumber should be used even though the enterprise prefix is clear by its context. This is because enterpriseNumber is a fixed denomination (declared in Crossroads Bank for Enterprises) and using just number would rather cause confusion to type of identifier being used.

{
  "name": "Proximus",
  "employerId": 93017373,
  "enterprise": {
    "enterpriseNumber": "0202239951"
    "href": "/organizations/0202239951"
  }
}
Rule: Null values [jsn-null]

Properties without a value SHOULD be stripped from the JSON message, instead of having a null value.

For properties or parameters with a specified type, OpenAPI disallows null values by default, unless nullable: true is specified.

A notable exception to this rule are JSON Merge Patch requests, in which a null value indicates the removal of a JSON property.

Note that this rule doesn’t apply to empty values (e.g. empty strings "" or empty arrays []) as they are considered different from a null value.

NOK

OK

{
  "name": "Proximus",
  "employerId": 93017373,
  "enterprise": null
}
{
  "name": "Proximus",
  "employerId": 93017373
}

The JSON properties have no specific order inside a JSON object.

{
  "name": "Proximus",
  "employerId": 93017373
}
{
  "employerId": 93017373,
  "name": "Proximus"
}

Dates are written in ISO 8601 full-date format: yyyy-MM-dd

Dates don’t include time zone or offset information. See OpenAPI 3.0 data types and RFC 3339 section 5.6.

{
  "birthday": "1930-07-19"
}
Schema with date
type: object
properties:
  birthDay:
    type: string
    format: date

Date/time are written in ISO 8601 date-time format: yyyy-MM-dd’T’HH:mm:ss.SSSXXX

See JSON Schema Validation 7.3.1. date-time and RFC 3339 section 5.6. The fraction part .SSS for sub-second precision is optional and may be of any length. The time offset relative to UTC (XXX) is mandatory and may be Z (Zulu = offset 00:00) or of format ("+" / "-") time-hour ":" time-minute.

Example UTC
{
  "lastModification": "2016-04-24T09:26:01.5214Z"
}
Example with numeric offset
{
  "lastModification": "2016-04-24T11:26:00+02:00"
}
Schema with date-time
type: object
properties:
  lastModification:
    type: string
    format: date-time

10. API specifications

Rule: API contract-first approach [ctr-first]

The contract-first principle SHOULD be followed when developing an API. In this approach, the specifications of the REST API are created first and not generated from the code.

A code-first approach may sometimes be necessary however, as current state of technology does not always fully support contract-first development. In this case, special attention should be given that the API specifications remain stable and loosely coupled to the API implementation technology.

10.1. OpenAPI (Swagger)

OpenAPI is the dominating standard for API documentation, having gained wide industry support. It is based on Swagger, which has been standardized as OpenAPI 2.0.

Rule: OpenAPI [oas-contra]

Specifications of the API SHOULD be provided using OpenAPI 2.0 (aka Swagger) or OpenAPI 3.0. OpenAPI uses the OpenAPI Schema Object to describe the JSON representation of resources, which is a variant of JSON Schema, with some significant incompatibilities.

OpenAPI 3.1 improves upon OpenAPI 3.0, but to avoid interoperability problems it SHOULD NOT be used yet because it is not yet widely supported by most tooling.

The OpenAPI standard also includes a list of supported primitive data types.

You can visualize OpenAPI files using some of the tools listed in Tools.

Rule: Additional documentation [ext-docs]

Any additional documentation SHOULD be linked from the API’s OpenAPI definition using the externalDocs property.

Rule: Use YAML format for OpenAPI [oas-yaml]

OpenAPI files SHOULD be made available in YAML format, and OPTIONALLY in JSON format as well.

YAML is a superset of JSON which allows a simpler notation, and also allows comments. According to usage, a conversion step to JSON might be necessary considering limitations of tools.

Rule: Use tags to group operations [oas-tags]

When an API has many operations, tags SHOULD be used to group them together.

An operation SHOULD NOT have more than one tag.

Each tag used on an operation SHOULD also be declared in the top level tags list of the OpenAPI document, with an optional description.

The name of a tag SHOULD be stylized like a title, i.e. a word or space-separated word group starting with a capital letter.

Using tags will make the visual representation (e.g. SwaggerUI) more readable. Multiple tags on an operation duplicates the operation in SwaggerUI, which may cause confusion.

Declaring each tag allows linting tooling to detect inconsistencies in tag names.

Example 33. Using tags
tags:
  - name: Employers
    description: Managing employers
  - name: Reference data
paths:
  /employers/{employerId}:
    get:
      summary: Get an employer by its identifier
      tags:
        - Employers
  /employers:
    get:
      summary: Find employers matching given search parameters
      tags:
        - Employers
    post:
      summary: Add a new employer
      tags:
        - Employers
  /refData/employerClasses:
    get:
      summary: Find enterprises matching given search parameters
      tags:
        - referenceData # BAD: doesn't match declared tag name, words aren't separated by space, doesn't start with capital letter
        - Employers # BAD: don't set multiple tags on an operation
Rule: operationId [openapi-opid]

A unique operationId MUST be specified on each operation. It SHOULD have a lowerCamelCase value following common programming naming conventions for method (function) names.

Tools and libraries often use the operationId to uniquely identify an operation. Code generators use it to generate method names and generally take care of transforming it to the language-specific naming conventions (e.g. UpperCamelCase or snake_case).

Example 34. operationId
/health:
  get:
    summary: Check health of the service
    operationId: checkHealth   # Bad values: health, CheckHealth, healthCheck
Rule: General HTTP headers [gen-header]

HTTP header parameters that may be present on all or most operations of an API SHOULD NOT be documented explicitly in the OpenAPI specification, as this would clutter the spec and make it difficult to keep up to date. Rather, refer API users to general documentation on these headers.

Operation-specific HTTP header parameters SHOULD be documented.

Example 35. General vs operation-specific HTTP headers

Following are examples of HTTP headers that shouldn’t be documented explicitly for each operation:

  • Accept-Language

  • BelGov-Trace-Id and BelGov-Related-Trace-Id (documented in Tracing)

  • Authorization

Examples of headers that should be specified on operations that support them:

  • ETag

  • If-Modified-Since

Rule: doc and refData resources [doc-refdata]

The OpenAPI specification file and other API documentation SHOULD be placed under a special doc resource under the API root location. Access to these documentation resources SHOULD be granted to any user or potential user of the API.

The swagger specification file is, by convention, named swagger.json or swagger.yaml. Root OpenAPI 3.0 documents SHOULD be named openapi.json or openapi.yaml, as specified in the OpenAPI 3.0 specification.

Resources representing reference data (code lists) specific to an API SHOULD be placed under a refData resource. As reference data is typically static, consider supporting Caching headers for these resources.

Note that these are exceptions to the rule that API resource URLs shouldn’t have a file extension.

Example 36. doc and refData resources
 /doc
     /openapi.json
     /openapi.yaml
     /<optional other documentation>
 /refData
     /<list1OfCodes>
        /<code1>
        /<code2>
        /...
     /<list2OfCodes>
        /...
 /<resource1>
     /...
 /<resource2>
     /...
 ...
Rule: default values [oas-defaul]

Absent optional properties in a request are set by the API provider to their default value if one is specified in the OpenAPI specification.

Rule: Naming of path parameters [path-param]

Path parameters SHOULD use lowerCamelCase notation.

KO OK
paths:
  /employers/{EmployerId}:
    # ....
paths:
  /employers/{employerId}:
    # ....
Rule: Add examples in OpenAPI [oas-exampl]

Add examples to an OpenAPI document.

Examples SHOULD be provided under the example or examples OpenAPI properties rather than in a description. examples allows specifying multiple examples, but can’t be used within schema definitions.

Example values MUST be schema-valid.

OpenAPI 2.0 only allows a single example per media type under examples. Any additional examples should be put in external documentation or specified using a x-examples custom extension following the OpenAPI 3.0 format.
Example 37. Examples in OpenAPI
OpenAPI 3.0
/enterprises/{enterpriseNumber}:
  get:
    operationId: getEnterprise
    parameters:
    - in: path
      name: enterpriseNumber
      required: true
      schema:
        type: string
    responses:
      "200":
        description: successful operation
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Enterprise'
            examples:
              success:
                description: Successful response with enterprise data
                value: {
                  "name": "Proximus",
                  "enterpriseNumber": "0202239951"
                }
OpenAPI 2.0
/enterprises/{enterpriseNumber}:
  get:
    operationId: getEnterprise
    parameters:
    - in: path
      name: enterpriseNumber
      required: true
      type: string
    responses:
      "200":
        description: successful operation
        schema:
          $ref: '#/definitions/Enterprise'
        examples:
          application/json:
            {
              "name": "Proximus",
              "enterpriseNumber": "0202239951"
            }
Rule: Component definitions [oas-comp]

Duplication of definitions (schemas, responses, parameters, etc.) SHOULD be avoided. Rather, define them once under components, from which they can be referenced (using $ref).

All component names SHOULD be defined in American English and use UpperCamelCase notation. For abbreviations as well, all letters except the first one should be lowercased.

Do not use underscores (_), hyphens (-) or dots (.) in a component name, nor use a digit as first letter.

Rule: Reusable OpenAPI files [oas-reuse]

Instead of specifying everything directly in the openapi.yaml (or swagger.yaml) file of an API, OpenAPI allows to reference data types (schemas) and other components from other files. These files SHOULD follow the Swagger/OpenAPI file format as well and may include data type (schema) definitions, but also other component types like parameters, path items, request bodies and responses.

To work around limitations of certain tools, a conversion step to inline the definitions into the openapi.yaml file may be necessary.

Duplication of types in multiple APIs SHOULD be avoided. Rather, put the type in a reusable OpenAPI file. Files reusable from multiple APIs SHOULD be organized in this structure:

<domain>/<version>/<domain-version>.yaml
<domain>/<subdomain>/<version>/<domain-subdomain-version>.yaml

Definitions SHOULD be grouped per (sub)domain in a file. Each file has its own lifecycle, with a major version number in its directory and file name, that is increased when backwards compatibility is broken. This version, with optionally a minor and patch version added to it, MUST be specified in the info section in the swagger file as well.

While it is not strictly necessary for external definitions to be put in a valid OpenAPI file, doing so makes it possible to use standard OpenAPI/Swagger tooling on them.

Example 38. Reusable OpenAPI file
/person/identifier/v1/person-identifier-v1.yaml
openapi: "3.0.3"
info:
  title: person-identifier
  description: data types for person identifiers
  version: "1.1.2"
paths: {} # empty paths property required to be a valid OpenAPI file
components:
  schemas:
    Ssin:
      description: "Social Security Identification Number issued by the National Register or CBSS"
      type: string
      pattern: \d{11}

A type can be referenced from another OpenAPI file:

"$ref": "./person/identifier/v1/person-identifier-v1.yaml#/components/schemas/Ssin"
Rule: Common definitions for Belgian government institutions [oas-comdef]

Common definitions for use by Belgian government institutions are maintained in the openapi-* GitHub repositories, released as zip archives, organized per domain and also available in Maven Central. Types in these schemas SHOULD be used instead of defining your own variants.

The technical types referenced in this style guide are available in the openapi-common and openapi-problem repositories. Other types for business concepts commonly used by Belgian government institutions are available in other repositories.

10.2. JSON data types

Rule: Naming of data types (schemas) [oas-types]

As stated in [oas-comp], data type (schema) names should use UpperCamelCase and American English.

In addition, data type names SHOULD NOT include overly generic terms like info(rmation) and data.

A data type name SHOULD refer to the business meaning rather than how it is defined.

KO OK

SSIN

Ssin

CustomerInformation

Customer

LanguageEnumeration

Language

Rule: Data type description [oas-descr]

The description property MAY provide a textual description of a JSON data type. The title property MUST NOT be used because it hides the actual data type name in visualization tools like Swagger UI.

KO OK
Pet:
  title: a pet in the pet store
  type: object
Pet:
  description: a pet in the pet store
  type: object

additionalProperties can be used to put restrictions on other properties of a JSON object than those specified in the schema.

Rule: additionalProperties [addi-prop]

additionalProperties SHOULD be used exclusively to describe an object representing a map of key-value pairs. The keys of such maps don’t need to respect the naming rules for JSON properties (lowerCamelCase and English).

An example is the description a map of embedded resources, as described in Embedding resources. Other uses of additionalProperties than for maps are to be avoided, in order to support schema evolution.

Rule: Required properties [prop-req]

You can declare properties of a JSON object as mandatory by adding them to the required list.

Any properties listed in required MUST also be declared in the object’s properties, either inline or in a referenced subschema.

While undeclared required properties are permitted by the JSON Schema standard, they are most often the result of an error in the OpenAPI document (typo, oversight when renaming a property, …​). This rule allows linting tools to mark such errors.

Example 39. Required properties
GOOD BAD
type: object
properties:
  givenName:
    type: string
required: [givenName]
type: object
properties:
  givenName:
    type: string
required: [firstName] # firstName isn't explicitly declared as a property
Rule: Unknown input [req-valid]

An API SHOULD refuse unknown input, i.e. unknown body (JSON) properties in the payload or unknown query request parameters. A Bad Request problem response should be returned with an issue of type urn:problem-type:belgif:input-validation:unknownInput.

If an operation does allow and process input that is not defined in OpenAPI, its description should explicitly indicate this.

In specific situations, where a (known) input is not needed anymore and can be safely ignored:

  • either it can stay in the API definition with a deprecation flag and a "not used anymore" description

  • or it can be removed from the API definition as long as the server ignores this specific input.

Unknown HTTP header parameters MUST be accepted.

If unknown request fields would be accepted, certain client errors cannot be recognized by servers, e.g. parameter name typing errors will be ignored and the client’s actual intent will not be met.

Unknown HTTP headers are usually metadata added automatically by technical components that do not change the API’s expected behavior and thereby can be ignored.

Rule: readOnly properties [oas-rdonly]

Properties SHOULD be declared as readOnly: true when appropriate.

This means that the value of the property is managed exclusively by the owning authority, and attempts by a client to modify the value of this property are expected to be ignored or rejected.

Examples are an immutable identifier of a document, properties that are computed from other properties, or that represent a volatile state of a resource.

Properties marked as readOnly being true SHOULD NOT be in the required list of the defined schema.

Above guideline is compliant with the definition of readOnly used by the OpenAPI 3.1 Specification.

The OpenAPI 3.0 Specification has a different definition, that allows to omit properties from requests that are both readOnly and required. This has been changed in OpenAPI 3.1 to achieve full compatibility with the JSON Schema Specification. Hence, to be forward-compatible, OpenAPI documents should not depend on this 3.0 behavior.

Rule: Enum values [oas-enum]

A fixed list of possible values of a property can be specified using enum. However, this may make it harder to change the list of possible values, as client applications will often depend on the specified list e.g. by using code generation.

enum SHOULD only be used when the list of values is unlikely to change or when changing it has a big impact on clients of the API.

enum values SHOULD be valid to the schema in which they are defined.

Example 40. Enum declaration
State:
  type: string
  enum:
  - processing
  - failed
  - done

When defining a type for an identifier or code, like the above example, the guidelines under Identifier apply, even when not used as a URL path parameter of a document resource.

Rule: Decimals [dec-type]

Decimal numbers for which the fractional part’s precision is important, like monetary amounts, SHOULD be represented by a string-based type, with number as format. Depending on the context, a regular expression can enforce further restrictions like the number of digits allowed before/after comma or on the presence of a +/- sign.

When number would be used as type instead of string, some technologies will convert the values to floating point numbers, leading to a loss of precision and unintended calculation errors.

This problem may also be avoided by using an equivalent integer representation, for example by expressing a monetary amount in Euro cent rather than Euro.

Some more background on why floating point numbers can lead to loss of precision, can be found in this blog post.

Example 41. Number types preserving precision

belgif openapi-money defines a string-based type for monetary values:

MonetaryValue:
  type: string
  format: number # number is a custom string format that is supported by some, but not all tooling
  pattern: '^(\-|\+)?((\d+(\.\d*)?)|(\.\d+))$'  # Variable number of digits, with at least one digit required, before or after the decimal point. Allows both positive and negative values.
  x-examples:
  - "100.234567"
  - "010"
  - "-.05"
  - "+1"
  - "10"
  - "100."
MonetaryAmount:
  description: A monetary amount
  type: object
  properties:
    value:
      "$ref": "#/components/schemas/MonetaryValue"
    currency:
      "$ref": "#/components/schemas/Currency"
  required: [value, currency]
  example:
    value: "0.01"
    currency: "EUR"

It also defines integer-based types specific for monetary amounts expressed in Euro cent:

EuroCentPositiveAmount:
  description: Money amount in Euro cents >= 0
  type: integer # representation as Euro cent instead of Euro to avoid floating point rounding problems and need for custom 'number' format
  minimum: 0

EuroCentAmount:
  description: 'Money amount in Euro cents, also allows negative amounts.'
  type: integer # representation as Euro cent instead of Euro to avoid floating point rounding problems and need for custom 'number' format

10.3. Tools

Following tools can be used to edit OpenAPI files

Name Link Description

Swagger UI

https://swagger.io/swagger-ui/

Browser application. Graphical and text view of Swagger files. Does not support references to external files.

Zalando’s Swagger plugin

https://github.com/zalando/intellij-swagger

Open Source plugin for IntelliJ. Text-only editor.

Stoplight Studio

https://github.com/stoplightio/studio/releases

Commercial editor with a free version. Graphical and text view, both web based or as desktop application. Supports validation of API style guides (Spectral).

42Crunch OpenAPI (Swagger) Editor for VS Code

https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi

Open Source plugin for Visual Studio Code. Text editor with SwaggerUI preview and multi-file support.

42Crunch OpenAPI (Swagger) Editor for IntelliJ

https://plugins.jetbrains.com/plugin/14837-openapi-swagger-editor

Plugin for IntelliJ. Text editor with multi-file support.

IntelliJ OpenAPI Specifications

https://www.jetbrains.com/help/idea/openapi.html

Plugin bundled with IntelliJ Ultimate (commercial). Text editor with SwaggerUI preview and multi-file support.

Following tools can be used to generate server stubs and API client libraries from OpenAPI specification files.

Name Link Comments

openapi-generator

https://openapi-generator.tech/

Started as fork of swagger-codegen.

swagger-codegen

https://github.com/swagger-api/swagger-codegen

Following tools allow to verify REST styleguide rules for an OpenAPI document:

Name Link Comments

belgif-rest-guide-validator

https://github.com/belgif/rest-guide-validator

Open Source Maven Plugin to validate OpenAPI document against rules in this guide

Spectral

Open Source tool (javascript-based) to enforce REST styleguide rules.

Note: There currently is no implementation of this REST guide for Spectral.

11. Hypermedia controls

Thanks to hypermedia controls a client can navigate through the REST API without hardcoding URIs to specific resources. It suffices to know a limited set of entry points and navigate to the desired information. This adds flexibility to the evolution of the API and increases loose coupling between client and server.

For brevity, most URIs in the style guide examples are shortened, but in reality URIs should always be absolute.

All href hyperlinks are derived from this minimal link type:

HttpLink JSON data type (from common-v1.yaml)
HttpLink:
  description: A base type of objects representing links to resources.
  type: object
  properties:
    href:
      description: Any absolute URI that is using http or https protocol
      type: string
      format: uri
      readOnly: true

self hyperlinks are derived from the following type:

SelfLink JSON data type (from common-v1.yaml)
SelfLink:
  description: A base type representing a link to the resource's own location within its representation
  type: object
  properties:
    self:
      description: Absolute URI (http or https) to the the resource's own location.
      type: string
      format: uri
      readOnly: true

Links should not be used in request bodies in a PUT, POST or PATCH context, as indicated by the readOnly: true property. In a JSON response, they can be added anywhere.

Example 42. Resource links
{
   "self": "/companies/1"
   "owner": {
      "ssin": "12345678901",
      "href": "http://example.org/v1/people/12345678901"
   },
   "website": "https://wwww.mycompany.com"
}
{
   "owner": {
      "ssin": "12345678902"
   },
   "website": "https://wwww.mynewwebsite.com"
}

The corresponding JSON data type includes the link types using allOf. As website isn’t a reference to another API resource, it is not defined using the HttpLink type.

components:
  schemas:
    Company:
      allOf:
      - "$ref": "./common/v1/common-v1.yaml#/components/schemas/SelfLink"
      - type: object
        properties:
          owner:
            "$ref": "#/components/schemas/PersonReference"
          website:
            type: string
            format: uri
    PersonReference:
      allOf:
        - "$ref": "./common/v1/common-v1.yaml#/components/schemas/HttpLink"
        - type: object
          properties:
            ssin:
              "$ref": "./person-identifier/v1/person-identifier-v1.yaml#/components/schemas/Ssin"

Hyperlinks for Pagination inside collections and self-references should use a simple URI value in combination with their corresponding link relations (next, prev, first, last, self) instead of the extensible link type.

The use of Web Linking and Hypertext Application Language (HAL) is not recommended.

12. Reserved words

A list of reserved words exists for common used practices

12.1. Query parameters

Term Description Example Ref

page

When a collection resources is paged, use this parameter to request a specific page. Page numbers are 1-based.

?page=3&pageSize=20

Pagination

pageSize

When a collection resources is paged, use this parameter to specify the page size.

?page=3&pageSize=20

Pagination

q

The standard search parameter to do a full-text search.

?q=Belgacom

Filtering

select

Filter the resource properties to the ones specified.

?select=(name,address)

Consult (Document)

sort

Multi-value query param with list of properties to sort on. Default sorting direction is ascending. To indicate descending direction, the property may be prefixed with -.

?sort=age&sort=-name

Collection

embed

Request to embed subresource

?embed=mainAddress

Embedding resources

lang

language to filter multi-language descriptions

?lang=fr

Multi-language descriptions

12.2. JSON properties

Term Description Example Ref

next

The next-reference contains the absolute URL of the next page in a paged collection result.

Links

prev

The previous-reference contains the absolute URL of the previous page in a paged collection result.

Links

self

The self-reference contains the absolute URL of the resource itself.

Links

href

A reference (absolute URL) to another resource.

Links

first

A reference (absolute URL) to the first page in a paged collection result.

Pagination

last

A reference (absolute URL) to the last page in a paged collection result.

Pagination

items

an array with the items of a collection result.

Collection

total

the total number of items in a collection result, after filtering.

Collection

page

the index of a page in a paged collection result

Pagination

pageSize

the maximum number of items in a page of a paged collection result.

Pagination

embedded

a map of embedded subresources, with URIs as property key and the resource as value

Embedding resources

12.3. HTTP headers

This list includes all standardized and common non-standard HTTP headers.

Rule: HTTP header names [hdr-case]

Applications MUST treat HTTP header names as case-insensitive as per the HTTP specification.

However, by convention, Kebab-Case with uppercase SHOULD be used for readability and consistency.

Rule: Custom HTTP headers [cst-header]

Custom HTTP headers SHOULD be prefixed with the organization’s name.

Custom headers that are standardized across Belgian Government organizations use the BelGov- prefix.

X- headers were initially reserved for unstandardized parameters, but the usage of X- headers is deprecated (RFC-6648). Instead, it is recommended that company specific header' names should incorporate the organization’s name. However, for backwards compatibility reasons, headers with the X- prefix may still be used.

Table 5. Standard HTTP Headers referenced in the style guide
HTTP Header Type Description Reference

Location

Response

Used in redirection, or when a new resource has been created.

Create a new resource​, POST, status codes 301, 303 and 307

Accept

Request

Media type(s) that is(/are) acceptable for the response.

Media Types

Content-Type

Request/Response

The Media type of the body of the request.

Media Types

X-HTTP-Method-Override

Request

Override the method specified in the request.

PATCH

Retry-After

Response

Suggest amount of time for the client to wait before retrying the request when temporarily unavailable or quota reached

Service Unavailable, Too Many Requests, Too Many Failed Requests

Allow

Response

Valid methods for a specified resource.

[http-405]

ETag

Request

Identifier for returned response content

Conditional requests

If-None-Match

Response

Return resource if ETag changed

Conditional requests

Last-Modified

Request

Time on which resource was last modified

Conditional requests

If-Modified-Since

Response

Return resource only if changed since specified timestamp

Conditional requests

Vary

Response

Specifies which request headers change response content

Client caching directives

Cache-Control

Response

Indicates HTTP client how to cache responses

Client caching directives

Table 6. BelGov HTTP headers
HTTP Header Type Description Reference

BelGov-Trace-Id

Request/Response

Unique ID for tracing purposes, identifying the request or response

Tracing

BelGov-Related-Trace-Id

Response

BelGov-Trace-Id value used on related request

Tracing

12.4. Resource names

path Description Reference

/doc
/doc/swagger.yaml
/doc/swagger.json
/doc/openapi.yaml
/doc/openapi.json

API documentation (swagger file and other)

doc resource

/refData

resources representing reference data used in the API (i.e. code lists)

doc resource

/health

API health status

Health

13. Error handling

13.1. Problem Details

Problem Details for HTTP APIs (RFC 9457, obsoletes RFC7807) defines a simple JSON format to convey additional information about an error to an API consumer. This information complements the HTTP status codes [RFC7231].

For any problem occurred during processing, whether it be caused by the client or the server, the API provider supplies

  1. a suitable status code: 4xx for consumer or 5xx for server errors

  2. a RFC 9457 problem detail message

Problem Details JSON schema (from problem-v1.yaml)
    Problem:
      description: |
        A Problem Details object (RFC 9457).

        Additional properties specific to the problem type may be present.
      type: object
      properties:
        type:
          type: string
          format: uri
          description: An absolute URI that identifies the problem type
          default: about:blank  # kept for backwards-compatibility, type will be mandatory in problem-v2
        href:
          type: string
          format: uri
          description: An absolute URI that, when dereferenced, provides human-readable documentation for the problem type (e.g. using HTML).
        title:
          type: string
          description: A short summary of the problem type. Written in English and readable for engineers (usually not suited for non technical stakeholders and not localized).
          example: Service Unavailable
        status:
          type: integer
          format: int32
          description: The HTTP status code generated by the origin server for this occurrence of the problem.
          minimum: 400
          maximum: 600
          exclusiveMaximum: true
          example: 503
        detail:
          type: string
          description: A human-readable explanation specific to this occurrence of the problem
        instance:
          type: string
          format: uri
          description: An absolute URI that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.
Rule: Use of RFC 9457 Problem Details [err-problem]

Any information on a problem SHOULD be provided in the Problem Detail format, as specified in Problem Details for HTTP APIs (RFC 9457, which obsoletes RFC 7807) :

  • the media type for problems SHOULD be application/problem+json

  • status: the HTTP status code

  • type: a unique identifier for the type of the problem that MUST remain stable over time. It can be used by API clients during problem handling.

  • title: a short summary of the problem type in English

  • instance: a unique identifier of the problem instance. It could be used to track the message for support purposes.

  • detail: consumer-friendly information about the specific failing request. It MAY be localized using language negotiation.

  • additional properties may be added to provide more structured information on the problem

Consumers SHOULD NOT parse the title or detail properties to extract information; additional properties are a more suitable and less error-prone way to obtain such information.

The Problem Details response body MAY be absent when the HTTP status code itself provides sufficient information (e.g. for 405 Method Not Allowed, 406 Not Acceptable or 415 Unsupported Media Type).

This guide applies following additional restrictions on the Problem Details structure:

  • type and instance SHOULD be specified as absolute URIs in order to avoid issues when resolving relative URIs (see Links)

  • The type property, a stable identifier for the type of problem, is MANDATORY instead of optional

  • a new optional href property SHOULD be used to provide a URI to human-readable documentation on the problem type instead of dereferencing the type value

Note that using href instead of type for documentation intentionally deviates from the recommendation in the RFC. href allows use of a URL for documentation purposes that may change over time, while type can be specified as a URN that must remain stable. This is especially useful for API-specific problem types for which the documentation URL may depend on technical aspects, like deployment environment.

Example 44. Example Problem response
POST /enterprises/0206731645/employers
{
    "name": "John"
}
HTTP/1.1 404 Not Found
Content-Type: application/problem+json
BelGov-Trace-Id: d9e35127-e9b1-4201-a211-2b52e52508df
{
  "type": "urn:problem-type:belgif:resourceNotFound",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/resourceNotFound.html",
  "instance": "urn:uuid:d9e35127-e9b1-4201-a211-2b52e52508df",
  "status": 404,
  "title": "Resource is not found",
  "detail": "There is no enterprise in CBE with enterprise number 0206731645",
  "issues": [
      {
        "in": "path",
        "name": "enterpriseNumber",
        "detail": "the enterprise number is not assigned",
        "value": "0206731645"
      }
  ]
}
Rule: Problem type [prb-type]

Problem type SHOULD be specified as a URN in one of following formats:

  • urn:problem-type:<org>:<api>:<type>

  • urn:problem-type:<org>:<type>

with:

  • <org>: a short identifier for the organization

  • <api>: name of the API. It MAY be used for API-specific problem types

  • <type>: type of the problem in lowerCamelCase

A description of an API-specific problem type MAY be provided as a reference data resource : /refData/problemTypes/{problemType}. If provided, at least the HTML media type SHOULD be supported.

Reverse FQDN notation for <org> is not recommended in order to keep the identifiers short and readable.

Example 45. API-specific problem type
{
  "type": "urn:problem-type:cbss:socialStatus:searchCriteriaTooWide",
  "href": "https://api.ksz-bcss.fgov.be/socialStatus/v2/refData/problemTypes/urn:problem-type:cbss:socialStatus:searchCriteriaTooWide",
  "status": 400,
  "title": "Search criteria should be more specific",
  "detail": "Only one search parameter specified. Please specify at least two.",
  "instance": "urn:uuid:123e4567-e89b-12d3-a456-426614174000"
}
Rule: Error sanitization [err-sanit]

A problem MUST NOT leak sensitive implementation and infrastructure details. Providers must strip this information (e.g. DB error codes, internal hostnames, stacktraces) before returning the problem detail to the consumer.

E.g. this should not be returned to a client:

HTTP/1.1 500 Internal Server Error
Server: Apache-Coyote/1.1
Content-Type: application/problem+json
{
  "type": "urn:problem-type:belgif:internalServerError",
  "instance": "urn:uuid:ac19acc6-5e11-4b2a-8c10-f9680998d07a",
  "title": "Internal Server Error",
  "detail": "Unexpected error code 24879 in server product XYZ v1.0.1",
  "stackTrace": [  [
      "EJBException: java.lang.RuntimeException: Something horrible happened on the server",
      "org.jboss.as.ejb3.CMTTxInterceptor.handleExceptionInOurTx(CMTTxInterceptor.java:191)",
      "org.jboss.as.ejb3.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:282)",
      "org.jboss.as.ejb3.CMTTxInterceptor.required(CMTTxInterceptor.java:345)",
      "org.jboss.as.ejb3.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:243)"
    ],
    [
      "Caused by: java.lang.RuntimeException: Something horrible has happened on the server",
      "sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
      "sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)",
      "sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)",
      "java.lang.reflect.Method.invoke(Method.java:606)"
    ]
  ]
}
Rule: Default problem response in OpenAPI [prb-defaul]

Problem types that may occur on all or most operations of an API (e.g. a missing permission, an internal server error, request violates schema, …​) SHOULD NOT each be documented explicitly in its OpenAPI specification, as this would clutter the spec and make it difficult to keep up to date.

Rather, a default Problem response SHOULD be added to each operation. Operation-specific problem types should still be documented.

Specifying a default problem response ensures compatibility with clients that perform strict OpenAPI validation on response messages.

Example 46. Default problem response (using ProblemResponse from problem-v1.yaml)
/orders/{orderId}/archive:
  post:
    summary: archive the order
    operationId: archiveOrder
    parameters:
    - "$ref": "#/components/schemas/OrderId"
    responses:
      "200":
        "$ref": "#/components/responses/OrderArchivedResponse"
      "409":
        description: Order can not be archived when it's not completed
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/InvalidStateProblem"
      default:
        "$ref": "./problem/v1/problem-v1.yaml#/components/responses/ProblemResponse"

13.2. Standardized problem types

A list of problem types is standardized in this guide for reuse across all REST APIs. They are defined as URNs of format urn:problem-type:belgif:<type>.

href links to descriptions of standardized problem types will change in the future to recommended format containing full problem type identifier without HTML extension. Current URLs will redirect.

The library belgif-rest-problem-java provides implementation support for these problem types when developing Java applications.

13.2.1. Bad Request

Status code 400 Bad Request

Description The input message is incorrect. Look for more details in the issues property.

The following issue types may be returned by any API:

Issue type description

urn:problem-type:belgif:input-validation:schemaViolation

violation of the OpenAPI schema

urn:problem-type:belgif:input-validation:unknownInput

Request contains an unknown input field (see [req-valid])

POST /enterprises/abc

{
  "name": "exampleEnterprise",
  "boardMembers": [
    {
      "ssin": "12345678901",
      "period": {
         "startDate": "2020-12-31",
         "endDate": "2020-01-01"
      }
    }
  ]
}

returns

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "urn:problem-type:belgif:badRequest",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/badRequest.html",
  "title": "Bad Request",
  "status": 400,
  "detail": "The input message is incorrect",
  "instance": "urn:uuid:123456-1234-1235-4567489798",
  "issues": [
    {
      "type": "urn:problem-type:belgif:input-validation:schemaViolation",
      "title": "Input isn't valid with respect to schema",
      "detail": "enterpriseNumber abc should be numeric",
      "in": "path",
      "name": "enterpriseNumber",
      "value": "abc"
    },
    {
      "type": "urn:problem-type:cbss:input-validation:replacedSsin",
      "href": "https://example.cbss.be/problems/replacedSsin",
      "title": "SSIN has been replaced. Use new SSIN.",
      "detail": "SSIN 12345678901 has been replaced by 23456789012",
      "in": "body",
      "name": "boardMembers[0].ssin",
      "value": "12345678901",
      "replacedBy": "23456789012"
    },
    {
      "type": "urn:problem-type:cbss:input-validation:invalidPeriod",
      "title": "Period is invalid",
      "detail": "endDate should be after startDate",
      "in": "body",
      "name": "boardMembers[0].period",
      "value": {
        "startDate": "2020-12-31",
        "endDate": "2020-01-01"
      }
    }
  ]
}
InputValidationProblem schema definition (from problem-v1.yaml)
InputValidationProblem:
  type: object
  allOf:
  - $ref: "#/components/schemas/Problem"
  properties:
    issues:
      type: array
      items:
        $ref: "#/components/schemas/InputValidationIssue"
InputValidationIssue:
  type: object
  description: |
    An issue detected during input validation.

    `status` is usually not present.
    `href`, if present, refers to documentation of the issue type.
    Additional properties specific to the issue type may be present.
  allOf:
    - $ref: "#/components/schemas/Problem"
  properties:
    in:
      type: string
      description: The location of the invalid input
      enum:
        - body
        - header
        - path
        - query
    name:
      type: string
      description: The name of the invalid input
    value:
      description: The value of the erroneous input
      # no type specified, allowing any type. This is valid in OpenAPI even though some editors may indicate an error

The possible type values used within issues should be documented for each API. They follow the same URN structure as problem types. It is RECOMMENDED to use input-validation as infix to distinguish them.

InputValidationProblem replaces InvalidParamProblem previously used in this guide, which is now deprecated.

13.2.2. No Access Token

Status code 401 Unauthorized

Description The consumer must pass a valid access token in the Authorization HTTP header for each request to a secure resource.

POST /enterprises
Authorization: Bearer ds4d2f13sdds2q13qxcgbdf245

If no access token was found, the service returns:

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json

{
   "type": "urn:problem-type:belgif:noAccessToken",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/noAccessToken.html",
   "status": 401,
   "title": "No Access Token",
   "detail": "No Bearer access token found in Authorization HTTP header"
}

13.2.3. Invalid Access Token

Status code 401 Unauthorized

Description The consumer must pass a valid access token in the Authorization HTTP header for each request to a secure resource.

POST /enterprises
Authorization: Bearer foo
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json

{
   "type": "urn:problem-type:belgif:invalidAccessToken",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/invalidAccessToken.html",
   "status": 401,
   "title": "Invalid Access Token",
   "detail": "The Bearer access token found in the Authorization HTTP header is invalid"
}

13.2.4. Expired Access Token

Status code 401 Unauthorized

Description The access token passed in the Authorization HTTP header has expired and cannot be used anymore. Renew the access token and resubmit the request.

POST /enterprises
Authorization: Bearer ds4d2f13sdds2q13qxcgbdf245
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json

{
   "type": "urn:problem-type:belgif:expiredAccessToken",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/expiredAccessToken.html",
   "status": 401,
   "title": "Expired Access Token",
   "detail": "The Bearer access token found in the Authorization HTTP header has expired"
}

13.2.5. Missing Scope

Status code 403 Forbidden

Description The consumer access token doesn’t have the required scope to invoke the operation. The requiredScopes property lists the required scopes.

GET /enterprises/202239951
Authorization: Bearer ds4d2f13sdds2q13qxcgbdf245
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
   "type": "urn:problem-type:belgif:missingScope",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/missingScope.html",
   "status": 403,
   "title": "Missing Scope",
   "detail": "Forbidden to consult the enterprise resource",
   "requiredScopes": ["enterprise-read"]
}

13.2.6. Missing Permission

Status code 403 Forbidden

Description The consumer doesn’t have the right to invoke an operation on the given resource. The refusal is not related to the consumer scope but to access to the specific resource itself (data access).

PUT /enterprises/202239951
Authorization: Bearer ds4d2f13sdds2q13qxcgbdf245
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
   "type": "urn:problem-type:belgif:missingPermission",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/missingPermission.html",
   "status": 403,
   "title": "Missing Permission",
   "detail": "Not permitted to update the details of this enterprise"
}

13.2.7. Resource Not Found

Status code 404 Not Found

Description The requested resource cannot be found. The detail property reveals additional information about why the resource was not found.

Either the resource path isn’t specified in the API’s OpenAPI specification.

GET /enterprises

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "urn:problem-type:belgif:resourceNotFound",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/resourceNotFound.html",
  "status": 404,
  "title": "Resource Not Found",
  "detail": "No resource /enterprises found"
}

Either the path parameters don’t resolve to an existing resource. Look for the more details in the issues property.

GET /enterprises/0206731645

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "urn:problem-type:belgif:resourceNotFound",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/resourceNotFound.html",
  "status": 404,
  "title": "Resource not found",
  "detail": "No resource /enterprises/0206731645 found",
  "issues": [
      {
        "in": "path",
        "name": "enterpriseNumber",
        "detail": "the enterprise number 0206731645 is not assigned",
        "value": "0206731645"
      }
  ]
}

The InputValidationProblem Schema Object SHOULD be used to represent this type of problems.

13.2.8. Payload Too Large

Status code 413 Payload Too Large

Description The consumer request was refused because its payload is too large. The property limit contains the maximum payload size expressed in bytes.

POST /attachments

<very large binary attachment>

returns

HTTP/1.1 413 Payload Too Large
Content-Type: application/problem+json

{
   "type": "urn:problem-type:belgif:payloadTooLarge",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/payloadTooLarge.html",
   "status": 413,
   "title": "Payload Too Large",
   "detail": "Request message must not be larger than 10 MB",
   "limit": 10485760
}

13.2.9. Too Many Requests

Status code 429 Too Many Requests

Description The consumer quota was exceeded. The property limit contains the number of requests allowed. retryAfter or retryAfterSec indicate when the consumer may submit new requests again, respectively at a specific time or as a number of seconds after the reply. The Retry-After HTTP header conveys the same information.

Retry after a specific time

HTTP/1.1 429 Too many requests
Content-Type: application/problem+json
Retry-After: Thu, 05 Aug 2021 10:30:00 GMT

{
   "type": "urn:problem-type:belgif:tooManyRequests",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/tooManyRequests.html",
   "status": 429,
   "title": "Too Many Requests",
   "detail": "No more requests accepted before 2021-08-05T10:30:00Z",
   "limit": 200,
   "retryAfter": "2021-10-30T10:30:00Z",
   "retryAfterSec": 60
}

Retry after a number of seconds

HTTP/1.1 429 Too many requests
Retry-After: 60
Content-Type: application/problem+json
{
  "type": "urn:problem-type:belgif:tooManyRequests",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/tooManyRequests.html",
   "status": 429,
   "title": "Too Many Requests",
   "detail": "No more requests accepted during next 60 seconds",
   "limit": 200,
   "retryAfterSec": 60
}

13.2.10. Too Many Failed Requests

Status code 429 Too Many Requests

Description No more new requests are accepted because the consumer quota for failed requests was exceeded. The property limit contains the number of failed requests allowed. retryAfter or retryAfterSec indicate when the consumer may submit new requests again, respectively at a specific time or as a number of seconds after the reply. The Retry-After HTTP header conveys the same information.

GET /enterprises/123invalidEnterpriseId

Retry after a specific time

HTTP/1.1 429 Too many requests
Content-Type: application/problem+json
Retry-After: Thu, 05 Aug 2021 10:30:00 GMT

{
   "type": "urn:problem-type:belgif:tooManyFailedRequests",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/tooManyFailedRequests.html",
   "status": 429,
   "title": "Too Many Failed Requests",
   "detail": "No more requests are accepted because of previous failures until 2021-08-05T10:30:00Z",
   "limit": 50,
   "retryAfter": "2021-08-05T10:30:00Z"
}

Retry after a number of seconds

HTTP/1.1 429 Too many requests
Retry-After: 300
Content-Type: application/problem+json
{
   "type": "urn:problem-type:belgif:tooManyFailedRequests",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/tooManyFailedRequests.html",
   "status": 429,
   "title": "Too Many Failed Requests",
   "detail": "No more requests are accepted because of previous failures during next 300 seconds",
   "limit": 50,
   "retryAfterSec": 300
}

13.2.11. Internal Server Error

Status code 500 Internal Server Error

Description The server has encountered a situation it doesn’t know how to handle.

HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{
   "type": "urn:problem-type:belgif:internalServerError",
   "href": "https://www.belgif.be/specification/rest/api-guide/problems/internalServerError.html",
   "status": 500,
   "title": "Internal Server Error"
}

13.2.12. Bad Gateway

Status code 502 Bad Gateway

Description The API, acting as a gateway or proxy to an API provided by a third party, received an invalid response from the upstream server.

HTTP/1.1 502 Bad Gateway
Content-Type: application/problem+json

{
  "type": "urn:problem-type:belgif:badGateway",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/badGateway.html",
  "status": 502,
  "title": "Bad Gateway",
  "detail": "Error in communication with upstream service"
}

13.2.13. Service Unavailable

Status code 503 Service Unavailable

Description The service is unavailable, because of a planned or unplanned downtime. If present, retryAfter or retryAfterSec indicate when to retry, respectively at a specific time or as a number of seconds after the reply. The Retry-After HTTP header conveys the same information.

Retry after a specific time

HTTP/1.1 503 Service Unavailable
Retry-After: Thu, 05 Aug 2021 10:30:00 GMT
Content-Type: application/problem+json
{
  "type": "urn:problem-type:belgif:serviceUnavailable",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/serviceUnavailable.html",
  "instance": "d9e35127-e9b1-4201-a211-2b52e52508df",
  "status": 503,
  "title": "Service is unavailable",
  "detail": "The service is unavailable due to a scheduled maintenance, and is expected to be available again at 10h30 on Aug 5th, 2021",
  "retryAfter": "2021-08-05T10:30:00Z"
}

Retry after a number of seconds

HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: application/problem+json
{
  "type": "urn:problem-type:belgif:serviceUnavailable",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/serviceUnavailable.html",
  "instance": "d9e35127-e9b1-4201-a211-2b52e52508df",
  "status": 503,
  "title": "Service is unavailable",
  "detail": "The service experiences an unplanned downtime. Please try again after 120 seconds.",
  "retryAfterSec": 120
}

Without indication when to retry

HTTP/1.1 503 Service Unavailable
Content-Type: application/problem+json
{
  "type": "urn:problem-type:belgif:serviceUnavailable",
  "href": "https://www.belgif.be/specification/rest/api-guide/problems/serviceUnavailable.html",
  "instance": "d9e35127-e9b1-4201-a211-2b52e52508df",
  "status": 503,
  "title": "Service is unavailable",
  "detail": "The service experiences an unplanned downtime."
}

14. Versioning

14.1. Preserving compatibility

Don’t Break Backward Compatibility

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 service contracts that cannot be broken via unilateral decisions.

There are two techniques to change APIs without breaking them:

  • evolve the API, allowing clients to gradually implement changes by following rules for compatible extensions and deprecation

  • introduce new major API version and still support older version(s) for a period in which clients can transition to the new version

Backwards-incompatible changes, requiring a big bang release, are only feasible if:

  • there is a very limited number of clients

  • the release is well coordinated

  • a temporary service interruption is allowed.

In this case, the major version number could be kept the same as only a single version of the API is active at a given time and it can be easier to keep the base URL of the API unchanged.

Rule: Prefer API evolution [api-evol]

We strongly encourage using API evolution when possible and discourage versioning:

  • versioning requires maintaining and supporting two service contracts

  • API evolution maintains stable URIs (e.g. links from other APIs) and allows for a more gradual migration of clients

Use of media type versioning (using Accept/Content-Type HTTP headers) isn’t included as an option in this guide as it has various practical problems

14.2. API evolution

Rule: JSON object as top level structure [evo-object]

In a request or response body, if any, 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.

With Postel’s Law in mind, here are some rules for providers and consumers that allow us to make compatible changes without versioning:

Rule: Compatible extensions [evo-compat]

Apply the following rules to evolve RESTful APIs in a backward-compatible way:

  • New fields may be added to response. Clients MUST ignore unknown fields in response payloads.

  • Add only optional, never mandatory fields to request payloads

  • Never change the meaning of a field.

  • Never change input validation logic to be more restrictive

  • 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 values can be reduced when used as output parameters, but not extended as clients might not be ready to handle them.

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

  • a new resource variant (with another name) can be created in addition to the old resource variant

Note that while clients must ignore unknown response fields, APIs may refuse requests containing unknown fields ([req-valid]).

Rule: Deprecation [evo-deprec]

Deprecation MAY be used to indicate parts of an API are not recommended to be used anymore. Their deprecation SHOULD be documented in OpenAPI . Deprecated elements MAY be completely removed them after a period in which clients can adapt. Always communicate clearly about the deprecated fields and transition period to clients.

Newer versions of the OpenAPI standard have improved support to indicate deprecation using deprecated: true:

  • OpenAPI 2.0 only allows specifying deprecation on operations

  • OpenAPI 3.0 adds support for deprecating parameters and Schema Objects (data types), though not yet everywhere like on object properties

deprecated: true SHOULD be used if the OpenAPI version allows it, otherwise the custom OpenAPI extension x-deprecated: true SHOULD be used.

Use of deprecated elements in requests may be monitored by the server in order to evaluate the impact of their removal. However, this isn’t possible for response elements, so there is always remains some risk for breaking clients.

14.3. API versioning

Rule: Version numbering [api-version]

The version string of an API SHOULD contain a major, minor and optionally a patch number.

Only the major version of the API is part of the base URL (basePath in Swagger) of the API. Thereby, for a major version only a single minor and patch version can be available in production at a time. The version of the API MUST be included in the info section of the OpenAPI definition.

Note that reusable OpenAPI files are to be versioned as well, as specified in OpenAPI (Swagger).

Example 47. API version
OpenAPI 3.0
openapi: "3.0.3"
info:
  title: petShop
  description: API exposing my pet shop’s functionality
  version: "2.1.2"
servers:
  url: https://example.org/petShop/v2
OpenAPI 2.0
swagger: "2.0"
info:
  title: petShop
  description: API exposing my pet shop’s functionality
  version: "2.1.2"
host: example.org
basePath: /petShop/v2

Similar guidelines apply to sunset an old major version of an API apply as for removal of deprecated elements.

Rule: Removing an old version [ver-del]

Before removing an old API version:

  • provide a transition period supporting old and new versions at the same time before removing the old version

  • always communicate clearly about the transition period to clients

  • use of the old version may be monitored by the server in order to evaluate the impact of their removal.

15. Internationalization (I18N)

Textual descriptions part of a resource may need to be offered in the language of a user.

One of following strategies may be chosen for internationalization:

  • Do not offer textual descriptions in the API response. Offer them only separate from the data, for example as a dedicated collection resource in the API returning a code list with descriptions. This list can be cached by a client.

  • Only include descriptions in a single language, specified by the caller in the Accept-Language header.

  • Offer descriptions in all applicable languages, i.e. for APIs offered by the Belgian Government in French, Dutch, German and sometimes English as well. This approach is not scalable to high number of languages. The descriptions may be filtered by a lang query parameter.

15.1. Accept-Language

As for Media Types, HTTP supports content negotiation for the language used in response content.

Rule: HTTP language headers [int-negot]

Users can inform the server that a specific language is requested by adding the Accept-Language HTTP header to each request (RFC 7231).

Its value is a language tag (RFC 5646), consisting of a ISO 639-1 lowercase language code, optionally followed by an uppercase country code indicating a country-specific variant. Internationalized APIs of Belgian Federal Government organizations MUST also support the variants for the official Belgian languages ending on -BE. Multiple languages may be specified in order of preference. A wildcard * matches any language.

The Content-Language response header MUST be used to indicate the language used in the response for internationalized content.

In case the server could not honor any of the requested languages, it SHOULD return a 406 Not Acceptable error. If the resource supports caching, the Vary: Accept-Language MUST be included in the response.

Example 48. HTTP language headers
Request headers
Accept-Language: nl-BE, nl, fr-BE, fr, en, *
Response headers
Content-Language: nl

15.2. Multi-language descriptions

It may be decided to include all supported translations for descriptions in a resource’s representation.

Rule: Multi-language descriptions [multi-lang]

A multi-language description SHOULD be represented by a JSON object with ISO 639-1 language codes as property names and the corresponding textual description as values.

An API MAY offer to filter the representation to a single language by using the reserved query parameter lang.

A LocalizedString type is defined supporting the three official Belgian languages.

LocalizedString JSON Schema (from common-v1.yaml)
LocalizedString:
  description: A description specified in multiple languages
  type: object
  properties:
  fr:
    type: string
  nl:
    type: string
  de:
    type: string
Example 49. Multi-language descriptions
Request

GET /countries/BE

Response
"name": {
 "fr": "Belgique",
 "nl": "België",
 "de": "Belgien"
}
Filtering a single language

GET /countries/BE?lang=fr

"name": {
 "fr": "Belgique"
}

16. Tracing

In order to uniquely identify requests or response messages, a tracing ID may be added as HTTP header. This tracing id can be used to be able to trace the flow of calls across APIs and organizations.

The trace ID could be used for example:

  • for operational purposes, to enable lookup related technical logs

  • for audit logging, in order to be able to trace the origin of each request. It may be a legal requirement to have such audit logging when sensitive data is exchanged.

Rule: HTTP headers for tracing [trc-header]

APIs SHOULD support the tracing HTTP header BelGov-Trace-Id on both requests and responses. Generated trace IDs MUST NOT be longer than 36 characters and unique. The trace id on a response MUST be generated by the API provider and is not copied from the request. Using UUIDs as tracing headers is RECOMMENDED.

Additionally, the BelGov-Related-Trace-Id HTTP header MAY be present on a response. Its value MUST be the same as the BelGov-Trace-Id on the request.

Within an organization, other headers MAY be used for tracing calls, for instance the headers supported by tracing tools like Zipkin or Jaeger.

In communication external to an organization, each BelGov-Trace-Id value should be unique. For instance, when a RESTful service makes multiple outgoing calls within the context of the same incoming request, every outgoing request should be uniquely identifiable with its own ID. For auditing purposes, it should be possible to correlate the various tracing IDs used in the context of a request.

An API provider should not be dependent on the client generating correct unique trace IDs.

17. Events

When systems are in need of subscribe-notify capabilities, they tend to define their own custom event format.

To improve interoperability, this styleguide promotes and adopts the CloudEvents specification prepared by the Cloud Native Computing Foundation (CNCF): https://cloudevents.io/.

On October 24, 2019 they released a v1.0 version of this specification which thoroughly separates the core conceptual event model from any format and protocol details. Moreover, some SDKs are in development to support integration of CloudEvents in some popular programming languages.

Table 7. CloudEvents documentation
Description Link

CloudEvents overview

https://github.com/cloudevents/spec/blob/main/README.md

Conceptual model

https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md

JSON Event Format

https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/json-format.md

HTTP Protocol Binding

https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/bindings/http-protocol-binding.md

Web Hooks for Event Delivery

https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/http-webhook.md

Extensions (Sequence, Claim Check, Partitioning, …​)

https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/documented-extensions.md

18. Health

Rule: Health check operation [hlth-res]

Each REST API SHOULD expose a GET /health operation which returns the availability of the API’s functionality for its clients.

Access to this resource SHOULD be granted to any client of the API.

The payload SHOULD be a JSON object with a property status of type string, that has one of the standardized values below, or a custom value for other API-specific use cases. It SHOULD NOT contain any implementation or infrastructure details for security reasons, unless called by a trusted privileged client.

The HTTP response status SHOULD be either 200 OK when the service is up or partially available, or 503 Service Unavailable when the service is down or out of service.

Clients SHOULD interpret any other HTTP 5xx status, or lack of HTTP response, as the API being unreachable.

Status Status Code Description

UP

200

The API is functioning as expected.

DEGRADED

200

The API is partly unavailable but service can be continued with reduced functionality.

DOWN

503

The API is suffering unexpected failures.

The health status typically depends on the availability of multiple components used to provide the API’s functionality (e.g. application container, database, external web service dependency, …​). It differs from a container liveness status, which only reflects the availability of a single component.
If the health checks triggered by calling GET /health are resource-intensive, API implementations should consider caching the result. This may also protect against denial-of-service (DoS) attacks if the operation can be called from untrusted sources.
Example 50. Service is available
{
  "status": "UP"
}
Example 51. Service is down
{
  "status": "DOWN"
}

When called by a trusted privileged client, the response may contain additional properties with detailed information.

{
  "status": "DOWN",
  "details": {
    "datastore": {
      "status": "DOWN",
      "errorMessage": "connection timeout"
    }
  }
}

The health resource is specified in common-v1.yaml. Note that uppercase is used for the status values, which deviates from [cod-design], in order to align with existing health checks provided by frameworks like Spring Boot and MicroProfile Health. The format of additional component-level details is not specified.