Overview
The@timeback/qti package provides a client for the QTI (Question and Test Interoperability) API, enabling:
- Assessment Items: Query and manage question items
- Assessment Tests: Query tests with nested structure
- Stimuli: Query shared content for questions
- Lessons: Submit and retrieve lesson feedback
- Validation: Validate QTI XML content
Installation
npm install @timeback/qti
pnpm add @timeback/qti
yarn add @timeback/qti
bun add @timeback/qti
pip install timeback-qti
uv add timeback-qti
Quick Start
import { QtiClient } from '@timeback/qti'
const client = new QtiClient({
env: 'staging',
auth: {
clientId: process.env.QTI_CLIENT_ID!,
clientSecret: process.env.QTI_CLIENT_SECRET!,
},
})
const { items, total, page, pages } = await client.assessmentItems.list()
const item = await client.assessmentItems.get('item-123')
from timeback_qti import QtiClient
client = QtiClient(
env="staging",
client_id=os.environ["QTI_CLIENT_ID"],
client_secret=os.environ["QTI_CLIENT_SECRET"],
)
items = await client.assessment_items.list()
item = await client.assessment_items.get("item-123")
Assessment Items
List & Get Items
client.assessmentItems.list()
client.assessmentItems.list({ page: 2, limit: 50, sort: 'title', order: 'asc' })
client.assessmentItems.get(itemId)
client.assessmentItems.stream()
client.assessmentItems.stream({ max: 500 }).toArray()
await client.assessment_items.list()
await client.assessment_items.list({"page": 2, "limit": 50, "sort": "title", "order": "asc"})
await client.assessment_items.get(item_id)
async for item in client.assessment_items.stream():
...
| Method | Returns | Description |
|---|---|---|
list() | { items, total, page, pages } | List with pagination metadata |
get() | AssessmentItem | Get item by ID |
stream() | AsyncIterable<Item> | Memory-efficient streaming |
.toArray() | Item[] | Collect streamed items |
Create, Update, Upsert & Delete
client.assessmentItems.create({ identifier, title, ...content })
client.assessmentItems.update(itemId, { title, ...content })
client.assessmentItems.upsert(itemId, { title, ...content })
client.assessmentItems.delete(itemId)
client.assessmentItems.processResponse(itemId, { response })
await client.assessment_items.create({"identifier": identifier, "title": title, **content})
await client.assessment_items.update(item_id, {"title": title, **content})
await client.assessment_items.upsert(item_id, {"title": title, **content})
await client.assessment_items.delete(item_id)
await client.assessment_items.process_response(item_id, {"response": response})
Assessment Tests
client.assessmentTests.list()
client.assessmentTests.get(testId)
client.assessmentTests.stream()
client.assessmentTests.create({ identifier, title, ...content })
client.assessmentTests.update(testId, { title, ...content })
client.assessmentTests.upsert(testId, { title, ...content })
client.assessmentTests.delete(testId)
client.assessmentTests.updateMetadata(testId, metadata)
client.assessmentTests.getQuestions(testId)
await client.assessment_tests.list()
await client.assessment_tests.get(test_id)
async for test in client.assessment_tests.stream():
...
await client.assessment_tests.create({"identifier": identifier, "title": title, **content})
await client.assessment_tests.update(test_id, {"title": title, **content})
await client.assessment_tests.upsert(test_id, {"title": title, **content})
await client.assessment_tests.delete(test_id)
await client.assessment_tests.update_metadata(test_id, metadata)
await client.assessment_tests.get_questions(test_id)
Test Parts
Assessment tests contain test parts, which contain sections:client.assessmentTests.testParts(testId).list()
client.assessmentTests.testParts(testId).get(testPartId)
client.assessmentTests.testParts(testId).create({ identifier, ...content })
client.assessmentTests.testParts(testId).update(testPartId, { ...content })
client.assessmentTests.testParts(testId).delete(testPartId)
await client.assessment_tests.test_parts(test_id).list()
await client.assessment_tests.test_parts(test_id).get(test_part_id)
await client.assessment_tests.test_parts(test_id).create({"identifier": identifier, **content})
await client.assessment_tests.test_parts(test_id).update(test_part_id, {**content})
await client.assessment_tests.test_parts(test_id).delete(test_part_id)
Sections
Sections live within a test part:client.assessmentTests.testParts(testId).sections(testPartId).list()
client.assessmentTests.testParts(testId).sections(testPartId).get(sectionId)
client.assessmentTests.testParts(testId).sections(testPartId).create({ identifier, ...content })
client.assessmentTests.testParts(testId).sections(testPartId).update(sectionId, { ...content })
client.assessmentTests.testParts(testId).sections(testPartId).delete(sectionId)
await client.assessment_tests.test_parts(test_id).sections(test_part_id).list()
await client.assessment_tests.test_parts(test_id).sections(test_part_id).get(section_id)
await client.assessment_tests.test_parts(test_id).sections(test_part_id).create({"identifier": identifier, **content})
await client.assessment_tests.test_parts(test_id).sections(test_part_id).update(section_id, {**content})
await client.assessment_tests.test_parts(test_id).sections(test_part_id).delete(section_id)
Section Items
Manage items within a section:client.assessmentTests.testParts(testId).sections(testPartId).items(sectionId).add(itemRef)
client.assessmentTests.testParts(testId).sections(testPartId).items(sectionId).remove(itemIdentifier)
client.assessmentTests.testParts(testId).sections(testPartId).items(sectionId).reorder(orderInput)
await client.assessment_tests.test_parts(test_id).sections(test_part_id).items(section_id).add(item_ref)
await client.assessment_tests.test_parts(test_id).sections(test_part_id).items(section_id).remove(item_identifier)
await client.assessment_tests.test_parts(test_id).sections(test_part_id).items(section_id).reorder(order_input)
Stimuli
Stimuli are shared content blocks referenced by multiple items:client.stimuli.list()
client.stimuli.get(stimulusId)
client.stimuli.stream()
client.stimuli.create({ identifier, title, ...content })
client.stimuli.update(stimulusId, { title, ...content })
client.stimuli.upsert(stimulusId, { title, ...content })
client.stimuli.delete(stimulusId)
await client.stimuli.list()
await client.stimuli.get(stimulus_id)
async for stimulus in client.stimuli.stream():
...
await client.stimuli.create({"identifier": identifier, "title": title, **content})
await client.stimuli.update(stimulus_id, {"title": title, **content})
await client.stimuli.upsert(stimulus_id, {"title": title, **content})
await client.stimuli.delete(stimulus_id)
upsert() is available on all three writable QTI resources.update(id, data): strict (throws if the resource doesn’t exist)upsert(id, data): loose (creates the resource if it doesn’t exist)
Lessons
Submit and retrieve lesson feedback:client.lesson.submitLesson({ lessonId, studentId, ...feedbackData })
client.lesson.getLesson(lessonId)
client.lesson.submitQuestion({ questionId, studentId, ...feedbackData })
await client.lesson.submit_lesson({"lesson_id": lesson_id, "user_id": user_id, **feedback_data})
await client.lesson.get_lesson(lesson_id)
await client.lesson.submit_question({"question_id": question_id, "user_id": user_id, **feedback_data})
Validation
Validate QTI XML content:client.validate.validate({ schema: 'item', xml: '<qti-assessment-item>...</qti-assessment-item>' })
client.validate.validate({ schema: 'test', xml: '<qti-assessment-test>...</qti-assessment-test>' })
client.validate.validate({ schema: 'stimulus', xml: '<qti-stimulus>...</qti-stimulus>' })
client.validate.batch({
xml: ['<qti-assessment-item>...</qti-assessment-item>', '...'],
schema: 'item',
entityIds: ['item-1', 'item-2'],
})
await client.validate.validate({"schema": "item", "xml": "<qti-assessment-item>...</qti-assessment-item>"})
await client.validate.validate({"schema": "test", "xml": "<qti-assessment-test>...</qti-assessment-test>"})
await client.validate.validate({"schema": "stimulus", "xml": "<qti-stimulus>...</qti-stimulus>"})
await client.validate.batch({
"xml": ["<qti-assessment-item>...</qti-assessment-item>", "..."],
"schema": "item",
"entity_ids": ["item-1", "item-2"],
})
| Schema | Description |
|---|---|
item | QTI 3.0 assessment item |
test | QTI 3.0 assessment test |
stimulus | QTI 3.0 stimulus |
{ valid: boolean, errors?: string[] }
Item Types
QTI supports various interaction types:| Type | Description |
|---|---|
choiceInteraction | Multiple choice (single or multiple select) |
textEntryInteraction | Text input |
extendedTextInteraction | Long text/essay |
inlineChoiceInteraction | Dropdown select |
orderInteraction | Ordering/ranking |
matchInteraction | Matching pairs |
gapMatchInteraction | Fill in the blank |
hotspotInteraction | Image hotspots |
Next Steps
PowerPath
Adaptive assessments
OneRoster
Link assessments to gradebook
Types
QTI type definitions