QTI 1.2 export and import contract

Project: QuestionBankToolkit

Status: Active export and import contract.

Primary target: Canvas-compatible QTI 1.2 question-bank ZIP packages.

1. Purpose and scope

This document defines the subset of QTI 1.2 implemented by course-questionbanks, the packaging and scoring conventions that the project generates, and the validation needed before publishing a package. It is not a replacement for the 1EdTech QTI specification.

The project’s YAML schema (docs/schema.md) is the source-of-truth authoring interface. QTI XML is a generated interchange format, not the preferred place to edit questions. This distinction also applies to imported packages: only content supported by the YAML model can be round-tripped reliably.

Support statement: The current implementation exports and imports simple, single-answer multiple-choice questions. It does not claim full QTI 1.2/1.2.1 conformance or support every QTI item type. Canvas compatibility is an additional integration requirement, not an automatic consequence of valid XML.

2. Normative standards and interoperability references

  1. 1EdTech QTI 1.2 ASI XML Binding - definitions of the item, presentation, response-processing, scoring, and feedback XML elements used here.

  2. QTI 1.2 Best Practice and Implementation Guide - illustrative item and assessment XML examples.

  3. QTI 1.2.1 Addendum - subsequent corrections and clarifications to version 1.2.

  4. 1EdTech Content Packaging 1.1.4 XML Binding - manifest and packaged-resource conventions.

  5. Canvas QTI import guide - Canvas course-content import behavior.

  6. Canvas New Quizzes item-bank QTI import guide - item-bank import limitations.

The XML namespace and resource labels below are compatibility details of the current implementation. The existence of the QTI 1.2.1 addendum does not mean the project performs 1.2.1 XSD validation.

3. Supported feature matrix

Capability

Current behavior

Qualification

Question types

Single-answer multiple choice

No multiple-answer, matching, essay, numeric, or fill-in-the-blank questions

Choices

At least two in YAML/export

Import currently accepts 2-26 choices; labels are reassigned A-Z

Correct answer

Exactly one selected choice

No partial credit or negative marking

Scoring

Export writes SCORE = 100 for the correct response; an unmatched response retains the declared zero minimum

Import accepts this Canvas form and the earlier explicit zero-score form

Text

Plain text or authored HTML

Does not convert LaTeX to Canvas equation content or package external media

Feedback

Export writes one general explanation per question

Import explicitly defaults an explanation when Canvas supplies no feedback

Ordering

Questions and choices retain YAML order in generated XML

Actual display ordering is controlled by the receiving LMS

Metadata

IDs, title, points, topic, area, difficulty, assessment eligibility

Some fields are extensions understood by this toolkit, not portable QTI semantics

Import

Direct QTI resources and Canvas Common Cartridge item-bank wrappers

Unsupported or ambiguous structures are rejected

Question rotation

Managed by local YAML/CSV tooling

Not encoded as a QTI assessment-selection rule

Choice shuffling

Export writes render_choice/@shuffle="Yes" when enabled and otherwise uses QTI’s omitted-attribute default

A question override takes precedence over the bank default; import stores the effective value per question

4. ZIP package layout

The exporter writes a Canvas Common Cartridge manifest and two assessment files under a directory named for the bank ID:

question-bank.zip
├── imsmanifest.xml
└── BANK_ID
    ├── assessment_qti.xml
    └── assessment_meta.xml

imsmanifest.xml uses the Common Cartridge namespace http://www.imsglobal.org/xsd/imsccv1p1/imscp_v1p1 and declares version 1.1.0. Its assessment resource has type imsqti_xmlv1p2/imscc_xmlv1p1/assessment, contains the assessment_qti.xml file reference, and depends on an associated-content resource for assessment_meta.xml.

assessment_meta.xml uses Canvas’s http://canvas.instructure.com/xsd/cccv1p0 namespace and supplies the quiz title, point total, attempt policy, and unpublished online-quiz assignment linkage required by the accepted Canvas package structure.

assessment_qti.xml uses the namespace http://www.imsglobal.org/xsd/ims_qtiasiv1p2 and declares the QTI 1.2.1 schema location used by Canvas exports. Its structure is:

questestinterop
└── assessment (bank ID and title)
    └── section (root_section)
        ├── item (question 1)
        ├── item (question 2)
        └── ...

The current exporter serializes UTF-8 XML and creates deterministic ZIP member timestamps and file permissions. This supports repeatable output for the same environment and ordered input data; it is not a guarantee of identical bytes across every Python or compression-library version.

5. Question representation

Each YAML question maps to a QTI item. The exporter uses these conventions:

YAML source

QTI representation

id

item/@ident

id + title

item/@title

stem

presentation/material/mattext

format

mattext/@texttype: text/plain or text/html

choices

Ordered response_label elements under response_lid/render_choice

answer

Correct varequal response identifier

explanation

itemfeedback with ID general_fb

points

qtimetadatafield named points_possible

Choice IDs

Comma-separated original_answer_ids metadata in source order

topic, area, difficulty

Project-specific qtimetadatafield values

assessments

JSON array string in a project-specific metadata field

The selection response has ident="response1" and rcardinality="Single". Choice identifiers are constructed as <question_id>_<choice_key>, for example M1C01_A.

For a question with ID M1C01 and correct choice A, the scoring logic corresponds to the following abbreviated XML (not a complete standalone item):

<response_lid ident="response1" rcardinality="Single">
  <render_choice shuffle="Yes">
    <response_label ident="M1C01_A">...</response_label>
    <response_label ident="M1C01_B">...</response_label>
  </render_choice>
</response_lid>
<resprocessing>
  <outcomes>
    <decvar varname="SCORE" vartype="Decimal"
            minvalue="0" maxvalue="100" />
  </outcomes>
  <respcondition continue="Yes">
    <conditionvar><other /></conditionvar>
    <displayfeedback feedbacktype="Response" linkrefid="general_fb" />
  </respcondition>
  <respcondition continue="No">
    <conditionvar><varequal respident="response1">M1C01_A</varequal></conditionvar>
    <setvar varname="SCORE" action="Set">100</setvar>
  </respcondition>
</resprocessing>

The actual generator nests each option’s display text within a material/mattext element. HTML input is stored as text marked with texttype="text/html"; the tool does not parse or validate the meaning of that HTML.

6. Choice shuffling

QTI 1.2 defines the optional shuffle attribute on render_choice, with values Yes and No and default No.

<render_choice shuffle="Yes">

The exporter writes shuffle="Yes" when the effective setting is enabled and omits the attribute when it is disabled, matching QTI’s No default and the accepted Canvas package structure. The optional bank-level default_shuffle_choices value defaults to false, and an optional per-question shuffle_choices value takes precedence. The importer accepts Yes, No, or an omitted attribute, rejects other values, and records the effective boolean on each imported question.

This is an interchange hint, not proof that Canvas will honor it after import. For exam delivery, inspect representative imported questions and confirm the relevant Canvas quiz settings.

7. Import and round-trip boundaries

coursebanks import-qti is intended for compatible single-answer items. It reads QTI item IDs, titles, text, ordered choices, one correct score condition, and available general explanations, then builds a validated YAML bank.

The current importer requires one single-cardinality response_lid, text/plain or text/html stems and choices, 2-26 choices, a valid optional render_choice/@shuffle value, and one identifiable correct answer. It accepts a direct QTI assessment resource or resolves one Canvas Common Cartridge wrapper to its referenced object bank. When feedback is absent, the YAML explanation states that the package provided none rather than inventing instructional content. It may use an instructor CSV to recover authoring metadata not present in the QTI package. If the format cannot otherwise be represented faithfully, import should fail rather than silently discard meaningful information.

Imported options are relabeled A, B, … in source order. A question ID is preserved when it satisfies the YAML grammar; Canvas hexadecimal IDs beginning with a digit receive a q_ prefix. Original option identifiers are not guaranteed to be preserved on import. Assessment-eligibility metadata is project-specific and cannot be expected from arbitrary third-party exports.

See the QTI import guide for the field-by-field compatibility table, metadata defaults, and CSV override precedence.

8. Validation levels

Level 1: YAML and internal data

coursebanks validate checks required fields, identifiers, duplicate questions, choice/answer consistency, metadata types, and other constraints described in schema.md. It does not certify XML output or Canvas behavior.

Level 2: Generated ZIP and QTI structure

coursebanks check-qti <archive.zip> currently checks, among other things:

  • A root-level imsmanifest.xml, exactly one supported QTI assessment resource, and either a resolvable direct assessment filename or one resolvable Canvas source-bank reference.

  • No more than 100 uniquely named members, 16 MiB per uncompressed member, 32 MiB total declared uncompressed data, and a maximum 100:1 compression ratio per nonempty member.

  • A QTI-namespace questestinterop document containing at least one item.

  • Unique, nonempty item IDs, at least two response labels per item, one key matching a response-label identifier, and one or two simple score assignments.

  • XML is parsed using defusedxml, rejecting DTD/entity-based constructs.

Verification applies archive limits before XML parsing, and import reapplies them when reopening the package. Reads of the manifest and assessment are bounded by the per-member limit. These controls reduce archive-expansion risk but do not make arbitrary QTI safe.

These checks are not complete XML Schema validation, complete QTI conformance testing, or complete scoring-semantic validation.

Level 3: LMS integration

For a release candidate, perform a test import into Canvas and verify that: all items appear; question titles, stems, choices, point values, and feedback render correctly; the expected answer is scored correctly; and any required shuffling settings work as intended. Keep a small, public, answer-neutral sample bank for regression testing.

On October 22, 2026, Canvas imported both questions from the toolkit-generated two-question regression package. Its Canvas re-export passed structural verification and imported into valid YAML with stems, choices, correct answers, explanations, authored HTML, Unicode, and effective shuffling represented. Canvas regenerated identifiers and order, encoded the plain-text question as HTML, omitted project-specific authoring metadata, and supplied no per-question point value in the object bank. Those differences follow the documented import normalization and default rules rather than loss inside the toolkit exporter.

For New Quizzes item banks, Canvas documents import into a new or empty item bank; importing into an existing populated item bank is not supported by that workflow. Canvas also documents QTI import separately for quiz creation and course-content import. See the official Canvas links in Section 2.

9. Development and release checklist

Before releasing a QTI-related change:

  1. Update docs/schema.md if YAML inputs or validation rules changed.

  2. Add focused unit tests for new XML elements and attributes, including correct-answer stability.

  3. Test build -> check-qti and, for supported features, build -> import-qti -> validate round trips.

  4. Verify deterministic generation with an unchanged input and environment.

  5. Run the repository’s complete automated check script.

  6. If XML conformance is claimed, validate against the appropriate official QTI and content-packaging schemas; current check-qti does not do this.

  7. Test import and grading in the targeted Canvas environment before releasing for instructional use.

  8. Never commit actual examination questions, answer keys, or administration history to the public toolkit repository.

10. Deliberate exclusions and possible future work

Not currently in scope: multiple-correct-answer scoring, partial credit, per-distractor feedback, equations converted automatically to Canvas math content, embedded or referenced media with packaging, generalized QTI assessment-selection rules, and QTI 2.x export.

Potential extensions should be proposed as separate features, with their required YAML schema, QTI representation, import behavior, validation tests, and Canvas compatibility evidence documented before implementation.

11. Relevant code

  • src/coursebanks/core.py: question_to_xml, qti_xml, manifest_xml, create_qti, verify_qti.

  • src/coursebanks/archive_io.py: ZIP member uniqueness, resource limits, and bounded member reads.

  • src/coursebanks/migrate.py: import_qti.

  • src/coursebanks/xml_io.py: XML parser protections.

  • docs/schema.md: authoring format, required fields, and project-specific validation rules.

  • tests/: expected export, validation, migration, and security behavior.

When this document and a code path disagree, treat the mismatch as a bug or documentation task. Do not silently reinterpret the supported QTI subset.