Table of contents
General guidelines
MUST write APIs in English (U.S.)
MUST secure endpoints
MUST use standard data formats
OpenAPI Specification Data Types
URLs
Common rules
MUST use all lowercase characters
For every part that composes the URI of an API, all characters must be lowercase.
DO
-
/path
DON’T
-
/Path -
/myPath -
/PATH
MUST use kebab-case
For every part that composes the URI of an API, every multiple-worded section must use kebab-case.
DO
-
/kebab-case-part
DON’T
-
/camelCasePart -
/PascalCasePart -
/onewordpart -
/snake_case_part
Path structure
Product API: {hostname}/{context-path}/api/domain/subdomain/version/resource
Project API: {hostname}/{context-path}/api/domain/subdomain/project/version/resource
Examples
-
{hostname}: s000009.dev.tech.eu1.platform.overit.cloud -
{context-path}: fsm
MUST use normalised paths
Do not specify paths with duplicate or trailing slashes. Also, do not use path variables with empty string values.
DO
-
/resources-a/addresses -
/resources-b
DON’T
-
/resources-a//addresses -
/resources-b/
API
The /api prefix is used to underline that the following part of the URI identifies a REST API resource, not something else. Since the NextGen Platform does not publish REST APIs only, there is a specification prefix, used to define other published content such as SOAP services and user interfaces.
Domain
The name of the functional domain (DDD) that groups all the published resources under that domain.
-
Warehouse domain:
api/warehouse/subdomain -
Custom Warehouse domain:
api/warehouse/subdomain/project/
Subdomain
The name of the functional subdomain (DDD) that groups a subset of the resources published in its domain. All APIs published in the same subdomain will be versioned together, as you will see in the Version chapter.
-
Warehouse domain, Material subdomain:
api/warehouse/material/ -
Custom Material subdomain:
api/warehouse/material/project/
Project
The acronym of the project is already being used to signal database custom objects. It must start with the x letter and then the actual project acronym will follow prj. This is mandatory for new custom APIs as well as customizations of standard APIs.
Custom API: api/domain/subdomain/xprj/version/resource
Version
The version must be indicated with the r letter and the version number, expressed with a single number and no special characters. Beware that every API in a subdomain will be versioned together, so any breaking change suffered by even a single API, will cause the entire subdomain to be versioned.
Versioned API: api/domain/subdomain/r3/resource
Resource
The name of the actual resource that a consumer will access through the API. It can be a real resource or a virtual resource (intent, explained after).
When publishing relationship-type APIs, MUST use the parent/child path format.
Use intent-resources when publishing relationships with metadata.
Use resources without the parent/child format when publishing resources that exist beyond their relationships (e.g. materials belonging to a work order operation are not a parent/child relationship, they must be named properly as a resource).
SHOULD avoid intent resources whenever possible
MUST use nouns, MUST NOT use verbs
DO
-
PUT /resources -
POST /resources
DON’T
-
/changeresources -
/createresources
MUST use plural resource name
DO
-
PUT /resources -
GET /resources
DON’T
-
PUT /resource -
GET /resource
MUST use domain-specific resource names
Using domain-specific nomenclature for resource names helps developers to understand the functionality and basic semantics of your resources. It also reduces the need for further documentation outside the API definition. This guideline can be lifted when defining a resource inside its domain, in that case, the specificity related to the domain MAY be omitted.
DO
-
/work-orders/r1/work-cycles -
/work-orders/r1/work-orders/{workOrderId}/operations -
/work-orders/r1/work-cycles/{workCycleId}/operations
DON’T
-
/work-orders/r1/cycles -
/r1/operations -
/work-orders/r1/cycles/operations
MUST avoid using duplicate names when the subdomain coincides with the domain
Use the subdomain name only when necessary, meaning that the subdomain does not coincide with the domain itself.
DO
-
/work-orders/r1/work-orders -
/work-orders/r1/work-orders/{workOrderId}/operations -
/work-orders/r1/work-cycles/{workCycleId}/operations -
/work-orders/projects/r1/projects
DON’T
-
/work-orders/work-orders/r1/work-orders -
/work-orders/r1/work-orders/{workOrderId}/work-order-operations -
/work-orders/r1/work-cycles/{workCycleId}/work-cycle-operations -
/work-orders/r1/projects -
/projects/r1/projects
SHOULD limit the number of nested resources
Keep the nesting limited to main resources and sub-resources. Use sub-resources if their life cycle is (loosely) coupled to the main resource, meaning that the main resource acts as a collector of sub-resources. Use < 3 nesting levels. More levels increase API complexity and URL path length (max URL length = 2000 characters).
Use sub-resources also for relationships between resources, instead of creating a relationship resource.
DO
-
POST /collection-one/{itemOneId}/collection-two-
Creates a new resource of
collection-twowhich is a sub-resource ofitemOneofcollection-one. -
e.g.
POST /work-orders/{workOrderId}/work-order-operationscreates a new Work Order Operation which is a sub-resource of a Work Order.
-
-
GET /collection-one/{itemOneId}/collection-two/{itemTwoId}-
Retrieves all data related to
itemTwoofcollection-twowhich is a sub-resource ofitemOneofcollection-one. -
e.g.
GET /work-orders/{workOrderId}/work-order-operations/{workOrderOperationId}retrieves the data of a Work Order Operation which is a nested sub-resource of a Work Order.
-
-
PUT /collection-one/{itemOneId}/collection-two-
Replaces the existing relationships between
itemOneofcollection-oneandcollection-tworesources. -
e.g.
PUT /operation-centers/{operationCenterId}/addressesreplace all existing relationships between an Operation Center and its Addresses with new ones.
-
-
GET /collection-one/{itemOneId}/collection-two-
Retrieves the IDs of all the items of
collection-twothat are related toitemOneofcollection-one -
e.g.
GET /operation-centers/{operationCenterId}/addressesretrieve all the existing relationships (IDs) between an Operation Center and its Addresses.
-
DON’T
-
POST /collection-one/{itemId}/collection-two/{itemId}/collection-three -
GET /collection-one/{itemId}/collection-two/{itemId}/collection-three/{itemId} -
PUT /collection-one/{itemId}/relationship-between-one-two -
GET /collection-one/{itemId}/relationship-between-one-two
MAY use the self pseudo-identifier to express the logged-in user information
When referring to resources related to the currently logged-in user the self pseudo-identifier.
DO
-
GET /resource/self -
GET /resource/{resourceId}/self
DON’T
-
/users/resource/self -
/users/self/resource -
/resource/me -
/resource/myself -
/current/resource
Intent resource
Every action that does not fall into a RESTful bucket must be designed as a sub-resource following RESTful principles (described for resources just above). The name of the resource should be as clear as possible and very well-documented, since it is not a RESTful resource a consumer must be well-informed about the effects of invoking such an endpoint. Think about it as a form that becomes an information resource describing what a client wants the server to do. An example would be:
GitHub APIs: star and unstar a gist
-
PUT /gists/{gist_id}/star -
DELETE /gists/{gist_id}/star
As far as REST is concerned, the spelling of the URI absolutely does not matter; but from the point of view of a human readable naming convention, start from the fact that the resource is the document, not the side effect that you want the document to have. So, for example, it's totally normal and compliant with REST that the document that describes the current state of an entity and the document that describes changes you want to make to the entity are different documents with different identifiers.
Having said that, when designing an intent there are a few key points to pay attention to:
-
MUST use plural nouns instead of verbs, e.g.
/activationsinstead of/activate -
Intents performed on a singular resource MUST be designed as a sub-resource of a resource instance, e.g.
/work-orders/{workOrderId}/activations -
Possible pseudo-identifiers (such as
/selfor/batch) MUST come before the intent name, e.g./work-orders/batch/activations
DO
-
/resource/intents -
/resource/{resourceId}/intents -
/resource/pseudo/intents
DON’T
-
/intents/{resourceId} -
/intents/resource -
/resource/intent
Duplicate resource
Since we currently have several products that contribute to the NextGen APIs catalog, there might be REST resources that clash between different products because both products need to manage the same resource.
The correct solution is to decide which product is the owner of that REST resource and let it publish every needed API for the resource.
If the correct solution is not applicable, a new API is needed, and it must not be the same; there cannot be a clash between different resources from different products. Therefore, the URL (or better, the complete name) of the REST resource must change. An owner of the resource must be identified, and all its APIs do not need to change names. For every other product that is not the owner of the resource, an additional part of the URL MUST be changed: it represents the acronym or short name for that product and it coincides with the context-path variable.
-
NextGen Field Collaboration:
/fc -
NextGen GEO:
/geo -
NextGen FSM:
/fsm
This way, the entire domain and subdomain can be versioned according to the specific product, and the owner remains independent from others' evolutions.
DO
-
/{context-path}/api/domain/subdomain/version/resource -
e.g.:
/fc/api/work-orders/execution/r1/outcome-classes
Asynchronous resources
In case of asynchronous resources, meaning APIs that will publish a message in an Event Streaming Queue (e.g.: SNS, Kafka, …) the pseudo to be used is /async and it will be placed after the resource and before any other pseudo or intent.
DO
-
/resource/async/intents -
/resource/{resourceId}/async
DON’T
-
/async/{resourceId} -
/async/resource
Data and parameters
Common rules
MAY use acronyms when they are well-known
DO
-
workOrderSLAwhere SLA is the well-known acronym for Service Level Agreement -
workOrderServiceLevelAgreement
DON’T
-
workOrderServicelevelagreement -
workOrderPCMCIAwhere PCMCIA stands for People Can’t Memorize Computer Industry Acronyms which is a not-so-well-known backronym
MUST use uppercase letters for acronyms
DO
-
appointmentSLAwhere SLA is the well-known acronym for Service Level Agreement
DON’T
-
appointmentSla -
appointmentsla
Path Params
MUST use entityId to name parameters
DO
-
{resourceId}
DON’T
-
/{Id} -
/{id} -
/{param}
MUST use camelCase
DO
-
{resourceId}
DON’T
-
{resource-id} -
{ResourceId} -
{resourceid} -
{resource_id} -
{RESOURCEID}
Query Params
MUST use camelCase
DO
-
/resources?camelCaseQueryParam=value
DON’T
-
/resources?kebab-case-query-param -
/resources?PascalCaseQueryParam -
/resources?onewordqueryparam -
/resources?snake_case_query_param
MUST explode filters for Collections
GET operations which allow for filtering results, the filter input data MUST be declared exploded (meaning one QueryParam per filter field). This is done to allow collections of data as a single filter parameter.
DO
-
/resources?myParamField1=value&myParamField2=value1,value2,value3&myparamField3=value
DON’T
-
/resources?filter=myParamField1,myValue1,myParamField2,myValue2
MAY use complex Query Parameters for Objects
In case of Objects that needs to be passed as Query Parameters (such as the Page object, see also Pagination and How to use APIs: Pagination) you MAY use a non-exploded Query Parameter.
DO
-
/resources?page=size,10,num,1&myObject=pippo,1,pluto,2
SHOULD use resource specific filter names
Using resource-specific nomenclature for attributes names helps developers to understand the functionality and basic semantics of your filter attributes. It also reduces the need for further documentation outside the API definition.
This guideline becomes a MUST whenever the resources are multiple, such as in an orchestration.
This guideline can be lifted when defining a filter for a single resource inside its own domain, in that case the specificity related to the domain MAY be omitted.
Payload
MUST use camelCase
DO
-
{"camelCaseField": ""}
DON’T
-
{"kebab-case-field": ""} -
{"PascalCaseField": ""} -
{"onewordfield": ""} -
{"snake_case_field": ""}
MUST use Id suffix in foreign key fields
DO
-
{"myForeignKeyId": 33}
DON’T
-
{"myForeignKey": ""} -
{"Id": ""} -
{"myId_myForeignKey": ""}
Response MUST be compliant with Problem Details specification https://datatracker.ietf.org/doc/rfc9457/
The format of the response of every API MUST be exactly the one defined by the RFC 9457. An example follows but for more information please consult the RFC guideline:
POST /details HTTP/1.1
Host: account.example.com
Accept: application/json
{
"age": 42.3,
"profile": {
"color": "yellow"
}
}
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
Content-Language: en
{
"type": "https://example.net/validation-error",
"title": "Your request is not valid.",
"errors": [
{
"detail": "must be a positive integer",
"pointer": "#/age"
},
{
"detail": "must be 'green', 'red' or 'blue'",
"pointer": "#/profile/color"
}
]
}
HTTP status codes
MAY use any standard HTTP status code
As per https://developer.mozilla.org/en-US/docs/Web/HTTP/Status an API designer may use any of the standard HTTP status codes. WebDAV status codes are excluded and prohibited.
MUST document client-side errors
In case of 400-499 errors, meaning client-side errors, there must be a complete and thorough explanation of the error and how can the client fix the request to solve it.
SHOULD stick to a maximum of three error codes
To not overcomplicate the client-side error management, try to not generate more than 3 different error codes. If you need more than 3 error codes to be returned, try to group similar errors into the same code and differentiate the error description better.
In any case, it is not prohibited to handle more than 3 codes, especially when for certain APIs there must be several different client-side error management procedures, just be sure to properly document every error code and why it’s needed.
MUST use concurrency issues error codes
If an API handles concurrency, be sure to use the proper HTTP status code to inform the client of the concurrency-type error.
MAY document banal status codes in a common documentation space
For both private and public APIs, there can be a common documentation space dedicated to banal status codes. These are, for instance, 404 Not Found, 200 OK, and so on.
This MUST be done if not documenting every possible status code in APIs, more information below, separated between the private API Suite and the public API Catalog.
Common response HTTP codes
Codes that might always be returned:
403 (FORBIDDEN)
500 (INTERNAL SERVER ERROR)
GET
-
/users/{userId}-
200 (OK)
-
404 (NOT FOUND)
-
-
/users?name=pippo-
200 (OK) even if a
usernamedpippodoes not exist, the response MUST be 200 (OK) with an empty list
-
-
/users?nonExistentParameter=-1-
400 (BAD REQUEST)
-
POST
-
/users-
201 (CREATED)
-
-
/users?nonExistentParameter=-1-
400 (BAD REQUEST)
-
-
/userswith a non-compliant request payload-
400 (BAD REQUEST)
-
-
/resources/{resourceId}/children
-
201 (CREATED)
-
404 (NOT FOUND) if
resources/{resourceId}does not exist
-
PATCH
-
/users/{userId}-
204 (NO CONTENT)
-
404 (NOT FOUND)
-
PUT
-
/users-
201 (CREATED)
-
204 (NO CONTENT) in case of an update
-
-
/users/{userId}-
204 (NO CONTENT)
-
404 (NOT FOUND)
-
-
/users?nonExistentParameter=-1-
400 (BAD REQUEST)
-
-
/userswith a non-compliant request payload-
400 (BAD REQUEST)
-
-
/resources/{resourceId}/children
-
201 (CREATED)
-
204 (NO CONTENT) in case of an update
-
404 (NOT FOUND) if
resources/{resourceId}does not exist
-
DELETE
-
/users/{userId}-
204 (NO CONTENT)
-
404 (NOT FOUND)
-
-
/users?nonExistentParameter=-1-
400 (BAD REQUEST)
-
Private API Suite
SHOULD document only non-banal codes
The complete documentation of all status codes can be avoided for private APIs, since they are internally managed.
Public API Catalog
SHOULD document every status code
In the case of public APIs, every status code that can be returned to the consumer SHOULD be documented. Completely banal status codes MAY be avoided when deemed absolutely not necessary.
Configurability
MUST prefix parameters with the _ (underscore) character
Every configuration-related parameter MUST be prefixed with the _ (underscore) character to increase readability and to allow anyone to use one of the reserved keywords as another parameter.
Pagination
SHOULD implement pagination in GET operations
The implementation and publishing of a paginated GET operation is strongly advised. Having said that, it is not mandatory since not every GET operation will access a resource which needs pagination. Typically the pagination mechanism is combined with the ordering mechanism, explained below. Pagination
"_page" : {
"num": 1,
"size": 10
}
GET /resource?_page=num,1,size,10
OpenAPI Specification
QueryParameter
- name: _page
in: query
required: true
style: form
explode: false
schema:
$ref: '../my-schemas.yaml#/components/schemas/Page'
QueryParameter schema
Page:
required:
- num
type: object
properties:
num:
type: integer
description: "This parameter is an Integer that allows the client to specify which page number it wants
to retrieve. The default value is zero, which means that the first page will be retrieved in case
the Page object is not specified by the client"
format: int32
size:
type: integer
description: "This parameter is an Integer that allows the client to specify the maximum number of entries
that a single page is allowed to contain.If the client does not respect the Page contract, meaning that it
specifies the Page object but not its num attribute, an error 400 Bad Request is generated and returned to
the client."
format: int32
Response object schema
PageResponse:
type: object
properties:
page:
type: object
properties:
num:
type: integer
description: "The number of the page retrieved."
has-more:
type: boolean
description: 'true if more results are present, false otherwise.'
Order
SHOULD implement ordering in GET operations
The implementation and publishing of a sortable GET operation is strongly advised. Having said that, it is not mandatory since not every GET operation will access a resource which needs ordering. Typically the ordering mechanism is combined with the pagination mechanism, explained above.
To specify the ordering you MUST use a Query Param, the keyword MUST be order and the type of the parameter MUST be a string:
"_order" : "+field1,-field2"
GET /resource?order=+field1,-field2
To request an ascending order, add as prefix the character + (plus).
To request a descending order, add as prefix the character - (minus or hyphen).
MUST document every possible order of a resource
When a consumer is allowed to order the results of a GET operation, there must be a documentation of every possible order published by the API.
If a resource is particularly complex or numerous, there MUST be a documentation of an ordered subset of fields by which a consumer is allowed to order. To specify the order the documentation MUST provide an alias that the consumer can use as attribute name.
If a resource is particularly simple and not numerous, there MAY be a free ordering policy on the API.
OpenAPI Specification
"name" : "_order",
"in" : "query",
"schema" : {
"type" : "string"
}
Language
MUST allow a consumer to specify the desired language
A consumer MUST be able to specify the desired language as a Query Parameter of a GET request. The possible values are the following:
-
ALL: Ask for all available translations
-
USER: Ask for the current user translation
-
IANA 2-char code: Ask for a specific translation using the IANA 2-char code that identified a language
"_language" : "ALL,USER,EN"
OpenAPI Specification
QueryParameter
- name: language
in: query
required: false
schema:
type: string
default: user
examples:
LangUser:
value: user
summary: User's language
LangAll:
value: all
summary: All languages
LangIANA:
value: en
summary: IANA compliant language
Response object schema
LocalizedItem:
required:
- "lang"
type: "object"
properties:
lang:
type: "string"
value:
type: "string"
Response fields
MUST implement configurability of the data returned in GET operations
Every GET operation must allow consumers to specify a subset of data that they need to be returned by the API.
For this purpose, you MUST use a Query Param, the keyword MUST be _fields and the type of the parameter MUST be an array of string.
"_fields" : [ "admin", "editor", "contributor" ]
OpenAPI Specification
QueryParameter
"name" : "_fields",
"in" : "query",
"schema" : {
"type" : "array",
"items" : {
"type" : "string"
}
}
Extension
MUST implement extensibility of all data in an API
Every HTTP verb must implement the extensibility mechanism, in the form of a Query Parameter for filtering, and a JSON object for request and response data.
OpenAPI Specification
The format when extending QueryParameters is a prefix named precisely extension. (dot), which must be followed by the name of the desired extended field name, i.e. extension.customCode=value. The extended attributes can be repeated to obtain collections of extended attributes, i.e.: extension.customCodes=A&extension.customCodes=B.
QueryParameter
- in: query
name: extension
schema:
type: object
patternProperties:
# Parameter names
'^(extension\.)[A-Za-z][A-Za-z0-9]*$':
# Parameter values
type: array
additionalProperties: false
examples:
- extension.myFieldName: myValue
Request or response object schema
extension:
type: object
description: Extended fields and their respective values
additionalProperties: true
examples:
- customCode: myCustomCode
Java implementation
QueryParameter
@RestController
@RequestMapping("/api/test/")
public class TestRestController {
@GetMapping("test1")
public UserFilter test1(@ParameterObject UserFilter filter, @RequestParam MultiValueMap<String, Object> extension) {
Map<String, Object> filtered = new HashMap<>();
extension.entrySet()
.stream()
.filter(entry -> entry.getKey().startsWith("extension."))
.forEach(entry -> {
if (entry.getValue().size() == 1) {
filtered.put(normalizeExtensionKeyName(entry), entry.getValue().get(0));
} else {
filtered.put(normalizeExtensionKeyName(entry), entry.getValue());
}
});
filter.setExtension(filtered);
return filter;
}
private String normalizeExtensionKeyName(Map.Entry<String, ?> entry) {
return entry.getKey().replace("extension.", "");
}
}
Response object
WORK IN PROGRESS
Expanding Work in progress
Expanding related resources (also known as Resource embedding) is a great way to reduce the number of requests. In cases where clients know upfront that they need some related resources, they can instruct the server to prefetch that data eagerly. Whether this is optimized on the server, e.g., a database join, or done in a generic way, e.g., an HTTP proxy that transparently expands resources, is up to the implementation.
WARNING: Expanding is not a substitute for orchestration.
Any resource that does not belong in the same domain MUST be accessed separately using an orchestration layer.
Expanding a sub-resource can look like this, where an order resource has its order operations as a sub-resource /work-orders/{workOrderId}/operations:
GET /work-orders/123?expand=operations HTTP/1.1
{
"id": "123",
"description": "Order number 123",
"...": "..."
"operations": [
{
"order": 1,
"description": "1234-ABCD-7890",
"...": "..."
}
]
}
Batch operations
Batch operations MUST be published via the proper verb and a collection resource. To easily identify such operations the keyword batch MUST be used in the resource URI, after the resource specific name, e.g. POST /work-orders/batch is an endpoint which allows POSTing several work orders in one invocation.
Documentation
MUST document server-side validation policies
When an API implements at least one validation mechanism beyond its OAS specification, the validation process and the mandatory data must be documented. This also includes fields which are defined as optional in the OAS specification, but are mandatory in certain circumstances.
MUST tag every resource with its subdomain name
When documenting a REST resource, one MUST use the OpenAPI tag feature and the tag itself MUST be equal to the subdomain name to which the resource belongs. Example:
Resource: /domain/subdomain/r1/my-resource
Tag: Domain / Subdomain
paths:
/domain/subdomain/my-resource:
get:
tags:
- Domain / Subdomain
summary: Get a resource
description: "Returns the complete resource instance with all relevant information.
@Configuration
public static class Identity {
public static final String TAG_NAME = "Configurations / Identity";
public static final String TAG_DESCRIPTION = "Services to manage the information and the operations about the identity";
public static final String GROUP_NAME = "configurations-identity";
@Bean
GroupedOpenApi identityApi() {
return GroupedOpenApi.builder()
.group(GROUP_NAME)
.displayName(TAG_NAME)
.pathsToMatch("/api/configurations/identity/**")
.build();
}
}
Validation
MUST validate the intra-subdomain consistency of the CRUD operations
The consistency of a single API CRUD operation must be validated. This has to be done only against the resource itself or against resources in the same subdomain as the resource. Validations to be done are both technical and functional, some examples include: start date must be before the end date; denormalized keys towards resources (in the same subdomain) must exist, be consistent, and the linked resource must be functionally able to receive the link itself.
Every validation against resources outside the subdomain of the resource MUST be done in an outer layer, i.e. an orchestration layer.