Response lifecycle
A response is the unit of respondent data. Its life:
POST …/responses → IN_PROGRESS (pinned to a versionNumber)PATCH …/responses/{id} → answers saved (repeatable)POST …/{id}/complete → COMPLETED (frozen for respondents)Creation is lazy and pinned
Section titled “Creation is lazy and pinned”The embed does not create a response when the page loads — only on the first forward navigation or first file upload. Page views don’t mint empty rows.
Creation names an explicit versionNumber (the version the embed loaded),
and that pin is permanent. Everything later — resuming, uploading,
interpreting the answers — happens against that version’s definition, no
matter what has been published since. An optional externalRef is
captured at creation and immutable afterwards.
The response document also records meta.origin and meta.userAgent from
the creating request, plus createdAt / updatedAt / completedAt
timestamps — all visible in the management API.
Saving is full-replace, per screen
Section titled “Saving is full-replace, per screen”PATCH /public/v1/responses/{responseId} replaces the entire answers
map and the last screen position — it is not a merge. The embed sends a
save on every screen change (latest-wins if the respondent moves fast),
not per keystroke. To clear an answer, omit its key; explicit null
values are rejected.
Validation on this endpoint is shape-and-size only (see answer shapes) — the server does not re-run rules or check answers against the definition’s constraints in v1. The renderer is the enforcement point for those; server-side rule re-validation is on the roadmap.
Resuming
Section titled “Resuming”GET /public/v1/responses/{responseId} returns everything needed to
resume: status, the pinned versionNumber and that version’s full
definition, the answers, and the last position. This is what powers
refresh-resilient sessions — the embed stores only the response id in
sessionStorage and rehydrates the rest. A deleted response (or one whose
questionnaire was deleted) returns 404, which the embed treats as the
signal to start fresh.
Completion
Section titled “Completion”POST …/complete flips the status to COMPLETED and stamps
completedAt. It is idempotent — completing twice returns 200 and
keeps the original timestamp. Under a one-per-reference policy, the
transition is guarded by an atomic database write, so two same-reference
sessions racing to complete yield exactly one COMPLETED and one
409 ALREADY_SUBMITTED.
After completion the response is frozen for respondents: saving answers, uploading, or deleting files all return 409. Rehydration still works (the embed shows the completion state). Management operations — browsing, export, hard delete — are unaffected by the freeze.
Deletion
Section titled “Deletion”Hard delete exists only on the management API
(DELETE /api/v1/questionnaires/{id}/responses/{responseId}) and is
data-subject erasure: the document is removed and uploaded files are
deleted from storage. There is no soft delete and no undo.
Statuses at a glance
Section titled “Statuses at a glance”| Status | Meaning | Respondent can modify? |
|---|---|---|
IN_PROGRESS |
Session exists; answers accumulating | Yes |
COMPLETED |
Finished; completedAt set |
No — everything mutating returns 409 |
Those are the only two statuses. Abandoned IN_PROGRESS responses are
never auto-deleted (their orphaned files are, after a grace period) —
see retention for the cleanup one-liner.