“middleName”: “Howard”,

“lastName”: “Doe”,

“salutation”: “Mr John Doe”,

“title”: “Mr”

}

},

{

“id”: 98765,

“names”: {

“firstName”: “Mary”,

“middleName”: “Pauline”,

“lastName”: “Smith”,

“salutation”: “Ms Mary Smith”,

“title”: “Ms”

}

},

{

“…..lots more passengers here…..”: “…”

}

],

“_links”: [

{

“rel”: “12345”,

“href”: “https://api.example.govt.nz/passengers/12345”

},

{

“rel”: “98765”,

“href”: “https://api.example.govt.nz/passengers/98765”

},

{

“…..lots more links here…..”: “…”

}

]

}

If there were no results from a query against a filter— for example, passengers booked on a specific flight (GET https://api.example.govt.nz/passengers/flights/1234), the return value would be a 200 response code, because the query is not invalid and there may be such data in the future.

1.7.7. HATEOAS

Hypermedia as the Engine of Application State (HATEOAS) is the principle of not just returning a response to a request, but also returning links to other useful capabilities. So, if a POST has just been used to create an item, the response could return links to options to update the item, delete the item and view other items of the same type. This makes it possible for user interfaces (UIs) that utilise an API to be automatically generated (in terms of hyperlinks) and adaptable to change (response content links).

If a web page effectively captures a single state in time, then hyperlinks are transitions to other states. The HATEOAS principle suggests that REST should return not only response content (current state) but also links to transitions to other states. This could result in thinner clients and fatter APIs, and means UIs need to be built to adapt to potential new transitions, but makes it possible to update all UIs no matter what platform they sit on (mobile app, web application).

For example, consider the following representation of a person and sequence of requests.

Note: This example assumes that an address is a unique resource in its own right and that people are associated with addresses. (This may not always be a practical application and the example is used just to illustrate the hypermedia concept.)

1. User submits a request for a person record:

# Request person details:

Example

GET https://api.example.govt.nz/people/c70d37cf-3314-4a24-a2c3-0fca7ec0943f

Host: www.example.com

Accept: application/json;q=1.0

# Response with person details:

Example

HTTP/1.1 200 OK

Content-Location: https://api.example.govt.nz/people/c70d37cf-3314-4a24-a2c3-0fca7ec0943f

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

Cache-control: max-age=3600,must-revalidate

{

"personId": "d9e1a2f6-6bc9-46af-9dbb-50de7f6eacd5",

"name": "Joe Bloggs",

"email": "joe.bloggs@example.com",

"addresses": [

{

"id": "5046b8f2-e8aa-4ec9-a597-34d01af0fd32",

"streetNumber": "12",

"streetName": "Somewhere Street",

"City": "Springfield",

"type": "home"

},

{

"id": "18a1a85c-3b1e-41b9-909c-668f2a803b69",

"streetNumber": "1",

"streetName": "Some Other Street",

"City": "Springfield",

"type": "business"

}

],

"_links": [

{

"rel": "self",

"href": "https://api.example.govt.nz/people/d9e1a2f6-6bc9-46af-9dbb-50de7f6eacd5"

},

{

"rel": "address1",

"href": "https://api.example.govt.nz/addresses/046b8f2-e8aa-4ec9-a597-34d01af0fd32"

},

{

"rel": "address2",

"href": "https://api.example.govt.nz/addresses/18a1a85c-3b1e-41b9-909c-668f2a803b69"

}

]

}

2. The user then updates the address details changing the street number:

# Request to update the address details:

Example

PUT https://api.example.govt.nz/addresses/d9e1a2f6-6bc9-46af-9dbb-50de7f6eacd5 HTTP/1.1

Host: api.example.govt.nz

Content-Type: application/json

{

"streetNumber": "12a",

"streetName": "Somewhere Street",

"City": "Springfield"

}

# Response that the update was successful:

Example

HTTP/1.1 204 No Content

Location: https://api.example.govt.nz/addresses/046b8f2-e8aa-4ec9-a597-34d01af0fd32

If the user were now to re-fetch the person details again as per step 1, the representation would contain a stale version of the address (street number 12 instead of 12a) because the address representation had been cached. This caching could occur anywhere along the network path (including public internet) between the server and client.

Using hypermedia links for related resources improves the visibility of the API and avoids these types of problems. Consider the revised person representation below:

# Response that the update was successful:

Example

{

"personId": "d9e1a2f6-6bc9-46af-9dbb-50de7f6eacd5",

"name": "JoeBloggs",

"email": "joe.bloggs@example.com",

"addresses": [

{

"rel": "self",

"href": "https://api.example.govt.nz/addresses/5046b8f2-e8aa-4ec9-a597-34d01af0fd32"

},

{

"rel": "self",

"href": "https://api.example.govt.nz/addresses/18a1a85c-3b1e-41b9-909c-668f2a803b69"

}

],

"_links": [

{

"rel": "self",

"href": "https://api.example.govt.nz/people/d9e1a2f6-6bc9-46af-9dbb-50de7f6eacd5"

}

]

}

Using this approach, the embedded object is replaced with a link to the ‘real’ object. Although this approach requires a larger number of network requests, it avoids the stale data problems and reduces payload size, which is an important consideration for APIs being used by mobile devices or over high latency networks.

1.8. Consideration of state

In almost all cases a REST API should be entirely stateless. As part of processing, it is possible for an API to gather context and pass this to a downstream system, however an API should not maintain that context for future requests. This should not be confused with caching, as discussed in section 1.12. Caching.

1.9. Batch handling and transactions

APIs are not designed for large payloads like batch handling for retrieving or uploading batches of data. APIs are geared towards stateless, usually synchronous, web-like requests for individual discrete data transactions. However, batch handling can be achieved through bundling multiple calls to the same API. This helps achieve atomicity of transactions and aids recoverability in event of errors.

When handling transactions, it is important to consider the troubleshooting and recovery aspects of error handling. This includes visibility of transaction progress and the ability to perform root cause analysis. To achieve this, it is advisable that logging is performed on all transactions coming through an API, with accurate timestamping, so that monitoring tools can visualise transaction progress. It also requires transaction identifiers (see section 1.6.1. Request headers) to be built into transactional API calls to ensure the transaction is traceable end to end.

In some cases, it may be deemed appropriate to provide an asynchronous batch type capability using an API. This would usually be implemented in a scenario where legacy system impact is a concern. An example of this could be a bulk creation of person records in a database based on a batch event in a consuming legacy application.

In such a scenario it would be preferable for the consuming application to treat each person as a unique event and to POST to a person API for each new unique resource. While this may seem like an unnecessary overhead, by individualising the transactions each success or error scenario can be handled in its own right, and the consumer can be informed of their transaction status.

If this is not possible, due to some system restriction, it is possible to asynchronously POST multiple person details to an API. This type of interaction should not be attempted synchronously as large batches will tie up HTTP threads and in some cases require client / server timeout control.

Example of a singleton create person request

{

"names": {

"firstName": "John",

"middleName": "Howard",

"lastName": "Doe",

"salutation": "Mr John Doe",

"title": "Mr"

}

}

Example of a bulk person create API call

#POST a collection of person resources

POST https://api.example.govt.nz/people