cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
matt_price
Community Participant

Access Quiz-submitted files via the API

I have alittle API client that lets me download submitted files and mark them from my text editor before uploading with a grade.

I'd like to be able to do the same thing with files that have been handed in to quizzes. However, I can't seem to find the API endpoint that returns the attachments list for a quiz submission. I can get the submission list at /courses/COURSEID/quizzes/QUIZIP/submissions, but beyond that I can't navigate.  Any hints? Thank you!

 

0 Kudos
4 Replies
James
Community Champion

@matt_price 

I'm in-between classes and don't have time to look into this fully. Are you getting a URL for the download? If so, does it have /api/v1 in it? I remember a place many years ago where people were having trouble because they were sending their API authorization token to an endpoint that was not an API call.

The call that Canvas makes to zip up all of the files and download them is not an API call. I don't remember seeing a similar call for the API.

Time for class to start.

matt_price
Community Participant

Thanks @James .

I'm comparing results from these two endpoints:

/api/v1/courses/:course/assignments/:assignmentid/submissions

and

/api/v1/courses/:course/quizzes/:quizid/submissions/

The former returns an array of  objects. Each object has an "attachments" property, which among other things includes a download URL (not an API link).

The latter returns an object with two properties, "quiz_submissions" and "submissions". Each of these is an array of objects, but the objects do not contain an "attachments" property and I don't see an obvious way to cobble together an attachment ID/url from the information I do see.

The "submissions" array looks a bit more promising, and as this structure:

{
      "id": xxxxx,
      "excused": null,
      "assignment_id": 727746,
      "body": "user: xxxx, quiz: 218477, score: 0.0, time: 2021-10-23 23:30:54 +0000",
      "url": null,
      "grade": "0",
      "score": 0.0,
      "submitted_at": "2021-10-23T23:30:54Z",
      "user_id": xxxx,
      "submission_type": "online_quiz",
      "workflow_state": "pending_review",
      "grade_matches_current_submission": true,
      "graded_at": "2021-10-23T23:30:54Z",
      "grader_id": -218477,
      "attempt": 1,
      "cached_due_date": null,
      "late_policy_status": null,
      "points_deducted": null,
      "grading_period_id": null,
      "extra_attempts": null,
      "posted_at": null,
      "late": false,
      "missing": false,
      "seconds_late": 0,
      "entered_grade": "0",
      "entered_score": 0.0,
      "preview_url": "https://q.utoronto.ca/courses/xxxx/assignments/xxxx/submissions/xxxx?preview=1&version=1"
    }

It's a lot of information, but I still can't find the attachments.

Can you see something I'm missing?

James
Community Champion

@matt_price 

The file submission lives at the quiz question level, but you are fetching information about the entire quiz with either of those endpoints. That means that it doesn't have the level of detail needed to get you the answer you want.

The quiz questions API allows you to fetch the information about the quiz question, but does not provide information about what the student answered, just about the question in the first place.

The quiz submissions includes a result_url, but it is a link inside Canvas (that is, you get HTML instead of JSON). It's not an HTML page that makes a XHR request to get the JSON, the stuff comes as part of the page. You could parse the page and look for the link. Let's avoid that if we can.

Here's how I know to get the information.

The Submission API has List assignment submissions and List submissions for multiple assignments endpoints that supports include[]=submission_history.

I need to use the assignment ID, not the quiz ID to do this.

Here's an example. You will obviously need to make the numbers match your data and include your Canvas instance hostname at the beginning of any calls.

Let's say that I have course_id=896751 that has a quiz with quiz_id=3249889 that corresponds to assignment_id=9284082.

GET /api/v1/courses/896851/assignments/9284082/submissions?include[]=submission_history

This returns an array with one submission per student. Inside each entry is a submission_history object that contains a submission_data array. The submission_data array contains one entry for each question with information about the student's response.

You will need to find the correct question based off of the question_id. This can be done for questions, provided they are not linked to a question bank, using the List questions in a quiz or submission endpoint of the Quiz Questions API.

GET /api/v1/courses/896851/quizzes/3249889/questions

If you're looking up the quiz questions, the file upload questions will have question_type="file_upload_question"

Alternatively, you may not need to look up the question since the submission_history.submission_data entries have attachment_ids when it is a file upload. I'm not sure if other types allow attachments as well.

As mentioned, for file upload question types, there is an attachment_ids array that contains the numeric ID of the attachment.

In my test data, I got 175323619 as an attachment ID.

To get information about the file, I need to use the Get file endpoint of the Files API.

GET /api/v1/files/175323619

The JSON returned there has an URL that is a non-API call and contains a verifier code to allow access outside of Canvas.

Retrieving that URL will get you the file. You should not send the API Authorization Token since it is not an API call. The verifier contains the code that Canvas needs for authorization.

It seems that a recent change started allowing quiz responses by some other method, but I cannot think of what it was (or if I'm thinking of something else). The API change log doesn't mention anything similar to what I'm thinking of. There is very little development going on in the way of classic quizzes as Canvas moves to new quizzes.

Another way to get the information is to use Puppeteer or Selenium (or other headless browser) to automate the process of going to the quiz moderation page and clicking on Download All Files. You really don't even have to get that far, once you're authenticated, you can send the link directly.

GET /courses/896851/quizzes/3249889/submissions?zip=1

This process has to generate and zip the files for you, so you will need to wait for the download link to become available. Trying to turn that link into an API call does not work.

matt_price
Community Participant

wow, that's some amazing detective work.  I think you've solved it. The key steps I was missing were:

- request the Quiz object first, to acquire its "assignment_id" value

- request submissions from the assignments/ASSIGNMENT_ID/submissions endpoint (using the newly-found id), and remember to "include{}=submission_history"

- iterate through the submissions, in submission_data,  iterating through the attachments in each submission (or maybe just take the first/most recent submission). 

Once I have the file ids, acquiring the files themselves is familiar ground.

It might take a little while before I actually implement this (will probably do my grading this week in an old-fashioned way).  But it might not have figured this out at all without you. Many thanks!