Carnap API
Carnap has an experimental API, which is currently under development (see the tracking issues below).
Tracking issues
- Issue #226 "Perform instructor actions via an API"
- Issue #231 "Documents API"
- Issue #230 "Copy course"
Instructor setup
To use the API, you need an API key. Your API key should be kept a secret, since it allows anyone who has it to access and modify your courses.
You can create an API key for yourself on the instructor page, beneath the "Manage Uploaded Documents" tab, here:
Usage
Example Scripts
Here are some examples to help you start using the API with different
programming languages. We assume that you're testing, so the URL begins with
http://localhost:3000
. This should instead be https://YOURSERVERRDOMAIN
when you're using this with a non-local server
Python
Create the file apitest.py
:
import requests
= 'REDACTED'
apikey
= 'http://localhost:3000/api/v1'
base
def rq(meth, p, *args, key=apikey, **kwargs):
= {
h 'X-API-KEY': key,
}return requests.request(meth, base + p, *args, **kwargs, headers=h)
To issue the commands displayed below interactively ipython -i apitest.py
, or
python -i apitest.py
.
Bash
Just use curl
as indicated below.
Available API Methods
Document API
All of the methods in the documents API require that :instructorIdent
is
the same as the user who owns the API key (i.e. they do not work on other
users' documents yet).
/instructors/:instructorIdent/documents
GET Retrieves a list of documents owned by the given instructor and their metadata.
Here are some example commands:
Python
'GET', '/instructors/yourname@gmail.com/documents').json() rq(
Bash
curl -H "X-API-KEY:YOURAPIKEYHERE" localhost:3000/api/v1/instructors/yourname@youremail.com/documents
Result
And here's what your result will look like:
'creator': 1,
[{'date': '2021-02-16T09:41:39.445672522Z',
'scope': 'Public',
'id': 1,
'description': None,
'filename': 'api.md'}]
/instructors/:instructorIdent/documents
POST Creates a new document with empty contents.
Should be followed by a PUT
at
/instructors/:instructorIdent/documents/:documentId/data
in order to fill in
the document contents.
Python
'POST', '/instructors/yourname@gmail.com/documents',
rq(={
json"filename": "myfile.md",
"scope": "Private",
"description": "My file",
} ).json()
Bash
curl -H "X-API-KEY:YOURAPIKEY" \
-H "Content-Type: application/json" \
-d '{"filename":"myfile.md","scope":"Private", "description":"My file"}' \
localhost:3000/api/v1/instructors/yourname@gmail.com/documents
Result
The ID of the new document:
3
or an error is returned as an encoded JSON string.
The response will also include a Location
header pointed at the new resource.
scope
indicates the sharing scope of the document, and can be one of
Private
, Public
, LinkOnly
or InstructorsOnly
. Both the scope and
description fields can be omitted, with scope defaulting to Private
.
/instructors/:instructorIdent/documents/:documentId
GET
Like GET /instructors/:instructorIdent/documents
but for a
single document.
Python
'GET', '/instructors/yourname@gmail.com/documents/1').json() rq(
Bash
curl -H "X-API-KEY:YOURAPIKEYHERE" localhost:3000/api/v1/instructors/yourname@youremail.com/documents/1
Result
'creator': 1,
{'date': '2021-02-16T09:41:39.445672522Z',
'scope': 'Public',
'description': None,
'filename': 'api.md'}
/instructors/:instructorIdent/documents/:documentId
PATCH Updates the metadata for a single document.
Python
'PATCH', '/instructors/yourname@gmail.com/documents/1',
rq(={"scope": "Private"}).json() json
Bash
curl -H "X-API-KEY:YOURAPIKEY" -X "PATCH" -d '{"scope":"Public"}' \
localhost:3000/api/v1/instructors/yourname@youremail.com/documents/1
Result
'creator': 1,
{'date': '2021-02-16T09:41:39.445672522Z',
'scope': 'Private',
'description': None,
'filename': 'api.md'}
Currently scope
and description
fields can be updated. Passing in a null
value for description
will cause the document description to be cleared
entirely.
/instructors/:instructorIdent/documents/:documentId/data
GET Gets the content of the given document by ID and returns it.
Python
'GET', 'instructors/yourname@youremail.com/documents/1/data').text rq(
Bash
curl -H "X-API-KEY:YOURAPIKEYHERE" \
localhost:3000/api/v1/instructors/yourname@youremail.com/documents/1/data
Result
The contents of your document, as text
/instructors/:instructorIdent/documents/:documentId/data
PUT Overwrites the content of the given document by ID.
These examples use "aaaaaa" as a thing you might insert as the document contents.
Python
'PUT', '/instructors/yourname@gmail.com/documents/1/data', data='aaaaaa') rq(
Bash
echo aaaaaa | curl -H "X-API-KEY:YOURAPIKEYHERE" -T "-" \
localhost:3000/api/v1/instructors/gleachkr@gmail.com/documents/1940/data
Result
<Response [200]>
Course API
Note: :courseTitle
in the below needs to be properly URL-encoded for curl
,
which means in particular that each space needs to be replaced with %20
. The
python requests module does URL-encoding automatically, so spaces can be used
verbatim. The examples below are for a course with the title "test course".
https://carnap.io/api/v1/instructors/:instructorIdent/courses
GET Retrieves a list of courses.
'GET', '/instructors/gleachkr@gmail.com/courses/test course/students').json() rq(
curl -H "X-API-KEY:YOURAPIKEYHERE" \
'localhost:3000/api/v1/instructors/gleachkr@gmail.com/courses'
Result
[
{
"textBook": 12,
"instructor": 1123,
"enrollmentOpen": false,
"endDate": "2021-05-19T04:59:59Z",
"startDate": "2021-01-26T05:59:59Z",
"textbookProblems": {
"readAssignmentTable": [
[
1,
"2021-05-15T04:59:59Z"
],
...
]
},
"totalPoints": 0,
"title": "PHILO680 - Independent Study",
"timeZone": "America/Chicago",
"description": null
}
]
The instructor
field gives the main instructor's instructorId, which is
distinct from their userId. The textBook
field gives the assignmentId
for
an associated textbook, which is the same data as the id
field for an
assignment, retrieved via GET
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/assignments
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students
GET
Retrieves a list of student data for a given course,
including unique :studentId
identifiers for each student.
'GET', '/instructors/gleachkr@gmail.com/courses/test course/students').json() rq(
curl -H "X-API-KEY:YOURAPIKEYHERE" \
'localhost:3000/api/v1/instructors/gleachkr@gmail.com/courses/test%20course/students'
Result
[
{
"email": "groundworker@gmail.com",
"lastName": "Kant",
"universityId": null,
"userId": 1231,
"firstName": "Immanuel",
"isAdmin": false,
"id": 1313,
"enrolledIn": 141,
"instructorId": null,
"isLti": false
},
{
"email": "thinker@gmail.com",
"lastName": "Descartes",
"universityId": null,
"userId": 1232,
"firstName": "Rene",
"isAdmin": false,
"id": 1314,
"enrolledIn": 141,
"instructorId": null,
"isLti": false
},
]
The :studentId
for a student is the number in the id
field, not the
number in the userId
field.
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId
GET
Retrieves student data for the student with id
:studentId
'GET', '/instructors/gleachkr@gmail.com/courses/test course/students/1231').json() rq(
curl -H "X-API-KEY:YOURAPIKEYHERE" \
'localhost:3000/api/v1/instructors/gleachkr@gmail.com/courses/test%20course/students/1231'
Result
{
"email": "groundworker@gmail.com",
"lastName": "Kant",
"universityId": null,
"userId": 1231,
"firstName": "Immanuel",
"isAdmin": false,
"id": 1313,
"enrolledIn": 141,
"instructorId": null,
"isLti": false
}
The :studentId
for a student is the number in id
field, not the number in
the studentId
field.
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId/submissions
GET
Retrieves a list of all submission data for the student with
id :studentId
'GET', '/instructors/gleachkr@gmail.com/courses/test course/students/1231/submissions').json() rq(
curl -H "X-API-KEY:YOURAPIKEYHERE" \
'localhost:3000/api/v1/instructors/gleachkr@gmail.com/courses/test%20course/students/1231/submissions'
Result
[
{
'problemSubmissionAssignmentId': 3001,
'problemSubmissionCorrect': False,
'problemSubmissionData': {..depends on problem type..},
'problemSubmissionSource': {'tag': 'Assignment', 'contents': 'AssignmentMetadataKey {unAssignmentMetadataKey = SqlBackendKey {unSqlBackendKey = 3001}}'}, 'problemSubmissionCredit': 1,
'problemSubmissionIdent': 'Exercise-24',
'problemSubmissionUserId': 10385,
'problemSubmissionLateCredit': None,
'problemSubmissionType': 'Qualitative',
'problemSubmissionTime': '2021-04-26T19:14:06.563900857Z',
'problemSubmissionExtra': None
}
]
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId/extensions
GET
Retrieves a list of all assignment extensions granted to the
student with id :studentId
RESULT
[
{
"onAssignment": 1238,
"until": "2022-06-02T04:59:59Z",
"forUser": 12313,
"id": 499
}
]
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId/extensions
POST
Grants an extension to the student with id
:studentId
'POST', '/instructors/yourname@gmail.com/courses/test course/students/12313/extensions',
rq(={
json"onAssignment": 2756,
"until": "2022-06-02",
"forUser": 12313,
} ).json()
Bash
curl -H "X-API-KEY:YOURAPIKEY" \
-H "Content-Type: application/json" \
-d '{ "onAssignment": 2756, "until": "2022-06-02", "forUser": 12313 }' \
localhost:3000/api/v1//instructors/yourname@gmail.com/courses/test%20course/students/12313/extensions
Dates can be given in the format "2021-06-30T21:40:00Z", for UTC time, or in the format "2022-01-01 10:10" or simply "2022-01-01", in which case the date will assume the time zone associated with the course.
The template for the POST JSON is:
{
"onAsssignment": ...,
"until": ...,
"forUser": ...,
}
where the user field must the userId of the student, the contents of the
userId
field of the data returned by GET
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId
,
not the contents of the id
field returned by that GET request.
Result
The ID of the new document:
3
or an error is returned as an encoded JSON string.
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId/accomodations
GET
Retrieves a list of accommodations active for the student
with id :studentId
So if the student's time allowed on exams is (3 × STANDARD TIME) + 0 minutes
,
and they have their due-dates extended by one hour, then the result will be:
{
"timeFactor": 3,
"timeExtraMinutes": 0,
"forUser": 1314,
"id": 62,
"dateExtraHours": 1,
"forCourse": 121
}
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId/accomodations
PATCH
Modifies the accommodations active for the student with id
:studentId
For example:
'PATCH', '/instructors/yourname@gmail.com/courses/test course/students/11231/accomodations',
rq(={"timeFactor": "2"}).json() json
Bash
curl -H "X-API-KEY:YOURAPIKEY" -X "PATCH" \
-H "Content-Type: application/json" \
-d '{"timeFactor":"2"}' \
/instructors/yourname@gmail.com/courses/test%20course/students/11231/accomodations
The template for the PATCH JSON (with all fields optional) is:
{
"timeFactor": ...,
"timeExtraMinutes": ...,
"dateExtraHours": ...,
}
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/students/:studentId/assignmentTokens
GET
Retrieves a list of all access-restricted assignments that
have been accessed by the student with id :studentId
, along with access time
for each.
RESULT
[
{
"createdAt": "2021-06-10T19:10:42.757612899Z",
"user": 1,
"id": 17927,
"assignment": 3188
}
]
https://carnap.io/api/v1/instructor/:instructorIdent/courses/:courseTitle/students/:studentId/assignmentTokens/:tokenId
DELETE Deletes the record of a student having accessed an access-restricted assignment, allowing them to e.g. retake an exam for which a certain amount of time was allotted.
RESULT
"deleted token"
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/assignments
GET Retrieves a list of assignments for a given course.
RESULTS
[
{
"gradeRelease": "2021-05-20T19:30:00Z",
"totalProblems": null,
"visibleFrom": "2021-05-19T17:58:00Z",
"availability": null,
"date": "2021-05-18T21:41:35.12705102Z",
"document": 4293,
"pointValue": null,
"course": 258,
"id": 3276,
"duedate": "2021-05-19T19:30:00Z",
"title": "The Hardest Logic Puzzle Ever",
"visibleTill": "2021-06-30T21:40:00Z",
"assigner": null,
"description": null
}
]
Times are reported in UTC
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/assignments
POST Creates a new assignment.
'POST', '/instructors/yourname@gmail.com/courses/test course/assignments',
rq(={
json"document": 2756,
"title": "A new assignment",
"due-date": "2022-01-01 10:10",
} ).json()
Bash
curl -H "X-API-KEY:YOURAPIKEY" \
-H "Content-Type: application/json" \
-d '{"document": 2756, "title": "A new assignment", "due-date": "2022-01-01 10:10"}' \
localhost:3000/api/v1/instructors/yourname@gmail.com/courses/test%20course/assignments
The full template for the POST json is
{
"gradeRelease": ...,
"totalProblems": ...,
"visibleFrom": ...,
"availability": ...,
"document": ...,
"pointValue": ...,
"duedate": ...,
"title": ...,
"visibleTill": ...,
"description": ...
}
The only mandatory fields are document
and title
. Dates can be given in the
format "2021-06-30T21:40:00Z", for UTC time, or in the format "2022-01-01
10:10" or simply "2022-01-01", in which case the date will assume the time zone
associated with the course.
RESULT
The ID of the new assignment:
3
or an error is returned as an encoded JSON string.
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/assignments/:assignmentId
GET
Retrieves the details of the assignment with id
:assignmentId
.
RESULT
{
"gradeRelease": "2021-05-20T19:30:00Z",
"totalProblems": null,
"visibleFrom": "2021-05-19T17:58:00Z",
"availability": null,
"date": "2021-05-18T21:41:35.12705102Z",
"document": 4293,
"pointValue": null,
"course": 258,
"id": 3276,
"duedate": "2021-05-19T19:30:00Z",
"title": "The Hardest Logic Puzzle Ever",
"visibleTill": "2021-06-30T21:40:00Z",
"assigner": null,
"description": null
}
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/assignments/:assignmentId
PATCH
Modifies the details of the assignment with id :assignmentId
.
For example:
'PATCH', '/instructors/yourname@gmail.com/courses/test course/assignments/1313',
rq(={"gradeRelease": "2022-02-02"}).json() json
Bash
curl -H "X-API-KEY:YOURAPIKEY" -X "PATCH" \
-H "Content-Type: application/json" \
-d '{"gradeRelease": "2022-02-02"}' \
/instructors/yourname@gmail.com/courses/test%20course/assignments/1313
The template for the PATCH JSON (with all fields optional) is:
{
"gradeRelease": ...,
"totalProblems": ...,
"visibleFrom": ...,
"availability": ...,
"pointValue": ...,
"duedate": ...,
"title": ...,
"visibleTill": ...,
"description": ...,
}
To delete the contents of any field, except for title
, you can send an
explicit null
value in the patch for that field.
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/assignments/:assignmentId/submissions
GET
Retrieves a list of all submitted problems for the assignment with id :assignmentId
.
RESULT
[
{
'problemSubmissionAssignmentId': 3001,
'problemSubmissionCorrect': False,
'problemSubmissionData': {..depends on problem type..},
'problemSubmissionSource': {'tag': 'Assignment', 'contents': 'AssignmentMetadataKey {unAssignmentMetadataKey = SqlBackendKey {unSqlBackendKey = 3001}}'}, 'problemSubmissionCredit': 1,
'problemSubmissionIdent': 'Exercise-24',
'problemSubmissionUserId': 10385,
'problemSubmissionLateCredit': None,
'problemSubmissionType': 'Qualitative',
'problemSubmissionTime': '2021-04-26T19:14:06.563900857Z',
'problemSubmissionExtra': None
}
]
https://carnap.io/api/v1/instructors/:instructorIdent/courses/:courseTitle/assignments/:assignmentId/submissions/:studentId
GET
Retrieves a list of all submitted problems from the student
with id :studentId
, for the assignment with id :assignmentId
.
RESULT
[
{
'problemSubmissionAssignmentId': 3001,
'problemSubmissionCorrect': False,
'problemSubmissionData': {..depends on problem type..},
'problemSubmissionSource': {'tag': 'Assignment', 'contents': 'AssignmentMetadataKey {unAssignmentMetadataKey = SqlBackendKey {unSqlBackendKey = 3001}}'}, 'problemSubmissionCredit': 1,
'problemSubmissionIdent': 'Exercise-24',
'problemSubmissionUserId': 10385,
'problemSubmissionLateCredit': None,
'problemSubmissionType': 'Qualitative',
'problemSubmissionTime': '2021-04-26T19:14:06.563900857Z',
'problemSubmissionExtra': None
}
]