This article refers to APIs at the boundary of services - i.e. those we expose via primitives, between the server side of an app and its client interface, or to third parties - basically any API that could in theory be accessed from outside of the runtime of a component.
REpresentational State Transfer
REST is our go-to interface choice when exposing APIs. It is simple, widely adopted and if implemented well almost self-documenting. It has excellent library and language support.
We design RESTful APIs along the following principals
- URI paths are nouns not verbs. The URI is the ‘thing’ we are operating on.
- Use the plural form of nouns, so
- Operations applying to a set apply to the collective noun. For example, to search users
GET /users?q=. To create a new user,
- Use the appropriate HTTP verb1 to describe what you are doing to a URI
- Follow Postel’s law2: Be conservative in what you do, be liberal in what you accept from others
- Internal-use only APIs should be designed to the same standards as those we apply to external APIs
HTTP verb usage
GETretrieves things. Use the querystring to further refine or filter the operation we are performing
HEADreturns the same headers as the GET, but no body. Useful in conjunction with cache headers to see if things have changed
POSTis only ever used to mutate a thing, never to retrieve it
PUTcreates or replaces an entire thing
PATCHpartially modifies a thing
DELETEdeletes a thing
To a certain extent, and depending on the situation,
DELETE are optional and can be replaced by
POST (with an appropriate action in the body). This is because not all clients will support, or easily
support, these three verbs.
Ideally, you should strive to support both a broad usage POST and the finer grained verbs (see Postel’s law2).
Common querystring parameters
Use the following parameters rather than inventing new ones, unless the purpose you need is not listed below:
qrepresents a query or search term
offsetrepresents the starting point when paging through result sets
limitrepresents an upper limit on how many results to return when working with result sets
sortrepresents the sort operation to be applied when working with result sets
fromrepresents the lower bound on a date filter
torepresents the upper bound on a date filter
We version using an integer in the path. Only increment this integer when you implement a breaking change.
The convention is to version your APIs from the root of the path. There should be nothing other than the domain name before the version.
http://api.talis.com/1/thing/1234 http://api.talis.com/1/ntu/thing/1234 http://api.talis.com/1/my-app/thing/1234
Un-versioned paths usually default or redirect to the current complete major version, so their use in shipped and packaged code should be considered unstable and unsavory. Pick these up in code reviews.
You should consider web sockets where an API is exceptionally chatty, but a fallback to REST should also be provided.
Authentication is via bearer tokens issued by Talis Persona1. Try to stay away from IP limited or alternative authentication schemes such as cookie or session-based. Persona will issue users signing in to apps with a token so prefer that for APIs backing AJAX on interfaces rather than relying on a signed-in server session.
It is not mandatory to authenticate your API, but consider it even if you accept tokens of any scope. Unauthenticated routes are harder to rate limit or audit, so you should tend towards authentication unless you can think of a good reason for the API to be totally public.
Any APIs we expose to other teams, or externally, should be accompanied by a public API document. This details every route and general description and guidance on how to use the API.
Currently we use Apiary3 to power these documents, and also provide a stub API service.
Interfaces aside from REST and Web Sockets should only be considered in exceptional circumstances. Consult your team first and consider in depth why you can’t achieve what is necessary without introducing a new type of interface.