Skip to main content
Every request on this page requires a Bearer API key. This API turns one revision of a question package from your system into one new question package in the admin panel, with its passages, images, audio and answer keys. An import does not create an exam: once the import has finished, assign the question package to an exam session in the admin panel.

Flow

  1. Prepare the media. Images can be written as https links directly in the package, or uploaded first to POST /question-assets to get an asset_id. Audio can only be sent as an upload.
  2. (Optional) Check the package with POST /question-imports/validate.
  3. Send the package to POST /question-imports with an Idempotency-Key header. The package is checked right away; if it is valid, the response is 202 and the package is processed in the background.
  4. Follow the result through GET /question-imports/{id} or the question_import.completed and question_import.failed webhooks.
  5. Store question_bank.id for the revision and question_id for every question.
An imported question package cannot be changed in the admin panel: its questions, answers and passages are locked so every participant is scored against exactly the questions and answer keys you sent. Send corrections as a new revision. If you need to edit the content by hand, use Duplicate Question Package; the copy can be edited like any other question package.

Error format

The endpoints on this page return errors as a list, not as the per-field object of other endpoints, because one package can hold hundreds of questions. pointer is a JSON Pointer to the part of the body at fault and source_item_id is the source_id of that question or passage; for an error that is not about one question, both are null. One response carries at most 1,000 errors.

Endpoints

Uploads one file as multipart/form-data. The file type is read from its contents, not from its name or declared type.
  • Images: JPEG, PNG, WebP or GIF, up to 5 MB and 10,000 pixels per side.
  • Audio: MP3, WAV or OGG, up to 10 MB.
  • SVG and any other file are rejected.
The exact same file (identical contents) returns the same asset_id with status 200 and a renewed expiry. An asset can be used in any package until expires_at (7 days after the upload); after that it is deleted. An import copies the file into the question package, so questions are not affected when the asset expires.

Parameters

Example request

Example response

Status 201.
Takes the same body as POST /question-imports and checks it without storing anything. No Idempotency-Key is needed. Validation only checks the shape of image links; images are downloaded when the import is processed, so an unreachable link only shows up as image_download_failed on a failed import.

Example request

Example response

Status 200 when the package is valid. When it is not, status 422 with the error format above.
Checks the package and then processes it in the background. When there are errors, the response is 422 with every error and nothing is stored. When the package is valid, the response is 202 with a Location header pointing to GET /question-imports/{id}.An Idempotency-Key is required: up to 255 ASCII characters without spaces, for example {package_id}-r{revision}-{uuid}. A key is valid for 7 days:
  • Same key and same package body: the existing import is returned with status 200 and the header Idempotent-Replayed: true. This is safe to use when the connection drops before the response arrives.
  • Same key with a different package body: rejected with 422 and the code idempotency_key_mismatch.
Revisions. A source.package_id and source.revision pair can become a question package only once per project. A revision that was already imported is rejected with 409 (revision_already_imported). A failed import does not prevent sending the same revision again.

Parameters

Example request

Example response

Status 202.
Sorted from the newest import. List items do not include the ID mapping; use the import details for that.

Parameters

Example request

Once the status is completed, the response contains the question package that was created, plus stimuli (passages) and items (questions) with their IDs in the CBT. Store this mapping to trace your questions in the CBT.

Parameters

Example request

Example response

Package format

Question types

Each option has an id (unique within the question, up to 50 characters), content (HTML) or an image, and optional points (default 0). answer_key may give an id as a string or a number. points are used by the Sum of Answer Scores scoring method, so partial credit can be set per option. multiple_choice example:
categorization example (Fact/Opinion; for True/False just change the categories):
The first category is shown as the left column and the second as the right column. Category labels are up to 50 characters. short_answer example with several correct answers:
  • format: "numeric": the participant’s answer box only accepts numbers, and every correct answer must be a number. format: "text": the participant types free text. When format is not sent, the question uses numeric if every correct answer is a number, and text otherwise.
  • Two numbers are compared by value (0,5 equals 0.50); anything else is compared as text, ignoring letter case and extra spaces. There is no numeric tolerance; write every form you accept as its own answer. Each answer is up to 255 characters.
matching example:

Content and media

  • HTML keeps the same tags as the question editor in the admin panel: b i em span p u strong table th tr td ul li ol br sup sub pre code for questions, passages and explanations; options and statements keep only b i em span p strong ul li ol br sup sub pre code (no underline and no tables). Other tags are unwrapped (their text stays); script, style, iframe, svg and similar elements are removed with their content; every attribute is removed except colspan and rowspan on table cells. Content that was changed is reported in warnings with the code content_sanitized.
  • Write < and > inside text or formulas as &lt; and &gt;, for example \(a &lt; b\). Control characters (other than tabs and line breaks) are rejected.
  • Formulas are written in LaTeX with the delimiters \( ... \), \[ ... \], $ ... $ or $$ ... $$.
  • Question, option, statement and explanation text is up to 5,000 characters once tags are removed.
  • Images inside text are not supported and are removed. Send question images through media.images and option images through options.*.image.
  • Images from links must be https, publicly reachable, and JPEG, PNG, GIF or WebP up to 5 MB. Images are downloaded into the project’s storage; the original link is neither stored nor shown to participants. Links to internal network addresses, more than 3 redirects and files whose contents are not an image are rejected. Up to 300 distinct links per package.
  • Every image and audio file counts towards the project’s storage quota.
During the exam, a passage is shown as part of every question that uses it: the passage images come first with the question images, then the passage text sits right above the question text. When questions are shuffled, questions that share a passage stay together.

Import status

A failed import carries error.code, error.message, error.retryable and error.details (a list of per-question errors in the same shape as the error format):
When retryable is true, send the package again with a new Idempotency-Key. When it is false, fix the data as details describes, then send it again with a new Idempotency-Key.

Webhooks

Subscribe to the question_import.completed and question_import.failed events through the Webhooks API. Headers and signatures are the same as for other events.
On question_import.failed, question_bank is null and error holds the cause. The full ID mapping is read through GET /question-imports/{id}.

Limits

  • 500 questions and 100 passages per package; the body is up to 10 MB.
  • 20 imports per hour per project; one import is processed at a time and the others wait in the queue.
  • The API rate limit of 120 requests per minute still applies.
  • The package body is kept for 30 days after the import finishes and then deleted. The question package and the ID mapping are not deleted.