Consumer-Perspective. Design your API from the perspective of the consumer, NOT the perspective of your underlying implementation. For example:
If the underlying implementation requires accessing multiple models or multiple apps/projects, this does not need to be reflected in a public interface. From the perspective of the consumer, it's simply one thing they are requesting.
Keep CRUD operations together within its corresponding resource. Why have the client go to one resource to read it and then another resource to write it?
Simple. Keep the top-leveI resources clear and simple - focusing on what the client is looking to consume. You can hide complexity within the parameters.
Separation of concerns. Do not require the consumer to know any implementation details. For example, an API shouldn't require the client to know the inter-dependencies of fields. Rather, all such business rules should be owned by the server. This allows the client to be lightweight and easily maintainable in the future.
Explicit Support Levels. APIs should make it clear whether they are experimental or more permanent as a part of their documentation.
Discoverability. Support HATEOAS where possible. This allows us to change our URLs without needing to worry about updating the mobile apps if the app discovers its URLs from a base URL.
1. URL Naming
Must: Keep your base URL simple and intuitive.
Suggestion: Follow this basic URL structure if you are concerned about collisions within your service. By "collisions", we mean TBD.
If collisions are not a concern (for example, when exposing APIs from context-specific IDAs), then you can eliminate the use of API_NAME.
Must: Keep the API name flat. Use two base URLs per resource: collection/identifier, e.g.
It is not necessary that there is a one-to-one correspondence between an API and the Django app that provides it. In fact, which Django app implements an API is an implementation detail and need not be bound to a public interface.
Keep verbs out of the base URL
Must: Use plural rather than singular nouns
Must: Use concrete (e.g., blogs, videos, news) rather than abstract (e.g., items, assets) names
Must: Use Python conventions: use_underscores instead of CamelCase
When no resource is involved, be direct and use Verbs instead of Nouns (p 19). But only when absolutely necessary as we try to use nouns as much as possible.
Make it clear in your API documentation that these “non-resource” scenarios are different.
Most use cases can be solved using partial updates via PATCH
PUT can be used if a complete update of the resource, including all of its sub-resources, is desired.
A typical PUT use case: The resource is a singular primitive value addressable via URI, such as when updating the value for a particular User Preference.
DELETE - Remove a resource (or a relationship) from the system
Must return HTTP 204 No Content
A note on HTTP PATCH
"Plain" HTTP PATCH (RFC 5789) is neutral with respect to content type and only specifies that the request body provide instructions for how to update the resource in question, not the format of those instructions. Several flavors of PATCH specific to JSON documents have more explicit definition, as noted above.
In DRF, the default PATCH handling fairly closely resembles what is specified by merge patch, but it does not require (or understand) the "application/merge-patch+json" media type. In edX REST APIs, if an implementation will use DRF's default PATCH handling, the implementation MUST recognize and accept the merge patch media type; API clients SHOULD use this media type, preferring it to the more typical "application/json" type.
The reason for this stricture is to explicitly leave room for supporting multiple PATCH styles in any given API, should this become desirable, without breaking existing clients.
4. URL Parameters
Must: Sweep complexity behind the ‘?’ (e.g., GET /dogs?color=red&state=running&location=park) (p 9)
fields parameter - Allow clients to specify/filter the fields in the response by supporting a fields parameter as a comma-delimited list. "Partial Response allows you to give developers just the information they need." (p 16)
"error_code": "course_not_started" # a short string that the client can rely upon for handling different errors
"developer_message" : "Verbose, plain language description of the problem for the app developer with hints about how to fix it.",
"user_message":"Pass this message on to the app user if needed.",
Put the version in the URL so the client can see it easily when handling the response logic.
Exception: Put it in the header only if it doesn't change the response handling logic, like the OAuth endpoint.
Must: Versions are major only ex. /v0/ /v1/ /v2/
Must: Major version should only be updated when changing the existing contract in a way that breaks backwards compatibility. The previous major version should continue to be supported for old clients according to the service's deprecation policy.
Must: Additive changes to the contract of the API, or the results, should not bump the major version, unless those changes are non-optional, and break existing contracts with the API.
(TODO) Responses should have a header containing minor / patch versions.
X-API-Version (TODO look into established convention for header name)
Deprecation Process: varies from project to project based on product requirements and developer usage, client usage, etc. Should be well documented.
TODO: deeper dive into Deprecation.
We use the conventions established by DRF, with some extra fields:
next: A URL to the next page, or null.
previous: A URL to the previous page, or null.
count: The total number of items.
num_pages: The total number of pages.
results: List of results.
The query parameters are as follows:
page - the page of results (zero indexed)
page_size - the number of results to return per page
sort_order - a string indicating the desired order (no direction modifiers)
In the past, this was our strategy for generating API documentation; however, the tools we previously used to generate structured documentation from docstrings are no longer supported.
These long docstrings are much better than nothing! However, to avoid duplicating information, these docstrings can be shortened once the endpoint is documented using the edx-api-doc-tools library.
Use hyperlinks (with absolute URLs) to represent (primary/foreign) keys.
When there are links to other resources, we should have a hyperlink in the response.
10. Multiple Formats
JSON as the default format
Clients can request different formats by specifying the "Accept" header
The server should always include a "Content-Type" header which specifies the format of the data being returned
By convention, our REST APIs support the following two authentication schemes:
OAuth2 - for mobile clients and micro-services
Session-based authentication - for mobile webviews and browser clients
12. Serialization Conventions
Dates and Timestamps
Should be serialized to strings in the ISO 8601 standard format
Timestamps should include explicit timezone offsets
UTC timestamps are preferred
To Be Discussed
Consolidate all API requests under one API subdomain. (p 23)
And also do Web redirects for common names
Expanding referenced objects
APIs can support expanding related objects, e.g. theteam API optionally includes user profile information for each member. This is preferable to requiring the client to make multiple subsequent AJAX calls.
For example, there can be three levels for a related object (using "username" as an example):
Returning just the id
Returning an object containing the id and a URL to get more details
For objects that have their own APIs, use option (b) and return both an id and a URL
For other objects, use option (a) and just return the id
Support a query parameter indicating an optional list of objects to be expanded: e.g. ?expand=user,team
APIs must go through a community review process and review by the architecture council before being accepted into the platform. See the Architecture Review Process for the process for gaining approval.
There are a number of existing APIs that you can crib from: