REST API Design Guide

How should endpoints i.e. the resources that our API allows the API consumers to use, be named? What features and thus endpoints should be included in the same API? To version or not and how?

Detailed guide related to APIOps Cycles phase:
Build APIs

There are no actual standards for how endpoints etc. should be named. There are some conventions and best practices that help the APIs to be RESTful. These conventions make the API easy to understand by humans as well as programming and integration tools. Design with the next phase, API Audit criteria in mind. When designing REST APIs, use the REST API style guide as your guideline.

At first, add only the endpoints and data attributes that you know are necessary.  This reduces the need for versioning, support and maintenance. When an API is versioned the API consumers are at risk of having to make changes. There are many APIs that have succeeded in avoiding breaking changes.

Overview

Our goal is consistency, maintainability, and best practices across applications. APIOps aim to balance a truly RESTful API interface with a positive developer experience (DX).

In a nutshell:

  • Keep APIs’ functionalities as simple as possible. The endpoints do only one thing, but they do it well.
  • Avoid overlapping functionalities between different APIs.
  • In case of an error include the API response verbose description. Include also a description of the erroneous parameter value, if it is feasible.
  • Implement in each API (or bundle of APIs) ability to generate its own machine-readable document about its functionality.
  • API must have support for the OPTIONS endpoint, which is needed for example in a preflight request in SwaggerUI

Minimum developer experience

  • Each API must have a descriptive title
  • The description of API has to be sufficient.
  • Each API must have documentation, either an OpenAPI file or other standard specification that is supported by the API management solution used.
  • Each API should have a getting started section to provide a low learning curve and fast 1st positive experience for the consumer

The format of API specification

Use the most recent OpenAPI version supported by the API management platform to describe your API, unless you are developing your API using GraphQL, AsyncAPI, gRPC or some other standard. OpenAPI Specification (Wikipedia) originally known as the Swagger Specification, is a specification for machine-readable interface files for describing, producing, consuming, and visualizing RESTful Web services.

Information about supported versions:

Use examples-attribute with JSON schemas (validated) to provide automatically generated documentation and smart mock data to help use the API.

Naming standards

  • All URI parts including resource names are written in small letters.
  • Use only camelCase in all attribute (field) names.
  • Do not use special characters in URI or in attribute names.
  • Use English only in OpenAPI specification.
  • Use common legally used or industry-specific (not company-specific) words about resources and attributes. Refer to ISO standards as a primary source, then use WTO, EU or other trade area-specific vocabularies, http://schema.org/ or industry-specific vocabularies for naming. As a last resort, refer to company-specific vocabularies before inventing your own.
  • Avoid using generic names like “type”, “status” etc. without specifying what type, what status or better yet, avoiding those words altogether.
  • Use ISO standard or other generally used (see above) values for attribute values such as languages, country names etc. Avoid using magic numbers as values or provide also the human language alternative, preferably according to the Accept-Language header in the request.​​

Localization

The Accept-header should be used to support localized strings and possible localized logic. All timestamps should be in ISO format which contains timezone information. All dates and clock times with no specific timezone information associated should be informed in UTC +0.

All money values should be informed in specific currency and currency information should be contained in a custom x-companyname-currency header (in both request and response). Currency values should default to some provider or consumer based base currency if no specific currency has been requested.

Privacy and security

Private or confidential data should not be passed in URI, Query or header parameters as they are logged and cached. Security constraints are defined in API Product level, but privacy and security should be considered when splitting requirements to APIS and endpoints, as security schemes are easier to implement on API level than endpoint level or by reflecting authorization in the allowed operations or response payloads.

Versioning

Private or confidential data should not be passed in URI, Query or header parameters as they are logged and cached. Security constraints are defined in API Product l

Each API consumer needs to know which version of the API they are using and to be able to subscribe and use the version they need. There are mixed opinions about the way the versioning is indicated: whether API version should be included in the URL or in the header.

The common convention is to have the version in the URL of APIs. The reason is to ensure that the browser is able to explore the resources across versions.

In some API management systems, the version does not need to be in the URI nor in the header because each API product has its own version and each API consuming client application is only able to use 1 version of the API at a time. If the same client used multiple versions of the same API at a time, they would need different subscriptions. This versioning strategy works with all clients and is suited for caching and HATEOAS.

Versioning in - Azure API Management

When the version number is used it should always be in the URI since not all clients (for example marketing tools) can set headers and using version number as a query parameter might cause slowness as query parameters are not cached. Also, most API management platforms require that the URIs are unique if multiple versions of the same API are deployed at the same time.

The URI should include /vN with the major version (N) as a prefix. Having the letter v in front of the number is important to separate the version number from a resource identifier.

When APIs are upgraded with a breaking change, it may lead to breaking existing products or services using upgraded APIs.

Privacy and security should be considered when splitting requirements to APIs and endpoints, as security schemes are easier to implement on API level than endpoint level or by reflecting authorization in the allowed operations or response payloads.

Examples of breaking changes:
  • renaming fields or resource paths or endpoints
  • changing field type (e.g. from string to a list of strings)
  • changing the structure of payload (removing/renaming/retyping fields)
  • altering HTTP verbs
  • changing response HTTP codes

In case of breaking changes making a new version of the updated API is mandatory.

URI Template

/v{version}/

Example

https://domain.com/v1/apis

If there is any major breaking update, the new set of APIs is named as v (version number as integer).

Namespaces

In any URI, the first noun (which may be singular or plural, depending on the situation) should be considered a “namespace”. Namespaces should reflect the customer’s perspective on how the product works, not necessarily hierarchy in the company.

Namespace separates different logical APIs from each other, which is useful if you have lots of APIs with different purposes and they may end up using same resource or operation names.

Namespaces in different API management solutions:
  • IBM API Connect uses organizations, catalogs and spaces to organize publishing of APIs. These are useful when there are multiple teams in charge of different APIs. Organization and catalog form the namespace of the API and those are always added automatically before version number or anything defined in the OpenAPI document basepath value or path-variable of each operation.
  • Azure API management requires you to define a URI template when adding a new operation or by setting it for all operations in the OpenAPI document basepath value (required).

URI Template

/{namespace}/{version}

Example /hardware/v1/

Resources and HTTP methods

Try to use only one resource level, absolutely avoid using more than two levels to keep the URLs short to allow room for using variables.

URI Template

/{namespace}/{version}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}

Endpoints

Resource endpoints should follow at least View and CRUD (Create, Read, Update, Delete) operations. These should be handled by using the same URI but using different HTTP verbs (POST/GET/PUT/PATCH/DELETE, more details about each verb in ( https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html ) ).

Use nouns in the plural as resource names e.g. /products.

All used methods and their parameters have to be described in generated documentation endpoint by endpoint.

GET (read)

The GET method requests data from the resource and should not produce any side effect. The possible parameters are sent as part of the URL or as query parameters.

E.g GET /users
  • return a list of all users
E.g GET /users?limit=10&skip=30
  • returns a list containing 10 users after skipping 30 users

POST (create, do partial updates)

The POST method requests the server to create a resource in the database. The payload is sent in the request message body.

POST is a non-idempotent operation which means that multiple requests targeted to the same endpoint with the same payload will have a different outcome.

E.g POST /organizations/:id/managers
  • The first attempt creates a new Manager of Organization identified with :id. (If it did not exist already).
E.g POST /organizations/:id/managers
  • The second attempt with the same payload fails (because manager already existed) and an error response (4xx, manager already exists) is returned.

PUT (create and update complete resources)

The PUT method requests the server to update a resource, but it can also create it. The payload is sent in the request message body.Normally it is not possible to create a resource with PUT method, because resources are referenced with :id. In case resource with :id does not exist, a response with 404 Resource not found is sent unless you give a resource name (like file name) in the PUT request. Use POST to create a new resource and POST or PATCH to do partial updates.

PUT is an idempotent operation which means multiple requests with the same payload targeted to the same endpoint will have the same effect and outcome, either successful update or resource not found.

E.g. PUT /organizations/:id
  • The first attempt will request the server to update the resource (identified with :id) in Organizations collection.
  • A successful response with 2xx is returned, if a resource is found.
  • In case the resource is not found, an error response with 4xx is returned.
E.g. PUT /organizations/:id
  • Second attempt (with the same payload) will request the server to update the resource (identified with :id) in Organizations collection.
  • A successful response to 2xx is returned, if the resource is found.
  • In case the resource is not found, an error response with 4xx is returned.

DELETE (delete)

DELETE method requests that the resource, or its instance, should be removed from the database. The operation is irreversible.

E.g DELETE /apis/:id
  • Will request the server to delete the API identified with :id from Apis collection.
  • In a successful case a response with 204 is returned (no payload included).
  • In an unsuccessful case, a response with 4xx is returned.

Error handling

Just like an HTML error page shows a useful error message to a visitor, an API should provide a useful error message in a known consumable format. The representation of an error should be no different from the representation of any resource, just with its own set of fields.

The API should always return sensible HTTP status codes. API errors typically break down into 2 types: 400 series status codes for client issues & 500 series status codes for server issues. At a minimum, the API should standardize that all 400 series errors come with consumable JSON error representation. If possible (i.e. if load balancers & reverse proxies can create custom error bodies), this should extend to 500 series status codes.

A JSON error body should provide a few things for the developer - a useful error message, a unique error code (that can be looked up for more details in the docs) and possibly a detailed description.

JSON output representation for something like this would look like:

{ "code" : 1234, "title" : "Organization is not found", "detail" : "Organization with specified ID is not found" }

Validation errors for PUT, PATCH and POST requests will need a field breakdown. This is best modeled by using a fixed top-level error code for validation failures and providing the detailed errors in an additional errors field, like so:

{ "code" : 1024, "title" : "Validation Failed", "detail" : [ { "code" : 5432, "title" : "first_name", "detail" : "First name cannot have fancy characters" }, { "code" : 5622, "title" : "password", "detail" : "Password cannot be blank"} ] }

Filtering

Paging

Pages of results should be referred to consistently by the query parameters page and pageSize, where pageSize refers to the number of results per request, and page refers to the requested page.

Fields like totalItems and totalPages help provide context to paged results. Use the same fields with all resources to be consistent.

Hypermedia links

Hypermedia links are high value in navigating paged resource collections, as page/pageSize query parameters can be maintained while navigating pages of results.

Links should be provided with reels of next, previous, first, last wherever appropriate.

Time selection

startTime or {propertyName}After, endTime or {propertyName}Before query parameters should be provided if time selection is needed. All time values in the parameters and in the data have to be in the ISO format including timezone.

Sorting

sortBy and sortOrder can be provided to allow for collection results to be sorted. sortBy should be a field in the individual resources, and sortOrder should be asc or desc.

URI Template

GET /{namespace}/{version}/{resource}

Example Request

GET /hardware/v1/products

Resource collection

A list of all of the given resources, including any related metadata. The array of resources should be in the items field to help handle other fields than the actual resources being returned in the response.

Plan for security and provide a list of only those resources the requesting party is allowed to see.

If the resource response is really big, provide a possibility to include only those fields the client requires. Also add only those fields in the response, that you consider absolutely necessary. It’s easier to add more later when needed instead of removing that would be a breaking change.

Single resource

A single resource, typically derived from the parent collection of resources (often more detailed than the collection resource items).

All identifiers for sensitive data should be non-sequential, and preferably non-numeric. In scenarios where this data might be used as a subordinate to other data, immutable string identifiers should be utilized for easier readability and debugging (i.e. “nameOfValue” vs 1421321).

URI Template

GET /{namespace}/{version}/{resource}/{resource-id}

Example Request

GET /hardware/v1/products/6438313255314



Back to related APIOps Cycles phase:
Build APIs
APIOps Cycles

method for lean api development

Great APIs need skilled people and a good method, which let's you create APIs as products - fast.
APIOps Cycles method is vendor & technology-neutral.

Read the free e-book "The 8 wastes of lean in API development". Learn quick tips on how to remove the wastes using the APIOps Cycles method.