Content-Type Negotiation
Table stakes for this contract is that all APIs must support JSON as a request and response format. Additional formats
are left to the discretion of the service. For example, a report service may want to return application/pdf, while
some other services may prefer application/yaml.
Content-Type: application/json
When creating a request, there are two components to consider:
- What format the request is sending.
- What format the request wants to receive.
In all cases, the API must support requesting and receiving application/json, assuming a body is sent/expected. There
are a variety of error cases that need to be outlined.
Requests for HTML
In all cases where the requested response type is Accepts: text/html, the resource server has two choices. Either,
is to simply return a 415 Unsupported Media Type. This is the easiest response, but also the least helpful. The better
choice is to ensure that your service also offers an API explorer style user interface, such as a Swagger-UI. In this
case, the server should return a 303 See Other to this API explorer.
POST /namespace/v1/entity
Accepts: text/html
HTTP/1.1 303 See Other
Location: https://api.example.com
Request with unsupported body formats
If a request sends a body that is not supported, the server must respond with 415 Unsupported Media Type.
POST /v1/entity
Content-Type: text/unsupported
HTTP/1.1 415 Unsupported Media Type
Requests for unsupported media types
If a client asks for a response in a media type that is not supported, we return a 400.
Accepts: text/unsupported
HTTP/1.1 400 Bad Request
Requests for multiple media types
Accepts: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8
It is common for browsers to ask for several HTML-like MIME types in standard HTTP requests, though it’s less common for API requests. If a request such as this is received, the first most relevant media type that is supported by the API must be observed.
Example 1
This resolves to a Location header, as outlined above.
Accepts: text/html, application/json
Example 2
This resolves to application/json.
Accepts: text/plain, application/yaml, application/json;q=0.4
Requests for Files
In cases where a request is asking for a file response (such as a report), two approaches may be taken.
Approach 1: Build the file in JS
A UI can read JSON data and compose the format in memory. This is useful for small, ad-hoc reports and files, scripts that need to be prepopulated with customer data (such as onboarding scripts), and other things that require decoration with the current user context.
Approach 2: Redirect to an authorized GET URL
For larger files, or files that are generated and stored for later download, we must balance authorization requirements against default browser behavior. A browser’s default behavior to download when asked to load a PDF, for example, is too convenient to pass up, though building URI’s that include authorization information in their querystring is problematic.
As such, we require that larger files be downloaded via a two-step process. First, accessing the resource via the
regular resource path and the desired Content-Type. The response must be a 302 Found, with a Location header
containing such an authorized URL, with the issued authorization method being very short lived.
GET /v1/report/my-report-id
Accepts: application/pdf
HTTP/1.1 302 Found
Location: /v1/report/my-report-id.pdf?<some_short_lived_authorization_method>
We are specifically using 302 Found, because it instructs the browser to convert all requests to GET methods. Thus if
an HTTP request asks for a report generation, the POST operation to create that report will - upon completion - be
converted into a GET to download this report. For on-demand reports, please also review the standards around
Long-Running Operations.