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

Ground a Quiz in Instructions, Validate, and Restore

Write quiz questions from a job's released Instructions, pin the pages they cite, validate without writing, and publish behind a fence that catches changed pages.

POST/api/public/v1/project-todos/quizzes/manage

A grounded quiz is a quiz whose questions cite the job’s Instructions. The model in your agent writes the questions. OpenTrain supplies the released Instructions, a snapshot of page checksums, validation, a publish fence, and the immutable published version. OpenTrain does not generate questions.

The publish fence protects AI trainers from a quiz that tests rules the Instructions no longer contain: if a page that a question cites has changed since you pinned it, publishing stops and names the questions to revise.

Read the released Instructions

Page reads return the draft by default. Write grounded questions from the released page, the version AI trainers see: GET /api/public/v1/instructions/jobs/{jobId}/pages/{pageId}?released=true, or getJobInstructionPage(jobId, pageId, { released: true }) in the SDK. Each block carries a stable id in attrs.id. Questions cite those ids.

Take the grounding snapshot

Read the snapshot and copy its grounding block, unchanged, into definition.grounding. The definition must use schemaVersion: 2.

Cite blocks from each question

Give each grounded question up to 20 sourceRefs ({ "pageId", "nodeId" }, where the page is pinned in the grounding) and an optional rationale of up to 2,000 characters. AI trainers never see either.

Validate

Run form_validate on the candidate or the stored draft. Fix every finding it lists.

Publish or roll out

form_publish or quiz_rollout_confirm checks the cited pages again before it writes.

GET /api/public/v1/project-todos/quizzes/grounding?jobId=… returns the job’s released effective Instructions: the effective instruction set id, a page-tree checksum, and each page’s pageId, title, parentPageId, checksum, and stableNodeIdCount. A set inherited from a folder is included and marked inheritedFromFolder: true. Google Doc pages are read live from the linked document.

REST

Terminal window
curl -s "https://www.opentrain.ai/api/public/v1/project-todos/quizzes/grounding?jobId=job_123" \
-H "Authorization: Bearer $OPENTRAIN_API_TOKEN" \
-H "X-OpenTrain-Features: quiz-grounding-draft-differs-v1"

The grounding block you copy has this shape:

{
"jobId": "job_123",
"instructionSetId": "set_789",
"treeChecksum": "…",
"pages": [{ "pageId": "page_ppe", "checksum": "…" }],
"capturedAt": "2026-11-01T10:00:00.000Z"
}

With the X-OpenTrain-Features: quiz-grounding-draft-differs-v1 header, each page of a released snapshot also carries draftDiffers. It is true when the page’s unreleased draft differs from the released text, no longer has the page, or could not be read. For those pages, write questions from the released read, not from the draft. Google Doc pages always report false. The SDK, CLI, and MCP send the header for you.

A job with no released Instructions answers 409 CONFLICT with details.reason INSTRUCTIONS_NOT_RELEASED. Release the Instructions first, or use a draft preview.

Use a draft preview only when the person asks to ground on edits that are not released yet. Pass draftPreview=true (--draft-preview in the CLI, { draftPreview: true } in the SDK and MCP). The result carries "source": "draft_preview" and a previewLabel. Show that label to the person.

A draft-preview grounding never publishes. form_validate reports GROUNDING_DRAFT_PREVIEW, the rollout plan lists it as a blocker, and form_publish refuses it with 409 GROUNDING_DRAFT_PREVIEW. To publish, release the Instructions, take the snapshot again without the option, copy it into definition.grounding, and publish. OpenTrain never promotes a preview pin for you. A stored draft that holds a preview grounding shows its previewLabel on the form reads, so a later session sees it before it tries to publish.

form_validate reports every schema finding, publication-rule finding, form-state gate, and grounding finding at once. It needs only project_todos:read and no idempotency key, and it writes no content. Pass formId to check the stored draft, or jobId plus definition and settings to check a candidate before you create it, never both. A grounded definition also needs instructions:read.

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' \
-H "X-OpenTrain-Features: quiz-grounding-draft-differs-v1" -d '{
"action": "form_validate",
"formId": "form_456"
}'

The response has publishable, findings[], publication (total points and question counts, including groundedQuestionCount), grounding (status is NONE, VERIFIED, or SOURCE_CHANGED, plus its findings and the live page checksums), and affectedQuestionIds. Each finding has a source:

sourceCodes
SCHEMAINVALID_DEFINITION, INVALID_SETTINGS, with a path.
PUBLICATIONPublication rules (for example, a pass rule needs at least one answerable scored question) and form-state gates such as FORM_ARCHIVED, ASSET_UNUSABLE, LEARNING_PLAN_MISMATCH, and GROUNDING_DRAFT_PREVIEW.
GROUNDINGcode: "SOURCE_CHANGED" with a severity, a reason, the pageId and nodeId, and the questionIds that cite it.

Grounding reason values:

ReasonSeverityMeaning
PAGE_CHANGED, PAGE_MISSING, NODE_MISSINGErrorA cited page changed or is gone, or a cited block no longer exists.
JOB_MISMATCH, NO_INSTRUCTIONS, INSTRUCTION_SET_CHANGEDErrorThe grounding belongs to another job, the job has no Instructions now, or the effective set changed.
PAGE_UNREADABLEErrorA cited page could not be read now. Try again.
TREE_CHANGEDWarningPages were added, moved, or removed elsewhere in the tree. Does not block.
DRAFT_DIFFERS, DRAFT_UNREADABLEWarningA cited page has unreleased draft edits, or its draft could not be read to compare. Check those questions against the released text. Does not block. Sent only with the feature header.

Only pages that some question cites are read again. A pinned page that no question cites never blocks publishing.

form_publish and quiz_rollout_confirm read the cited pages again. Publishing a grounded draft also needs instructions:read. An ungrounded publish needs only project_todos:write.

ResponseWhat to do
409 CONFLICT, details.reason SOURCE_CHANGED, with details.affectedQuestionIds and details.findingsRead those pages again, revise those questions, pin the pages at the checksum you just read, and save the draft with acknowledgeSourceChanges: { "questionIds": [...] } naming the revised questions. Then publish again.
409, details.reason SOURCE_CHANGED_UNACKNOWLEDGED, with details.movedPinsYou pinned a cited page at a new checksum without naming the questions that cite it. Add them to acknowledgeSourceChanges.
409, details.reason GROUNDING_DRAFT_PREVIEWSee Draft preview.
503, details.reason PAGE_UNREADABLEThe page could not be read right now. Retry.
Terminal window
opentrain todos quiz update --form-id form_456 --expected-revision 1 --title "Onboarding quiz" \
--definition-file quiz.json --settings-file settings.json \
--acknowledge-source-changes q1,q4 --confirm-live

The published version stores the grounding it was checked against, pruned to the pages questions cite. AI trainers never see grounding, sourceRefs, or rationale.

form_restore brings an archived form back. Restoring a form that is not archived does nothing. Published versions are never changed. It needs project_todos:write, a claimed account, and an idempotencyKey.

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_restore",
"idempotencyKey": "restore-safety-quiz-1",
"formId": "form_456"
}'