Rest API Standards
Software engineers in most CRUK teams will support Web Services resources via HTTP interfaces. Although each team might use language-specific frameworks to wrap their APIs, all of their operations eventually boil down to HTTP requests.
The goal of this document is to ensure CRUK APIs can be easily and consistently consumed by any client with basic HTTP support.
Rationale
To provide the smoothest possible experience, it's important all our APIs follow consistent design guidelines, making using them easy and intuitive. This document establishes the guidelines to be followed by CRUK engineers for developing APIs consistently.
Consistency allows teams to leverage common code, patterns, documentation and design decisions.
This guide aims to achieve the following:
- Define consistent practices and patterns for all API endpoints across CRUK.
- Adhere as closely as possible to accepted REST/HTTP best practices in the industry.
- Make accessing CRUK Services via REST interfaces easy for all our engineers.
- Allow engineers to leverage the prior work of other teams to implement, test and document REST endpoints defined consistently.
Description
We do not recommend making a breaking change to any API that pre-dates these guidelines simply for compliance sake. APIs should try to become compliant at the next version release when compatibility is being broken anyway.
Whenever we add a new API endpoint, that endpoint should be consistent with any other endpoint of the same API.
Summary
Do | Don't |
---|---|
✔️ Use well-structured URL | ❌ Limit URL lengths |
✔️ Support GET, PUT, POST and DELETE methods as when needed | ❌ Create unnecessary API endpoints |
✔️ Safely ignore API response data that is not expected/needed | ❌ Rely on the order of API response data |
✔️ Use HTTPS | ❌ Redirect HTTP requests to their equivalent HTTPS resource |
✔️ Use JSON standardizations | |
✔️ Use standard HTTP status codes | |
✔️ Use standard request headers | |
✔️ Use canonical identifiers | |
✔️ Return an error if a non-supported feature is requested | |
✔️ Pre-Flighting validation on async requests | |
✔️ Support explicit versioning |
URLs
URL structure
URLs should be able to easily read and construct.
An example of a well-structured URL is:
https://cruk-api-example.org.uk/v1/people/jdoe@cancer.org.uk/inbox
An example URL that is not friendly is:
https://cruk-api-example.org.uk/EWS/OData/Users('jdoe@cancer.org.uk')/Folders('AAMkADdiYzI1MjUzLTk4MjQtNDQ1Yy05YjJkLWNlMzMzYmIzNTY0MwAuAAAAAACzMsPHYH6HQoSwfdpDx-2bAQCXhUk6PC1dS7AERFluCgBfAAABo58UAAA=')
A frequent pattern that comes up is the use of URLs as values. Services may use URLs as values. For example, the following is acceptable:
https://cruk-api-example.org.uk/v1/items?url=https://resources.example.com/shoes/fancy
URLs should be lower cased with conjoined words being hyphen separated and query string parameters (especially ones that map to resource attributes) should be camelCased
https://cruk-api-example.org.uk/v1/brass-razoos?
URL length
The HTTP 1.1 message format defines no length limit on the Request Line, which includes the target URL.
HTTP does not place a predefined limit on the length of a request-line. A server that receives a request-target longer than any URI it wishes to parse should respond with a 414 (URI Too Long) status code.
Services that can generate URLs longer than 2,083 characters should make accommodations for the clients they wish to support.
Please note that some technology stacks have hard and adjustable url limits, so keep this in mind as you design your services.
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.
The stable identifier is not required to be a GUID.
An example of a URL containing a canonical identifier is:
https://cruk-api-example.org.uk/v1/people/7011042402/inbox
Multiple Identifiers
When possible our entities should have a single identifier for DELETE/POST/PUT methods. However there might be instances where an entity might have multiple IDs that need to be searchable e.g. on a GET request
In this case, we suggest the following:
If we want to return a specific entity object given a unique identifier, pass this identifier in the url as previously advised.
For example:
GET /trials/{id}
But if we instead would like to filter our entity objects and return only those that match a specific criteria, please use query parameters to achieve this.
For example:
GET /trials?iras=123
This will return all objects that match the criteria.
Methods
Supported methods
Operations should use the proper HTTP methods whenever possible.
below is a list of methods that CRUK services should support. Not all resources will support all methods, but all resources using the methods below should conform to their usage.
Method | Description | Is Idempotent |
---|---|---|
GET | Return the current value of an object | True |
PUT | Replace an object, or create a named object, when applicable | True |
DELETE | Delete an object | True |
POST | Create a new object based on the data provided, or submit a command | False |
POST
POST operations should support the Location response header to specify the location of any created resource that was not explicitly named, via the Location header.
As an example, imagine a service that allows creation of hosted servers, which will be named by the service:
POST http://cruk-api-example.org.uk/account1/servers
The response would be something like:
201 Created
Location: http://cruk-api-example.org.uk/account1/servers/server321
Where "server321" is the service-allocated server name.
Services may also return the full metadata for the created item in the response.
Versioning
All APIs compliant with the CRUK APIs should support explicit versioning. It's critical that clients can count on services to be stable over time, and it's critical that services can add features and make changes.
Versioning formats
Services are versioned using a MAJOR versioning scheme as defined by Semantic Versioning. The version is to be specified in the URL, prefixed with a 'v':
GET https://api.example/v1/things
When to version
Services should increment their version number in response to any breaking API change. See the following section for a detailed discussion of what constitutes a breaking change. Services may increment their version number for non-breaking changes as well, if desired.
Use a new major version number to signal that support for existing clients will be deprecated in the future. When introducing a new major version, services should provide a clear upgrade path for existing clients and develop a plan for deprecation that is consistent with their business group's policies.
Online documentation of versioned services should indicate the current support status of each previous API version and provide a path to the latest version.
Definition of a breaking change
Changes to the contract of an API are considered a breaking change. Changes that impact the backwards compatibility of an API are a breaking change.
Clear examples of breaking changes:
- Removing or renaming APIs or API parameters
- Changes in behaviour for an existing API
- Changes in Error Codes and Fault Contracts
- Anything that would violate the [Principle of Least Astonishment][principle-of-least-astonishment]
Headers
Request headers
The table of request headers below should be used by CRUK REST API services. Using these headers is not mandated, but if used they should be used consistently.
All header values should follow the syntax rules set forth in the specification where the header field is defined. Many HTTP headers are defined in [RFC7231][rfc-7231], however a complete list of approved headers can be found in the [IANA Header Registry][iana-headers]."
Header | Type | Description |
---|---|---|
Authorization | String | Authorization header for the request |
Date | Date | Timestamp of the request in RFC 3339 format |
Content type | "application/json", "application/xml", etc. | The requested content type for the response |
Accept-Encoding | Gzip, deflate | REST endpoints should support GZIP and DEFLATE encoding, when applicable. For very large resources, services may ignore and return uncompressed data. |
Accept-Language | "en", "es", etc. | Specifies the preferred language for the response. Services are not required to support this, but if a service supports localization it should do so through the Accept-Language header. |
Accept-Charset | Charset type like "UTF-8" | Default is UTF-8, but services may also be able to handle ISO-8859-1. |
Prefer | return=minimal, return=representation | If the return=minimal preference is specified, services should return an empty body in response to a successful insert or update. If return=representation is specified, services should return the created or updated resource in the response. Services should support this header if they have scenarios where clients would sometimes benefit from responses, but sometimes the response would impose too much of a hit on bandwidth. |
Response headers
Services should return the following response headers, except where noted in the "required" column.
Response Header | Required | Description |
---|---|---|
Date | All responses | The date the request was processed |
Content-Type | All responses | The content type |
Content-Encoding | All responses | GZIP or DEFLATE, as appropriate |
Preference-Applied | When specified in request | Whether a preference indicated in the Prefer request header was applied |
Custom headers
Custom headers should not be required for the basic operation of a given API.
Some of the guidelines in this document prescribe the use of non-standard HTTP headers. In addition, some services may need to add extra functionality, which is exposed via HTTP headers. The following guidelines help maintain consistency across usage of custom headers.
Headers that are not standard HTTP headers should have one of two formats:
- A generic format for headers that are registered as "provisional"
- A scoped format for headers that are too usage-specific for registration
PII parameters
Consistent with their organization's privacy policy, clients should not transmit personally identifiable information (PII) parameters in the URL (as part of path or query string) because this information can be inadvertently exposed via client, network, and server logs and other mechanisms.
Consequently, a service should accept PII parameters transmitted as headers.
However, there are many scenarios where the above recommendations cannot be followed due to client or software limitations. To address these limitations, services should also accept these PII parameters as part of the URL consistent with the rest of these guidelines.
Services that accept PII parameters -- whether in the URL or as headers -- should be compliant with privacy policy specified by their organization's engineering leadership. This will typically include recommending that clients prefer headers for transmission and implementations adhere to special precautions to ensure that logs and other service data collection are properly handled.
API Response
Response formats
For organizations to have a successful platform, they should serve data in formats engineers are accustomed to using, and in consistent ways that allow engineers to handle responses with common code.
Web-based communication, especially when a mobile or other low-bandwidth client is involved, has moved quickly in the direction of JSON for a variety of reasons, including its tendency to be lighter weight and its ease of consumption with JavaScript-based clients.
JSON property names should be camelCased.
Services should provide JSON as the default encoding.
The message data returned should be 'pretty printed' to make the API more usable for development/debugging. Adding some whitespace will not appreciably increase transfer data, especially where gzip encoding is also supported.
Clients-specified response format
In HTTP, response format should be requested by the client using the Accept header. This is a hint, and the server may ignore it if it chooses to, even if this isn't typical of well-behaved servers. Clients may send multiple Accept headers and the service may choose one of them.
The response format may be specified in the URL after the dot. (e.g. .json/.xml) This should be used to override the Accept header, if specified.
The default response format (no Accept header provided) should be application/json, and all services should support application/json.
Accept Header | Response type | Notes |
---|---|---|
application/json | Payload should be returned as JSON | Also accept text/javascript for JSONP cases |
GET https://cruk-api-example.org.uk/v1/products/user
Accept: application/json
Error condition responses
For non-success conditions, engineers should be able to write one piece of code that handles errors consistently across different CRUK APIs. This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses.
The error response should be a single JSON object. This object should contain name/value pairs with the names "error" and "errorDescription," and it may contain name/value pairs with the names "debug" and "data".
The value for the "error" name/value pair is a language-independent string. Its value is a service-defined error code that should be human-readable. This code serves as a more specific indicator of the error than the HTTP error code specified in the response. Services should have a relatively small number (about 20) of possible values for "error," and all clients should be capable of handling all of them.
The value for the "errorDescription" name/value pair should be a human-readable representation of the error. It is intended as an aid to engineers and is not suitable for exposure to end users.
The value for the "debug" name/value pair may be present and is used to relay more specific debugging information. This should not be present on production systems.
The value for "data" should be used for 409 validation errors to provide specific feedback on what field(s) have been submitted incorrectly.
This gives us two similar types of error document. One that is specifically for data validation errors and one for everything else, both of which share a common root. below are their definitions in Swagger notation:
"ResponseError": {
"required": [
"error",
"errorDescription"
],
"properties": {
"error": {
"type": "string",
"readOnly": true,
"example": "validation"
},
"errorDescription": {
"type": "string",
"readOnly": true,
"example": "There's something wrong with your data"
}
},
"xml": {
"name": "error"
}
},
"ResponseErrorValidation": {
"description": "Standard Error Response structure where validations have been tripped",
"required": [
"error",
"errorDescription",
"data"
],
"properties": {
"error": {
"type": "string",
"readOnly": true,
"example": "validation"
},
"errorDescription": {
"type": "string",
"readOnly": true,
"example": "There's something wrong with your data"
},
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/ResponseErrorValidationItem"
},
"maxItems": 30,
"minItems": 1,
"readOnly": true
}
},
"xml": {
"name": "errorValidation"
}
},
"ResponseErrorValidationItem": {
"description": "A lovely chunk of error",
"required": [
"field",
"errorMessages"
],
"properties": {
"field": {
"type": "string",
"readOnly": true,
"example": "participant.address.postalCode"
},
"errorMessages": {
"type": "array",
"items": {
"type": "string",
"readOnly": true,
"example": "Invalid format"
},
"maxItems": 30,
"minItems": 1
}
},
"xml": {
"name": "item"
}
}
Examples
Example of a standard error
{
"error": "timed_out",
"errorDescription": "Access to this resource has been timed out",
"debug": [{}]
}
Example of a validation error
{
"error": "validation",
"errorDescription": "There's something wrong with your data",
"data": [
{
"field": "participant.address.postalCode",
"errorMessages": ["Invalid format"]
}
]
}
HTTP Status Codes
Standard HTTP Status Codes should be used in CRUK APIs.
JSON standardizations
Primitive types
Primitive values should be serialized to JSON following the rules of [RFC4627][rfc-4627].
Dates
Producing dates
Services should produce dates using the ISO 8601 standard (e.g. 2017-02-26T20:00:00+00:00). Week dates and Ordinal dates should not be returned.
Consuming dates
Services should accept dates using the ISO 8601 standard (e.g. 2017-02-26T20:00:00+00:00). Week dates and Ordinal dates may be supported.
Durations
[Durations][wikipedia-iso8601-durations] need to be serialized in conformance with [ISO 8601][wikipedia-iso8601-durations]. Durations are "represented by the format P[n]Y[n]M[n]DT[n]H[n]M[n]S
." From the standard:
- P is the duration designator (historically called "period") placed at the start of the duration representation.
- Y is the year designator that follows the value for the number of years.
- M is the month designator that follows the value for the number of months.
- W is the week designator that follows the value for the number of weeks.
- D is the day designator that follows the value for the number of days.
- T is the time designator that precedes the time components of the representation.
- H is the hour designator that follows the value for the number of hours.
- M is the minute designator that follows the value for the number of minutes.
- S is the second designator that follows the value for the number of seconds.
For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds."
Intervals
[Intervals][wikipedia-iso8601-intervals] are defined as part of [ISO 8601][wikipedia-iso8601-intervals].
- Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
- Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M"
- Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
- Duration only, such as "P1Y2M10DT2H30M," with additional context information
Repeating intervals
[Repeating Intervals][wikipedia-iso8601-repeatingintervals], as per [ISO 8601][wikipedia-iso8601-repeatingintervals], are:
Formed by adding "R[n]/" to the beginning of an interval expression, where R is used as the letter itself and [n] is replaced by the number of repetitions. Leaving out the value for [n] means an unbounded number of repetitions.
For example, to repeat the interval of "P1Y2M10DT2H30M" five times starting at "2008-03-01T13:00:00Z," use "R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M."
Collections
Item keys
Services may support durable identifiers for each item in the collection, and that identifier should be represented in JSON as "id". These durable identifiers are often used as item keys.
Serialization
Collections should be represented in the following format when returned:
{
"metadata": {
"count": 238,
"offset": 0,
"limit": 50
},
"results": [
{
.. result object ..
}
]
}
In the metadata
section the fields are defined as:
- count: To total number of records in the record set (not the total number returned in the query). This is handy for paging
- offset: The number of records into the collection this result set is from. 0 is the first item.
- limit: The maximum possible number of results in the result set. (notE it is possible for the limit number therefore to be higher than the total number of results)
In the results
section we simply have an array of each item. If there are no items returned in the search, the results
key should still exist, containing an empty array.
Collection URL patterns
Collections are located directly under the service root when they are top level, or as a segment under another resource when scoped to that resource.
For example:
GET https://cruk-api-example.org.uk/v1/people
Nested collections and properties
Collection items may contain other collections. For example, a user collection may contain user resources that have multiple addresses:
GET https://cruk-api-example.org.uk/v1/people/123/addresses
{
"metadata": {
"count": 10,
"offset": 0,
"limit": 50
},
"results": [
{
"id": 123,
"name": "John",
"addresses": [
{ "street": "1st Avenue", "city": "Seattle" },
{ "street": "124th Ave NE", "city": "Redmond" }
]
}
]
}
Nested collections do not require metadata.
Changing collections
POST requests are not idempotent. This means that two POST requests sent to a collection resource with exactly the same payload may lead to multiple items being created in that collection. This is often the case for insert operations on items with a server-side generated id.
For example, the following request:
POST https://cruk-api-example.org.uk/v1/people
Would lead to a response indicating the location of the new collection item:
201 Created
Location: https://cruk-api-example.org.uk/v1/people/123
And once executed again, would likely lead to another resource:
201 Created
Location: https://cruk-api-example.org.uk/v1/people/124
While a PUT request would require the indication of the collection item with the corresponding key instead:
PUT https://cruk-api-example.org.uk/v1/people/123
Sorting collections
The results of a collection query may be sorted based on property values. The property is determined by the value of the orderBy query parameter.
The value of the orderBy parameter contains a comma-separated list of expressions used to sort the items. A special case of such an expression is a property path terminating on a primitive property.
The expression may include the suffix "asc" for ascending or "desc" for descending, separated from the property name by one colon (:). If "asc" or "desc" is not specified, the service should order by the specified property in ascending order.
NULL values should sort as "less than" non-NULL values.
Items should be sorted by the result values of the first expression, and then items with the same value for the first expression are sorted by the result value of the second expression, and so on. The sort order is the inherent order for the type of the property.
For example:
GET https://cruk-api-example.org.uk/v1/people?orderBy=name
Will return all people sorted by name in ascending order.
For example:
GET https://cruk-api-example.org.uk/v1/people?orderBy=name:desc
Will return all people sorted by name in descending order.
Sub-sorts can be specified by a comma-separated list of property names with OPTIONAL direction qualifier.
For example:
GET https://cruk-api-example.org.uk/v1/people?orderBy=name:desc,hireDate
Will return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order.
Interpreting a sorting expression
Sorting parameters should be consistent across pages, as both client and server-side paging is fully compatible with sorting.
Filtering
Filtering of collections should be done via the query string by adding the attribute name, an equals sign, and the value that it should equal. Filtering by every possible attribute in a resource may not be supported. The attributes that it is possible to filter by should be documented.
Example: returning all closed events
GET https://cruk-api-example.org.uk/v1/events?statusCode=closed
Filtering by multiple values of an attribute should be achieved by comma separated query string values.
Example: returning all closed or cancelled events
GET https://cruk-api-example.org.uk/v1/events?statusCode=closed,cancelled
Filtering by multiple attributes is achieved by multiple key/value pairs. This should always result in an AND query.
Example: returning all male only events that have been closed or cancelled
GET https://cruk-api-example.org.uk/v1/events?statusCode=closed,cancelled&gender=male
A logical not may be represented as a minus sign before the value
Example: returning all non-male events
GET https://cruk-api-example.org.uk/v1/events?gender=-male
Ranges may be represented by adding the following to the end to the attribute:
- GT = Greater Than
- GTE = Greater Than or Equals to
- LT = Less Than
- LTE = Less Than or Equals to
Example: returning all events created on 5th September 2016
GET https://cruk-api-example.org.uk/v1/events?createdGTE=2016-09-05T00:00:00&createdLT=2016-09-06T00:00:00
Paging
Server-driven paging
For all collections that are returned, if not specified by the client, the server should implement a hard limit that cannot be exceeded to stop DOS attacks (both intentional or otherwise). The limit should be based on the collection itself. For example, if the collection is a simple list of values, the limit may be high, whereas for complex, nested collections, the limit might need to be low. Plan for success and ensure that the limit is in place even if there isn't much data in the collection today.
As mentioned above, all collections should come with a metadata block to allow the called to page throw the results (see Client-driven paging)
Client-driven paging
Collections should support the offset and limit query string parameters that allow the client to page through the collection. offset should start at 0 for the first record. If the limit exceeds the collection's hard limit it should not result in an error being thrown, the hard limit should be used as the limit value instead.
Example: Requesting the second page of 10 results from a collection of events
GET http://cruk-api-example.org.uk/v1/events?offset=10&limit=10
Additional considerations
Stable order prerequisite: Both forms of paging depend on the collection of items having a stable order. The server should supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently.
Missing/repeated results: Even if the server enforces a consistent sort order, results may be missing or repeated based on creation or deletion of other resources. Clients should be prepared to deal with these discrepancies.
Combining client- and server-driven paging: Note that client-driven paging does not preclude server-driven paging. If the page size requested by the client is larger than the default page size supported by the server, the expected response would be the number of results specified by the client, paginated as specified by the server paging settings.
Paginating embedded collections: This should not be supported to cut down on system complexity. The API designer should be mindful of resources that contain large nested connections and expose them in their own endpoint.
Compound collection operations
Filtering, Sorting and Pagination operations may all be performed against a given collection. When these operations are performed together, the evaluation order should be:
- Filtering. This includes all range expressions performed as an AND operation.
- Sorting. The potentially filtered list is sorted according to the sort criteria.
- Pagination. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination.
Errors and Faults
Errors
Service Errors, are defined as a client passing invalid data to the service and the service correctly rejecting that data. Examples include invalid credentials, incorrect parameters, unknown version IDs, or similar. These are generally "4xx" HTTP error codes and are the result of a client passing incorrect or invalid data.
Errors do not contribute to overall API availability.
Faults
Faults, or more specifically Service Faults, are defined as the service failing to correctly return in response to a valid client request. These are generally "5xx" HTTP error codes.
Faults do contribute to the overall API availability.
Calls that fail due to rate limiting or quota failures should not count as faults. Calls that fail as the result of a service fast-failing requests (often for its own protection) do count as faults.
Latency
Latency is defined as how long a particular API call takes to complete, measured as closely to the client as possible. This metric applies to both synchronous and asynchronous APIs in the same way. For long running calls, the latency is measured on the initial request and measures how long that call (not the overall operation) takes to complete.
Time to complete
Services that expose long operations should track "Time to Complete" metrics around those operations.
Long running API faults
For a Long Running API, it's possible for both the initial request to begin the operation and the request to retrieve the results to technically work (each passing back a 200), but for the underlying operation to have failed. Long Running faults should roll up as Faults into the overall Availability metrics.
Further guidance
Client guidance
To ensure the best possible experience for clients talking to a REST service, clients should adhere to the following best practices:
Ignore rule
For loosely coupled clients where the exact shape of the data is not known before the call, if the server returns something the client wasn't expecting, the client should safely ignore it.
Some services may add fields to responses without changing versions numbers. Services that do so should make this clear in their documentation and clients should ignore unknown fields.
Variable order rule
Clients should not rely on the order in which data appears in JSON service responses. For example, clients should be resilient to the reordering of fields within a JSON object. When supported by the service, clients may request that data be returned in a specific order. For example, services may support the use of the $orderBy querystring parameter to specify the order of elements within a JSON array. Services may also explicitly specify the ordering of some elements as part of the service contract. For example, a service may always return a JSON object's "type" information as the first field in an object to simplify response parsing on the client. Clients may rely on ordering behaviour explicitly identified by the service.
Silent fail rule
Clients requesting OPTIONAL server functionality (such as optional headers) should be resilient to the server ignoring that particular functionality.
Long running operations
Long running operations, sometimes called async operations, refer to operations where the client is not expected to wait for the final answer.
There are two obvious ways that you might then get the result. One is by polling the server and asking "are we nearly there yet?" like an annoying child on a car journey, or you can use callbacks, so when the server as completed its task, it tells the client that it's done.
This document only outlines a method for the latter as it's a more sensible way to handling inter-service communication. The former is useful when handling requests from a Client that has no fixed, always-on endpoint to call back to (like a direct connection from a web browser via an AJAX request.)
Pre-Flighting
Services should perform as much synchronous validation as practical on requests. Services should prioritize returning errors in a synchronous way, with the goal of having only "Valid" operations processed using the long running operation.
Security
All service URLs should be HTTPS (that is, all inbound calls should be HTTPS). Services that deal with Callbacks should accept HTTPS.
Services should not redirect HTTP requests to their equivalent HTTPS resource. They should return a 404 response code. The reason for this is that your POST to an HTTP end point would get redirected to a GET on the HTTPS version of the URL which would certainly produce different results from those expected.
We recommend that services that allow client defined Callback URLs should not transmit data over HTTP. This is because information can be inadvertently exposed via client, network, server logs and other mechanisms.
Unsupported requests
RESTful API clients may request functionality that is currently unsupported. RESTful APIs should respond to valid but unsupported requests consistent with this section.
Essential guidance
RESTful APIs will often choose to limit functionality that can be performed by clients. For instance, auditing systems allow records to be created but not modified or deleted. Similarly, some APIs will expose collections but require or otherwise limit filtering and ordering criteria, or may not support client-driven pagination.
Feature allow list
If a service does not support any of the below API features, then an error response should be provided if the feature is requested by a caller. The features are:
- Key Addressing in a collection, such as:
https://cruk-api-example.org.uk/v1/people/user1@cancer.org.uk
- Filtering a collection by a property value, such as:
https://cruk-api-example.org.uk/v1/people?name=david
- Filtering a collection by range, such as:
http://cruk-api-example.org.uk/v1/people?hireDateGTE=2014-01-01&hireDateLTE=2014-12-31
- Client-driven pagination via offset and limit, such as:
http://cruk-api-example.org.uk/v1/people?offset=5&limit=2
- Sorting by orderBy, such as:
https://cruk-api-example.org.uk/v1/people?orderBy=name:desc
Error response
Services should provide an error response if a caller requests an unsupported feature found in the feature allow list. The error response should be an HTTP status code from the 4xx series, indicating that the request cannot be fulfilled. Unless a more specific error status is appropriate for the given request, services should return "400 Bad Request" and an error payload conforming to the error response guidance provided in the CRUK APIs. Services should include enough detail in the response message for a developer to determine exactly what portion of the request is not supported.
Example:
GET https://cruk-api-example.org.uk/v1/people?orderBy=name HTTP/1.1
Accept: application/json
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "validation",
"errorDescription": "One or more submitted fields have errors with the data.",
"data": [
{
"field": "orderDirection",
"errorMessages": [
"This value is not valid."
]
}
]
}
API documentation
All APIs should be documented using OpenAPI specification and the resulting JSON documentation added to the Engineering Portal.
A convenient way to document APIs automatically is to use Zod for schema validation and definition and then use zod-to-openapi to translate Zod schemas into an OpenAPI specification of your API.