This document captures the conventions to be used for all edX REST APIs.
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} |
Resource name
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.
Users should be referenced by username.
Example 1: Choose "/profiles/john_harvard" over "/profile"
Example 2: Choose "/courses?username=john_harvard" over "/courses"
Remember, it's entirely possible that "/profiles/john_harvard" or "/courses?username=john_harvard" could be requested by the "administrator" user
/api/enrollments/v1/enrollments/?user={username}&course={course_id}
/api/enrollments/v1/enrollments/{username},{course_id}
/api/enrollments/v1/enrollments/{enrollment_id}
"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.
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)
/dogs?fields=name,color,location /dogs?fields=title,media:group(media:thumbnail) |
HTTP status codes - Use the appropriate value amongst the following (or document in the code why an exception is needed)
Must: Use 404 instead of 403 when the actual existence would be leaking information that we don't want - Dave O.
Error Description
Must: Be verbose and use plain language descriptions.
Nice: Add as many hints as your API team can think of about what's causing an error.
Nice: Add a link in your description to more information, like Twilio does.
Error format:
developer_message (no i18n)
user_message(optional, but if provided, i18n as the expectation is that this may surface in a UI)
field_errors (if applicable)
error_code (for example: see /wiki/spaces/MS/pages/31687272) Include description in your REST API Docs.
{ "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.", "field_errors": { "foo": { "developer_message": "", "user_message": "" } } } |
Location.
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.
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.
Use hyperlinks (with absolute URLs) to represent (primary/foreign) keys.
Clients can request different formats by specifying the "Accept" header
By convention, our REST APIs support the following two authentication schemes:
APIs can support expanding related objects, e.g. the team 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):
"username": "andya" |
"user": { "username": "andya", "url": "https://openedx.example.com/api/user/v1/accounts/andya" } |
"user": { "username": "andya", "country": "UK", "profile_image": { "has_image": True, "image_url_full": "http://my-image-storage.net/media/profile_images/123456789_500.jpg", "image_url_large": "http://my-image-storage.net/media/profile_images/123456789_120.jpg", "image_url_medium": "http://my-image-storage.net/media/profile_images/123456789_50.jpg", "image_url_small": "http://my-image-storage.net/media/profile_images/123456789_30.jpg", }, ... } |
By default:
expand=user,team
This section describes the format for DRF APIs only. See the edX Python guidelines for conventions for ordinary Python docstrings: https://github.com/edx/edx-platform/wiki/Python-Guidelines |
edX API docstrings serve two purposes:
These two tools put constraints on the format of the docstring, so we developed the following compromise that should work for both:
Here is a representative docstring (from https://github.com/edx/edx-platform/blob/master/common/djangoapps/enrollment/views.py#L67):
class EnrollmentView(APIView, ApiKeyPermissionMixIn): """ **Use Cases** Get the user's enrollment status for a course. **Example Requests**: GET /api/enrollment/v1/enrollment/{user_id},{course_id} **Response Values** * created: The date the user account was created. * mode: The enrollment mode of the user in this course. * is_active: Whether the enrollment is currently active. * course_details: A collection that includes: * course_id: The unique identifier for the course. * enrollment_start: The date and time that users can begin enrolling in the course. If null, enrollment opens immediately when the course is created. * enrollment_end: The date and time after which users cannot enroll for the course. If null, the enrollment period never ends. * course_start: The date and time at which the course opens. If null, the course opens immediately when created. * course_end: The date and time at which the course closes. If null, the course never ends. * course_modes: An array of data about the enrollment modes supported for the course. Each enrollment mode collection includes: * slug: The short name for the enrollment mode. * name: The full name of the enrollment mode. * min_price: The minimum price for which a user can enroll in this mode. * suggested_prices: A list of suggested prices for this enrollment mode. * currency: The currency of the listed prices. * expiration_datetime: The date and time after which users cannot enroll in the course in this mode. * description: A description of this mode. * invite_only: Whether students must be invited to enroll in the course; true or false. * user: The ID of the user. """ ... |
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:
Here are some representative URLs
/api/course/v1/courses/ /api/enrollment/v1/enrollments/ /api/discussion/v1/threads/{thread_id}/ |