Skip to content

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)

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.

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.

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.

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.

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.

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.