The data model
- An item is one logical to-do on a job, organized under an optional category.
- Every edit publishes a new immutable version. History is never rewritten.
- An assignment is one person’s obligation for one item version, with an append-only event and evidence trail.
- Quizzes are to-do items pinned to one immutable published form version.
Audiences are always explicit
Every create, assign, or reissue names its audience. There is no default: omittingaudience fails with 400 instead of assigning anyone.
SELECTED fails closed. The list must be non-empty and duplicate-free, and every ID must be an active contract of that exact job. An unknown, ended, or foreign ID rejects the whole request with each offender named — nothing is silently dropped, and SELECTED can never turn on future-hire auto-assignment.
Preflight receipts for broad assignments
ACTIVE, FUTURE, and ACTIVE_AND_FUTURE fan out beyond an explicit list, so they require a two-step confirmation:
- Call the no-write preflight with the exact request you intend to send. It returns the live recipients (masked names, contract IDs, and counts), future-hire semantics, projected due and blocking behavior, duplicate candidates, and a signed
receipt. - Send the mutation unchanged with
preflightReceiptset to that token.
409 and a PREFLIGHT_RECEIPT_* reason, and you preflight again. Roster changes that land between the receipt check and the write itself also fail closed with 409 RECIPIENTS_CHANGED.
SELECTED does not need a receipt: the explicit ID list is already the exact confirmation.
Idempotency is bound to the whole request
Every mutation requires anidempotencyKey (8–200 characters). For item creation the key is bound to the complete request — definition, audience, selected contracts, placement, and future-hire semantics:
- The same key with the same request replays: you get the committed result with
replayed: true, and no new assignments are created. A replay never re-resolves the roster, so a person hired after the original create is not silently added. - The same key with a changed request fails with
409— a reused key can never widen a fan-out. - A different key with an identical active request fails with
409 LIKELY_DUPLICATEunless you explicitly passallowDuplicate: true.
Normalized results
Mutations return an explicit, machine-readable outcome: the item and version IDs, the audience echo with future-hire semantics, a fan-out summary (matchedContracts, createdAssignments, assignmentIds), the masked recipients the request was verified against, a replayed flag, and warnings. Archiving returns the exact number of open assignments it closed, and that archiving is not reversible through this surface while history is retained.
Privacy
Agent responses identify people by contract ID, user ID, and a masked display name (first name plus last initial). Personal emails never appear in list responses, and the CSV export serves theworker_email column blank.
Permissions
Reads requireproject_todos:read; every mutation requires project_todos:write, employer access to the job, and a claimed account. Both scopes are available on fine-grained keys and included in full-access keys. The account must also have the default-off project_todos_integrations agent-surface family enabled; a full-access key does not bypass that separate gate. See Scopes and capabilities.
Where to go next
- API reference — endpoints, actions, and errors.
- Quiz surface — authoring, assigning, grading, and retakes.
- CLI — preview-first
opentrain todoscommands. - MCP tools — the same safety contract for MCP agents.