Guidelines and best practices for development of APIs and Apps
This API guideline support API development in finnish public sector. It is adapted and expanded from Whitehouse API guidelines. Contents of this guideline are technical and directed for developers. It should be used in conjunction with API instructions provided by 6Aika, for example their recommendations.
This guide will provide basic instructions on how to build a service focusing on best practices of API design. The guide is divided into several sections which are recommended to be read before starting service development.
API development should be design-first driven when ever possible. All APIs must have machine readable and public specification. Use standard format specifications such as Swagger, open api definition format, RAML or API Blueprint.
Although this guide provides relatively clear rules, note that these must be adjusted to at least the following situations:
-
The software product is based on another project, which provides an API and the API differs from the ruleset defined by this guideline. In this case, keep your extensions and modifications consistent with the rest of the product. This should not nonetheless disencourage you to propose changes to the base product.
-
The security guidelines or similar of your organisation bring restrictions. You should respect your organisation's rules, yet again trying to improve these rules when needed is encouraged.
-
Getting to know other API guidelines is good practice: they might offer instructions not mentioned in this guideline, on the other hand they could give a different yet valid option to doing things.
This document provides guidelines and examples for Web APIs, encouraging consistency, maintainability, and best practices across applications. Aim is to balance a truly RESTful API interface with a positive developer experience (DX).
This document borrows heavily from:
- Designing HTTP Interfaces and RESTful Web Services
- API Facade Pattern, by Brian Mulloy, Apigee
- Web API Design, by Brian Mulloy, Apigee
- Fielding's Dissertation on REST
API development should be design-first driven when ever possible. All APIs must have machine reabadle and public specification. Use standard format specifications such as Swagger, open api definition format, RAML or API Blueprint.
Use national vocabularies is data modeling. This in long term creates semantic interoperability. Vocabularies are in Finnish, Swedish and English. If national vocabulary for specific need does not exist, use some international standard vocabulary.
Resources:
Good documentation answers to following questions:
- What can I do (and not do) With Your API?
- Does Your API Fit my Company’s Needs?
- How Does Your API View my World?
- How do You Secure Your API?
- How Long Will it Take to Get Started?
- Do You Offer SDKs?
- What API Endpoints and Event Integrations Does Your API Offer?
- Why am I Getting This Error Code or Unexpected Response?
[TODO] Here info about DX, checklist for developer portal.
The guidelines aim to support a truly RESTful API. Here are a few exceptions:
- Put the version number of the API in the URL (see examples below). Don’t accept any requests that do not specify a version number.
- Allow users to request formats like JSON or XML like this:
- A URL identifies a resource.
- URLs should include nouns, not verbs.
- Use plural nouns only for consistency (no singular nouns).
- Use HTTP verbs (GET, POST, PUT, DELETE) to operate on the collections and elements.
- You shouldn’t need to go deeper than resource/identifier/resource.
- Put the version number at the base of your URL, for example http://example.com/v1/path/to/resource.
- URL v. header:
- If it changes the logic you write to handle the response, put it in the URL.
- If it doesn’t change the logic for each response, like OAuth info, put it in the header.
- Specify optional fields in a comma separated list.
- Formats should be in the form of api/v2/resource/{id}.json
- List of magazines:
- Filtering is a query:
- A single magazine in JSON format:
- All articles in (or belonging to) this magazine:
- All articles in this magazine in XML format:
- Specify optional fields in a comma separated list:
- Add a new article to a particular magazine:
- Non-plural noun:
- Verb in URL:
- Filter outside of query string
HTTP verbs, or methods, should be used in compliance with their definitions under the HTTP/1.1 standard. The action taken on the representation will be contextual to the media type being worked on and its current state. Here's an example of how HTTP verbs map to create, read, update, delete operations in a particular context:
HTTP METHOD | POST | GET | PUT | DELETE |
---|---|---|---|---|
CRUD OP | CREATE | READ | UPDATE | DELETE |
/dogs | Create new dogs | List dogs | Bulk update | Delete all dogs |
/dogs/1234 | Error | Show Bo | If exists, update Bo; If not, error | Delete Bo |
(Example from Web API Design, by Brian Mulloy, Apigee.)
- No values in keys
- No internal-specific names (e.g. "node" and "taxonomy term")
- Metadata should only contain direct properties of the response set, not properties of the members of the response set
No values in keys:
"tags": [
{"id": "125", "name": "Environment"},
{"id": "834", "name": "Water Quality"}
],
Values in keys:
"tags": [
{"125": "Environment"},
{"834": "Water Quality"}
],
Error responses should include a common HTTP status code, message for the developer, message for the end-user (when appropriate), internal error code (corresponding to some specific internally determined ID), links where developers can find more info. For example:
{
"status" : 400,
"developerMessage" : "Verbose, plain language description of the problem. Provide developers
suggestions about how to solve their problems here",
"userMessage" : "This is a message that can be passed along to end-users, if needed.",
"errorCode" : "444444",
"moreInfo" : "http://www.example.gov/developer/path/to/help/for/444444,
http://drupal.org/node/444444",
}
Use three simple, common response codes indicating (1) success, (2) failure due to client-side problem, (3) failure due to server-side problem:
- 200 - OK
- 400 - Bad Request
- 500 - Internal Server Error
- Never release an API without a version number.
- Versions should be integers, not decimal numbers, prefixed with ‘v’. For example:
- Good: v1, v2, v3
- Bad: v-1.1, v1.2, 1.3
- Maintain APIs at least one version back.
- If no limit is specified, return results with a default limit.
- To get records 51 through 75 do this:
- http://example.gov/magazines?limit=25&offset=50
- offset=50 means, ‘skip the first 50 records’
- limit=25 means, ‘return a maximum of 25 records’
Information about record limits and total available count should also be included in the response. Example:
{
"metadata": {
"resultset": {
"count": 227,
"offset": 25,
"limit": 25
}
},
"results": []
}
Example: http://example.gov/api/v1/magazines.json
Response body:
{
"metadata": {
"resultset": {
"count": 123,
"offset": 0,
"limit": 10
}
},
"results": [
{
"id": "1234",
"type": "magazine",
"title": "Public Water Systems",
"tags": [
{"id": "125", "name": "Environment"},
{"id": "834", "name": "Water Quality"}
],
"created": "1231621302"
},
{
"id": 2351,
"type": "magazine",
"title": "Public Schools",
"tags": [
{"id": "125", "name": "Elementary"},
{"id": "834", "name": "Charter Schools"}
],
"created": "126251302"
}
{
"id": 2351,
"type": "magazine",
"title": "Public Schools",
"tags": [
{"id": "125", "name": "Pre-school"},
],
"created": "126251302"
}
]
}
Example: http://example.gov/api/v1/magazines/[id].json
Response body:
{
"id": "1234",
"type": "magazine",
"title": "Public Water Systems",
"tags": [
{"id": "125", "name": "Environment"},
{"id": "834", "name": "Water Quality"}
],
"created": "1231621302"
}
Example: Create – POST http://example.gov/api/v1/magazines/[id]/articles
Request body:
[
{
"title": "Raising Revenue",
"author_first_name": "Jane",
"author_last_name": "Smith",
"author_email": "[email protected]",
"year": "2012",
"month": "August",
"day": "18",
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eget ante ut augue scelerisque ornare. Aliquam tempus rhoncus quam vel luctus. Sed scelerisque fermentum fringilla. Suspendisse tincidunt nisl a metus feugiat vitae vestibulum enim vulputate. Quisque vehicula dictum elit, vitae cursus libero auctor sed. Vestibulum fermentum elementum nunc. Proin aliquam erat in turpis vehicula sit amet tristique lorem blandit. Nam augue est, bibendum et ultrices non, interdum in est. Quisque gravida orci lobortis... "
}
]
It is suggested that each resource accept a 'mock' parameter on the testing server. Passing this parameter should return a mock data response (bypassing the backend).
Implementing this feature early in development ensures that the API will exhibit consistent behavior, supporting a test driven development methodology.
Note: If the mock parameter is included in a request to the production environment, an error should be raised.
JSONP is easiest explained with an example. Here's one from StackOverflow:
Say you're on domain abc.com, and you want to make a request to domain xyz.com. To do so, you need to cross domain boundaries, a no-no in most of browserland.
The one item that bypasses this limitation is
<script>
tags. When you use a script tag, the domain limitation is ignored, but under normal circumstances, you can't really DO anything with the results, the script just gets evaluated.
Enter JSONP. When you make your request to a server that is JSONP enabled, you pass a special parameter that tells the server a little bit about your page. That way, the server is able to nicely wrap up its response in a way that your page can handle.
For example, say the server expects a parameter called "callback" to enable its JSONP capabilities. Then your request would look like:
http://www.xyz.com/sample.aspx?callback=mycallback
Without JSONP, this might return some basic javascript object, like so:
{ foo: 'bar' }
However, with JSONP, when the server receives the "callback" parameter, it wraps up the result a little differently, returning something like this:
mycallback({ foo: 'bar' });
As you can see, it will now invoke the method you specified. So, in your page, you define the callback function:
mycallback = function(data){ alert(data.foo); };
http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about?answertab=votes#tab-top
Alternative to JSONP is CORS - Cross-Origin Resource Sharing. APIs are the threads that let you stitch together a rich web experience. But this experience has a hard time translating to the browser, where the options for cross-domain requests are limited to techniques like JSON-P (which has limited use due to security concerns) or setting up a custom proxy (which can be a pain to set up and maintain).
Cross-Origin Resource Sharing (CORS) is a W3C spec that allows cross-domain communication from the browser. By building on top of the XMLHttpRequest object, CORS allows developers to work with the same idioms as same-domain requests.
Read more from http://enable-cors.org