Skip to Content
GuidelinesREST APIs

REST API Guidelines

Introduction

Our digital products we produce is centred around decoupled services that is provided via RESTful APIs with JSON payloads. Every team that produce software to the digital platform own, deploy and operate their APIs. This guide provide guidance for how to design high-quality, long-lasting APIs that fits in to the overall platform strategy.

We adopt an “API first” mindset, where we treat APIs as products. This helps us to facilitates a service ecosystem, which can be evolved more easily and be used to experiment quickly with new business ideas by recombining core capabilities. It´s also easy to implement cross platform and can generate client libraries with ease with tools.

APIs should be stable even if we replace complete service implementation including its underlying technology stack.

We focus on:

  • API first mindset
  • REST all the things
  • Design for cloud native
  • Have a “SaaS mindset”

We adhere to “Postels Law” and stipulates:

We are liberal in what we accept, but conservative in what we send

We encourage developers to define APIs first, before coding its implementation, using a standard specification language, this will help to get early review feedback from peers and client developers. We do this via the Open API specification where we only use U.S. English as preferred language.

Teams are responsible for using these guidelines during API development and are encouraged to contribute to this guideline via pull requests.

Hostname location and URLs

Humans should be able to easily read and construct URLs. This facilitates exploration and interaction with our platform without a well-supported client library. All public API endpoints shall use https and use the service name in the URL, like https://api.[service].truesec.app for production workloads if they are part of the overall platform. Services that can generate URLs longer than 2,083 characters shall make accommodations for the clients they wish to support.

Consistent with our privacy policy, clients should not transmit personally identifiable information (PII) parameters in the URLs.

Resource orientation

REST is centered around business (data) entities exposed as resources that are identified via URIs and can be modified via standardized CRUD-like http methods.

  • Use nouns as names
  • Use pluralize names
  • Use URL friendly resource names
  • Use as domain-specific resource names as possible
  • Use UUID as identifiers
  • Never nest resource more then two levels deep
  • Never end with a trailing slash
  • Keep URLs verb-free
  • Identify resources and sub-resources via path segments

Avoid introducing dependencies between the web API and the underlying data store. For example, if your data is stored in a relational database, the web API doesn’t need to expose each table as a collection of resources. Instead, think of the web API as an abstraction of the database. Instead introduce a mapping layer between the database and the web API. This keep the client applications isolated from changes to the underlying database scheme.

Instance isolation

In most API/services we do have separation of instances (be it on multiple workspaces or tenant), we always use UUID as separator for this.

Examples

https://api.truesec.app/tenants/38631565-d3b2-4a03-b177-f76c032a2c08 https://api.truesec.app/workspaces/de5c23f2-5fdd-4371-b92c-2ff0b50abf8e/incidents/aeb2d251-34ae-4fb1-9a4a-1dba63acc1c4

HTTP Methods

The table below lists the http methods used by our services:

MethodDescriptionResponse Status Code
PUTCreate/Replace the whole resource200-OK (if nothing is changed), 201-Created
POSTCreate new resource (ID set by service)201-Created with URL of created resource
POSTAction200-OK, 204-No Content (only when nothing returned in response body), 202-Accepted (only when long running task)
GETRead (i.e. list) a resource collection200-OK
GETRead the resource200-OK
DELETERemove the resource204-No Content; avoid 404-Not Found

HTTP Headers

All header values shall follow the syntax rules set forth in the specification where the header field is defined. Many HTTP headers are defined in RFC7231, however a complete list of approved headers can be found in the IANA Header Registry.

The table below lists the headers used by our APIs

Header KeyApplies toExample
authorizationRequestBearer eyJ0…Xd6j
traceparentRequest(see Distributed Tracing)
tracecontextRequest(see Distributed Tracing)
acceptRequestapplication/json
If-MatchRequest”67ab43” or * (no quotes) (see Conditional Requests)
If-None-MatchRequest”67ab43” or * (no quotes) (see Conditional Requests)
If-Modified-SinceRequestSun, 06 Nov 1994 08:49:37 GMT (see Conditional Requests)
If-Unmodified-SinceRequestSun, 06 Nov 1994 08:49:37 GMT (see Conditional Requests)
api-versionRequest1 (see Versioning)
dateBothSun, 06 Nov 1994 08:49:37 GMT (see RFC7231, Section 7.1.1.2 )
content-typeBothapplication/json
content-lengthBoth1024
ETagResponse”67ab43” (see Conditional Requests)
last-modifiedResponseSun, 06 Nov 1994 08:49:37 GMT
retry-afterResponse180 (see RFC 7231, Section 7.1.3 )

Content types

Use JSON (RFC 7159) to represent structured (resource) data passed with HTTP requests and responses as body payload. The JSON payload shall use a JSON object as top-level data structure (if possible) to allow for future extension. This also applies to collection resources, where you ad-hoc would use an array. We use UTF-8 encoding and valid unicode strings. JSON property names shall be camelCased.

Non-JSON media types may be supported, if you stick to a business object specific standard format for the payload data. This could be image data format (JPG, PNG, GIF), document format (PDF, DOC, ODF, PPT), or archive format (TAR, ZIP).

Generic structured data interchange formats other than JSON (e.g. XML, CSV) may be provided, but only additionally to JSON as default format using content negotiation, an only for specific use cases where clients may not interpret the payload structure! We strongly advice to not use any other format than JSON as inputs.

Data formats

Primitive values shall be serialized to JSON following the rules of RFC8259 .

Important note for 64bit integers: JavaScript will silently truncate integers larger than Number.MAX_SAFE_INTEGER (2^53-1) or numbers smaller than Number.MIN_SAFE_INTEGER (-2^53+1). If the service is expected to return integer values outside the range of safe values, strongly consider returning the value as a string in order to maximize interoperability and avoid data loss.

TypeFormatSpecificationExample
integerint324 byte signed integer between -231 and 231-17721071004
integerint648 byte signed integer between -263 and 263-1772107100456824
integerbigintarbitrarily large signed integer number77210710045682438959
numberint32binary32 single precision decimal number — see IEEE 754-2008/ISO 60559:20113.1415927
numberdoublebinary64 double precision decimal number — see IEEE 754-2008/ISO 60559:20113.141592653589793
numberdecimalarbitrarily precise signed decimal number3.141592653589793238462643383279
stringdate-timeRFC 3339 internet profile — subset of ISO 8601”2019-07-30T06:43:40.252Z”

Query options

Parameter nameTypeDescriptionExample
namestringa property on the resource type that selects the resources to be returnedhttps://api.truesec.app/tenants?name=test 
orderBystringa property value on the model that is supportedhttps://api.truesec.app/tenants?orderBy=name 
pageNumberintegeran offset into the collection of the first resource to be returnedhttps://api.truesec.app/tenants?pageNumber=1 
pageSizeintegerthe maximum number of resources to include in a single responsehttps://api.truesec.app/tenants?pageSize=50 

NOTE

Filtering, Sorting and Pagination operations can all be performed against a given collection. When these operations are performed together, the evaluation order shall be:

  1. Filtering. This includes all range expressions performed as an AND operation.
  2. Sorting. The potentially filtered list is sorted according to the sort criteria.
  3. Pagination. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination.

Pagination

Operations that return a collection of resources shall consider pagination. There are hard limits to the payload size of HTTP responses, and when the size of a collection or the resources themselves can grow arbitrarily large there is the risk of exceeding this limit if the operation does not support pagination. Later adding support for pagination is a breaking change so it should be supported in the initial design of the service if there is any possibility that it will eventually be needed! Services should almost always support server-driven paging and may optionally support client-driven paging.

The service determines how many items to include in the response and may choose a different number for different collections and even for different pages of the same collection. An operation may allow the client to specify a maximum number of items in a response with an optional pageSize parameter. Operations that support pageSize should return no more than the value specified in pageSize but may return fewer.

Clients shall be resilient to collection data being either paged or non-paged for any given request.

Example of a paged result data

{ "items": [ { "id": "87acb32b-199d-4e48-a1ee-8216bd59e1c3", "title": "Incident 1", "description": "This is a incident 1", "status": "WAITING", "createdAt": "2022-08-17T07:40:51.3898292Z", "updatedAt": "2022-08-17T07:40:51.3898296Z" }, { "id": "9e87b440-42b2-4694-9d2c-56876ba31611", "title": "Incident 2", "description": "This is a incident 2", "status": "NEW", "createdAt": "2022-08-17T07:40:51.3900369Z", "updatedAt": "2022-08-17T07:40:51.3900371Z" } ], "pageNumber": 1, "pageSize": 50, "totalItems": 2, "totalPages": 1 }

Versioning

For using explicit version (try to avoid), the consumer shall specify this explicit with an api-version header with format 1.0. We always default to the latest version.

Changes that impact the backwards compatibility of an API are a breaking change.

Use a new version number to signal that support for existing clients will be deprecated in the future. When introducing a new version, services shall provide a clear upgrade path for existing clients and develop a plan for deprecation.

Online documentation of versioned services shall indicate the current support status of each previous API version and provide a path to the latest version.

Partial responses

A resource may contain large binary fields, such as files or images. To overcome problems caused by unreliable network and to improve response times, consider enabling binary resources to be retrieved in chunks. Also consider implementing HTTP HEAD requests for these resources. A HEAD request is similar to a GET request, except that it only returns the HTTP headers that describe the resource.

Long running actions

If the need to process long running actions arise in the api, one should accept the trigger via a POST request that returns the action code 202-Accepted that include a location header where a status monitoring resource reside. For all long-running actions, the client can than run a GET request on the status monitor resource to obtain the current status of the operation.

In the event that the long running task creates a new resource on completion, the status monitor resource GET request can result in a 303 status code, with location header set to the new resource created.

Example of a monitoring resource

HTTP/1.1 200 OK Content-Type: application/json { "status":"In progress", "link": { "rel":"cancel", "method":"delete", "href":"/api/status/0721aa2b-61a8-4835-948e-25fcdb4d9792" } }

Example of a monitoring resource status that show where to locate a new resource created

HTTP/1.1 303 See Other Location: /api/tenants/40a26375-919d-472b-b1c3-1ac937dc10d9

Error handling

For non-success conditions, developers should be able to write one piece of code that handles errors consistently across different REST API services. This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses.

The error response shall be a single JSON object. The value shall be a JSON object. We conform to RFC 7231  and send a status code of 400. For problem details we use RFC 7807 .

Example of an 400 error object for invalid input parameters

{ "type": "https://example.net/validation-error", "title": "Your request parameters didn't validate.", "invalid-params": [ { "name": "age", "reason": "shall be a positive integer" }, { "name": "color", "reason": "shall be 'green', 'red' or 'blue'" } ] }

Secure endpoints

Every API endpoint shall be protected and armed with authentication and authorization. The majority of our APIs are protected using JWT tokens provided by the platform (both external and internal). You should use the http typed Bearer Authentication security scheme.

Assigned permissions and scopes

APIs shall define permissions to protect their resources. Thus, at least one permission shall be assigned to each API endpoint. On public APIs we use somewhat opaque tokens that mostly express whom the user is and then exchange the token against our access control system with claims where every action shall be expressed.

Example of claim

ApplicationResourceOperationExample
socincidentsreadsoc.incidents.read
adminmemberswriteadmin.members.write

CORS

API compliant with our guidelines shall support CORS (Cross Origin Resource Sharing). Web developers usually don’t need to do anything special to take advantage of CORS. All of the handshake steps happen invisibly as part of the standard XMLHttpRequest calls they make.

Add an Access-Control-Max-Age pref response header containing the number of seconds for which this preflight response is valid.

Distributed Tracing

Distributed tracing allows the consumer to trace their code from frontend to backend and between services. The distributed tracing library creates spans (units of unique work) to facilitate tracing. Each span is in a parent-child relationship. As you go deeper into the hierarchy of code, you create more spans. We conform to OpenTelemetry for tracing.

Conditional Requests

An ETag should also be used to reflect the create, update, and delete policies of our services. We implement an “optimistic concurrency” strategy, where the incoming state of the resource is first compared against what currently resides in the service.

OperationHeaderValueETag checkReturn codeResponse
PATCH / PUTIf-None-Match*check for any version of the resource (’*’ is a wildcard used to match anything), if none are found, create the resource.200-OK or
201-Created
Response header shall include the new ETag value. Response body SHOULD include the serialized value of the resource (typically JSON).
PATCH / PUTIf-None-Match*check for any version of the resource, if one is found, fail the operation412-Precondition FailedResponse body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.
PATCH / PUTIf-Matchvalue of ETagvalue of If-Match equals the latest ETag value on the server, confirming that the version of the resource is the most current200-OK or
201-Created
Response header shall include the new ETag value. Response body SHOULD include the serialized value of the resource (typically JSON).
PATCH / PUTIf-Matchvalue of ETagvalue of If-Match header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource412-Precondition FailedResponse body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request.
DELETEIf-Matchvalue of ETagvalue matches the latest value on the server204-No ContentResponse body SHOULD be empty.
DELETEIf-Matchvalue of ETagvalue does NOT match the latest value on the server412-Preconditioned FailedResponse body SHOULD be empty.

Useful resource

Last updated on