Open edX REST API Conventions
- 1 Background
- 2 High Level Requirements
- 3 Conventions
- 3.1 1. URL Naming
- 3.2 2. Identifying Resources
- 3.3 3. HTTP Verbs
- 3.3.1 A note on HTTP PATCH
- 3.4 4. URL Parameters
- 3.5 5. Errors
- 3.6 6. Version
- 3.7 7. Pagination
- 3.8 8. Documentation
- 3.9 9. Discoverability
- 3.10 10. Multiple Formats
- 3.11 11. Authentication
- 3.12 12. Serialization Conventions
- 3.13 To Be Discussed
- 4 Expanding referenced objects
- 5 Review Process
- 6 Example APIs
Background
This document captures the conventions to be used for all Open edX REST APIs.
Useful reading:
these conventions are derived from a very useful set defined by Apigee: http://pages.apigee.com/rs/apigee/images/api-design-ebook-2012-03.pdf.
the initial discussions about APIs were captured in API Thoughts.
Django REST Framework version 2 documentation (we are currently using version 2.3.14, which has different APIs from the version 3).
for comparison, it is worth studying public API documentation from the big internet companies
High Level Requirements
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.
Conventions
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.
/api/{API_NAME}/{VERSION}/...If collisions are not a concern (for example, when exposing APIs from context-specific IDAs), then you can eliminate the use of API_NAME.
/api/{VERSION}/...
Must: Keep the API name flat. Use two base URLs per resource: collection/identifier, e.g.
/api/user/v1/accounts/{USERNAME}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.
Resource name
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
Verbs
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.
/convert?from=EUR&to=CNY&amount=100
Make it clear in your API documentation that these “non-resource” scenarios are different.
See list of example APIs at the end of this document.
2. Identifying Resources
Nice: Do not expose database IDs where possible (Must for external APIs, per dev ops)
Must: edX resource identifiers
Users should be referenced by username.
Courses runs should be referenced via course run keys (eg. course-v1:edX+BlendedX+1T2018)
Catalog courses should be referenced via a catalog UUID.
Course blocks should be referenced via usage keys (eg. block-v1:org+course+run+type@sequential+block@2aa6fc9d8278)
Model system resource URL schemes as if all resources are available to all users
Include all necessary filters in the URL such that any user could theoretically access the resource
Separate filtering and authorization – ie, do not return different resource representations via the same URL based on the requesting user
Example 1: Choose "/profiles/john_harvard" over "/profile"Example 2: Choose "/courses?username=john_harvard" over "/courses"
See relevant conversation here for more context.
Remember, it's entirely possible that "/profiles/john_harvard" or "/courses?username=john_harvard" could be requested by the "administrator" user
Several benefits exist from an explicit filtering approach:
Ensures resources/results are individually-addressable
Enables discovery/sharing of resources, among other potential uses
Resource filtering mechanisms can be modified without impacting authorization mechanisms
Intermediate network gear can cache resource representations to improve performance (for open/unprotected resources)
Composite Resource (with multiple dimensions)
If an endpoint represents a relationship among multiple dimensions, the dimensions can be specified in the following ways:
As filter parameters:
/api/enrollments/v1/enrollments/?user={username}&course={course_id}
As comma-delimited resource keys in the URL:
/api/enrollments/v1/enrollments/{username},{course_id}
As a UUIDs to uniquely identify the relationship:
/api/enrollments/v1/enrollments/{enrollment_id}