REST API Guidelines 📜

Aug 11, 2021    m. Feb 18, 2024    #rest  

This is a shortened merge of Google’s API design guide and Microsoft REST API Guidelines .

To write OpenAPI schema swagger documentation is useful. To quickly iterate on a design, semantic versioning should be used. To generate human-readable diff of different versions openapi-diff can be useful.

1. Resource-oriented design

In general URL paths should follow this scheme:

API Service NameCollection IDResource IDCollection IDResource ID
//storage.googleapis.com/buckets/bucket-id/objects/object-id

A storage service has a collection of buckets, where each bucket has a collection of objects.

Another example:

https://api.contoso.com/v1.0/people/7011042402/inbox

Resource names should be noun and not verb. Resource names should be plural.

2. Naming and standards

Microsoft mostly uses camelCase, Google mostly uses snake_case - this guide will be using camelCase.

Common property names should be used where applicable:

NameTypeDescription
namestringThe name field should contain the relative resource name.
parentstringFor resource definitions and List/Create requests, the parent field should contain the parent relative resource name.
createTimeTimestampThe creation timestamp of an entity.
updateTimeTimestampThe last update timestamp of an entity. Note: update_time is updated when create/patch/delete operation is performed.
deleteTimeTimestampThe deletion timestamp of an entity, only if it supports retention.
expireTimeTimestampThe expiration timestamp of an entity if it happens to expire.
startTimeTimestampThe timestamp marking the beginning of some time period.
endTimeTimestampThe timestamp marking the end of some time period or operation (regardless of its success).
readTimeTimestampThe timestamp at which a particular an entity should be read (if used in a request) or was read (if used in a response).
timeZonestringThe time zone name. It should be an IANA TZ name, such as “America/Los_Angeles”. For more information, see Wikipedia .
regionCodestringThe Unicode country/region code (CLDR) of a location, such as “US” and “419”. For more information, see unicode.org .
languageCodestringThe BCP-47 language code, such as “en-US” or “sr-Latn”. For more information, see unicode.org .
mimeTypestringAn IANA published MIME type (also referred to as media type). For more information, see iana.org .
displayNamestringThe display name of an entity.
titlestringThe official name of an entity, such as company name. It should be treated as the formal version of display_name.
descriptionstringOne or more paragraphs of text description of an entity.
filterstringThe standard filter parameter for List methods. See AIP-160.
querystringThe same as filter if being applied to a search method (ie :search)
pageTokenstringThe pagination token in the List request.
pageSizeint32The pagination size in the List request.
totalSizeint32The total count of items in the list irrespective of pagination.
nextPageTokenstringThe next pagination token in the List response. It should be used as page_token for the following request. An empty value means no more result.
orderBystringSpecifies the result ordering for List requests.
progressPercentint32Specifies the progress of an action in percentage (0-100). The value -1 means the progress is unknown.
requestIdstringA unique string id used for detecting duplicated requests.
validateOnlyboolIf true, it indicates that the given request should only be validated, not executed.
idstringSee Canonical identifier section.
locationstringOpaque URL for some resource.
messagestringArbitrary message.

For resource fields that cannot be set by the clients, they must be documented as “Output only” fields.

Canonical identifier

In addition to friendly URLs, resources that can be moved or be renamed SHOULD expose a URL that contains a unique stable identifier. It MAY be necessary to interact with the service to obtain a stable URL from the friendly name for the resource, as in the case of the /my shortcut used by some services.

One option is to use ULID .

Time

All time values are should be represented as RFC 3339 timestamps:

2019-10-12T07:20:50.52Z      (UTC+0)
2019-10-12T07:20:50.52+00:00 (UTC+0)
2019-10-12T14:20:50.52+07:00 (UTC+7)
2019-10-12T03:20:50.52-04:00 (UTC-4)

Durations

Durations need to be serialized in conformance with ISO 8601 with the following format:

P[n]Y[n]M[n]DT[n]H[n]M[n]S

Example (3 years, 6 months, 4 days, 12 hours, 30 minutes, and 5 seconds):

P3Y6M4DT12H30M5S

Intervals

Intervals are defined as part of ISO 8601. It seems sufficient to support only the following formats:

3. HTTP Methods

MethodHTTP Request BodyHTTP Response BodyIs IdempotentDescription
GET N/AResource listTrueReturn the specified resource.
GET N/AResourceTrueReturn a list of resources.
POST ResourceResourceFalseCreate a new object, or submit a command.
PUT ResourceResourceTrueReplace an object.
PATCH ResourceResourceFalseApply a partial update to an object.
DELETE N/ATrueDelete an object.
HEADN/ATrueReturn metadata of an object for a GET response.

4. Headers

A good reference for standard headers can be found on MDN . The most common are listed below.

Standard request headers

HeaderDescriptionExamples
AuthorizationAuthorization header for the request.Authorization: Bearer <token>
DateTimestamp of the request in client time.Date: Wed, 21 Oct 2015 07:28:00 GMT[1]
AcceptThe requested content type for the response.Accept: application/json
Content-TypeMedia type of the resource.Content-Type: text/html; charset=UTF-8, Content-Type: multipart/form-data; boundary=something
If-None-MatchSee ETags sectionIf-None-Match: "686897696a7c876b7e"

Standard response headers

Response HeaderDescriptionExamples
DateTimestamp of the response in server time.Date: Wed, 21 Oct 2015 07:28:00 GMT
Content-TypeMedia type of the resource.Content-Type: application/json
Content-EncodingLists any encodings that have been applied to the representation, and in what order.Content-Encoding: deflate, gzip
ETagSee ETags section.ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

5. Status codes

Full list of HTTP status codes can be found on MDN . The most common are listed below.

CodeDescription
200 OKThe request succeeded.
201 CreatedThe request succeeded, and a new resource was created as a result.
202 AcceptedThe request was accepted for processing, but the processing has not been completed.
304 Not ModifiedIndicates that the resource has not been modified (see Etags section).
400 Bad RequestThe server cannot or will not process the request due to something that is perceived to be a client error
401 UnauthorizedThe request requires user authentication.
403 ForbiddenThe client does not have access rights to the content.
404 Not FoundA specified resource is not found.
405 Method Not AllowedThe request method is known by the server but is not supported by the target resource.
409 ConflictThis response is sent when a request conflicts with the current state of the server.
429 Too Many RequestsThe user has sent too many requests in a given amount of time (“rate limiting”).
500 Internal Server ErrorThe server has encountered a situation it does not know how to handle.
502 Bad GatewayThis error response means that the server, while working as a gateway to get a response needed to handle the request, got an invalid response.
503 Service UnavailableThe server is not ready to handle the request.

6. JSON body

Success

To separate actual data from metadata (lake pagination, etc), the JSON body of response should put data object under value key.

{
  "value": [
    { "street": "1st Avenue", "city": "Seattle" },
    { "street": "124th Ave NE", "city": "Redmond" }
  ]
}

Error

Error object should be under error key. Response body should use the following structure:

{
  "error": {
    "code": "BadArgument",
    "message": "Multiple errors in ContactInfo data",
    "target": "ContactInfo",
    "details": [
      {
        "code": "NullValue",
        "target": "PhoneNumber",
        "message": "Phone number must not be null"
      },
      {
        "code": "NullValue",
        "target": "LastName",
        "message": "Last name must not be null"
      },
      {
        "code": "MalformedValue",
        "target": "Address",
        "message": "Address is not valid"
      }
    ]
  }
}

code, message and details (can be empty array) are required fields.

Note, that message fields are not to be used to display to client. Instead client and server should design a set of error codes specified by code field (that does not nececcerily match HTTP status codes).

7. Common patterns

Pagination

All APIs that return lists should support pagination. Client-driven paging seems to be the most popular.

Clients MAY use $top and $skip query parameters to specify a number of results to return and an offset into the collection. When both $top and $skip are given by a client, the server SHOULD first apply $skip and then $top on the collection.

GET http://api.contoso.com/v1.0/people?$top=5&$skip=2

For server-driven paging, Google’s guide is useful.

Sorting

Sorting is defined by a query parameter $orderBy.

The string value should follow SQL syntax: comma separated list of fields. For example: foo,bar. The default sorting order is ascending. To specify descending order for a field, a suffix desc should be appended to the field name. For example: foo desc,bar.

Filtering

$filter query parameter is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response.

Example 1: return all Products whose Price is less than $10.00:

GET https://api.contoso.com/v1.0/products?$filter=price lt 10.00

Example 2: all products with the name ‘Milk’ that also have a price less than 2.55:

GET https://api.contoso.com/v1.0/products?$filter=name eq 'Milk' and price lt 2.55

The value of the $filter option is a Boolean expression.

Microsoft lists the set of operations filter should support:

OperatorDescriptionExample
Comparison Operators
eqEqualcity eq ‘Redmond’
neNot equalcity ne ‘London’
gtGreater thanprice gt 20
geGreater than or equalprice ge 10
ltLess thanprice lt 20
leLess than or equalprice le 100
Logical Operators
andLogical andprice le 200 and price gt 3.5
orLogical orprice le 3.5 or price gt 200
notLogical negationnot price le 3.5
Grouping Operators
()Precedence grouping(priority eq 1 or city eq ‘Redmond’) and price gt 100

For more examples and operator precedence look at Microsoft’s guide .

When a filter is performed on a collection and the result set is empty you must respond with a valid response body and a 200 response code.

Long Running Operations

Services should be as responsive as possible, so as not to block callers. As a rule of thumb any API call that is expected to take longer than 0.5 seconds in the 99th percentile, should consider using the following Long-running Operations pattern for those calls.

The operation resource must be returned directly as the response message and any immediate consequence of the operation should be reflected in the API. For example, when creating a resource, that resource should appear in GET method though the resource should indicate that it is not ready for use. When the operation is complete, the value field should contain the value that would have been returned directly, if the method was not long running.

Access Sub-Collections

Sometimes, an API needs to let a client search across sub- collections. For example, the Library API has a collection of shelves, and each shelf has a collection of books, and a client wants to search for a book across all shelves.

GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx
GET https://library.googleapis.com/v1/shelves/-/books/{id}

ETags

In typical usage, when a URL is retrieved, the Web server will return the resource’s current representation along with its corresponding ETag value, which is placed in an HTTP response header “ETag” field:

ETag: "686897696a7c876b7e"

The client may then decide to cache the representation, along with its ETag. Later, if the client wants to retrieve the same URL resource again, it will first determine whether the locally cached version of the URL has expired (through the Cache-Control and the Expire headers). If the URL has not expired, it will retrieve the locally cached resource. If it is determined that the URL has expired (is stale), the client will send a request to the server that includes its previously saved copy of the ETag in the “If-None-Match” field.

If-None-Match: "686897696a7c876b7e"

On this subsequent request, the server may now compare the client’s ETag with the ETag for the current version of the resource. If the ETag values match, meaning that the resource has not changed, the server may send back a very short response with a HTTP 304 Not Modified status. The 304 status tells the client that its cached version is still good and that it should use that.

Throttling

Services should be as responsive as possible, so as not to block callers. As a rule of thumb any API call that is expected to take longer than 0.5 seconds in the 99th percentile, should consider using the Long-running Operations pattern for those calls.

HTTP specifies two return codes for these scenarios: ‘429 Too Many Requests’ and ‘503 Service Unavailable’. Services should use 429 for cases where clients are making too many calls and can fix the situation by changing their call pattern. Services should respond with 503 in cases where general load or other problems outside the control of the individual callers is responsible for the service becoming slow.

To avoid cascading failures, Circuit Breaker pattern can be used (Go implementation ).

Notes