Line Items
A line item is a column in your gradebook — one gradeable thing, with a maximum score. Tools create line items so they have somewhere to put the grades they later post.
Five request types cover the full lifecycle: LINEITEMS_GET, LINEITEMS_POST, LINEITEM_GET, LINEITEM_PUT and LINEITEM_DELETE.
Requires Assignment and Grades enabled on your account. Reading needs LINEITEMS_READ in the tool's permissions; creating, updating and deleting need LINEITEMS_READ_WRITE.
The Shape of a Line Item
{
"id": "412",
"label": "My first grade line",
"scoreMaximum": 99,
"resourceLinkId": "412",
"tag": "new_tag"
}
| Field | Required | Meaning |
|---|---|---|
id | yes | Your stable identifier. LTIAAS builds the tool-facing URL from it. |
label | yes | The column name a teacher sees |
scoreMaximum | yes | Highest possible score |
resourceLinkId | no | Ties the line item to one activity |
tag | no | A tool-defined label, used to group or find its own line items |
id must be stable. The tool stores the URL built from it and uses that URL for every later read and write. If your IDs move, the tool's grade writes start landing in the wrong place — or nowhere.
List Line Items
LINEITEMS_GET may carry optional filters. Apply them when present.
{
"type": "LINEITEMS_GET",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "348080fn9du9b9ufvb92rfb9l",
"filters": { "resourceLinkId": "17", "tag": "myLineItems" }
}
}
async function handleLineItemsList(decoded, res) {
const { context, filters = {} } = decoded.parameters
let lineItems = await db.lineItems.forCourse(context)
if (filters.resourceLinkId) {
lineItems = lineItems.filter(li => li.resourceLinkId === filters.resourceLinkId)
}
if (filters.tag) {
lineItems = lineItems.filter(li => li.tag === filters.tag)
}
return res.status(200).json(lineItems.map(serializeLineItem))
}
Return an array — an empty one is fine when nothing matches.
Create a Line Item
LINEITEMS_POST carries the proposed line item under parameters.lineItem. Store it and return the stored record, including the id you assigned.
async function handleLineItemCreate(decoded, res) {
const { context, lineItem } = decoded.parameters
const created = await db.lineItems.create({
courseId: context,
label: lineItem.label,
scoreMaximum: lineItem.scoreMaximum,
resourceLinkId: lineItem.resourceLinkId ?? null,
tag: lineItem.tag ?? null
})
return res.status(200).json(serializeLineItem(created))
}
Return 200, not 201 — LTIAAS converts your response into the status the tool expects.
Fetch One
async function handleLineItemGet(decoded, res) {
const { context, lineItemId } = decoded.parameters
const lineItem = await db.lineItems.find(lineItemId)
if (!lineItem || lineItem.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
return res.status(200).json(serializeLineItem(lineItem))
}
Checking that the line item actually belongs to the requested context is worth the extra line — it stops a tool in one course from reading grade lines in another.
Update
LINEITEM_PUT carries both lineItemId and a lineItem object. Apply the change and return the stored record.
async function handleLineItemUpdate(decoded, res) {
const { context, lineItemId, lineItem } = decoded.parameters
const existing = await db.lineItems.find(lineItemId)
if (!existing || existing.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
const updated = await db.lineItems.update(lineItemId, {
label: lineItem.label,
scoreMaximum: lineItem.scoreMaximum,
resourceLinkId: lineItem.resourceLinkId ?? null,
tag: lineItem.tag ?? null
})
return res.status(200).json(serializeLineItem(updated))
}
Delete
Remove it and return 200 with an empty body.
async function handleLineItemDelete(decoded, res) {
const { context, lineItemId } = decoded.parameters
const existing = await db.lineItems.find(lineItemId)
if (!existing || existing.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
await db.lineItems.delete(lineItemId)
return res.status(200).send()
}
Deleting a line item usually means deleting real grades. Consider soft-deleting and hiding the column instead — teachers who lose a gradebook column to a tool's cleanup routine are rarely pleased.
A Shared Serializer
function serializeLineItem(li) {
return {
id: String(li.id),
label: li.label,
scoreMaximum: li.scoreMaximum,
...(li.resourceLinkId && { resourceLinkId: String(li.resourceLinkId) }),
...(li.tag && { tag: li.tag })
}
}
id and resourceLinkId must be strings. Numeric IDs from your database will fail LTIAAS's schema validation, which is a common first bug here.
Next Steps
Scores and results — putting grades into these columns.
