---
title: "Update a response"
method: PATCH
path: "/api/v3/responses/{responseId}"
tags: ["V3 Responses"]
---

# Update a response

`PATCH /api/v3/responses/{responseId}`

## Request

- Base URL: `https://app.formbricks.com`
- URL: `https://app.formbricks.com/api/v3/responses/{responseId}`
- Auth: one of:
  - API key in cookie `next-auth.session-token`
  - API key in header `x-api-key`
  - HTTP bearer

Partially updates a response. Requires read-write access; the workspace is resolved from the response. An unknown or inaccessible `responseId` answers **403**.

`PATCH` is the only update verb — there is no `PUT`. Within it, `data` is replaced wholesale rather than deep-merged, so send the complete answer map; `embeddedData` merges by field name. Unknown top-level keys are rejected with **400** rather than ignored.

Every patch emits `responseUpdated` and runs the response pipeline, including Hub ingestion. Transitioning `finished` to `true` additionally emits `responseFinished`; patching a response that is already finished does not re-emit it.

## Path parameters

- `responseId` string, cuid2, required

## Request body

- PatchResponseRequest: Partially updates a response. Strict at the top level: unknown keys return **400** rather than being ignored, and at least one patchable field must be present. `data` is **replaced wholesale**, not deep-merged — send the complete answer map you want stored. `embeddedData` is the exception and merges by name: it addresses individual fields, so a field the payload omits keeps its value rather than being cleared. v3 replaces on purpose, where v1 and v2 merge: a merge makes it impossible to delete an answer, which a correction flow needs. **Every patch runs the response pipeline**, emitting `responseUpdated` — so webhooks, integrations and follow-ups fire for a correction as much as for a substantive edit. A patch that *transitions* the response to finished additionally emits `responseFinished`; one that patches an already-finished response does not re-emit it. Not patchable, and rejected if sent: `createdAt`, `updatedAt`, `surveyId`, `contactId`, `displayId`, `singleUseId`, `contactAttributes`, and the create-only `ttc` and `meta` — timing and submission context describe the original submission event, so they are set once and not revised. `contactId` is create-only deliberately — re-attributing an existing response to a different person is a capability only v2 offers, and it is not carried forward.
  - `finished` boolean
  - `endingId` string, nullable
  - `language` string, nullable
  - `data` ResponseDataMapInput: The same map on the way in, with the cardinality bounds a v3 write enforces. At most 500 keys, and at most 1000 entries in a multi-select array or matrix object. Both sit far above any real survey — a response holds one entry per element, a multi-select answer one per choice — and exist because every later reader pays per entry: the serializer's per-answer mapping, export columns, and the positional probe the "Other" response filter emits. Exceeding either is a **400** from the request schema, not a 422. Deliberately separate from `ResponseDataMap`, which the read returns. These bounds are v3-only — the v1 and v2 write paths store the same map without them — so a stored response may exceed them, and the read schema must not claim otherwise.
  - `embeddedData` ResponseEmbeddedDataInput: Embedded Data values to write, **keyed by field name**. This is the write counterpart of `embeddedData[]` on the read, and it is keyed the way an author names a field rather than the way storage addresses it. A hidden field is stored under its name but a variable is stored under its cuid, and that split is an implementation detail of the storage layout — the server resolves a name to its storage key through the survey's Embedded Data links, so a caller never sees a cuid and never has to know which kind it is writing. **Which kinds are writable:** - `ingested` (hidden fields) — accepted, through the same ingest contract the SDK uses: the name must be declared on the survey, the value is coerced to the field's declared `type`, a `locked` field ignores the write, and the whole payload is bounded. A name the survey does not declare is a **422** rather than a silent drop — knowing whether a name is declared means loading the survey, and these operations route every failure that needs stored state to 422. - `computed` (variables) — accepted, because a management caller correcting a response needs to, and v1 and v2 both allow it today. - `reserved` — refused. Auto-captured context describes the submission environment, and a caller-supplied value would be fiction. The three exceptions are `source`, `url` and `action`, which `ResponseMetaInput` accepts for exactly the replay case. Values follow their field's declared type. A field the payload omits is left as it is; this is a merge over named fields, not a replacement of the collection. `null` **clears** a field. Omission and `null` therefore mean different things — leave it alone versus remove its value — which is the only reading that makes a merge-shaped map able to express deletion at all. A cleared field is then absent from `embeddedData[]` on the next read, exactly as one that was never written, and a field whose definition carries a `defaultValue` falls back to that default rather than disappearing. **An ambiguous name is refused.** The read says `key` is unique per `kind` rather than across the collection, because a survey predating the reserved-name guard can hold a variable and a hidden field under one name. This map has no `kind`, so on such a survey a name cannot say which field it means — and guessing would write the caller's value into the wrong one. A name that matches more than one declared field is a **422** naming it, for the same reason as an undeclared one; rename one side on the survey to clear it. The set is finite, frozen and tracked, and nothing new can enter it. Reserved names collide differently and are not ambiguous: `reserved` is not writable at all, so a name that matches both a declared field and a catalog entry resolves to the declared one. **At most 500 names.** Every name that matches no declared field becomes its own `invalid_params` entry naming it, so without a cap a large body answers with a 422 several times its own size. Far above any real survey; a v3-only bound, as on `data`.
  - `tags` string[]: Complete set of tag ids for this response, replacing whatever is currently applied. Repeats are allowed and collapse, because the set is applied as a set. Tags must already exist in the same workspace — create them via `POST /api/v3/tags`.

## Response `200`

Response updated successfully

- object
  - `data` ResponseResource, required: A survey response in the detailed view, as returned by `GET /api/v3/responses/{responseId}` and by the write endpoints. Everything in `ResponseListItem`, plus the identity and provenance fields the list view omits. It also carries `data` — the answers as stored, returned unchanged, so a client can read a response, edit an answer and send it straight back. Embedded Data is not echoed in stored form: the read's `embeddedData[]` and a request's `embeddedData` map already address the same fields by the same names, so a second raw map would only republish the storage layout. That is the property v1/v2 had for free by being symmetric, and the one OpenAPI 3.1.1 argues for directly: its "Validating `readOnly` and `writeOnly`" section notes that making clients strip fields before a write is "burdensome for clients, particularly when the JSON data is complex or deeply nested". They are not the *same* view, and the differences are deliberate. A write accepts `ingested` and `computed` fields only: a `reserved` entry describes the submission environment, and being able to read one back is not a licence to author it. An unset field is **omitted** from the read while `null` on a write **clears** one, because a merge-shaped map has no other way to spell deletion. And a name the read returns twice under different `kind`s cannot be written at all — see `ResponseEmbeddedDataInput`, which refuses an ambiguous name rather than guessing. Values themselves round-trip: within those two kinds every value the read emits is accepted verbatim on a write, and a `date` reads and writes as the same ISO-8601 string. So a read-edit-write cycle loses nothing except in two cases, both confined to rows v1 or v2 wrote. The first is the finite set of legacy surveys carrying one name as both a variable and a hidden field, where the write refuses the name outright. The second is a `data` map holding more keys than `ResponseDataMapInput` accepts: the stored map is unbounded and the write bound is not, so such a response cannot be patched here — and trimming it to fit is not a workaround, since `data` is replaced wholesale and an omitted key is a deletion. Note which fields carry `readOnly` here and which do not, because it is the opposite of what an "echo" framing would suggest. `readOnly` means a value is managed exclusively by the server, so it marks the **derived** views — `answers[]`, `embeddedData[]`, `unresolved[]`, `resolution`, the denormalized `surveyName` and `workspaceId`, the timestamps, `durationSeconds` and `id`. `data` is *not* read-only: it is exactly what the write endpoints accept, and leaving it unmarked is what makes this representation round-trippable rather than merely informative. To correct one answer, read this resource, take `data`, change the entry you want and `PATCH` `{ "data": … }`. Send only the fields you are changing — `PATCH` rejects unknown and non-patchable keys, so passing the whole representation back verbatim is not the intended flow; passing `data` back is. **Never exposed on this resource, in either view:** the respondent's IP address, and the `contactAttributes` snapshot that v1/v2 return. That snapshot is copied from the contact at submission time and never updated after, so it is stale PII by construction — `contact` covers the legitimate need to know who answered.
    - `id` string, cuid2, required: Response identifier.
    - `surveyId` string, cuid2, required: The survey this response belongs to.
    - `surveyName` string, required: The survey's current name, denormalized into the payload so a consumer listing responses need not resolve survey names separately.
    - `workspaceId` string, cuid2, required: The owning workspace, resolved through the survey. Responses carry no workspace column of their own; this is a derived field.
    - `createdAt` string, date-time, required: When the response was created. **Server-owned** — it cannot be supplied on create or changed by a patch. This is the AIP-148 output-only convention, and it is deliberate on two counts: monthly response metering counts rows by `(surveyId, createdAt)`, and a client-chosen `createdAt` can insert rows into the middle of another consumer's already-paginated stream. Backdating remains available on v1/v2; a dedicated import path is future work.
    - `updatedAt` string, date-time, required: When the response was last modified. Server-owned. Neither filterable nor sortable — it has no index, and being mutable it cannot anchor a pagination cursor.
    - `finished` boolean, required: Whether the respondent reached an ending.
    - `endingId` string, nullable, required: The ending card the respondent reached. `null` for unfinished responses.
    - `language` string, nullable, required: The survey language this response was collected in, as a survey language code (not necessarily a BCP-47 locale — `default` is a legitimate value). `null` when the survey is single-language or no language was stamped.
    - `durationSeconds` number: Total seconds the respondent spent, summed from per-element timing and **excluding** the stored `_total` bucket, which the server maintains alongside real element ids and which would otherwise be double-counted. Absent when the submission carried no timing. Summing rather than reading `_total` is deliberate: the stored bucket is only written when a response is finished, so reading it would leave every partial response without a duration even when it carries per-element timing. This is also why the value can differ from the reserved `durationSeconds` catalog entry, which does read the bucket and rounds to whole seconds — that entry is not projected into `embeddedData[]`, so the two never appear side by side.
    - `resolution` ResponseResolution, required: Receipt describing how `answers[]` and `embeddedData[]` labels were produced. It exists so a consumer can tell whether the labels it is reading are the wording the respondent actually saw.
      - `labelPolicy` 'currentSurveyDefinition', required: Where labels came from. Today the only value is `currentSurveyDefinition`: labels are resolved at **read time** against the survey as it stands now. Nothing about the survey is snapshotted when a response is written and there is no survey-version model, so a response collected under an older headline reads back under today's wording — text the respondent may never have seen. Typeform, SurveyMonkey, Qualtrics, Google Forms and Jotform all behave this way, so this is the industry norm rather than a shortcut. Compare `surveyUpdatedAt` against the response's `createdAt` to detect that the definition moved after collection. If a write-time label snapshot ever ships, a second value `asAsked` is added here and consumers branch on it — treat this as an open enum.
      - `labelsLanguage` string, nullable, required: The language the labels in this payload were resolved in. Follows the response's `language` when it is set and the survey has that language enabled, otherwise the survey's default language. `null` when the survey carries no translations.
      - `surveyUpdatedAt` string, date-time, required: When the survey definition was last modified. Later than the response's `createdAt` means the wording in `elementLabel` may have drifted from what was asked.
    - `answers` ResponseAnswer[], required: One entry per element the respondent reached, in survey order. Empty for a response that recorded no answers.
      - union: One answer, carrying the element's id, its label, its type and its value. `elementType` selects the shape: switch on it rather than probing for which value field is present. Answers are a **typed array**, not a map keyed by element id, because a consumer holding only a response would otherwise see bare ids and have to fetch the survey and join to learn what was asked — a join that is easy to get wrong and that agent clients cannot make cheaply. The array also separates answers from hidden fields and variables, which the stored representation mixes into one flat map. Non-answer data lives in `embeddedData[]`. Several element types share a value shape and therefore a schema — the four numeric scales, the four choice styles, the two composites. All seventeen element types are covered. Only elements the respondent actually reached appear here. Data whose element has since been deleted appears in `unresolved[]` instead.
        - ResponseAnswerText: Free-text answer to an `openText` element.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'openText', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `valueText` string, required: The submitted text, verbatim.
          - `inputType` 'text' | 'email' | 'url' | 'number' | 'phone': The element's configured input type in the current definition. Useful for rendering and for knowing how the value was validated at collection time; the value itself is always returned as a string, including for `number`.
        - ResponseAnswerNumber: Numeric-scale answer. Covers `nps` (0–10), `rating` (configurable range and presentation), `csat` and `ces`.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'nps' | 'rating' | 'csat' | 'ces', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `valueNumber` number, required: The submitted score.
          - `range` object, required: The scale the score was collected on, from the current definition. Returned because a bare `8` is uninterpretable without it, and because scales are editable — a consumer comparing scores across time needs to see that the range moved.
            - `min` number, required
            - `max` number, required
          - `scale` 'number' | 'smiley' | 'star': How the scale was presented. Carried by the element types that configure one — `rating`, `csat` and `ces` — and absent for `nps`, which has no scale presentation.
        - ResponseAnswerBoolean: Acknowledgement answer. Covers `consent` (accepted) and `cta` (clicked through). A `cta` element the respondent skipped has no entry in `answers[]` at all.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'consent' | 'cta', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `valueBoolean` boolean, required: Whether the respondent accepted (`consent`) or clicked through (`cta`). Derived from the stored token, which is why `rawValue` is returned alongside it.
          - `rawValue` string, required: The token as stored — conventionally `accepted` for `consent` and `clicked` for `cta`. Returned because the boolean is a derivation and this is the underlying fact.
        - ResponseAnswerSelection: Choice-style answer. Covers `multipleChoiceSingle`, `multipleChoiceMulti`, `pictureSelection` and `ranking`. See `ResponseValueMatch` for how each selection was resolved and why an unresolvable value is never reclassified as a write-in.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'multipleChoiceSingle' | 'multipleChoiceMulti' | 'pictureSelection' | 'ranking', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `selections` ResponseSelection[], required: The chosen options, resolved against the current definition. Always an array, including for `multipleChoiceSingle`, so consumers need one code path rather than two. For `ranking`, the array is in rank order and each entry also carries an explicit `rank`.
            - `optionId` string, nullable, required: Resolved option id, or the literal `other` for a write-in. `null` when `match` is `unmatched`.
            - `optionLabel` string, nullable, required: Option label in the resolved language. `null` when `match` is `unmatched`.
            - `rawValue` string, required: The value as stored on the response, verbatim and before resolution — always present, and the string to use when rebuilding the stored map. Note what is stored differs by element type: `multipleChoiceSingle`, `multipleChoiceMulti` and `ranking` store the option **label** in the respondent's language, while `pictureSelection` stores the option **id**. `""` occurs when the respondent selected Other and left it blank, and is returned rather than dropped.
            - `match` 'exact' | 'label' | 'other' | 'unmatched', required: How a stored raw value was resolved against the current survey definition. Precedence is fixed and applied in this order: - `exact` — the stored value matched an option **id** in the current definition. - `label` — the stored value matched an option's **label text** (ids are stable, labels are editable and language-dependent, so a label match is weaker evidence). - `other` — the value arrived through the element's **Other** free-text input. Only reported when the element actually has that input enabled and the value was submitted through it. - `unmatched` — no option resolves. The raw value is still returned; it is **not** silently reclassified as `other`, because a *renamed* option is also unmatched text and filing it as a write-in would misattribute the answer.
            - `rank` integer: 1-based rank, present only for `ranking` elements. Array order already reflects the rank; this field makes it explicit so consumers need not rely on ordering.
        - ResponseAnswerDate: Date answer.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'date', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `valueDate` string, required: The submitted date as stored, an ISO-8601 date or date-time string. No `format` is asserted because historical rows were written by several client versions and the stored precision varies; parse defensively.
        - ResponseAnswerFileUpload: File-upload answer.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'fileUpload', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `fileUrls` string[], required: The uploaded files, **as stored** — workspace-relative paths, not absolute URLs, and neither resolved nor signed. A deliberate choice between two things this field could be. Storage writes a relative path and the app's own read paths rewrite it to an absolute URL for display; v3 returns the stored bytes instead, because `answers[]` promises verbatim values a client can write straight back, and an absolute URL is neither what is stored nor what a write accepts. Fetching a file is a separate concern with its own ticket (ENG-513, signed short-lived URLs behind a dedicated endpoint); this field identifies the file, it does not serve it. Not publicly fetchable in any case: access follows the same authorization as the response, and deleting the response deletes the underlying files.
          - `fileCount` integer: Convenience count of `fileUrls`, so a list consumer need not materialize the array.
        - ResponseAnswerBooking: Scheduling answer for a `cal` element. Only the fact of the booking is recorded on the response; the meeting itself lives in the scheduling provider and is not proxied by this API.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'cal', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `booked` boolean, required: Whether the respondent completed a booking.
          - `rawValue` string, required: The token as stored, verbatim — conventionally `booked`. Required, like the equivalent field on `consent` and `cta`, because `booked` is a derivation and this is the underlying value a client needs to rebuild the stored map.
        - ResponseAnswerMatrix: Matrix answer — one selected column per answered row.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'matrix', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `rows` object[], required: One entry per row the respondent answered. Rows left blank are omitted rather than returned with a null column. Both sides of a matrix cell need resolving against the current definition, so each entry reports its own `match` — see `ResponseValueMatch`.
            - `rawKey` string, required: The stored map key for this row, verbatim. Matrix answers are stored keyed by the **localized row label** in the respondent's language, not by row id — so this is the string to use when rebuilding the stored object, and it is not necessarily equal to `rowLabel`, which is resolved in the *current* definition's language.
            - `rowId` string, nullable: Row id resolved from the current definition, or `null` when the stored key matches no current row. Derived, never stored.
            - `rowLabel` string, required: Row label in the resolved language. Falls back to `rawKey` when the row no longer resolves.
            - `columnId` string, nullable: Column id resolved from the current definition, or `null` when it no longer resolves. Derived, never stored.
            - `columnLabel` string, nullable, required: Column label in the resolved language. `null` when `match` is `unmatched`.
            - `rawValue` string, required: The selected column as stored, verbatim — the localized column label, not a column id. This is the value to use when rebuilding the stored object.
            - `match` 'exact' | 'label' | 'other' | 'unmatched', required: How a stored raw value was resolved against the current survey definition. Precedence is fixed and applied in this order: - `exact` — the stored value matched an option **id** in the current definition. - `label` — the stored value matched an option's **label text** (ids are stable, labels are editable and language-dependent, so a label match is weaker evidence). - `other` — the value arrived through the element's **Other** free-text input. Only reported when the element actually has that input enabled and the value was submitted through it. - `unmatched` — no option resolves. The raw value is still returned; it is **not** silently reclassified as `other`, because a *renamed* option is also unmatched text and filing it as a write-in would misattribute the answer.
        - ResponseAnswerComposite: Composite answer whose value is a set of labelled sub-fields. Covers `address` and `contactInfo`. Stored as a positional array of strings, which is why every slot is reported.
          - `position` integer, required: 1-based position of the element in the current survey definition, so a consumer can present answers in survey order without fetching the survey. Elements the respondent never reached are absent from `answers[]` entirely rather than present-and-empty.
          - `elementId` string, required: Stable element id, as authored on the survey.
          - `elementType` 'address' | 'contactInfo', required: The element's type in the current survey definition. Matches the `type` of the corresponding `SurveyElement`.
          - `elementLabel` string, required: The element's headline, resolved in `resolution.labelsLanguage`. Read at request time from the current definition — see `ResponseResolution` for what that means for older responses.
          - `durationSeconds` number: Seconds the respondent spent on this element. Converted from the stored per-element time-to-complete, which is recorded in milliseconds. This telemetry is reported by the client and is therefore not trustworthy input: treat it as an indication rather than a measurement. Absent when the submission carried no timing for this element. The bounds hold on **every** row, including ones collected before the write-side clamp shipped. The stored representation is deliberately unbounded so that historical data still parses, and a row written then can hold a negative value or a tab left open for days; those are clamped into range on read rather than returned as-is, because a schema a real row violates is worse than a slightly blunted outlier. So `86400` means "at least 24 hours", not exactly.
          - `fields` object[], required: The element's sub-fields, **every slot, in storage order**, including ones the respondent left blank (`valueText: ""`). Blanks are returned rather than omitted because the stored value is a positional fixed-length array — `address` has 6 slots, `contactInfo` has 5 — so dropping an empty slot would shift every later value onto the wrong field and make the stored array impossible to rebuild.
            - `slot` integer, required: 0-based index of this sub-field in the stored array. Reconstruct the stored value by ordering on this and taking `valueText`.
            - `fieldId` 'addressLine1' | 'addressLine2' | 'city' | 'state' | 'zip' | 'country' | 'firstName' | 'lastName' | 'email' | 'phone' | 'company', required: Stable sub-field id. `address` uses `addressLine1`, `addressLine2`, `city`, `state`, `zip`, `country` in that order; `contactInfo` uses `firstName`, `lastName`, `email`, `phone`, `company`.
            - `fieldLabel` string, required: Sub-field label in the resolved language.
            - `valueText` string, required: The submitted value, verbatim. `""` when the respondent left it blank.
    - `embeddedData` ResponseEmbeddedDatum[], required: Hidden fields, variables and auto-captured context, as one collection. Empty when the response carries none.
      - `key` string, required: The field's **name** — what an author calls it, and the key `embeddedData` on a write accepts. Not the storage key. A hidden field happens to be stored under its name and a variable under its cuid, and that split is a property of the storage layout rather than of the field; exposing it would publish an internal id and make the writable and readable views of one field disagree on how to address it. **Unique per `kind`, not across the collection.** A survey created before the reserved names were enforced can declare a hidden field called `country`, and the auto-captured `country` still exists — so the same name can appear once as `ingested` and once as `reserved`. That set is finite and no longer growing: a new declaration may no longer take a reserved name, or the name of a variable or hidden field the survey already has. Key on `(key, kind)` rather than `key` alone, or read the pair you care about.
      - `kind` 'ingested' | 'computed' | 'reserved', required: Provenance of the value: - `ingested` — supplied by the caller or the embedding page (hidden fields). - `computed` — derived during the submission by survey logic (variables). - `reserved` — auto-captured context from the submission environment (for example `country`, `deviceType`), drawn from the Embedded Data field catalog.
      - `type` 'string' | 'number' | 'boolean' | 'date', required: Declared type of `value`, so a consumer need not infer it. `computed` entries are only ever `string` or `number`, because survey variables are typed that narrowly.
      - `label` string, required: Human-readable name for the key, in the resolved language where one exists.
      - `value` union, required: The value, matching `type`. `date` values are ISO-8601 strings. Always a scalar. A legacy row can hold an array or an object under a declared field's key — the ingest contract refuses those now, but older rows have them — and such a value resolves to nothing rather than being coerced, so the entry is omitted here and reported in `unresolved[]` with the bytes intact. Nothing is silently dropped, and no `type` would describe an array anyway. There is no `null` member: a declared key that resolves to nothing is **omitted** from the collection rather than carried with a null. So `embeddedData[]` enumerates what the response actually holds, not what the survey declares — a survey with twelve declared fields and two filled in yields two entries. A field with a default value is never unset, since the default is the fallback before the key is considered missing.
        - string
        - number
        - boolean
    - `unresolved` ResponseUnresolvedEntry[], required: Stored data whose element or key no longer exists in the survey definition. Empty in the normal case.
      - `key` string, required: The key as stored on the response. For `elementNotInSurvey` it is a former element id or hidden-field name; for `variableNotInSurvey` a variable's cuid, since that map is keyed by id rather than by name; and for `valueShapeMismatch` the id or name of the element or declared field that *does* still exist — that reason means the key resolved and the value did not.
      - `rawValue` union, required: One stored answer or field value, in the four shapes the v3 contract can carry: a string, a number, a list of strings, or a string-to-string map. Declared once and referenced by `ResponseDataMap` and `ResponseUnresolvedEntry.rawValue`, so the two places that publish stored JSON cannot drift apart. A stored value outside these shapes (a legacy JSON `null`, an array with non-string items, a map with non-string values) is not published at all rather than reaching a client as a body this contract would reject.
        - string
        - number
        - string[]
        - object
      - `reason` 'elementNotInSurvey' | 'variableNotInSurvey' | 'valueShapeMismatch', required: Why the entry could not be resolved: - `elementNotInSurvey` — nothing in the current definition claims this key. It covers a deleted element **and** a deleted hidden field, because the two cannot be told apart: an answer is stored under its element id and a hidden field under its name, in one shared map, and storage keeps no record of which kind a vanished key used to be. - `variableNotInSurvey` — no variable with this id exists in the current definition. Variables *are* distinguishable, because they live in their own map keyed by cuid rather than sharing the answer map. - `valueShapeMismatch` — the key resolves to a current element **or a declared Embedded Data field**, but the stored value's shape cannot be read as an answer of that element's type (for example a string where the element expects a selection list). Signals element-type churn rather than deletion.
    - `tags` ResponseTag[], required: Tags applied to this response, as `{ id, name }` references. Empty when the response is untagged.
      - `id` string, cuid2, required
      - `name` string, required: Tag label as shown in the app.
    - `data` ResponseDataMap, required: Answers as stored, **keyed by element id**. Hidden-field values do not live here. They are stored in the same underlying map, but that is a storage detail rather than part of this contract: a hidden field is an Embedded Data field, and Embedded Data is addressed by name through `embeddedData`. A hidden field's name appearing as a key here is rejected rather than silently written to the wrong namespace — with **422**, not 400, because knowing that a key names a hidden field means loading the survey, and these operations route every failure that needs stored state to 422. The problem carries `invalid_params[]` with `name` set to the offending key, `code: unsupported_field` and `referenceType: hiddenField`, so a client can point at it without parsing prose. A key that is **also** an element id is the exception, and it is **accepted**: the answer owns that address. Answers and hidden fields do share one slot, and the older surveys that declare a field named after a question have exactly one value under that key — but it is always the answer's. The field is dropped at ingest with `element_id_collision` (`packages/types/embedded-data-ingest.ts`) and filtered again in the renderer before it can reach a submission, precisely so a display-time field value cannot land on top of what the respondent answered. So on such a survey `answers[]` carries the value, `embeddedData` neither reports it nor accepts a write for it, and refusing the key here would block the only write that fits it. New surveys cannot reach that state through the editor or `POST`/`PATCH /api/v3/surveys`, which refuse an element id colliding with a declared field name. Both v1 management survey writes still accept one — that guard reads variable and hidden-field names but never element ids — so this set, unlike the reserved-name one, is still growing. Keeping the two apart is what lets the storage layout change underneath — the legacy columns go away eventually — without this contract either following it or breaking a round-trip it promised. The value shapes are the stored ones, and they differ by element type. `ResponseAnswer` documents each; `answers[]` on the read is the resolved view of this same map. **Bounds are a write-side concern and live on `ResponseDataMapInput`, not here.** This schema is the stored shape, and a read must describe what the API can actually return: the v1 and v2 write paths store this same map with no cardinality limit, so a response written through them can carry more keys — or a longer multi-select — than a v3 write would accept. Declaring the write bounds on this schema would make `GET /api/v3/responses/{responseId}` promise something it cannot keep for those rows.
    - `contact` ResponseContact, required: The contact that submitted the response, when the response is identified. Present on the detailed view only — the list view omits it. `null` for anonymous (link-survey) responses.
      - `id` string, cuid2, required: Contact identifier.
      - `userId` string, nullable: The contact's `userId` attribute — the id your application knows the person by. This is a contact **attribute**, not a column, and it is not guaranteed unique within a workspace, so it identifies the person for display but must not be used as a lookup key. `null` when the contact has no `userId` attribute set.
    - `displayId` string, nullable, required: The display (survey impression) this response was submitted against, when one was recorded.
    - `singleUseId` string, nullable, required: The single-use link id this response was submitted through, when the survey uses single-use links.

## Other responses

- `400`: Bad Request — the body failed schema validation, or it carried a field that is not patchable. `createdAt`, `updatedAt`, `surveyId`, `contactId`, `displayId`, `singleUseId`, `contactAttributes` and `meta` are all rejected here with `code: unsupported_field`. An empty body is also a 400.
- `401`: Not authenticated (no valid session or API key).
- `403`: Forbidden — no workspace access, or resource does not exist (404 not used; avoids existence leak).
- `413`: Payload Too Large — the request body exceeds the 2 MB limit.
- `422`: Unprocessable Content — the body is well-formed but conflicts with stored state: `endingId` is not an ending of the response's survey, `language` is not enabled on it, a tag id does not resolve in the same workspace, an `embeddedData` name is not declared on the survey, is ambiguous, or is addressed twice by one payload, a file-upload answer references a file outside this survey's upload element, or an answer violates the survey's validation rules.
- `429`: Rate limit exceeded.
- `500`: Internal Server Error.

## Changes

- **2026-10-28** `84820a01e550`: 21 warning
  - added the new `chart_in_use` enum value to the `code` response property for the response status `400`
  - added the new `chart_in_use` enum value to the `code` response property for the response status `401`
  - added the new `chart_in_use` enum value to the `code` response property for the response status `403`
  - added the new `chart_in_use` enum value to the `code` response property for the response status `413`
  - …17 more
- **2026-10-27** `aad5dc1d8236`: 14 warning
  - added the new `creatives_not_entitled` enum value to the `code` response property for the response status `400`
  - added the new `creatives_not_entitled` enum value to the `code` response property for the response status `401`
  - added the new `creatives_not_entitled` enum value to the `code` response property for the response status `403`
  - added the new `creatives_not_entitled` enum value to the `code` response property for the response status `413`
  - …10 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/formbricks/apis/formbricks-api-v3/changes/api/v3/responses/:responseId/patch.md)

---

[API](https://skmtc.dev/formbricks/apis/formbricks-api-v3.md) - [All operations](https://skmtc.dev/formbricks/apis/formbricks-api-v3/llms.txt) - [OpenAPI document](https://skmtc.dev/formbricks/apis/formbricks-api-v3/revisions/25335c959a13?raw)
