Skip to main content

Scores and Results

Once a tool has a line item, it can write grades to it and read them back. Two request types: SCORE_POST to record a grade, and RESULTS_GET to read them.

caution

Requires Assignment and Grades enabled on your account, plus GRADES_WRITE for posting and GRADES_READ for reading in the tool's permissions.

Recording a Score

{
"type": "SCORE_POST",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "348080fn9du9b9ufvb92rfb9l",
"lineItemId": "412",
"score": {
"userId": "41",
"activityProgress": "Completed",
"gradingProgress": "FullyGraded",
"comment": "Great work!",
"scoreGiven": 99,
"scoreMaximum": 100
}
}
}
async function handleScore(decoded, res) {
const { context, lineItemId, score } = decoded.parameters

const lineItem = await db.lineItems.find(lineItemId)
if (!lineItem || lineItem.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}

// A score without scoreGiven is a progress update, not a grade.
if (score.gradingProgress === 'FullyGraded' && score.scoreGiven !== undefined) {
await db.grades.upsert({
lineItemId,
userId: score.userId,
score: score.scoreGiven,
maximum: score.scoreMaximum,
comment: score.comment
})
}

await db.activityProgress.upsert({
lineItemId,
userId: score.userId,
activityProgress: score.activityProgress,
gradingProgress: score.gradingProgress
})

return res.status(200).json({})
}

Return 200 with an empty JSON object.

info

Your status code is passed straight back to the tool. Reject a grade with a 4xx and the tool sees the rejection — many will retry later, which is usually what you want for a transient failure.

Scores Are Not Always Grades

scoreGiven is optional. A tool may post a score purely to report progress — "the learner opened this", "the learner submitted, grading is pending" — with no number attached. Recording those without touching the gradebook is what the two progress fields are for.

activityProgress — how far the learner has got:

ValueMeaning
InitializedThe activity exists but has not been started
StartedThe learner has begun
InProgressActively working
SubmittedHanded in, awaiting grading
CompletedFinished

gradingProgress — how far the tool has got with grading:

ValueMeaning
NotReadyNothing to grade yet
PendingQueued for automatic grading
PendingManualWaiting for a human
FullyGradedDone — the score is final
FailedGrading failed
tip

Only treat a score as final when gradingProgress is FullyGraded. Writing Pending scores into the gradebook shows learners marks that are about to change.

caution

When scoreGiven is present, scoreMaximum is present too — and it may differ from the line item's scoreMaximum. Store the pair, or scale as you save. Treating scoreGiven as a percentage is a common and expensive mistake.

Returning Results

{
"type": "RESULTS_GET",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "348080fn9du9b9ufvb92rfb9l",
"lineItemId": "412",
"filters": { "userId": "14" }
}
}
async function handleResults(decoded, res) {
const { context, lineItemId, filters = {} } = decoded.parameters

const lineItem = await db.lineItems.find(lineItemId)
if (!lineItem || lineItem.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}

let grades = await db.grades.forLineItem(lineItemId)
if (filters.userId) {
grades = grades.filter(g => g.userId === filters.userId)
}

return res.status(200).json(grades.map(g => ({
id: `${lineItemId}-${g.userId}`,
userId: String(g.userId),
resultScore: g.score,
resultMaximum: g.maximum,
...(g.comment && { comment: g.comment })
})))
}

Always return an array, even for a single-user filter. id just has to be unique and stable — combining the line item and user IDs is fine.

FieldRequiredMeaning
idyesIdentifier for the result
userIdyesMust match the user value used on launches
resultScorenoThe grade
resultMaximumnoWhat it was out of
commentnoFeedback text

Grades a Teacher Changed

If a teacher overrides a tool-posted grade in your gradebook, return the override — RESULTS_GET should reflect what your gradebook actually holds, not what the tool last sent. Your LMS is the system of record.

Next Steps

All trademarks, logos, and service marks displayed on this website are the property of their respective owners. LTIAAS is a trademark of GatherAct, LLC, doing business as LTIAAS. Learning Tools Interoperability (LTI)® and LTI® are trademarks of 1EdTech Consortium, Inc. LTIAAS is not affiliated with, endorsed or sponsored by 1EdTech Consortium, Inc. or by any other owners of third-party trademarks used on this website. LTIAAS is not responsible for the content, quality, or accuracy of any websites linked to or from this website that are not owned by LTIAAS. If you have any questions or concerns about the use of any trademarks or content on this website, please contact us.