Skip to main content
Version: 2026-09-21 (current)

Grades

When a student finishes graded work, your tool reports the score and Voshi puts it in the LMS gradebook. One call, no gradebook plumbing:

async finish(correct, total) {
if (!this.api.canSubmitGrade) return // no gradebook column here: say nothing about grades

this.setStatus('Reporting your score to your course…')
try {
await this.api.submitGrade({
score: correct / total, // a fraction from 0 to 1
maxScore: total, // the points the activity is worth
comment: `${correct} of ${total} correct`,
})
this.setStatus('Your score has been reported to your course.')
} catch (err) {
this.setStatus(`Your score could not be reported: ${err.message}`)
}
}

When you can report a grade​

api.canSubmitGrade is true when this launch has a gradebook column: the launched location is an assessment, and the instructor placed it through the picker so the column was created. It is false for every other launch — a content or practice location, or an assessment placed as a plain link.

When it is false, run the activity and say nothing about grades. Calling submitGrade() anyway throws a VoshiGradeUnavailableError before anything is sent.

note

Check api.isStudent as well before scoring. An instructor previewing the activity would otherwise report a score for themself.

submitGrade(options)​

scorenumberrequired

The fraction earned, from 0 to 1. 0.85 is 85%. Not points.

maxScorenumber

The points the activity is worth. The gradebook column is set to this many points before the score is published, so the LMS shows score × maxScore out of maxScore — a 0.7 with maxScore: 10 shows as 7/10. Send the same number every time for a location, and make it the location's points. Without it, the fraction applies to whatever the column is already worth, which the instructor may have changed.

commentstring

Shown to the student beside the grade in the LMS. The one place your tool can explain the number.

activityProgressstringdefault: Completed

Completed or Submitted: has the student finished the activity?

gradingProgressstringdefault: FullyGraded

FullyGraded or Pending: is this score final? Send Submitted and Pending for a provisional score, then call again with the defaults when it is final.

Returns the recorded attempt — status is 'success', and score, max_score, and submitted say what was published. Throws:

  • VoshiGradeUnavailableError — canSubmitGrade was false.
  • VoshiGradeRejectedError — Voshi recorded the attempt, but the LMS refused the score. err.message says why; err.passback.data.error has the LMS's own words.
  • Any other VoshiApiError for a network or server problem.

How to call it​

  • Once per completed attempt, at the moment the learner finishes — not on every answer, and never on load.
  • Awaited, inside try/catch, with the outcome shown to the learner. Never say a score was reported before the call has resolved.
  • Again, if they try again. Every call is a new attempt. Nothing is overwritten and there is no "update": a second call publishes a second score, which the LMS takes as the newer one. Regrading, correcting, and retrying a failure are all just calling again.

Where it goes​

A score lands in the gradebook column for this student, at this location, in this course — the launch already decided all three. One location is one column, which is why two graded activities need two locations.

The dashboard's Grades tab lists every attempt your tool has made, with the LMS's answer; in the Sandbox, the course's own Grades tab shows the same thing as a gradebook.