Generating Api Key Best Practices
The current internet ecosystem has literally been invaded by APIs, and for good reasons. By using third party APIs in your products or services, you have access to a ton of useful features — such as authentication or storage services — that can benefit both you and your users. By exposing your own API, your application becomes “part of the mix” and will be used in ways you’ve never thought before… if you do it the right way, obviously.
- Api Key Best Practices
- Generating Api Key Best Practices For Beginners
- Generating Api Key Best Practices Pdf
- Api Key Generator
In this two part series I’ll show you how to create a RESTful API layer for your PHP applications, using a collection of real world best practices.
In short, api key identify a user & an api, and api secret is like a RSA private key in some way, it's used to generate a signature for each request when the user request that specific api, so that to identify itself. The benefits of a well-designed API include: improved developer experience, faster documentation, and higher adoption for your API. But what exactly goes into good API design? In this blog post, I will detail a few best practices for designing RESTful APIs. Characteristics of a well-designed API. Oct 30, 2015 All of this comes down to a single fact: API Keys were never meant to be used as a security feature. Most developers utilize API Keys as a method of authentication or authorization, but the API Key was only ever meant to serve as identification. API Keys are best for two things: identification and analytics.
The full source code of this project will be available at the end of part 2.
REST: A pleasant UI for developers
First of all, an API is a user interface for developers, so it must be friendly, simple, easy to use and of course pleasant; or else it will end up being another piece of digital junk out there.
Documentation, even in the form of a simple but well written README
file, is a good place to start. The minimal information we need is a summary of the service’s scope and the list of methods and access points.
A good summary can be:
Our application is a simple contact list service that manages contacts with linked notes. It has two object types, contacts and notes. Each contact has basic attributes such as first name, last name, and email address. Also, each contact can have a number of markdown-formatted notes linked to it.
Then, it’s a good idea to make a list of all the resources and actions that we are going to implement. This can be seen as the equivalent of wireframing for visual applications. Following the key principles of REST, each resource is represented by a URL, where the action is the HTTP method used to access it.
For example GET /api/contacts/12
retrieves the contact with id
of 12, while PUT /api/contacts/12
will update that same contact.
The full list of methods is displayed below:
For a more complete and professional documentation, you may consider some tools like Swagger, apiDoc or Google APIs Discovery Service: your users will love you!
Tools and setup
The main tool I’m going to use to build our API is Slim Framework. Why?
[It] helps you quickly write simple yet powerful web applications and APIs.
And that’s true. Its powerful routing feature makes it easy to use methods other than the standard GET
and POST
, it provides built-in support for HTTP method overridess (via both HTTP header and hidden POST fields) and it can be hooked with middleware and extra features to make apps and API development really easy.
Along with Slim I’m using Idiorm to access the database layer and Monolog for logging. So our composer.json
file will look like this:
The slim/extras
and slim/middleware
packages provide useful features such as content type parsing and basic authentication. Our custom classes are under the namespace of API
and sit inside the lib
directory.
At this point our working directory structure would look like this:
The front controller of our application is public/index.php
, where all the non-file-or-directory traffic is redirected via standard URL rewrite rules.
I’ve then placed all the initialization code in bootstrap.php
which we’ll see later. The share
directory contains data such as logs, configuration files, the SQLite databases and dump files, and the SSL certificates. The bin
directory contains utility scripts that create the database and import some data using the provided .sql
files.
SSL Everywhere
Our API will be accessible only in HTTPS mode, with no redirection. This simplifies the authentication logic and prevents poorly configured clients to access non encrypted endpoints. The easiest and more logical way to set this up is acting directly on the web server or through a proxy server.
I’m using old trusted Apache to do this, and my the virtual host file looks like this:
Directory settings are defined first, so that they are in common with both the HTTP and HTTPS version of our site. In the non-secure host configuration I’m using mod_rewrite
to issue a 403 Forbidden
error for any non secure connection, then in the secure section I’m setting up SSL using my self-signed certificates, along with the SLIM_ENV
variable that tells Slim the current application mode.
For more information on how to create a self signed certificate an install it on your Apache see this article on SSLShopper.
Now that we have clear objectives, a basic directory structure, and a server setup, let’s run a composer.phar install
and start writing some code.
Bootstrap and Front Controller
As said before, the bootstrap.php
file is responsible for loading our application settings and autoloader setup.
First I get the current environment. If a file named <EnvName>.php
is present it’s loaded, if not the default config file is loaded. Slim specific settings are stored in the $config['app']
array and passed to the constructor of our application that extends the basic Slim object (optional but recommended).
For example the statement:
configures a Monolog logger that writes to a file at app/path/share/logs/EnvName_YYYY-mm-dd.log
.
Then, after some refinements (you can see them in the source), I get the generated log writer and try to connect to the database:
Lastly I add the needed middleware to my application instance. Slim’s middleware is like the layers of an onion, the first middleware you add will be the innermost layer, so the order of our middleware is important. I’m using the following middleware in our API:
- Cache (the innermost);
- ContentTypes: parses JSON-formatted body from the client;
- RateLimit: manages users API limits;
- JSON: utility middleware for “JSON only responses” and “JSON encoded bodies” best practices;
- Authentication (outermost).
We will write all these except for the pre-existing ContentTypes
.
At the end of the bootstrap file I’ve defined the two global variables $app
(the application instance) and $log
(the log writer). The file is loaded by our front controller index.php
where the magic happens.
Routing structure
Slim has a nice feature called Route Groups. With this feature we can define our application routes like this:
I’ve created two nested groups, /api
and /v1
, so we can easily adhere to the “versioning in the URL” best practice. I’ve also created some optional routes for /api/
, that could contain user-readable content, and a generic root URL (/
) URL that in the real world could contain the public user interface for the app.
JSON middleware
My initial approach here was using a route middleware (another type of Slim’s middleware) inside the /v1
group for authentication and JSON requests/responses, but I’ve found it is more practical and clean to use classic middleware. As seen previously, a middleware is an instance of a class that inherits from SlimMiddleware
. The call()
method of a Slim middleware is where the action takes place, it’s executed automatically when the middleware is linked as global, with the $app->add()
method.
Our JSON middleware achieves two best practices: “JSON only responses” and “JSON encoded bodies”. Here’s how:
Use OpenSSL 'Pass Phrase arguments'. If you want to supply a password for the output-file, you will need the (awkwardly named) -passout parameter. This is a multi-dimensional parameter and allow you to read the actual password from a number of sources. Such as file or from an environment variable. Use the following command to generate your private key using the RSA algorithm: $ openssl genrsa -aes256 -passout pass:foobar -out private.key 2048 - Use the following command to extract your public key: $ openssl rsa -in private.key -passin pass:foobar -pubout -out public.key - Use the following command to sign the file: $ openssl dgst -sha512 -sign private.key -passin pass:foobar -out. Openssl generate key from csr. To then obtain the matching public key, you need to use openssl rsa, supplying the same passphrase with the -passin parameter as was used to encrypt the private key: openssl rsa -passin file:passphrase.txt -pubout (This expects the encrypted private key on standard input - you. That generates a 2048-bit RSA key pair, encrypts them with a password you provide and writes them to a file. You need to next extract the public key file. You will use this, for instance, on your web server to encrypt content so that it can only be read with the private key.
We can pass a root path to our middleware constructor. In this case I’m passing /api/v1
so that our middleware is applied only on the API part of our site. If the current path matches the response content type header is forced to application/json
, then I check the request method. If the request method is one of the write-enabled ones (PUT
, POST
, PATCH
) the request content type header must be application/json
, if not the application exits with a 415 Unsupported Media Type
HTTP status code.
If all is right the statement $this->next->call()
runs the next middleware in the chain.
Authentication
Since our application will run on HTTPS by default, I decided to use the token-over-basic-authentication approach: an API key is sent in the username field of the basic HTTP AUTH headers (no password required). In order to do this I wrote a Slim middleware class called TokenOverBasicAuth
, by modifying the existing Slim HttpBasicAuth
. This middleware is run first in the chain so it is added as last, and it takes an optional root path parameter in the constructor.
The method searches for the auth token within the PHP_AUTH_USER
request header, if it does not exist or is invalid a 401 Forbidden
status and authentication headers are passed to the client. The verify()
method is protected so that it can be overridden by child classes; my version here is simple:
Here I’m simply checking for the presence of the API key in the users
table, and If I find a valid user it’s added to the application context to be used with the next layer (RateLimit). You can modify or extend this class to inject your own authentication logic or use an OAuth module. For more info on OAuth see Jamie Munro’s article.
Consumable error payloads
Our API should show useful error messages in consumable format, if possible in JSON representation. We need a minimal payload that contains an error code and message. In addition, validation errors require more breakdown. With Slim we can redefine both 404
errors and server errors with the $app->notFound()
and $app->error()
method respectively.
Not found errors are simpler: first I’m grabbing the media type of the request, then the $isAPI
flag tells me if the current URL is under the /api/v*
group. If the client requested an API URL or sent a JSON content type header I’m returning a JSON output, else I can render a template or simply print some static HTML, as in this example.
Other errors are a little bit tricky, the $app->error()
method is triggered when there is an exception and Slim transforms standard PHP errors in ErrorException
objects. We need a way to give a useful error to the client without exposing too much of our internal mechanism in order to avoid security flaws. I’ve created two custom exceptions for this application, an APIException
and an APIExceptionValidationException
that are exposed to the public, all other exception types are logged, and displayed only in development mode.
The $app->error()
method receives the thrown exception as argument. By default I fetch all the data I need and fill the $error
array, then if I’m in production mode I unset the private data and rewrite the message with a generic one. The custom ValidationException
class has a custom getData()
method that returns an array of validation errors that are added to the final payload. Then the error is displayed in JSON or HTML depending on the request. On the API side we can have a simple error like this:
or a full validation error like this:
Conclusion
We have the core of our API in place now. In the next part we’ll add some flesh in order to have a full functioning service. In the meanwhile, feel free to read through the articles linked throughout this part – they’re a treasure trove of useful API design principles.
-->Most modern web applications expose APIs that clients can use to interact with the application. A well-designed web API should aim to support:
Platform independence. Any client should be able to call the API, regardless of how the API is implemented internally. This requires using standard protocols, and having a mechanism whereby the client and the web service can agree on the format of the data to exchange.
Service evolution. The web API should be able to evolve and add functionality independently from client applications. As the API evolves, existing client applications should continue to function without modification. All functionality should be discoverable so that client applications can fully use it.
This guidance describes issues that you should consider when designing a web API.
Introduction to REST
In 2000, Roy Fielding proposed Representational State Transfer (REST) as an architectural approach to designing web services. REST is an architectural style for building distributed systems based on hypermedia. REST is independent of any underlying protocol and is not necessarily tied to HTTP. However, most common REST implementations use HTTP as the application protocol, and this guide focuses on designing REST APIs for HTTP.
A primary advantage of REST over HTTP is that it uses open standards, and does not bind the implementation of the API or the client applications to any specific implementation. For example, a REST web service could be written in ASP.NET, and client applications can use any language or toolset that can generate HTTP requests and parse HTTP responses.
Here are some of the main design principles of RESTful APIs using HTTP:
REST APIs are designed around resources, which are any kind of object, data, or service that can be accessed by the client.
A resource has an identifier, which is a URI that uniquely identifies that resource. For example, the URI for a particular customer order might be:
Clients interact with a service by exchanging representations of resources. Many web APIs use JSON as the exchange format. For example, a GET request to the URI listed above might return this response body:
REST APIs use a uniform interface, which helps to decouple the client and service implementations. For REST APIs built on HTTP, the uniform interface includes using standard HTTP verbs to perform operations on resources. The most common operations are GET, POST, PUT, PATCH, and DELETE.
REST APIs use a stateless request model. HTTP requests should be independent and may occur in any order, so keeping transient state information between requests is not feasible. The only place where information is stored is in the resources themselves, and each request should be an atomic operation. This constraint enables web services to be highly scalable, because there is no need to retain any affinity between clients and specific servers. Any server can handle any request from any client. That said, other factors can limit scalability. For example, many web services write to a backend data store, which may be hard to scale out. For more information about strategies to scale out a data store, see Horizontal, vertical, and functional data partitioning.
REST APIs are driven by hypermedia links that are contained in the representation. For example, the following shows a JSON representation of an order. It contains links to get or update the customer associated with the order.
In 2008, Leonard Richardson proposed the following maturity model for web APIs:
- Level 0: Define one URI, and all operations are POST requests to this URI.
- Level 1: Create separate URIs for individual resources.
- Level 2: Use HTTP methods to define operations on resources.
- Level 3: Use hypermedia (HATEOAS, described below).
Level 3 corresponds to a truly RESTful API according to Fielding's definition. In practice, many published web APIs fall somewhere around level 2.
Organize the API around resources
Focus on the business entities that the web API exposes. For example, in an e-commerce system, the primary entities might be customers and orders. Creating an order can be achieved by sending an HTTP POST request that contains the order information. The HTTP response indicates whether the order was placed successfully or not. When possible, resource URIs should be based on nouns (the resource) and not verbs (the operations on the resource).
A resource doesn't have to be based on a single physical data item. For example, an order resource might be implemented internally as several tables in a relational database, but presented to the client as a single entity. Avoid creating APIs that simply mirror the internal structure of a database. The purpose of REST is to model entities and the operations that an application can perform on those entities. A client should not be exposed to the internal implementation.
Entities are often grouped together into collections (orders, customers). A collection is a separate resource from the item within the collection, and should have its own URI. For example, the following URI might represent the collection of orders:
Sending an HTTP GET request to the collection URI retrieves a list of items in the collection. Each item in the collection also has its own unique URI. An HTTP GET request to the item's URI returns the details of that item.
Adopt a consistent naming convention in URIs. In general, it helps to use plural nouns for URIs that reference collections. It's a good practice to organize URIs for collections and items into a hierarchy. For example, /customers
is the path to the customers collection, and /customers/5
is the path to the customer with ID equal to 5. This approach helps to keep the web API intuitive. Also, many web API frameworks can route requests based on parameterized URI paths, so you could define a route for the path /customers/{id}
.
Also consider the relationships between different types of resources and how you might expose these associations. For example, the /customers/5/orders
might represent all of the orders for customer 5. You could also go in the other direction, and represent the association from an order back to a customer with a URI such as /orders/99/customer
. However, extending this model too far can become cumbersome to implement. A better solution is to provide navigable links to associated resources in the body of the HTTP response message. This mechanism is described in more detail in the section Use HATEOAS to enable navigation to related resources.
In more complex systems, it can be tempting to provide URIs that enable a client to navigate through several levels of relationships, such as /customers/1/orders/99/products
. However, this level of complexity can be difficult to maintain and is inflexible if the relationships between resources change in the future. Instead, try to keep URIs relatively simple. Once an application has a reference to a resource, it should be possible to use this reference to find items related to that resource. The preceding query can be replaced with the URI /customers/1/orders
to find all the orders for customer 1, and then /orders/99/products
to find the products in this order.
Tip
Avoid requiring resource URIs more complex than collection/item/collection.
Another factor is that all web requests impose a load on the web server. The more requests, the bigger the load. Therefore, try to avoid 'chatty' web APIs that expose a large number of small resources. Such an API may require a client application to send multiple requests to find all of the data that it requires. Instead, you might want to denormalize the data and combine related information into bigger resources that can be retrieved with a single request. However, you need to balance this approach against the overhead of fetching data that the client doesn't need. Retrieving large objects can increase the latency of a request and incur additional bandwidth costs. For more information about these performance antipatterns, see Chatty I/O and Extraneous Fetching.
Avoid introducing dependencies between the web API and the underlying data sources. For example, if your data is stored in a relational database, the web API doesn't need to expose each table as a collection of resources. In fact, that's probably a poor design. Instead, think of the web API as an abstraction of the database. If necessary, introduce a mapping layer between the database and the web API. That way, client applications are isolated from changes to the underlying database scheme.
Finally, it might not be possible to map every operation implemented by a web API to a specific resource. You can handle such non-resource scenarios through HTTP requests that invoke a function and return the results as an HTTP response message. For example, a web API that implements simple calculator operations such as add and subtract could provide URIs that expose these operations as pseudo resources and use the query string to specify the parameters required. For example, a GET request to the URI /add?operand1=99&operand2=1 would return a response message with the body containing the value 100. However, only use these forms of URIs sparingly.
Define operations in terms of HTTP methods
Api Key Best Practices
The HTTP protocol defines a number of methods that assign semantic meaning to a request. The common HTTP methods used by most RESTful web APIs are:
- GET retrieves a representation of the resource at the specified URI. The body of the response message contains the details of the requested resource.
- POST creates a new resource at the specified URI. The body of the request message provides the details of the new resource. Note that POST can also be used to trigger operations that don't actually create resources.
- PUT either creates or replaces the resource at the specified URI. The body of the request message specifies the resource to be created or updated.
- PATCH performs a partial update of a resource. The request body specifies the set of changes to apply to the resource.
- DELETE removes the resource at the specified URI.
The effect of a specific request should depend on whether the resource is a collection or an individual item. The following table summarizes the common conventions adopted by most RESTful implementations using the e-commerce example. Not all of these requests might be implemented—it depends on the specific scenario.
Resource | POST | GET | PUT | DELETE |
---|---|---|---|---|
/customers | Create a new customer | Retrieve all customers | Bulk update of customers | Remove all customers |
/customers/1 | Error | Retrieve the details for customer 1 | Update the details of customer 1 if it exists | Remove customer 1 |
/customers/1/orders | Create a new order for customer 1 | Retrieve all orders for customer 1 | Bulk update of orders for customer 1 | Remove all orders for customer 1 |
The differences between POST, PUT, and PATCH can be confusing.
A POST request creates a resource. The server assigns a URI for the new resource, and returns that URI to the client. In the REST model, you frequently apply POST requests to collections. The new resource is added to the collection. A POST request can also be used to submit data for processing to an existing resource, without any new resource being created.
A PUT request creates a resource or updates an existing resource. The client specifies the URI for the resource. The request body contains a complete representation of the resource. If a resource with this URI already exists, it is replaced. Otherwise a new resource is created, if the server supports doing so. PUT requests are most frequently applied to resources that are individual items, such as a specific customer, rather than collections. A server might support updates but not creation via PUT. Whether to support creation via PUT depends on whether the client can meaningfully assign a URI to a resource before it exists. If not, then use POST to create resources and PUT or PATCH to update.
A PATCH request performs a partial update to an existing resource. The client specifies the URI for the resource. The request body specifies a set of changes to apply to the resource. This can be more efficient than using PUT, because the client only sends the changes, not the entire representation of the resource. Technically PATCH can also create a new resource (by specifying a set of updates to a 'null' resource), if the server supports this.
PUT requests must be idempotent. If a client submits the same PUT request multiple times, the results should always be the same (the same resource will be modified with the same values). POST and PATCH requests are not guaranteed to be idempotent.
Conform to HTTP semantics
This section describes some typical considerations for designing an API that conforms to the HTTP specification. However, it doesn't cover every possible detail or scenario. When in doubt, consult the HTTP specifications.
Media types
As mentioned earlier, clients and servers exchange representations of resources. For example, in a POST request, the request body contains a representation of the resource to create. In a GET request, the response body contains a representation of the fetched resource.
In the HTTP protocol, formats are specified through the use of media types, also called MIME types. For non-binary data, most web APIs support JSON (media type = application/json) and possibly XML (media type = application/xml).
The Content-Type header in a request or response specifies the format of the representation. Here is an example of a POST request that includes JSON data:
If the server doesn't support the media type, it should return HTTP status code 415 (Unsupported Media Type).
A client request can include an Accept header that contains a list of media types the client will accept from the server in the response message. For example:
If the server cannot match any of the media type(s) listed, it should return HTTP status code 406 (Not Acceptable).
GET methods
A successful GET method typically returns HTTP status code 200 (OK). If the resource cannot be found, the method should return 404 (Not Found).
POST methods
If a POST method creates a new resource, it returns HTTP status code 201 (Created). The URI of the new resource is included in the Location header of the response. The response body contains a representation of the resource.
If the method does some processing but does not create a new resource, the method can return HTTP status code 200 and include the result of the operation in the response body. Alternatively, if there is no result to return, the method can return HTTP status code 204 (No Content) with no response body.
If the client puts invalid data into the request, the server should return HTTP status code 400 (Bad Request). The response body can contain additional information about the error or a link to a URI that provides more details.
PUT methods
If a PUT method creates a new resource, it returns HTTP status code 201 (Created), as with a POST method. If the method updates an existing resource, it returns either 200 (OK) or 204 (No Content). In some cases, it might not be possible to update an existing resource. In that case, consider returning HTTP status code 409 (Conflict).
Consider implementing bulk HTTP PUT operations that can batch updates to multiple resources in a collection. The PUT request should specify the URI of the collection, and the request body should specify the details of the resources to be modified. This approach can help to reduce chattiness and improve performance.
PATCH methods
With a PATCH request, the client sends a set of updates to an existing resource, in the form of a patch document. The server processes the patch document to perform the update. The patch document doesn't describe the whole resource, only a set of changes to apply. The specification for the PATCH method (RFC 5789) doesn't define a particular format for patch documents. The format must be inferred from the media type in the request.
JSON is probably the most common data format for web APIs. There are two main JSON-based patch formats, called JSON patch and JSON merge patch.
JSON merge patch is somewhat simpler. The patch document has the same structure as the original JSON resource, but includes just the subset of fields that should be changed or added. In addition, a field can be deleted by specifying null
for the field value in the patch document. (That means merge patch is not suitable if the original resource can have explicit null values.)
For example, suppose the original resource has the following JSON representation:
Here is a possible JSON merge patch for this resource:
This tells the server to update price
, delete color
, and add size
, while name
and category
are not modified. For the exact details of JSON merge patch, see RFC 7396. The media type for JSON merge patch is application/merge-patch+json
.
Generating Api Key Best Practices For Beginners
Merge patch is not suitable if the original resource can contain explicit null values, due to the special meaning of null
in the patch document. Also, the patch document doesn't specify the order that the server should apply the updates. That may or may not matter, depending on the data and the domain. JSON patch, defined in RFC 6902, is more flexible. It specifies the changes as a sequence of operations to apply. Operations include add, remove, replace, copy, and test (to validate values). The media type for JSON patch is application/json-patch+json
.
Here are some typical error conditions that might be encountered when processing a PATCH request, along with the appropriate HTTP status code.
Error condition | HTTP status code |
---|---|
The patch document format isn't supported. | 415 (Unsupported Media Type) |
Malformed patch document. | 400 (Bad Request) |
The patch document is valid, but the changes can't be applied to the resource in its current state. | 409 (Conflict) |
DELETE methods
If the delete operation is successful, the web server should respond with HTTP status code 204, indicating that the process has been successfully handled, but that the response body contains no further information. If the resource doesn't exist, the web server can return HTTP 404 (Not Found).
Asynchronous operations
Sometimes a POST, PUT, PATCH, or DELETE operation might require processing that takes a while to complete. If you wait for completion before sending a response to the client, it may cause unacceptable latency. If so, consider making the operation asynchronous. Return HTTP status code 202 (Accepted) to indicate the request was accepted for processing but is not completed.
You should expose an endpoint that returns the status of an asynchronous request, so the client can monitor the status by polling the status endpoint. Include the URI of the status endpoint in the Location header of the 202 response. For example:
If the client sends a GET request to this endpoint, the response should contain the current status of the request. Optionally, it could also include an estimated time to completion or a link to cancel the operation.
If the asynchronous operation creates a new resource, the status endpoint should return status code 303 (See Other) after the operation completes. In the 303 response, include a Location header that gives the URI of the new resource:
For more information, see Asynchronous Request-Reply pattern.
Filter and paginate data
Exposing a collection of resources through a single URI can lead to applications fetching large amounts of data when only a subset of the information is required. For example, suppose a client application needs to find all orders with a cost over a specific value. It might retrieve all orders from the /orders URI and then filter these orders on the client side. Clearly this process is highly inefficient. It wastes network bandwidth and processing power on the server hosting the web API.
Instead, the API can allow passing a filter in the query string of the URI, such as /orders?minCost=n. The web API is then responsible for parsing and handling the minCost
parameter in the query string and returning the filtered results on the server side.
Generating Api Key Best Practices Pdf
GET requests over collection resources can potentially return a large number of items. You should design a web API to limit the amount of data returned by any single request. Consider supporting query strings that specify the maximum number of items to retrieve and a starting offset into the collection. For example:
Also consider imposing an upper limit on the number of items returned, to help prevent Denial of Service attacks. To assist client applications, GET requests that return paginated data should also include some form of metadata that indicate the total number of resources available in the collection.
You can use a similar strategy to sort data as it is fetched, by providing a sort parameter that takes a field name as the value, such as /orders?sort=ProductID. However, this approach can have a negative effect on caching, because query string parameters form part of the resource identifier used by many cache implementations as the key to cached data.
You can extend this approach to limit the fields returned for each item, if each item contains a large amount of data. For example, you could use a query string parameter that accepts a comma-delimited list of fields, such as /orders?fields=ProductID,Quantity.
Give all optional parameters in query strings meaningful defaults. For example, set the limit
parameter to 10 and the offset
parameter to 0 if you implement pagination, set the sort parameter to the key of the resource if you implement ordering, and set the fields
parameter to all fields in the resource if you support projections.
Support partial responses for large binary resources
A resource may contain large binary fields, such as files or images. To overcome problems caused by unreliable and intermittent connections and to improve response times, consider enabling such resources to be retrieved in chunks. To do this, the web API should support the Accept-Ranges header for GET requests for large resources. This header indicates that the GET operation supports partial requests. The client application can submit GET requests that return a subset of a resource, specified as a range of bytes.
Also, consider implementing HTTP HEAD requests for these resources. A HEAD request is similar to a GET request, except that it only returns the HTTP headers that describe the resource, with an empty message body. A client application can issue a HEAD request to determine whether to fetch a resource by using partial GET requests. For example:
Here is an example response message:
The Content-Length header gives the total size of the resource, and the Accept-Ranges header indicates that the corresponding GET operation supports partial results. The client application can use this information to retrieve the image in smaller chunks. The first request fetches the first 2500 bytes by using the Range header:
The response message indicates that this is a partial response by returning HTTP status code 206. The Content-Length header specifies the actual number of bytes returned in the message body (not the size of the resource), and the Content-Range header indicates which part of the resource this is (bytes 0-2499 out of 4580):
A subsequent request from the client application can retrieve the remainder of the resource.
Use HATEOAS to enable navigation to related resources
One of the primary motivations behind REST is that it should be possible to navigate the entire set of resources without requiring prior knowledge of the URI scheme. Each HTTP GET request should return the information necessary to find the resources related directly to the requested object through hyperlinks included in the response, and it should also be provided with information that describes the operations available on each of these resources. This principle is known as HATEOAS, or Hypertext as the Engine of Application State. The system is effectively a finite state machine, and the response to each request contains the information necessary to move from one state to another; no other information should be necessary.
Note
Currently there are no standards or specifications that define how to model the HATEOAS principle. The examples shown in this section illustrate one possible solution.
For example, to handle the relationship between an order and a customer, the representation of an order could include links that identify the available operations for the customer of the order. Here is a possible representation:
In this example, the links
array has a set of links. Each link represents an operation on a related entity. The data for each link includes the relationship ('customer'), the URI (https://adventure-works.com/customers/3
), the HTTP method, and the supported MIME types. This is all the information that a client application needs to be able to invoke the operation.
The links
array also includes self-referencing information about the resource itself that has been retrieved. These have the relationship self.
The set of links that are returned may change, depending on the state of the resource. This is what is meant by hypertext being the 'engine of application state.'
Versioning a RESTful web API
It is highly unlikely that a web API will remain static. As business requirements change new collections of resources may be added, the relationships between resources might change, and the structure of the data in resources might be amended. While updating a web API to handle new or differing requirements is a relatively straightforward process, you must consider the effects that such changes will have on client applications consuming the web API. The issue is that although the developer designing and implementing a web API has full control over that API, the developer does not have the same degree of control over client applications, which may be built by third-party organizations operating remotely. The primary imperative is to enable existing client applications to continue functioning unchanged while allowing new client applications to take advantage of new features and resources.
Versioning enables a web API to indicate the features and resources that it exposes, and a client application can submit requests that are directed to a specific version of a feature or resource. The following sections describe several different approaches, each of which has its own benefits and trade-offs.
No versioning
This is the simplest approach, and may be acceptable for some internal APIs. Significant changes could be represented as new resources or new links. Adding content to existing resources might not present a breaking change as client applications that are not expecting to see this content will ignore it.
For example, a request to the URI https://adventure-works.com/customers/3
should return the details of a single customer containing id
, name
, and address
fields expected by the client application:
Note
For simplicity, the example responses shown in this section do not include HATEOAS links.
If the DateCreated
field is added to the schema of the customer resource, then the response would look like this:
Existing client applications might continue functioning correctly if they are capable of ignoring unrecognized fields, while new client applications can be designed to handle this new field. However, if more radical changes to the schema of resources occur (such as removing or renaming fields) or the relationships between resources change then these may constitute breaking changes that prevent existing client applications from functioning correctly. In these situations, you should consider one of the following approaches.
URI versioning
Each time you modify the web API or change the schema of resources, you add a version number to the URI for each resource. The previously existing URIs should continue to operate as before, returning resources that conform to their original schema.
Extending the previous example, if the address
field is restructured into subfields containing each constituent part of the address (such as streetAddress
, city
, state
, and zipCode
), this version of the resource could be exposed through a URI containing a version number, such as https://adventure-works.com/v2/customers/3
:
This versioning mechanism is very simple but depends on the server routing the request to the appropriate endpoint. However, it can become unwieldy as the web API matures through several iterations and the server has to support a number of different versions. Also, from a purist's point of view, in all cases the client applications are fetching the same data (customer 3), so the URI should not really be different depending on the version. This scheme also complicates implementation of HATEOAS as all links will need to include the version number in their URIs.
Query string versioning
Rather than providing multiple URIs, you can specify the version of the resource by using a parameter within the query string appended to the HTTP request, such as https://adventure-works.com/customers/3?version=2
. The version parameter should default to a meaningful value such as 1 if it is omitted by older client applications.
This approach has the semantic advantage that the same resource is always retrieved from the same URI, but it depends on the code that handles the request to parse the query string and send back the appropriate HTTP response. This approach also suffers from the same complications for implementing HATEOAS as the URI versioning mechanism.
Note
Some older web browsers and web proxies will not cache responses for requests that include a query string in the URI. This can degrade performance for web applications that use a web API and that run from within such a web browser.
Header versioning
Rather than appending the version number as a query string parameter, you could implement a custom header that indicates the version of the resource. This approach requires that the client application adds the appropriate header to any requests, although the code handling the client request could use a default value (version 1) if the version header is omitted. The following examples use a custom header named Custom-Header. The value of this header indicates the version of web API.
Version 1:
Version 2:
As with the previous two approaches, implementing HATEOAS requires including the appropriate custom header in any links.
Media type versioning
When a client application sends an HTTP GET request to a web server it should stipulate the format of the content that it can handle by using an Accept header, as described earlier in this guidance. Frequently the purpose of the Accept header is to allow the client application to specify whether the body of the response should be XML, JSON, or some other common format that the client can parse. However, it is possible to define custom media types that include information enabling the client application to indicate which version of a resource it is expecting. The following example shows a request that specifies an Accept header with the value application/vnd.adventure-works.v1+json. The vnd.adventure-works.v1 element indicates to the web server that it should return version 1 of the resource, while the json element specifies that the format of the response body should be JSON:
The code handling the request is responsible for processing the Accept header and honoring it as far as possible (the client application may specify multiple formats in the Accept header, in which case the web server can choose the most appropriate format for the response body). The web server confirms the format of the data in the response body by using the Content-Type header:
If the Accept header does not specify any known media types, the web server could generate an HTTP 406 (Not Acceptable) response message or return a message with a default media type.
This approach is arguably the purest of the versioning mechanisms and lends itself naturally to HATEOAS, which can include the MIME type of related data in resource links.
Note
When you select a versioning strategy, you should also consider the implications on performance, especially caching on the web server. The URI versioning and Query String versioning schemes are cache-friendly inasmuch as the same URI/query string combination refers to the same data each time.
The Header versioning and Media Type versioning mechanisms typically require additional logic to examine the values in the custom header or the Accept header. In a large-scale environment, many clients using different versions of a web API can result in a significant amount of duplicated data in a server-side cache. This issue can become acute if a client application communicates with a web server through a proxy that implements caching, and that only forwards a request to the web server if it does not currently hold a copy of the requested data in its cache.
Open API Initiative
The Open API Initiative was created by an industry consortium to standardize REST API descriptions across vendors. As part of this initiative, the Swagger 2.0 specification was renamed the OpenAPI Specification (OAS) and brought under the Open API Initiative.
You may want to adopt OpenAPI for your web APIs. Some points to consider:
Api Key Generator
The OpenAPI Specification comes with a set of opinionated guidelines on how a REST API should be designed. That has advantages for interoperability, but requires more care when designing your API to conform to the specification.
OpenAPI promotes a contract-first approach, rather than an implementation-first approach. Contract-first means you design the API contract (the interface) first and then write code that implements the contract.
Tools like Swagger can generate client libraries or documentation from API contracts. For example, see ASP.NET Web API help pages using Swagger.
More information
Microsoft REST API guidelines. Detailed recommendations for designing public REST APIs.
Web API checklist. A useful list of items to consider when designing and implementing a web API.
Open API Initiative. Documentation and implementation details on Open API.