Skip to content
OpenTrain AIOpenTrain AIOpenTrain AIDocs

Ask OpenTrain

Answers from the documentation, with sources.

What would you like to do with OpenTrain?

AI answers can be mistaken. Check the linked sources. Don’t include private account information.

Open app

DEVELOPER DOCUMENTATION

Quiz-as-Code, Diff, and Templates

Export a quiz as a portable JSON or YAML document, edit it in a repository, import it back, compare versions, and reuse quizzes across jobs with organization templates.

GET/api/public/v1/project-todos/quizzes/code

A quiz can live in your repository as a document. Export it, edit it, review the change with a diff, and import it back. Organization templates let you start a quiz in a new job from one you already wrote.

A quiz document holds exactly the authoring content: title, description, settings, and definition, with your own stable question and option ids. It never holds database ids, timestamps, job scope, revisions, assets, attempts, or the confirmed learning-plan decision that OpenTrain stores per form.

opentrainQuizVersion: 1
title: Warehouse safety check
description: Five questions on the PPE page.
settings:
attemptPolicy: { mode: LIMITED, maxAttempts: 2 }
passRule: { mode: PERCENTAGE, thresholdPercent: 80 }
resultRelease: MANUAL
definition:
schemaVersion: 2
questions:
- id: q_goggles
type: MULTIPLE_CHOICE
prompt: When are safety goggles required?
required: true
options:
- { id: o_always, label: Always on the floor }
- { id: o_cutting, label: Only when cutting }
grading: { points: 1, answerKey: { optionId: o_always } }
- id: q_why
type: PARAGRAPH
prompt: Explain why the forklift lane stays clear.
required: true
manualGrading:
points: 3
rubric: Full credit names the hazard and the consequence.
FieldRules
opentrainQuizVersionAlways 1.
title, descriptiondescription may be null. An optional rich descriptionDoc may be included.
settings.attemptPolicy{ "mode": "UNLIMITED" } or { "mode": "LIMITED", "maxAttempts": n }.
settings.passRule{ "mode": "PERCENTAGE", "thresholdPercent": n }, { "mode": "ABSOLUTE", "thresholdPoints": n }, or null.
settings.resultReleaseIMMEDIATE or MANUAL.
definition.schemaVersion1, or 2 when the quiz is grounded (definition.grounding).
definition.questionsUp to 500 entries.

Question types:

typeExtra fieldsAuto-grading (grading.answerKey)
SHORT_TEXT, PARAGRAPHnone{ "value", "caseSensitive"? }. Leave out grading to grade by hand.
MULTIPLE_CHOICE, DROPDOWNoptions (2–200){ "optionId" }
CHECKBOXESoptions (1–200){ "optionIds": [...] }
LINEAR_SCALEmin, max (−100 to 100), optional labels{ "value" } with optional tolerance and partialCredit (NONE or LINEAR, with partialCreditRange)
NUMBERnoneSame as LINEAR_SCALE
DATEnone{ "value": "YYYY-MM-DD" }
SECTIONtitle, optional descriptionLayout only
TITLEtextLayout only

Every answerable question has id, prompt (up to 10,000 characters), and required, plus these optional fields: grading.points (1–10,000, default 1), manualGrading ({ "points", "rubric"? }, rubric up to 4,000 characters, never shown to AI trainers), practiceExplanation (shown after submission), sourceRefs and rationale (grounding only). A question uses grading or manualGrading, never both. Rich-text fields (promptDoc, descriptionDoc, textDoc) are optional.

The authoring capabilities read returns these limits and types as data.

GET /api/public/v1/project-todos/quizzes/code?formId=… exports the current draft. Add formVersionId to export a published version.

REST

Terminal window
curl -s "https://www.opentrain.ai/api/public/v1/project-todos/quizzes/code?formId=form_456" \
-H "Authorization: Bearer $OPENTRAIN_API_TOKEN"

The response has formId, scopeJobId, source (DRAFT or VERSION), draftRevision, contentHash, recordedContentHash, exportedAt, and document. contentHash identifies the portable content. recordedContentHash is the hash the publication stored, and it is null for a draft. The two differ for a version, because publishing prunes uncited grounding pins and includes the stored learning-plan decision.

Export, then import, then export again gives the same content hash.

form_import reads a document back. The target is one of:

  • { "kind": "CREATE", "jobId": "…" } creates a new form in the job. It takes the same optional confirmedPlan as form_create. Without it, the response carries a learning-plan hint.
  • { "kind": "UPDATE_DRAFT", "formId": "…", "expectedRevision": n } replaces an existing draft at that revision. It keeps the stored learning-plan decision. Add acknowledgeSourceChanges when the document pins a cited page at a new checksum.

Publishing is a separate step. A retry with the same key returns the original result, including after a lost reply. The same key with a different document or job is 409 IDEMPOTENCY_KEY_REUSED.

REST

Terminal window
curl -s -X POST "https://www.opentrain.ai/api/public/v1/project-todos/quizzes/manage" \
-H "Authorization: Bearer $OPENTRAIN_API_TOKEN" -H 'content-type: application/json' -d '{
"action": "form_import",
"idempotencyKey": "safety-quiz-import-2026-11",
"target": { "kind": "UPDATE_DRAFT", "formId": "form_456", "expectedRevision": 3 },
"document": {
"opentrainQuizVersion": 1, "title": "Warehouse safety check", "description": null,
"settings": { "attemptPolicy": { "mode": "UNLIMITED" }, "passRule": null, "resultRelease": "MANUAL" },
"definition": { "schemaVersion": 1, "questions": [
{ "id": "q_goggles", "type": "SHORT_TEXT", "prompt": "Name the required eye protection.",
"required": true, "grading": { "points": 1, "answerKey": { "value": "goggles" } } }
] }
}
}'

A document from a form grounded in its job’s Instructions can be created again only in that job. Aimed at another job, it is refused with 400 (“Grounding must reference the form’s own job”). To move it to another job, either:

  • make a template with detachGrounding: true and instantiate it (below), or
  • delete definition.grounding and every question’s sourceRefs from the document, create it in the new job, and ground it again there.

GET /api/public/v1/project-todos/quizzes/diff?formId=… compares the latest published version with the draft. Pass fromVersionId and toVersionId to compare any pair; DRAFT names the draft. Questions are matched by their stable id. Grounding is compared by its cited pins only.

REST

Terminal window
curl -s "https://www.opentrain.ai/api/public/v1/project-todos/quizzes/diff?formId=form_456" \
-H "Authorization: Bearer $OPENTRAIN_API_TOKEN"

The diff lists questions.added, questions.removed, questions.changed (each with the changed fields), questions.unchanged, and questions.reordered, plus settings, title, and description changes, affectedQuestionIds, and identical.

A template is a form owned by the organization, not a job. Instantiating one creates a new, independent form in a job. The new form is not linked to the template, and later changes to either do not affect the other.

OperationRequest
ListGET /quizzes/templates (add includeArchived=true to include archived ones)
Create from a formManage action template_create_from_form with formId, optional formVersionId, title, and detachGrounding
InstantiateManage action template_instantiate with templateId, jobId, and optional title

A grounded source cannot become a template as it is: the request is refused with 400 and details.reason GROUNDED_SOURCE. Set detachGrounding: true to template the portable content (grounding and sourceRefs removed, rationale kept). The response then reports groundingDetached: true. Ground the new form in its own job after you instantiate it.

Both creates are idempotent per organization and key. A retry returns the original form with replayed: true, even if the source changed since. The same key with different choices (another source, template, job, title, detachGrounding, or confirmed plan) is 409 IDEMPOTENCY_KEY_REUSED. A template id from another organization, or a form that is not a template, is 404.

REST

Terminal window
curl -s -X POST "https://www.opentrain.ai/api/public/v1/project-todos/quizzes/manage" \
-H "Authorization: Bearer $OPENTRAIN_API_TOKEN" -H 'content-type: application/json' -d '{
"action": "template_create_from_form",
"idempotencyKey": "safety-template-2026-11",
"formId": "form_456", "title": "Safety quiz (template)", "detachGrounding": true
}'
curl -s -X POST "https://www.opentrain.ai/api/public/v1/project-todos/quizzes/manage" \
-H "Authorization: Bearer $OPENTRAIN_API_TOKEN" -H 'content-type: application/json' -d '{
"action": "template_instantiate",
"idempotencyKey": "safety-template-job-b-2026-11",
"templateId": "form_tpl_1", "jobId": "job_b"
}'