Guide to Open edX REST APIs
For all the examples provided, http://local.openedx.io:8000 is used as the default Tutor devstack LMS url. Replace with your LMS url as applicable.
See also related upstream Open edX docs:
Authentication
Most Open edX REST APIs require authentication, which determines who you are and what data you are allowed to see.
For these, you will need to authenticate via the browser or using OAuth2 tokens.
Browser
Login as normal to the LMS with your browser.
Once this is done, you can make any GET requests by typing the URL into the browser bar.
Tips for performing POST requests are listed with the REST API details below.
OAuth2
OAuth2 is used for authentication outside of browser requests.
Using this is a two or three stage process:
- Create an OAuth2 Application from Django Admin.
- Use the OAuth2 Application credentials to request an access token.
- (Only required sometimes) retrieve a CSRF (cross site request forgery) token.
This access token is used later for authenticated API requests.
1. Create the OAuth2 Application
OAuth2 Applications can be created by a logged-in superuser on your Django Admin.
Follow How To Use the REST API for creating the OAuth2 application.
2. Retrieve an access token
To access most Open edX REST APIs, you'll need to use your OAuth2 client ID and client secret created above to retrieve an access token, which will expire after a period of time.
The common types of access tokens are Bearer tokens and JWTs (JSON Web Tokens).
The later examples will use Bearer tokens.
Some API endpoints may require a Bearer token while others may require a JWT; it's not consistent and the APIs and their docs are still being improved.
Bearer token
To retrieve a Bearer token, perform a POST request to this URL using your client ID and client secret, e.g.:
| curl -X POST http://local.openedx.io:8000/oauth2/access_token/ \
--data "client_id=$CLIENT_ID" \
--data "client_secret=$CLIENT_SECRET" \
--data "grant_type=client_credentials" \
--data "token_type=bearer"
|
The returned data will be in JSON format, and will look like this:
| {
"access_token": "edKsqf9FbiZjKTtzW0ILVHCyDGw9Vt",
"expires_in": 36000,
"token_type": "Bearer",
"scope": "read write email profile"
}
|
Use the returned access token in your requests to REST APIs which require authentication, and these APIs will respond as if you were the user attached to the OAuth2 client ID. E.g.,
| curl -X GET http://local.openedx.io:8000/api/enrollment/v1/enrollment \
--header "Authorization: Bearer $ACCESS_TOKEN"
|
JWT
Note: How To Use the REST API details an alternative method of retrieving a JWT in Python.
Authentication Related Code Samples also shows more examples of working with a JWT in Python.
To retrieve a JWT, perform a POST request to this URL using your client ID and client secret:
| curl -X POST http://local.openedx.io:8000/oauth2/access_token/ \
--data "client_id=$CLIENT_ID" \
--data "client_secret=$CLIENT_SECRET" \
--data "grant_type=client_credentials" \
--data "token_type=jwt"
|
The returned JSON data will look like this:
| {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJvcGVuZWR4IiwiZXhwIjoxNzY1OTUzMjAxLCJncmFudF90eXBlIjoiY2xpZW50LWNyZWRlbnRpYWxzIiwiaWF0IjoxNzY1OTQ5NjAxLCJpc3MiOiJodHRwOi8vbG9jYWwub3BlbmVkeC5pby9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzYW11ZWwiLCJzY29wZXMiOlsicmVhZCIsIndyaXRlIiwiZW1haWwiLCJwcm9maWxlIl0sInZlcnNpb24iOiIxLjIuMCIsInN1YiI6ImMyMDhlOGM5NzIxZTgyZDJmYTk2OGZmZmJmNDlkZThhIiwiZmlsdGVycyI6W10sImlzX3Jlc3RyaWN0ZWQiOmZhbHNlLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZW1haWwiOiJzYW11ZWxAb3BlbmNyYWZ0LmNvbSIsIm5hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJhZG1pbmlzdHJhdG9yIjp0cnVlLCJzdXBlcnVzZXIiOnRydWV9.NyKJNYvauBAgKlySN4wJQngze1yiZfvuLm98OtBlgvk",
"expires_in": 3600,
"token_type": "JWT",
"scope": "read write email profile"
}
|
To use the JWT, provide it in the Authorization header like this:
| curl -X GET http://local.openedx.io:8000/api/enrollment/v1/enrollment \
--header "Authorization: JWT $ACCESS_TOKEN"
|
CSRF Tokens
Some API requests require a CSRF (cross site request forgery) token.
If you need a CSRF token, retrieve one from the API like this:
| curl -X GET 'http://local.openedx.io:8000/csrf/api/v1/token'
|
The returned JSON data will look like this:
| {
"csrfToken": "0I42pAUpbJezqNoc5HzOCvRVBh7EOYA00by8VFeQ2pjVDkLwHqw2e2TP4Oh9A9lA"
}
|
You can use this token in the x-CSRFToken header for any API requests that need it:
| x-CSRFToken: 0I42pAUpbJezqNoc5HzOCvRVBh7EOYA00by8VFeQ2pjVDkLwHqw2e2TP4Oh9A9lA
|
Example APIs
Enrollments
Open edX provides various REST APIs for fetching enrollment data.
Enrollment Detail for a Course
This REST API does not require authentication, because it does not fetch any user-specific data.
Reference docs: GET /enrollment/v1/course/{course_id}
Example request:
| curl -X GET http://local.openedx.io:8000/api/enrollment/v1/course/course-v1:OpenedX+DemoX+DemoCourse
|
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | {
"course_id": "course-v1:OpenedX+DemoX+DemoCourse",
"course_name": "Open edX Demo Course",
"enrollment_start": null,
"enrollment_end": null,
"course_start": "2020-01-01T00:00:00Z",
"course_end": null,
"invite_only": false,
"course_modes": [
{
"slug": "audit",
"name": "Audit",
"min_price": 0,
"suggested_prices": "",
"currency": "usd",
"expiration_datetime": null,
"description": null,
"sku": null,
"bulk_sku": null
}
],
"pacing_type": "Self Paced"
}
|
Enrollments for a User
Returns a list of enrollments for the currently-logged in user, or for the requested user (if authenticated as staff).
Reference docs: GET /enrollment/v1/enrollment
Example requests:
| curl -X GET http://local.openedx.io:8000/api/enrollment/v1/enrollment \
--header "Authorization: Bearer $ACCESS_TOKEN"
curl -X GET http://local.openedx.io:8000/api/enrollment/v1/enrollment?user=honor \
--header "Authorization: Bearer $ACCESS_TOKEN"
|
JavaScript example.
Note: If calling this from a host with a different domain from edx-platform, you must set up Cross-Domain Origin Resource Sharing on the edxapp host.
| (await fetch(
"http://local.openedx.io:8000/api/enrollment/v1/enrollment",
{
credentials: "include",
}
)).json();
|
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | [
{
"created": "2025-12-16T03:36:12.063425Z",
"mode": "audit",
"is_active": true,
"course_details": {
"course_id": "course-v1:OpenedX+DemoX+DemoCourse",
"course_name": "Open edX Demo Course",
"enrollment_start": null,
"enrollment_end": null,
"course_start": "2020-01-01T00:00:00Z",
"course_end": null,
"invite_only": false,
"course_modes": [
{
"slug": "audit",
"name": "Audit",
"min_price": 0,
"suggested_prices": "",
"currency": "usd",
"expiration_datetime": null,
"description": null,
"sku": null,
"bulk_sku": null
}
],
"pacing_type": "Self Paced"
},
"user": "samuel"
}
]
|
Enroll a User in a Course
Enroll the currently-logged in user, or for the requested user (if authenticated as staff), into the given course.
Reference docs: POST /enrollment/v1/enrollment
Example requests:
| curl -X POST http://local.openedx.io:8000/api/enrollment/v1/enrollment \
--data '{"course_details":{"course_id":"course-v1:OpenedX+DemoX+DemoCourse"}}' \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json"
|
JavaScript example:
Note: POST requests are not accepted to the Enrollments API from different domains.
Note 2: this example doesn't currently work, because I think it wants the CSRF token that's set in the csrftoken cookie. I haven't been able to test this yet, as I can't get the csrftoken cookie value via JavaScript on a devstack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | let csrfToken = (await (await fetch("http://local.openedx.io:8000/csrf/api/v1/token")).json()).csrfToken;
await (await fetch(
"http://local.openedx.io:8000/api/enrollment/v1/enrollment",
{
method: "POST",
credentials: "include",
body: JSON.stringify({
"course_details": {
"course_id": "course-v1:OpenedX+DemoX+DemoCourse"
}
}),
headers: {
"x-CSRFToken": csrfToken,
"Accept": "application/json",
"Content-Type": "application/json",
},
}
)).json();
|
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | {
"created": "2025-12-16T03:36:12.063425Z",
"mode": "audit",
"is_active": true,
"course_details": {
"course_id": "course-v1:OpenedX+DemoX+DemoCourse",
"course_name": "Open edX Demo Course",
"enrollment_start": null,
"enrollment_end": null,
"course_start": "2020-01-01T00:00:00Z",
"course_end": null,
"invite_only": false,
"course_modes": [
{
"slug": "audit",
"name": "Audit",
"min_price": 0,
"suggested_prices": "",
"currency": "usd",
"expiration_datetime": null,
"description": null,
"sku": null,
"bulk_sku": null
}
],
"pacing_type": "Self Paced"
},
"user": "samuel"
}
|
Course Grades
User's course grades
Returns the user's current grade for the requested course, or the requested user, if authenticated as staff.
Reference docs: GET /grades/v1/courses/{course_id}/
Example requests:
| curl -X GET http://local.openedx.io:8000/api/grades/v1/courses/course-v1:OpenedX+DemoX+DemoCourse/ \
--header "Authorization: Bearer $ACCESS_TOKEN"
|
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | {
"next": null,
"previous": null,
"results": [
{
"username": "samuel",
"email": "",
"course_id": "course-v1:OpenedX+DemoX+DemoCourse",
"passed": false,
"percent": 0.0,
"letter_grade": null
}
]
}
|
Grading Policy for a course
Returns the details about the requested course's grading policy.
Reference docs: GET /grades/v1/policy/courses/{course_id}/
Example request:
| curl -X GET http://local.openedx.io:8000/api/grades/v1/policy/courses/course-v1:OpenedX+DemoX+DemoCourse/ \
--header "Authorization: Bearer $ACCESS_TOKEN"
|
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | [
{
"assignment_type": "Basic Assessment Tools",
"count": 1,
"dropped": 0,
"weight": 0.3
},
{
"assignment_type": "Intermediate Assessment Tools",
"count": 1,
"dropped": 0,
"weight": 0.35
},
{
"assignment_type": "Advanced Assessment Tools",
"count": 1,
"dropped": 0,
"weight": 0.35
}
]
|
Course List
To retrieve a list of publicly available courses, you don't need to authenticate.
But to see courses which are not visible to the public, you need to send the access token.
The response is paginated; see the pagination next URL to navigate to the next page.
Reference docs: GET /courses/v1/courses/
Example request:
| curl -X GET http://local.openedx.io:8000/api/courses/v1/courses/ \
--header "Authorization: Bearer $ACCESS_TOKEN"
|
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 | {
"results": [
{
"blocks_url": "http://local.openedx.io:8000/api/courses/v2/blocks/?course_id=course-v1%3AOpenedX%2BDemoX%2BDemoCourse",
"effort": null,
"end": null,
"enrollment_start": null,
"enrollment_end": null,
"id": "course-v1:OpenedX+DemoX+DemoCourse",
"media": {
"banner_image": {
"uri": "/asset-v1:OpenedX+DemoX+DemoCourse+type@asset+block@images_course_image.jpg",
"uri_absolute": "http://local.openedx.io:8000/asset-v1:OpenedX+DemoX+DemoCourse+type@asset+block@images_course_image.jpg"
},
"course_image": {
"uri": "/asset-v1:OpenedX+DemoX+DemoCourse+type@asset+block@thumbnail_demox.jpeg"
},
"course_video": {
"uri": null
},
"image": {
"raw": "http://local.openedx.io:8000/asset-v1:OpenedX+DemoX+DemoCourse+type@asset+block@thumbnail_demox.jpeg",
"small": "http://local.openedx.io:8000/asset-v1:OpenedX+DemoX+DemoCourse+type@asset+block@thumbnail_demox.jpeg",
"large": "http://local.openedx.io:8000/asset-v1:OpenedX+DemoX+DemoCourse+type@asset+block@thumbnail_demox.jpeg"
}
},
"name": "Open edX Demo Course",
"number": "DemoX",
"org": "OpenedX",
"short_description": "Explore Open edX® capabilities in this demo course, covering platform tools, content creation, assessments, social learning, and community stories. Ideal for course developers, online learning newcomers, and community members. ",
"start": "2020-01-01T00:00:00Z",
"start_display": "Jan. 1, 2020",
"start_type": "timestamp",
"pacing": "self",
"mobile_available": false,
"hidden": false,
"invitation_only": false,
"course_id": "course-v1:OpenedX+DemoX+DemoCourse"
}
],
"pagination": {
"next": null,
"previous": null,
"count": 1,
"num_pages": 1
}
}
|
Course Structure (Blocks)
Because the structure of a course is dependent on the user viewing it (due to hidden content, cohort-specific units, etc.), the Course Blocks API requires authentication.
Reference docs: GET /courses/v1/blocks/
This request takes several GET parameters, so to ensure that this example works on the command line, we've put the URL in double quotes.
Parameters used:
course_id: identifier for the course.
Unlike the above REST API examples, this one must be URL-encoded.
requested_fields: extra fields required. Here, we've used children, so that the course's block hierarchy can be interpreted from the results.
all_blocks: Pass either all_blocks=true, to fetch the blocks visible to the current user, or use the username parameter below.
username: Must pass all_blocks or a username.
depth: specify a numeric depth (default is 0), or all for all blocks.
Example request:
| curl -X GET \
"http://local.openedx.io:8000/api/courses/v1/blocks/?course_id=course-v1%3AOpenedX%2BDemoX%2BDemoCourse&requested_fields=children&all_blocks=true&depth=1" \
--header "Authorization: Bearer $ACCESS_TOKEN"
|
Example response:
Note: some values have been truncated (...) to make this easier to read.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 | {
"root": "block-v1:OpenedX+DemoX+DemoCourse+type@course+block@course",
"blocks": {
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@30b3fbb840024953b2d4b2e700a53002": {
"id": "block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@30b3fbb840024953b2d4b2e700a53002",
"block_id": "30b3fbb840024953b2d4b2e700a53002",
"lms_web_url": "http://local.openedx.io:8000/courses/course-v1:OpenedX+DemoX+DemoCourse/jump_to/...",
"legacy_web_url": "http://local.openedx.io:8000/courses/course-v1:OpenedX+DemoX+DemoCourse/jump_to/...",
"student_view_url": "http://local.openedx.io:8000/xblock/block-v1:OpenedX+DemoX+DemoCourse+type@...",
"type": "chapter",
"display_name": "Module 1: Dive into the Open edX® platform!"
},
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@478db06a3afb417d87e26c0eafe5e962": {
"id": "block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@478db06a3afb417d87e26c0eafe5e962",
"block_id": "478db06a3afb417d87e26c0eafe5e962",
"lms_web_url": "http://local.openedx.io:8000/courses/course-v1:OpenedX+DemoX+DemoCourse/jump_to/...",
"legacy_web_url": "http://local.openedx.io:8000/courses/course-v1:OpenedX+DemoX+DemoCourse/jump_to/...",
"student_view_url": "http://local.openedx.io:8000/xblock/block-v1:OpenedX+DemoX+DemoCourse+type@...",
"type": "chapter",
"display_name": "Conclusion"
},
"block-v1:OpenedX+DemoX+DemoCourse+type@course+block@course": {
"id": "block-v1:OpenedX+DemoX+DemoCourse+type@course+block@course",
"block_id": "course",
"lms_web_url": "http://local.openedx.io:8000/courses/course-v1:OpenedX+DemoX+DemoCourse/jump_to/...",
"legacy_web_url": "http://local.openedx.io:8000/courses/course-v1:OpenedX+DemoX+DemoCourse/jump_to/...",
"student_view_url": "http://local.openedx.io:8000/xblock/block-v1:OpenedX+DemoX+DemoCourse+type@...",
"type": "course",
"display_name": "Open edX Demo Course",
"children": [
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@30b3fbb840024953b2d4b2e700a53002",
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@35283385dd4947619c558f8bb888a031",
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@d6780558bc3042c7ab6dd441a06d3478",
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@7281f869d5f44704b56d6fe6ee96d886",
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@b17a430abc234382a04e7835b013912d",
"block-v1:OpenedX+DemoX+DemoCourse+type@chapter+block@478db06a3afb417d87e26c0eafe5e962"
]
}
}
}
|
API clients
The above examples use cURL, a command-line tool for making web requests.
Your toolset may differ.
There are many graphical and command line apps that can make testing of APIs easier.
Some examples:
GET Parameters
Parameters used in a GET request should be passed as name=value pairs, and chained together using an ampersand (&).
They are added to the url after a question mark (?).
For example:
| http://local.openedx.io:8000/api/courses/v1/blocks/?course_id=course-v1%3Aed%2BDemoX%2BDemo_Course&requested_fields=children&all_blocks=true&depth=1
|
URL-encoding
Course IDs and other data parameters passed to an API call must be "url-encoded", i.e, all special characters converted to their encoded equivalents.
For example, the edX Demo course ID is: course-v1:edX+DemoX+Demo_Course.
The URL-encoded edX Demo course ID is: course-v1%3AedX%2BDemoX%2BDemo_Course.
Tools like cURL (used in the examples above) will encode your --data parameters for you when you make POST requests, e.g. when requesting an access token.
But for GET requests, you may need to encode your own parameters (depending on the tool and how you're using it).
You can do this in various ways:
Javascript
| encodeURIComponent("course-v1:edX+DemoX+Demo_Course")
// "course-v1%3AedX%2BDemoX%2BDemo_Course"
|
Python 3
| from urllib.parse import quote
quote("course-v1:edX+DemoX+Demo_Course")
# 'course-v1%3AedX%2BDemoX%2BDemo_Course'
|
Online service
WARNING: use only for data that does not need to be kept secure.