Skip to main content
Version: 2026-08-19 (archived)

Grade passback

When a student finishes graded work in your app, push their score to Voshi and Voshi syncs it to the LMS gradebook. One POST, no gradebook plumbing on your side.

When you can send a grade​

A launch can accept a grade only when it arrived with "grade_passback": true. That requires both:

  1. The launched location is of type assessment, and
  2. The LMS created a gradebook line item for the placement (this happens automatically when an instructor places an assessment location through the content picker).

If either is missing, the grade API rejects the submission with a 422. An assessment location without a line item — for example, a placement that was never deep-linked — launches fine but reports grade_passback: false, so check the claim and degrade gracefully.

Scores​

score is a fraction from 0.0 to 1.0 — a percentage, not points. The LMS scales it to the points-possible on the placement's gradebook line item: on a 100-point line item, 0.85 shows as 85 points. Points-possible belongs to the instructor in the LMS — your app only sends the fraction.

One grade per launch​

There is exactly one grade per launch_id. Sending again for the same launch_id updates that grade and re-syncs — it doesn't create a duplicate, and this is safe even under concurrent submissions. This makes the API naturally idempotent: to regrade, retry, or correct a score, just POST the same launch_id again with the new values.

Note the granularity: a launch is one user's one entry into your app. If the student launches the assessment again and finishes again, that second launch has its own launch_id and gets its own grade — which the LMS applies to the same gradebook cell.

Grade any time — not just during the launch​

A grade POST is an ordinary server-to-server API call authenticated with your API key. It is not tied to the student's launch session, and it doesn't matter that the launch JWT itself expires after two hours — all you need is the launch_id you stored when the student launched.

That means grading can happen whenever it suits your app, not just in the moment the student submits:

  • Immediately, when the student finishes the activity.
  • Later, asynchronously — from a background job after manual or delayed grading.
  • From an instructor- or admin-facing page in your app — for example, an instructor reviewing a student's submission and posting (or correcting) that student's score. Send the same POST with the student's stored launch_id; because one grade per launch makes the API idempotent, reposting simply updates the grade and re-syncs it.

Store the launch_id alongside the student's work at launch time so it's available whenever you're ready to grade.

Sync status and retries​

The sync to the LMS happens inline during your POST. The response always comes back 200 with the result reported in-band:

sync_statusMeaning
syncedThe score reached the gradebook. synced_at is set.
failedThe LMS rejected or errored. sync_error says why.
pendingNot yet synced (transient — you'll normally see synced or failed).

To retry a failed sync, POST the same launch_id again. Each resubmission carries a fresh timestamp, so the LMS accepts the update.

You can also check a grade's current state any time with GET /grades/{grade_id}, or retry it from the dashboard's grades view.

Progress fields​

Two optional fields describe the submission state that the LMS shows alongside the score. The defaults fit the common case — grade when the student is done:

  • activity_progress: Completed (default) or Submitted — has the student finished the activity?
  • grading_progress: FullyGraded (default) or Pending — is this score final?

Send Submitted + Pending if you post provisional scores before final grading, then resubmit with the defaults when grading is complete.

Authentication​

Grade calls authenticate with your API key as a Bearer token — the key you received at registration:

Authorization: Bearer ltiaas_<app-id>_<secret>

Keep it server-side. The API key identifies your app; you can only grade launches that belong to it (anything else returns 404, so other apps' launches can't be probed).

Ready for the details? See the Grades API reference.